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