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.util;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.UnsupportedEncodingException;
24 import java.net.URI;
25 import java.net.URL;
26 import java.net.URLDecoder;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Enumeration;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Set;
33 import java.util.jar.JarEntry;
34 import java.util.jar.JarInputStream;
35
36 import org.apache.logging.log4j.Logger;
37 import org.apache.logging.log4j.core.util.Charsets;
38 import org.apache.logging.log4j.core.util.Loader;
39 import org.apache.logging.log4j.status.StatusLogger;
40 import org.osgi.framework.FrameworkUtil;
41 import org.osgi.framework.wiring.BundleWiring;
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 LOGGER = 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 public void find(final Test test, final String... packageNames) {
141 if (packageNames == null) {
142 return;
143 }
144
145 for (final String pkg : packageNames) {
146 findInPackage(test, pkg);
147 }
148 }
149
150
151
152
153
154
155
156
157
158
159
160 public void findInPackage(final Test test, String packageName) {
161 packageName = packageName.replace('.', '/');
162 final ClassLoader loader = getClassLoader();
163 Enumeration<URL> urls;
164
165 try {
166 urls = loader.getResources(packageName);
167 } catch (final IOException ioe) {
168 LOGGER.warn("Could not read package: " + packageName, ioe);
169 return;
170 }
171
172 while (urls.hasMoreElements()) {
173 try {
174 final URL url = urls.nextElement();
175 final String urlPath = extractPath(url);
176
177 LOGGER.info("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
178
179 if (VFSZIP.equals(url.getProtocol())) {
180 final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
181 final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
182 @SuppressWarnings("resource")
183 final JarInputStream stream = new JarInputStream(newURL.openStream());
184 try {
185 loadImplementationsInJar(test, packageName, path, stream);
186 } finally {
187 close(stream, newURL);
188 }
189 } else if (BUNDLE_RESOURCE.equals(url.getProtocol())) {
190 loadImplementationsInBundle(test, packageName);
191 } else {
192 final File file = new File(urlPath);
193 if (file.isDirectory()) {
194 loadImplementationsInDirectory(test, packageName, file);
195 } else {
196 loadImplementationsInJar(test, packageName, file);
197 }
198 }
199 } catch (final IOException ioe) {
200 LOGGER.warn("could not read entries", ioe);
201 }
202 }
203 }
204
205 String extractPath(final URL url) throws UnsupportedEncodingException {
206 String urlPath = url.getPath();
207
208
209
210 if (urlPath.startsWith("jar:")) {
211 urlPath = urlPath.substring(4);
212 }
213
214 if (urlPath.startsWith("file:")) {
215 urlPath = urlPath.substring(5);
216 }
217
218 if (urlPath.indexOf('!') > 0) {
219 urlPath = urlPath.substring(0, urlPath.indexOf('!'));
220 }
221
222
223
224 final String protocol = url.getProtocol();
225 final List<String> neverDecode = Arrays.asList(VFSZIP, BUNDLE_RESOURCE);
226 if (neverDecode.contains(protocol)) {
227 return urlPath;
228 }
229 if (new File(urlPath).exists()) {
230
231 return urlPath;
232 }
233 urlPath = URLDecoder.decode(urlPath, Charsets.UTF_8.name());
234 return urlPath;
235 }
236
237 private void loadImplementationsInBundle(final Test test, final String packageName) {
238
239 @SuppressWarnings("RedundantCast")
240 final BundleWiring wiring = (BundleWiring) FrameworkUtil.getBundle(
241 ResolverUtil.class).adapt(BundleWiring.class);
242 @SuppressWarnings("unchecked")
243 final Collection<String> list = (Collection<String>) wiring.listResources(packageName, "*.class",
244 BundleWiring.LISTRESOURCES_RECURSE);
245 for (final String name : list) {
246 addIfMatching(test, name);
247 }
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263 private void loadImplementationsInDirectory(final Test test, final String parent, final File location) {
264 final File[] files = location.listFiles();
265 if (files == null) {
266 return;
267 }
268
269 StringBuilder builder;
270 for (final File file : files) {
271 builder = new StringBuilder();
272 builder.append(parent).append('/').append(file.getName());
273 final String packageOrClass = parent == null ? file.getName() : builder.toString();
274
275 if (file.isDirectory()) {
276 loadImplementationsInDirectory(test, packageOrClass, file);
277 } else if (isTestApplicable(test, file.getName())) {
278 addIfMatching(test, packageOrClass);
279 }
280 }
281 }
282
283 private boolean isTestApplicable(final Test test, final String path) {
284 return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
285 }
286
287
288
289
290
291
292
293
294
295
296 private void loadImplementationsInJar(final Test test, final String parent, final File jarFile) {
297 @SuppressWarnings("resource")
298 JarInputStream jarStream = null;
299 try {
300 jarStream = new JarInputStream(new FileInputStream(jarFile));
301 loadImplementationsInJar(test, parent, jarFile.getPath(), jarStream);
302 } catch (final FileNotFoundException ex) {
303 LOGGER.error("Could not search jar file '" + jarFile + "' for classes matching criteria: " + test
304 + " file not found", ex);
305 } catch (final IOException ioe) {
306 LOGGER.error("Could not search jar file '" + jarFile + "' for classes matching criteria: " + test
307 + " due to an IOException", ioe);
308 } finally {
309 close(jarStream, jarFile);
310 }
311 }
312
313
314
315
316
317 private void close(final JarInputStream jarStream, final Object source) {
318 if (jarStream != null) {
319 try {
320 jarStream.close();
321 } catch (final IOException e) {
322 LOGGER.error("Error closing JAR file stream for {}", source, e);
323 }
324 }
325 }
326
327
328
329
330
331
332
333
334
335
336 private void loadImplementationsInJar(final Test test, final String parent, final String path,
337 final JarInputStream stream) {
338
339 try {
340 JarEntry entry;
341
342 while ((entry = stream.getNextJarEntry()) != null) {
343 final String name = entry.getName();
344 if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
345 addIfMatching(test, name);
346 }
347 }
348 } catch (final IOException ioe) {
349 LOGGER.error("Could not search jar file '" + path + "' for classes matching criteria: " +
350 test + " due to an IOException", ioe);
351 }
352 }
353
354
355
356
357
358
359
360
361 protected void addIfMatching(final Test test, final String fqn) {
362 try {
363 final ClassLoader loader = getClassLoader();
364 if (test.doesMatchClass()) {
365 final String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
366 if (LOGGER.isDebugEnabled()) {
367 LOGGER.debug("Checking to see if class " + externalName + " matches criteria [" + test + ']');
368 }
369
370 final Class<?> type = loader.loadClass(externalName);
371 if (test.matches(type)) {
372 classMatches.add(type);
373 }
374 }
375 if (test.doesMatchResource()) {
376 URL url = loader.getResource(fqn);
377 if (url == null) {
378 url = loader.getResource(fqn.substring(1));
379 }
380 if (url != null && test.matches(url.toURI())) {
381 resourceMatches.add(url.toURI());
382 }
383 }
384 } catch (final Throwable t) {
385 LOGGER.warn("Could not examine class '" + fqn, t);
386 }
387 }
388
389
390
391
392
393 public interface Test {
394
395
396
397
398
399
400 boolean matches(Class<?> type);
401
402
403
404
405
406
407 boolean matches(URI resource);
408
409 boolean doesMatchClass();
410
411 boolean doesMatchResource();
412 }
413
414 }