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