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