1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.apache.logging.log4j.core.config.plugins;
16
17 import org.apache.logging.log4j.Logger;
18 import org.apache.logging.log4j.status.StatusLogger;
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.lang.annotation.Annotation;
24 import java.net.URI;
25 import java.net.URL;
26 import java.net.URLDecoder;
27 import java.util.Enumeration;
28 import java.util.HashSet;
29 import java.util.Set;
30 import java.util.jar.JarEntry;
31 import java.util.jar.JarInputStream;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public class ResolverUtil<T> {
70
71 private static final Logger LOG = StatusLogger.getLogger();
72
73
74 private Set<Class<? extends T>> classMatches = new HashSet<Class<?extends T>>();
75
76
77 private Set<URI> resourceMatches = new HashSet<URI>();
78
79
80
81
82
83 private ClassLoader classloader;
84
85
86
87
88
89
90
91 public Set<Class<? extends T>> getClasses() {
92 return classMatches;
93 }
94
95
96
97
98
99 public Set<URI> getResources() {
100 return resourceMatches;
101 }
102
103
104
105
106
107
108
109
110 public ClassLoader getClassLoader() {
111 return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
112 }
113
114
115
116
117
118
119
120 public void setClassLoader(ClassLoader classloader) { this.classloader = classloader; }
121
122
123
124
125
126
127
128
129
130
131 public void findImplementations(Class parent, String... packageNames) {
132 if (packageNames == null) {
133 return;
134 }
135
136 Test test = new IsA(parent);
137 for (String pkg : packageNames) {
138 findInPackage(test, pkg);
139 }
140 }
141
142
143
144
145
146
147
148
149 public void findSuffix(String suffix, String... packageNames) {
150 if (packageNames == null) {
151 return;
152 }
153
154 Test test = new NameEndsWith(suffix);
155 for (String pkg : packageNames) {
156 findInPackage(test, pkg);
157 }
158 }
159
160
161
162
163
164
165
166
167 public void findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
168 if (packageNames == null) {
169 return;
170 }
171
172 Test test = new AnnotatedWith(annotation);
173 for (String pkg : packageNames) {
174 findInPackage(test, pkg);
175 }
176 }
177
178 public void findNamedResource(String name, String... pathNames) {
179 if (pathNames == null) {
180 return;
181 }
182
183 Test test = new NameIs(name);
184 for (String pkg : pathNames) {
185 findInPackage(test, pkg);
186 }
187 }
188
189
190
191
192
193
194
195
196 public void find(Test test, String... packageNames) {
197 if (packageNames == null) {
198 return;
199 }
200
201 for (String pkg : packageNames) {
202 findInPackage(test, pkg);
203 }
204 }
205
206
207
208
209
210
211
212
213
214
215
216 public void findInPackage(Test test, String packageName) {
217 packageName = packageName.replace('.', '/');
218 ClassLoader loader = getClassLoader();
219 Enumeration<URL> urls;
220
221 try {
222 urls = loader.getResources(packageName);
223 } catch (IOException ioe) {
224 LOG.warn("Could not read package: " + packageName, ioe);
225 return;
226 }
227
228 while (urls.hasMoreElements()) {
229 try {
230 String urlPath = urls.nextElement().getFile();
231 urlPath = URLDecoder.decode(urlPath, "UTF-8");
232
233
234 if (urlPath.startsWith("file:")) {
235 urlPath = urlPath.substring(5);
236 }
237
238
239 if (urlPath.indexOf('!') > 0) {
240 urlPath = urlPath.substring(0, urlPath.indexOf('!'));
241 }
242
243 LOG.info("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
244 File file = new File(urlPath);
245 if (file.isDirectory()) {
246 loadImplementationsInDirectory(test, packageName, file);
247 } else {
248 loadImplementationsInJar(test, packageName, file);
249 }
250 } catch (IOException ioe) {
251 LOG.warn("could not read entries", ioe);
252 }
253 }
254 }
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269 private void loadImplementationsInDirectory(Test test, String parent, File location) {
270 File[] files = location.listFiles();
271 StringBuilder builder;
272
273 for (File file : files) {
274 builder = new StringBuilder();
275 builder.append(parent).append("/").append(file.getName());
276 String packageOrClass = parent == null ? file.getName() : builder.toString();
277
278 if (file.isDirectory()) {
279 loadImplementationsInDirectory(test, packageOrClass, file);
280 } else if (isTestApplicable(test, file.getName())) {
281 addIfMatching(test, packageOrClass);
282 }
283 }
284 }
285
286 private boolean isTestApplicable(Test test, String path) {
287 return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
288 }
289
290
291
292
293
294
295
296
297
298
299 private void loadImplementationsInJar(Test test, String parent, File jarfile) {
300
301 try {
302 JarEntry entry;
303 JarInputStream jarStream = new JarInputStream(new FileInputStream(jarfile));
304
305 while ((entry = jarStream.getNextJarEntry()) != null) {
306 String name = entry.getName();
307 if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
308 addIfMatching(test, name);
309 }
310 }
311 } catch (IOException ioe) {
312 LOG.error("Could not search jar file '" + jarfile + "' for classes matching criteria: " +
313 test + " due to an IOException", ioe);
314 }
315 }
316
317
318
319
320
321
322
323
324 protected void addIfMatching(Test test, String fqn) {
325 try {
326 ClassLoader loader = getClassLoader();
327 if (test.doesMatchClass()) {
328 String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
329 if (LOG.isDebugEnabled()) {
330 LOG.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
331 }
332
333 Class type = loader.loadClass(externalName);
334 if (test.matches(type)) {
335 classMatches.add((Class<T>) type);
336 }
337 }
338 if (test.doesMatchResource()) {
339 URL url = loader.getResource(fqn);
340 if (url == null) {
341 url = loader.getResource(fqn.substring(1));
342 }
343 if (url != null && test.matches(url.toURI())) {
344 resourceMatches.add(url.toURI());
345 }
346 }
347 } catch (Throwable t) {
348 LOG.warn("Could not examine class '" + fqn + "' due to a " +
349 t.getClass().getName() + " with message: " + t.getMessage());
350 }
351 }
352
353
354
355
356
357 public interface Test {
358
359
360
361
362
363
364 boolean matches(Class type);
365
366
367
368
369
370
371 boolean matches(URI resource);
372
373 boolean doesMatchClass();
374 boolean doesMatchResource();
375 }
376
377
378
379
380 public abstract static class ClassTest implements Test {
381 public boolean matches(URI resource) {
382 throw new UnsupportedOperationException();
383 }
384
385 public boolean doesMatchClass() {
386 return true;
387 }
388 public boolean doesMatchResource() {
389 return false;
390 }
391 }
392
393
394
395
396 public abstract static class ResourceTest implements Test {
397 public boolean matches(Class cls) {
398 throw new UnsupportedOperationException();
399 }
400
401 public boolean doesMatchClass() {
402 return false;
403 }
404 public boolean doesMatchResource() {
405 return true;
406 }
407 }
408
409
410
411
412
413 public static class IsA extends ClassTest {
414 private Class parent;
415
416
417
418
419
420 public IsA(Class parentType) { this.parent = parentType; }
421
422
423
424
425
426
427 public boolean matches(Class type) {
428 return type != null && parent.isAssignableFrom(type);
429 }
430
431 @Override
432 public String toString() {
433 return "is assignable to " + parent.getSimpleName();
434 }
435 }
436
437
438
439
440 public static class NameEndsWith extends ClassTest {
441 private String suffix;
442
443
444
445
446
447 public NameEndsWith(String suffix) { this.suffix = suffix; }
448
449
450
451
452
453
454 public boolean matches(Class type) {
455 return type != null && type.getName().endsWith(suffix);
456 }
457
458 @Override
459 public String toString() {
460 return "ends with the suffix " + suffix;
461 }
462 }
463
464
465
466
467
468 public static class AnnotatedWith extends ClassTest {
469 private Class<? extends Annotation> annotation;
470
471
472
473
474
475 public AnnotatedWith(Class<? extends Annotation> annotation) {
476 this.annotation = annotation;
477 }
478
479
480
481
482
483
484 public boolean matches(Class type) {
485 return type != null && type.isAnnotationPresent(annotation);
486 }
487
488 @Override
489 public String toString() {
490 return "annotated with @" + annotation.getSimpleName();
491 }
492 }
493
494
495
496
497 public static class NameIs extends ResourceTest {
498 private String name;
499
500 public NameIs(String name) { this.name = "/" + name; }
501
502 public boolean matches(URI resource) {
503 return (resource.getPath().endsWith(name));
504 }
505
506 @Override public String toString() {
507 return "named " + name;
508 }
509 }
510 }