From 5b30e5fed14a38d299020dc408820716a20e0c88 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Wed, 25 Sep 2013 18:31:12 +0300 Subject: [PATCH] add PluginClasspath - now you can add any classes and lib directories to plugin classpath --- demo/app/src/main/resources/log4j.properties | 2 +- .../fortsoft/pf4j/DefaultExtensionFinder.java | 63 +--------- .../pf4j/DefaultPluginDescriptorFinder.java | 76 ++---------- .../fortsoft/pf4j/DefaultPluginManager.java | 18 ++- .../pf4j/ManifestPluginDescriptorFinder.java | 102 ++++++++++++++++ .../ro/fortsoft/pf4j/PluginClasspath.java | 59 +++++++++ .../java/ro/fortsoft/pf4j/PluginLoader.java | 114 +++++++++--------- .../fortsoft/pf4j/SezpozExtensionFinder.java | 80 ++++++++++++ 8 files changed, 326 insertions(+), 188 deletions(-) create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java create mode 100644 pf4j/src/main/java/ro/fortsoft/pf4j/SezpozExtensionFinder.java diff --git a/demo/app/src/main/resources/log4j.properties b/demo/app/src/main/resources/log4j.properties index f2160e8..f766388 100644 --- a/demo/app/src/main/resources/log4j.properties +++ b/demo/app/src/main/resources/log4j.properties @@ -2,5 +2,5 @@ log4j.rootLogger=DEBUG,Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout -log4j.appender.Console.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n +log4j.appender.Console.layout.conversionPattern=%-5p - %-30.30c{1} - %m\n diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java index 0de6318..66c1936 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 Decebal Suiu + * Copyright 2013 Decebal Suiu * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * the License. You may obtain a copy of the License in the LICENSE file, or at: @@ -12,69 +12,16 @@ */ package ro.fortsoft.pf4j; -import java.lang.reflect.AnnotatedElement; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import net.java.sezpoz.Index; -import net.java.sezpoz.IndexItem; - /** - * Using Sezpoz(http://sezpoz.java.net/) for extensions discovery. + * The default implementation for ExtensionFinder. + * Now, this class it's a "link" to {@link ro.fortsoft.pf4j.SezpozExtensionFinder}. * * @author Decebal Suiu */ -public class DefaultExtensionFinder implements ExtensionFinder { +public class DefaultExtensionFinder extends SezpozExtensionFinder { - private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class); - - private volatile List> indices; - private ClassLoader classLoader; - public DefaultExtensionFinder(ClassLoader classLoader) { - this.classLoader = classLoader; + super(classLoader); } - @Override - public List> find(Class type) { - log.debug("Find extensions for {}", type); - List> result = new ArrayList>(); - getIndices(); -// System.out.println("indices = "+ indices); - for (IndexItem item : indices) { - try { - AnnotatedElement element = item.element(); - Class extensionType = (Class) element; - log.debug("Checking extension type {}", extensionType); - if (type.isAssignableFrom(extensionType)) { - Object instance = item.instance(); - if (instance != null) { - log.debug("Added extension {}", extensionType); - result.add(new ExtensionWrapper(type.cast(instance), item.annotation().ordinal())); - } - } - } catch (InstantiationException e) { - log.error(e.getMessage(), e); - } - } - - return result; - } - - private List> getIndices() { - if (indices == null) { - indices = new ArrayList>(); - Iterator> it = Index.load(Extension.class, Object.class, classLoader).iterator(); - while (it.hasNext()) { - indices.add(it.next()); - } - } - - return indices; - } - } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java index 3e23ff1..144fc6e 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 Decebal Suiu + * Copyright 2013 Decebal Suiu * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * the License. You may obtain a copy of the License in the LICENSE file, or at: @@ -12,78 +12,16 @@ */ package ro.fortsoft.pf4j; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -import ro.fortsoft.pf4j.util.StringUtils; - /** - * Read the plugin descriptor from the manifest file. - * + * The default implementation for PluginDescriptorFinder. + * Now, this class it's a "link" to {@link ro.fortsoft.pf4j.ManifestPluginDescriptorFinder}. + * * @author Decebal Suiu */ -public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { - - @Override - public PluginDescriptor find(File pluginRepository) throws PluginException { - // TODO it's ok with classes/ ? - File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF"); - if (!manifestFile.exists()) { - throw new PluginException("Cannot find '" + manifestFile + "' file"); - } - - FileInputStream input = null; - try { - input = new FileInputStream(manifestFile); - } catch (FileNotFoundException e) { - // not happening - } - - Manifest manifest = null; - try { - manifest = new Manifest(input); - } catch (IOException e) { - throw new PluginException(e.getMessage(), e); - } finally { - try { - input.close(); - } catch (IOException e) { - throw new PluginException(e.getMessage(), e); - } - } - - PluginDescriptor pluginDescriptor = new PluginDescriptor(); - - // TODO validate !!! - Attributes attrs = manifest.getMainAttributes(); - String id = attrs.getValue("Plugin-Id"); - if (StringUtils.isEmpty(id)) { - throw new PluginException("Plugin-Id cannot be empty"); - } - pluginDescriptor.setPluginId(id); - - String clazz = attrs.getValue("Plugin-Class"); - if (StringUtils.isEmpty(clazz)) { - throw new PluginException("Plugin-Class cannot be empty"); - } - pluginDescriptor.setPluginClass(clazz); - - String version = attrs.getValue("Plugin-Version"); - if (StringUtils.isEmpty(version)) { - throw new PluginException("Plugin-Version cannot be empty"); - } - pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version)); - - String provider = attrs.getValue("Plugin-Provider"); - pluginDescriptor.setProvider(provider); - String dependencies = attrs.getValue("Plugin-Dependencies"); - pluginDescriptor.setDependencies(dependencies); +public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder { - return pluginDescriptor; + public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) { + super(pluginClasspath); } } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index a6c9fad..1c239dc 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -47,9 +47,11 @@ public class DefaultPluginManager implements PluginManager { */ private File pluginsDirectory; - private ExtensionFinder extensionFinder; + private final ExtensionFinder extensionFinder; - private PluginDescriptorFinder pluginDescriptorFinder; + private final PluginDescriptorFinder pluginDescriptorFinder; + + private final PluginClasspath pluginClasspath; /** * A map of plugins this manager is responsible for (the key is the 'pluginId'). @@ -114,6 +116,7 @@ public class DefaultPluginManager implements PluginManager { disabledPlugins = new ArrayList(); compoundClassLoader = new CompoundClassLoader(); + pluginClasspath = createPluginClasspath(); pluginDescriptorFinder = createPluginDescriptorFinder(); extensionFinder = createExtensionFinder(); @@ -278,7 +281,7 @@ public class DefaultPluginManager implements PluginManager { * Add the possibility to override the PluginDescriptorFinder. */ protected PluginDescriptorFinder createPluginDescriptorFinder() { - return new DefaultPluginDescriptorFinder(); + return new DefaultPluginDescriptorFinder(pluginClasspath); } /** @@ -288,6 +291,13 @@ public class DefaultPluginManager implements PluginManager { return new DefaultExtensionFinder(compoundClassLoader); } + /** + * Add the possibility to override the PluginClassPath. + */ + protected PluginClasspath createPluginClasspath() { + return new PluginClasspath(); + } + protected boolean isPluginDisabled(String pluginId) { if (enabledPlugins.isEmpty()) { return disabledPlugins.contains(pluginId); @@ -325,7 +335,7 @@ public class DefaultPluginManager implements PluginManager { // load plugin log.debug("Loading plugin '{}'", pluginPath); - PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory); + PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath); pluginLoader.load(); log.debug("Loaded plugin '{}'", pluginPath); diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java new file mode 100644 index 0000000..fd82793 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package ro.fortsoft.pf4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ro.fortsoft.pf4j.util.StringUtils; + +/** + * Read the plugin descriptor from the manifest file. + * + * @author Decebal Suiu + */ +public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder { + + private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class); + + private PluginClasspath pluginClasspath; + + public ManifestPluginDescriptorFinder(PluginClasspath pluginClasspath) { + this.pluginClasspath = pluginClasspath; + } + + @Override + public PluginDescriptor find(File pluginRepository) throws PluginException { + // TODO it's ok with first classes directory? Another idea is to specify in PluginClasspath the folder. + String classes = pluginClasspath.getClassesDirectories().get(0); + File manifestFile = new File(pluginRepository, classes + "/META-INF/MANIFEST.MF"); + log.debug("Lookup plugin descriptor in '{}'", manifestFile); + if (!manifestFile.exists()) { + throw new PluginException("Cannot find '" + manifestFile + "' file"); + } + + FileInputStream input = null; + try { + input = new FileInputStream(manifestFile); + } catch (FileNotFoundException e) { + // not happening + } + + Manifest manifest = null; + try { + manifest = new Manifest(input); + } catch (IOException e) { + throw new PluginException(e.getMessage(), e); + } finally { + try { + input.close(); + } catch (IOException e) { + throw new PluginException(e.getMessage(), e); + } + } + + PluginDescriptor pluginDescriptor = new PluginDescriptor(); + + // TODO validate !!! + Attributes attrs = manifest.getMainAttributes(); + String id = attrs.getValue("Plugin-Id"); + if (StringUtils.isEmpty(id)) { + throw new PluginException("Plugin-Id cannot be empty"); + } + pluginDescriptor.setPluginId(id); + + String clazz = attrs.getValue("Plugin-Class"); + if (StringUtils.isEmpty(clazz)) { + throw new PluginException("Plugin-Class cannot be empty"); + } + pluginDescriptor.setPluginClass(clazz); + + String version = attrs.getValue("Plugin-Version"); + if (StringUtils.isEmpty(version)) { + throw new PluginException("Plugin-Version cannot be empty"); + } + pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version)); + + String provider = attrs.getValue("Plugin-Provider"); + pluginDescriptor.setProvider(provider); + String dependencies = attrs.getValue("Plugin-Dependencies"); + pluginDescriptor.setDependencies(dependencies); + + return pluginDescriptor; + } + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java new file mode 100644 index 0000000..23cd8f6 --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package ro.fortsoft.pf4j; + +import java.util.ArrayList; +import java.util.List; + +/** + * The classpath of the plugin after it was unpacked. + * It contains classes directories and lib directories (directories that contains jars). + * All directories are relativ to plugin repository. + * The default values are "classes" and "lib". + * + * @author Decebal Suiu + */ +public class PluginClasspath { + + private static final String DEFAULT_CLASSES_DIRECTORY = "classes"; + private static final String DEFAULT_LIB_DIRECTORY = "lib"; + + private List classesDirectories; + private List libDirectories; + + public PluginClasspath() { + classesDirectories = new ArrayList(); + libDirectories = new ArrayList(); + + // add defaults + classesDirectories.add(DEFAULT_CLASSES_DIRECTORY); + libDirectories.add(DEFAULT_LIB_DIRECTORY); + } + + public List getClassesDirectories() { + return classesDirectories; + } + + public void setClassesDirectories(List classesDirectories) { + this.classesDirectories = classesDirectories; + } + + public List getLibDirectories() { + return libDirectories; + } + + public void setLibDirectories(List libDirectories) { + this.libDirectories = libDirectories; + } + +} diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java index d65b3f8..8bd2daa 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java @@ -15,6 +15,7 @@ package ro.fortsoft.pf4j; import java.io.File; import java.io.FileFilter; import java.net.MalformedURLException; +import java.util.List; import java.util.Vector; import org.slf4j.Logger; @@ -40,22 +41,12 @@ class PluginLoader { */ private File pluginRepository; - /* - * The directory with '.class' files. - */ - private File classesDirectory; - - /* - * The directory with '.jar' files. - */ - private File libDirectory; - + private PluginClasspath pluginClasspath; private PluginClassLoader pluginClassLoader; - public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository) { + public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository, PluginClasspath pluginClasspath) { this.pluginRepository = pluginRepository; - classesDirectory = new File(pluginRepository, "classes"); - libDirectory = new File(pluginRepository, "lib"); + this.pluginClasspath = pluginClasspath; ClassLoader parent = getClass().getClassLoader(); pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent); log.debug("Created class loader {}", pluginClassLoader); @@ -77,6 +68,60 @@ class PluginLoader { return loadClasses() && loadJars(); } + private boolean loadClasses() { + List classesDirectories = pluginClasspath.getClassesDirectories(); + + // add each classes directory to plugin class loader + for (String classesDirectory : classesDirectories) { + // make 'classesDirectory' absolute + File file = new File(pluginRepository, classesDirectory).getAbsoluteFile(); + + if (file.exists() && file.isDirectory()) { + log.debug("Found '{}' directory", file.getPath()); + + try { + pluginClassLoader.addURL(file.toURI().toURL()); + log.debug("Added '{}' to the class loader path", file); + } catch (MalformedURLException e) { + e.printStackTrace(); + log.error(e.getMessage(), e); + return false; + } + } + } + + return true; + } + + /** + * Add all *.jar files from lib directories to class loader. + */ + private boolean loadJars() { + List libDirectories = pluginClasspath.getLibDirectories(); + + // add each jars directory to plugin class loader + for (String libDirectory : libDirectories) { + // make 'libDirectory' absolute + File file = new File(pluginRepository, libDirectory).getAbsoluteFile(); + + // collect all jars from current lib directory in jars variable + Vector jars = new Vector(); + getJars(jars, file); + for (File jar : jars) { + try { + pluginClassLoader.addURL(jar.toURI().toURL()); + log.debug("Added '{}' to the class loader path", jar); + } catch (MalformedURLException e) { + e.printStackTrace(); + log.error(e.getMessage(), e); + return false; + } + } + } + + return true; + } + private void getJars(Vector bucket, File file) { FileFilter jarFilter = new JarFileFilter(); FileFilter directoryFilter = new DirectoryFileFilter(); @@ -95,47 +140,4 @@ class PluginLoader { } } - private boolean loadClasses() { - // make 'classesDirectory' absolute - classesDirectory = classesDirectory.getAbsoluteFile(); - - if (classesDirectory.exists() && classesDirectory.isDirectory()) { - log.debug("Found '{}' directory", classesDirectory.getPath()); - - try { - pluginClassLoader.addURL(classesDirectory.toURI().toURL()); - log.debug("Added '{}' to the class loader path", classesDirectory); - } catch (MalformedURLException e) { - e.printStackTrace(); - log.error(e.getMessage(), e); - return false; - } - } - - return true; - } - - /** - * Add all *.jar files from '/lib' directory. - */ - private boolean loadJars() { - // make 'jarDirectory' absolute - libDirectory = libDirectory.getAbsoluteFile(); - - Vector jars = new Vector(); - getJars(jars, libDirectory); - for (File jar : jars) { - try { - pluginClassLoader.addURL(jar.toURI().toURL()); - log.debug("Added '{}' to the class loader path", jar); - } catch (MalformedURLException e) { - e.printStackTrace(); - log.error(e.getMessage(), e); - return false; - } - } - - return true; - } - } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/SezpozExtensionFinder.java b/pf4j/src/main/java/ro/fortsoft/pf4j/SezpozExtensionFinder.java new file mode 100644 index 0000000..3f5014c --- /dev/null +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/SezpozExtensionFinder.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 Decebal Suiu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with + * the License. You may obtain a copy of the License in the LICENSE file, or at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package ro.fortsoft.pf4j; + +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.java.sezpoz.Index; +import net.java.sezpoz.IndexItem; + +/** + * Using Sezpoz(http://sezpoz.java.net/) for extensions discovery. + * + * @author Decebal Suiu + */ +public class SezpozExtensionFinder implements ExtensionFinder { + + private static final Logger log = LoggerFactory.getLogger(SezpozExtensionFinder.class); + + private volatile List> indices; + private ClassLoader classLoader; + + public SezpozExtensionFinder(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public List> find(Class type) { + log.debug("Find extensions for {}", type); + List> result = new ArrayList>(); + getIndices(); +// System.out.println("indices = "+ indices); + for (IndexItem item : indices) { + try { + AnnotatedElement element = item.element(); + Class extensionType = (Class) element; + log.debug("Checking extension type {}", extensionType); + if (type.isAssignableFrom(extensionType)) { + Object instance = item.instance(); + if (instance != null) { + log.debug("Added extension {}", extensionType); + result.add(new ExtensionWrapper(type.cast(instance), item.annotation().ordinal())); + } + } + } catch (InstantiationException e) { + log.error(e.getMessage(), e); + } + } + + return result; + } + + private List> getIndices() { + if (indices == null) { + indices = new ArrayList>(); + Iterator> it = Index.load(Extension.class, Object.class, classLoader).iterator(); + while (it.hasNext()) { + indices.add(it.next()); + } + } + + return indices; + } + +} -- 2.39.5