1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.any23.plugin;
19
20 import org.apache.any23.cli.Tool;
21 import org.apache.any23.configuration.DefaultConfiguration;
22 import org.apache.any23.extractor.ExtractorFactory;
23 import org.apache.any23.extractor.ExtractorGroup;
24 import org.apache.any23.extractor.ExtractorRegistry;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import java.io.File;
29 import java.io.FilenameFilter;
30 import java.io.IOException;
31 import java.net.MalformedURLException;
32 import java.net.URL;
33 import java.net.URLClassLoader;
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.ServiceLoader;
39 import java.util.Set;
40
41
42
43
44
45
46 public class Any23PluginManager {
47
48
49
50
51 public static final String CLI_PACKAGE = Tool.class.getPackage().getName();
52
53
54
55
56 public static final String PLUGIN_DIRS_PROPERTY = "any23.plugin.dirs";
57
58
59
60
61 public static final String PLUGIN_DIRS_LIST_SEPARATOR = ":";
62
63
64
65
66 private static final Logger logger = LoggerFactory.getLogger(Any23PluginManager.class);
67
68
69
70
71 private static final Any23PluginManager instance = new Any23PluginManager();
72
73
74
75
76 private final DynamicClassLoader dynamicClassLoader;
77
78
79
80
81 private Any23PluginManager() {
82 dynamicClassLoader = new DynamicClassLoader();
83 }
84
85
86
87
88 public static synchronized Any23PluginManager getInstance() {
89 return instance;
90 }
91
92
93
94
95
96
97
98
99
100 public synchronized boolean loadJAR(File jar) {
101 if (jar == null) {
102 throw new NullPointerException("jar file cannot be null.");
103 }
104 if (!jar.isFile() && !jar.exists()) {
105 throw new IllegalArgumentException(String.format(java.util.Locale.ROOT,
106 "Invalid JAR [%s], must be an existing file.", jar.getAbsolutePath()));
107 }
108 return dynamicClassLoader.addJAR(jar);
109 }
110
111
112
113
114
115
116
117
118
119 public synchronized Throwable[] loadJARs(File... jars) {
120 final List<Throwable> result = new ArrayList<>();
121 for (File jar : jars) {
122 try {
123 loadJAR(jar);
124 } catch (Throwable t) {
125 result.add(new IllegalArgumentException(
126 String.format(java.util.Locale.ROOT, "Error while loading jar [%s]", jar.getAbsolutePath()),
127 t));
128 }
129 }
130 return result.toArray(new Throwable[result.size()]);
131 }
132
133
134
135
136
137
138
139
140
141
142 public synchronized boolean loadClassDir(File classDir) {
143 if (classDir == null) {
144 throw new NullPointerException("classDir cannot be null.");
145 }
146 if (!classDir.isDirectory() && !classDir.exists()) {
147 throw new IllegalArgumentException(String.format(java.util.Locale.ROOT,
148 "Invalid class dir [%s], must be an existing file.", classDir.getAbsolutePath()));
149 }
150 return dynamicClassLoader.addClassDir(classDir);
151 }
152
153
154
155
156
157
158
159
160
161 public synchronized Throwable[] loadClassDirs(File... classDirs) {
162 final List<Throwable> result = new ArrayList<>();
163 for (File classDir : classDirs) {
164 try {
165 loadClassDir(classDir);
166 } catch (Throwable t) {
167 result.add(new IllegalArgumentException(String.format(java.util.Locale.ROOT,
168 "Error while loading class dir [%s]", classDir.getAbsolutePath()), t));
169 }
170 }
171 return result.toArray(new Throwable[result.size()]);
172 }
173
174
175
176
177
178
179
180
181
182
183 public synchronized boolean loadJARDir(File jarDir) {
184 if (jarDir == null)
185 throw new NullPointerException("JAR dir must be not null.");
186 if (!jarDir.exists())
187 throw new IllegalArgumentException("Given directory doesn't exist:" + jarDir.getAbsolutePath());
188 if (!jarDir.isDirectory())
189 throw new IllegalArgumentException(
190 "given file exists and it is not a directory: " + jarDir.getAbsolutePath());
191 boolean loaded = true;
192 for (File jarFile : jarDir.listFiles(new FilenameFilter() {
193 @Override
194 public boolean accept(File dir, String name) {
195 return name.endsWith(".jar");
196 }
197 })) {
198 loaded &= loadJAR(jarFile);
199 }
200 return loaded;
201 }
202
203
204
205
206
207
208
209
210
211 public synchronized Throwable[] loadFiles(File... files) {
212 final List<Throwable> errors = new ArrayList<>();
213 for (File file : files) {
214 try {
215 if (file.isFile() && file.getName().endsWith(".jar")) {
216 loadJAR(file);
217 } else if (file.isDirectory()) {
218 if (file.getName().endsWith("classes")) {
219 loadClassDir(file);
220 } else {
221 loadJARDir(file);
222 }
223 } else {
224 throw new IllegalArgumentException("Cannot handle file " + file.getAbsolutePath());
225 }
226 } catch (Throwable t) {
227 errors.add(t);
228 }
229 }
230 return errors.toArray(new Throwable[errors.size()]);
231 }
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248 public synchronized <T> Iterator<T> getPlugins(final Class<T> type) throws IOException {
249 return ServiceLoader.load(type, dynamicClassLoader).iterator();
250 }
251
252
253
254
255
256
257
258
259
260 public synchronized Iterator<Tool> getTools() throws IOException {
261 return getPlugins(Tool.class);
262 }
263
264
265
266
267
268
269
270
271
272 @SuppressWarnings("rawtypes")
273 public synchronized Iterator<ExtractorFactory> getExtractors() throws IOException {
274 return getPlugins(ExtractorFactory.class);
275 }
276
277
278
279
280
281
282
283
284
285 public synchronized String loadPlugins(File... pluginLocations) {
286 final StringBuilder report = new StringBuilder();
287 report.append("\nLoading plugins from locations {\n");
288 for (File pluginLocation : pluginLocations) {
289 report.append(pluginLocation.getAbsolutePath()).append('\n');
290 }
291 report.append("}\n");
292
293 final Throwable[] errors = loadFiles(pluginLocations);
294 if (errors.length > 0) {
295 report.append("The following errors occurred while loading plugins {\n");
296 for (Throwable error : errors) {
297 report.append(error);
298 report.append("\n\n\n");
299 }
300 report.append("}\n");
301 }
302 return report.toString();
303 }
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 public synchronized ExtractorGroup configureExtractors(final File... pluginLocations)
322 throws IOException, IllegalAccessException, InstantiationException {
323
324 final String pluginsReport = loadPlugins(pluginLocations);
325 logger.info(pluginsReport);
326
327 final StringBuilder report = new StringBuilder();
328 try {
329 final List<ExtractorFactory<?>> newFactoryList = new ArrayList<>();
330 @SuppressWarnings("rawtypes")
331 Iterator<ExtractorFactory> extractors = getExtractors();
332 while (extractors.hasNext()) {
333 ExtractorFactory<?> factory = extractors.next();
334
335 report.append("\n - found plugin: ").append(factory.getExtractorName()).append("\n");
336
337 newFactoryList.add(factory);
338 }
339
340 if (newFactoryList.isEmpty()) {
341 report.append("\n=== No plugins have been found.===\n");
342 }
343
344 return new ExtractorGroup(newFactoryList);
345 } finally {
346 logger.info(report.toString());
347 }
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366 public synchronized ExtractorGroup configureExtractors(ExtractorGroup initialExtractorGroup)
367 throws IOException, InstantiationException, IllegalAccessException {
368 final String pluginDirs = DefaultConfiguration.singleton().getPropertyOrFail(PLUGIN_DIRS_PROPERTY);
369 final File[] pluginLocations = getPluginLocations(pluginDirs);
370 return configureExtractors(pluginLocations);
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391 public synchronized ExtractorGroup getApplicableExtractors(ExtractorRegistry registry, File... pluginLocations)
392 throws IOException, IllegalAccessException, InstantiationException {
393 return configureExtractors(pluginLocations);
394 }
395
396
397
398
399
400
401
402
403
404
405
406
407 public synchronized Iterator<Tool> getApplicableTools(File... pluginLocations) throws IOException {
408 final String report = loadPlugins(pluginLocations);
409 logger.info(report);
410 return getTools();
411 }
412
413
414
415
416
417
418
419
420 private File[] getPluginLocations(String pluginDirsList) {
421 final String[] locationsStr = pluginDirsList.split(PLUGIN_DIRS_LIST_SEPARATOR);
422 final List<File> locations = new ArrayList<>();
423 for (String locationStr : locationsStr) {
424 final File location = new File(locationStr);
425 if (!location.exists()) {
426 throw new IllegalArgumentException(
427 String.format(java.util.Locale.ROOT, "Plugin location '%s' cannot be found.", locationStr));
428 }
429 locations.add(location);
430 }
431 return locations.toArray(new File[locations.size()]);
432 }
433
434
435
436
437 private static final class DynamicClassLoader extends URLClassLoader {
438
439 private final Set<String> addedURLs = new HashSet<>();
440
441 private final List<File> jars;
442
443 private final List<File> dirs;
444
445 public DynamicClassLoader(URL[] urls) {
446 super(urls, Any23PluginManager.class.getClassLoader());
447 jars = new ArrayList<>();
448 dirs = new ArrayList<>();
449 }
450
451 public DynamicClassLoader() {
452 this(new URL[0]);
453 }
454
455 public boolean addClassDir(File classDir) {
456 final String urlPath = "file://" + classDir.getAbsolutePath() + "/";
457 try {
458 if (addURL(urlPath)) {
459 dirs.add(classDir);
460 return true;
461 }
462 return false;
463 } catch (MalformedURLException murle) {
464 throw new RuntimeException("Invalid dir URL.", murle);
465 }
466 }
467
468 public boolean addJAR(File jar) {
469 final String urlPath = "jar:file://" + jar.getAbsolutePath() + "!/";
470 try {
471 if (addURL(urlPath)) {
472 jars.add(jar);
473 return true;
474 }
475 return false;
476 } catch (MalformedURLException murle) {
477 throw new RuntimeException("Invalid JAR URL.", murle);
478 }
479 }
480
481 private boolean addURL(String urlPath) throws MalformedURLException {
482 if (addedURLs.contains(urlPath)) {
483 return false;
484 }
485 super.addURL(new URL(urlPath));
486 addedURLs.add(urlPath);
487 return true;
488 }
489 }
490
491 }