1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.resourceloader.classpath;
35
36 import info.magnolia.init.MagnoliaConfigurationProperties;
37 import info.magnolia.resourceloader.classpath.service.ClasspathServiceConfiguration;
38
39 import java.net.URL;
40 import java.net.URLClassLoader;
41 import java.nio.charset.Charset;
42 import java.nio.charset.StandardCharsets;
43 import java.util.Collection;
44 import java.util.List;
45 import java.util.regex.Pattern;
46
47 import javax.inject.Inject;
48 import javax.inject.Singleton;
49 import javax.servlet.ServletContext;
50
51 import org.apache.commons.lang3.StringUtils;
52 import org.reflections.util.ClasspathHelper;
53 import org.reflections.util.FilterBuilder;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 import com.google.common.base.Joiner;
58 import com.google.common.base.Predicate;
59 import com.google.common.base.Predicates;
60 import com.google.common.collect.Collections2;
61 import com.google.common.collect.Lists;
62
63 import lombok.Value;
64
65
66
67
68
69
70
71
72 @Singleton
73 public class DefaultClasspathServiceConfigurations {
74
75 private static final Logger log = LoggerFactory.getLogger(DefaultClasspathServiceConfigurations.class);
76
77 public static final String LEGACY_PREFIX = "mgnl-resources";
78 private static final String PREFIX_AND_SLASH = LEGACY_PREFIX + "/";
79 private static final String PREFIX_AND_DOT = LEGACY_PREFIX + ".";
80
81 private final ServletContext servletContext;
82 private final MagnoliaConfigurationProperties configurationProperties;
83
84 @Inject
85 public DefaultClasspathServiceConfigurations(ServletContext servletContext, MagnoliaConfigurationProperties configurationProperties) {
86 this.servletContext = servletContext;
87 this.configurationProperties = configurationProperties;
88 }
89
90 public Builder developmentMode() {
91 return defaults();
92 }
93
94 public Builder legacy() {
95 return productionMode().
96 withResourceFilter(new StartsWith(Lists.newArrayList(PREFIX_AND_DOT, PREFIX_AND_SLASH)));
97 }
98
99 public Builder productionMode() {
100 return defaults();
101 }
102
103 public Builder defaults() {
104 return new Builder().
105 withCharset(StandardCharsets.UTF_8).
106 withScannableResourcePattern(defaultScannableResourcePattern()).
107 withResourceFilter(defaultResourceFilter()).
108 withClasspathLocations(defaultClasspathLocations());
109 }
110
111 public final Pattern defaultScannableResourcePattern() {
112 String pattern = configurationProperties.getProperty("magnolia.resources.classpath.observation.pattern");
113 if (StringUtils.isNotBlank(pattern)) {
114 return Pattern.compile(pattern);
115 } else {
116 log.debug("Classpath observation pattern is not being set explicitly, current observation is made for only ftl + yaml files");
117 return Pattern.compile(".*\\.(ftl|yaml)$");
118 }
119 }
120
121 public final Predicate<String> defaultResourceFilter() {
122 final FilterBuilder filterBuilder = new FilterBuilder();
123 return applyResourceExclusions(filterBuilder);
124 }
125
126 public final Iterable<URL> defaultClasspathLocations() {
127 final ClassLoader contextClassLoader = ClasspathHelper.contextClassLoader();
128 final Collection<URL> allURLs;
129 if (contextClassLoader instanceof URLClassLoader) {
130 allURLs = ClasspathHelper.forClassLoader(contextClassLoader);
131 } else {
132
133
134
135 allURLs = ClasspathHelper.forWebInfLib(servletContext);
136 }
137 return Collections2.filter(allURLs, urlsFilter());
138 }
139
140
141
142
143 public static final class Builder {
144 private Iterable<URL> classpathLocations;
145 private Predicate<String> resourceFilter;
146 private Charset charset;
147 private Pattern scannableResourcePattern;
148
149 public Builder withClasspathLocations(Iterable<URL> classpathLocations) {
150 this.classpathLocations = classpathLocations;
151 return this;
152 }
153
154 public Builder withResourceFilter(Predicate<String> resourceFilter) {
155 this.resourceFilter = resourceFilter;
156 return this;
157 }
158
159 public Builder withCharset(Charset charset) {
160 this.charset = charset;
161 return this;
162 }
163
164 public Builder withScannableResourcePattern(Pattern scannableResourcePattern) {
165 this.scannableResourcePattern = scannableResourcePattern;
166 return this;
167 }
168
169 public ClasspathServiceConfiguration build() {
170 return new ConfiguredClasspathServiceConfiguration(classpathLocations, resourceFilter, charset, scannableResourcePattern);
171 }
172
173 @Value
174 private static class ConfiguredClasspathServiceConfiguration implements ClasspathServiceConfiguration {
175 private Iterable<URL> classpathLocations;
176 private Predicate<String> resourceFilter;
177 private Charset charset;
178 private Pattern monitoredResourcePattern;
179 }
180 }
181
182 private Predicate<? super URL> urlsFilter() {
183 final String excludedUrlExtensionsPattern = extensionsPattern(excludedUrlExtensions());
184 return URLPredicate.excludedExtensions(excludedUrlExtensionsPattern);
185
186 }
187
188 private String extensionsPattern(String[] extensions) {
189 return ".*\\.(" + Joiner.on('|').join(extensions) + ")$";
190 }
191
192 private String[] excludedUrlExtensions() {
193 return new String[]{
194
195 "dylib", "dll", "so", "jnilib",
196 "class",
197
198 "pom"
199 };
200 }
201
202 private FilterBuilder applyResourceExclusions(FilterBuilder filterBuilder) {
203
204 filterBuilder.exclude("mgnl-bootstrap.*");
205 filterBuilder.exclude("mgnl-resources.*");
206 filterBuilder.exclude("mgnl-nodetypes.*");
207 filterBuilder.exclude("mgnl-files.*");
208 filterBuilder.include("mgnl-i18n.*");
209
210 filterBuilder.exclude(extensionsPattern(excludedResourcesExtensions()));
211
212 final String[] excludedPackages = excludedPackages();
213 for (String excludedPackage : excludedPackages) {
214 filterBuilder.excludePackage(excludedPackage);
215 }
216 return filterBuilder;
217 }
218
219 private String[] excludedResourcesExtensions() {
220 return new String[]{
221 "package\\.html", "java", "jar",
222
223 "dylib", "dll", "jnilib", "class"
224 };
225 }
226
227 private String[] excludedPackages() {
228 return new String[]{
229 "META-INF",
230 "com.oracle.java",
231 "com.oracle.tools",
232 "com.sun",
233 "sun",
234 "oracle",
235 "java",
236 "javax",
237 "jdk",
238 "org.apache",
239 "lombok",
240 "VAADIN",
241 "gwt-unitCache"
242 };
243 }
244
245
246
247
248 private static class URLPredicate implements Predicate<URL> {
249
250 public static Predicate<? super URL> excludedExtensions(String excludedUrlExtensionsPattern) {
251 final Predicate<CharSequence> exclude = Predicates.not(Predicates.containsPattern(excludedUrlExtensionsPattern));
252 return new URLPredicate(exclude);
253 }
254
255 private final Predicate<CharSequence> predicate;
256
257 protected URLPredicate(Predicate<CharSequence> predicate) {
258 this.predicate = predicate;
259 }
260
261 @Override
262 public boolean apply(URL input) {
263 return predicate.apply(input.toString());
264 }
265 }
266
267 private static class StartsWith implements Predicate<String> {
268
269 private final List<String> prefixes;
270
271 public StartsWith(List<String> prefixes) {
272 this.prefixes = prefixes;
273 }
274
275 @Override
276 public boolean apply(String input) {
277 for (String prefix : prefixes) {
278 if (input.startsWith(prefix)) {
279 return true;
280 }
281 }
282 return false;
283 }
284 }
285 }