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