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.function.Predicate;
46 import java.util.regex.Pattern;
47 import java.util.stream.Collectors;
48
49 import javax.inject.Inject;
50 import javax.inject.Singleton;
51 import javax.servlet.ServletContext;
52
53 import org.apache.commons.lang3.StringUtils;
54 import org.reflections.util.ClasspathHelper;
55 import org.reflections.util.FilterBuilder;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import com.google.common.base.Joiner;
60 import com.google.common.base.Predicates;
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 allURLs.stream()
138 .filter(urlsFilter())
139 .collect(Collectors.toList());
140 }
141
142
143
144
145 public static final class Builder {
146 private Iterable<URL> classpathLocations;
147 private Predicate<String> resourceFilter;
148 private Charset charset;
149 private Pattern scannableResourcePattern;
150
151 public Builder withClasspathLocations(Iterable<URL> classpathLocations) {
152 this.classpathLocations = classpathLocations;
153 return this;
154 }
155
156 public Builder withResourceFilter(Predicate<String> resourceFilter) {
157 this.resourceFilter = resourceFilter;
158 return this;
159 }
160
161 public Builder withCharset(Charset charset) {
162 this.charset = charset;
163 return this;
164 }
165
166 public Builder withScannableResourcePattern(Pattern scannableResourcePattern) {
167 this.scannableResourcePattern = scannableResourcePattern;
168 return this;
169 }
170
171 public ClasspathServiceConfiguration build() {
172 return new ConfiguredClasspathServiceConfiguration(classpathLocations, resourceFilter, charset, scannableResourcePattern);
173 }
174
175 @Value
176 private static class ConfiguredClasspathServiceConfiguration implements ClasspathServiceConfiguration {
177 private Iterable<URL> classpathLocations;
178 private Predicate<String> resourceFilter;
179 private Charset charset;
180 private Pattern monitoredResourcePattern;
181 }
182 }
183
184 private Predicate<? super URL> urlsFilter() {
185 final String excludedUrlExtensionsPattern = extensionsPattern(excludedUrlExtensions());
186 return URLPredicate.excludedExtensions(excludedUrlExtensionsPattern);
187
188 }
189
190 private String extensionsPattern(String[] extensions) {
191 return ".*\\.(" + Joiner.on('|').join(extensions) + ")$";
192 }
193
194 private String[] excludedUrlExtensions() {
195 return new String[]{
196
197 "dylib", "dll", "so", "jnilib",
198 "class",
199
200 "pom"
201 };
202 }
203
204 private FilterBuilder applyResourceExclusions(FilterBuilder filterBuilder) {
205
206 filterBuilder.exclude("mgnl-bootstrap.*");
207 filterBuilder.exclude("mgnl-resources.*");
208 filterBuilder.exclude("mgnl-nodetypes.*");
209 filterBuilder.exclude("mgnl-files.*");
210 filterBuilder.include("mgnl-i18n.*");
211
212 filterBuilder.exclude(extensionsPattern(excludedResourcesExtensions()));
213
214 final String[] excludedPackages = excludedPackages();
215 for (String excludedPackage : excludedPackages) {
216 filterBuilder.excludePackage(excludedPackage);
217 }
218 return filterBuilder;
219 }
220
221 private String[] excludedResourcesExtensions() {
222 return new String[]{
223 "package\\.html", "java", "jar",
224
225 "dylib", "dll", "jnilib", "class"
226 };
227 }
228
229 private String[] excludedPackages() {
230 return new String[]{
231 "META-INF",
232 "com.oracle.java",
233 "com.oracle.tools",
234 "com.sun",
235 "sun",
236 "oracle",
237 "java",
238 "javax",
239 "jdk",
240 "org.apache",
241 "lombok",
242 "VAADIN",
243 "gwt-unitCache"
244 };
245 }
246
247
248
249
250 private static class URLPredicate implements Predicate<URL> {
251
252 public static Predicate<? super URL> excludedExtensions(String excludedUrlExtensionsPattern) {
253 final Predicate<CharSequence> exclude = Predicates.containsPattern(excludedUrlExtensionsPattern).negate();
254 return new URLPredicate(exclude);
255 }
256
257 private final Predicate<CharSequence> predicate;
258
259 protected URLPredicate(Predicate<CharSequence> predicate) {
260 this.predicate = predicate;
261 }
262
263 @Override
264 public boolean test(URL input) {
265 return predicate.test(input.toString());
266 }
267 }
268
269 private static class StartsWith implements Predicate<String> {
270
271 private final List<String> prefixes;
272
273 public StartsWith(List<String> prefixes) {
274 this.prefixes = prefixes;
275 }
276
277 @Override
278 public boolean test(String input) {
279 for (String prefix : prefixes) {
280 if (input.startsWith(prefix)) {
281 return true;
282 }
283 }
284 return false;
285 }
286 }
287 }