Using Maven
-------------------
-First you must install the pf4j artifacts in your local maven repository with:
-
- mvn clean install
-
-I will upload these artifacts in maven central repository as soon as possible.
-
In your pom.xml you must define the dependencies to PF4J artifacts with:
```xml
It's very simple to add pf4j in your application:
public static void main(String[] args) {
- ...
-
+ ...
+
PluginManager pluginManager = new DefaultPluginManager();
pluginManager.loadPlugins();
pluginManager.startPlugins();
You can retrieve all extensions for an extension point with:
- List<ExtensionWrapper<Greeting>> greetings = pluginManager.getExtensions(Greeting.class);
- for (ExtensionWrapper<Greeting> greeting : greetings) {
- System.out.println(">>> " + greeting.getInstance().getGreeting());
+ List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
+ for (Greeting greeting : greetings) {
+ System.out.println(">>> " + greeting.getGreeting());
}
+The output is:
+
+ >>> Welcome
+ >>> Hello
For more information please see the demo sources.
import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager;
-import ro.fortsoft.pf4j.ExtensionWrapper;
import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.demo.api.Greeting;
pluginManager.loadPlugins();
pluginManager.startPlugins();
- List<ExtensionWrapper<Greeting>> greetings = pluginManager.getExtensions(Greeting.class);
- for (ExtensionWrapper<Greeting> greeting : greetings) {
- System.out.println(">>> " + greeting.getInstance().getGreeting());
+ List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
+ for (Greeting greeting : greetings) {
+ System.out.println(">>> " + greeting.getGreeting());
}
pluginManager.stopPlugins();
--- /dev/null
+/*
+ * 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;
+
+/**
+ * CyclicDependencyException will be thrown if a cyclic dependency is detected.
+ *
+ * @author Decebal Suiu
+ */
+class CyclicDependencyException extends PluginException {
+
+ private static final long serialVersionUID = 1L;
+
+ public CyclicDependencyException(String message) {
+ super(message);
+ }
+
+}
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFilter;
-
/**
* Default implementation of the PluginManager interface.
*
/**
* A map of plugins this manager is responsible for (the key is the 'pluginId').
*/
- private Map<String, Plugin> plugins;
+ private Map<String, PluginWrapper> plugins;
/**
* A map of plugin class loaders (he key is the 'pluginId').
/**
* A list with unresolved plugins (unresolved dependency).
*/
- private List<Plugin> unresolvedPlugins;
+ private List<PluginWrapper> unresolvedPlugins;
/**
* A list with resolved plugins (resolved dependency).
*/
- private List<Plugin> resolvedPlugins;
+ private List<PluginWrapper> resolvedPlugins;
/**
* A list with disabled plugins.
*/
- private List<Plugin> disabledPlugins;
+ private List<PluginWrapper> disabledPlugins;
private UberClassLoader uberClassLoader;
*/
public DefaultPluginManager(File pluginsDirectory) {
this.pluginsDirectory = pluginsDirectory;
- plugins = new HashMap<String, Plugin>();
+ plugins = new HashMap<String, PluginWrapper>();
pluginClassLoaders = new HashMap<String, PluginClassLoader>();
pathToIdMap = new HashMap<String, String>();
- unresolvedPlugins = new ArrayList<Plugin>();
- resolvedPlugins = new ArrayList<Plugin>();
- disabledPlugins = new ArrayList<Plugin>();
+ unresolvedPlugins = new ArrayList<PluginWrapper>();
+ resolvedPlugins = new ArrayList<PluginWrapper>();
+ disabledPlugins = new ArrayList<PluginWrapper>();
pluginDescriptorFinder = new DefaultPluginDescriptorFinder();
uberClassLoader = new UberClassLoader();
extensionFinder = new DefaultExtensionFinder(uberClassLoader);
/**
* Retrieves all active plugins.
*/
- public List<Plugin> getPlugins() {
- return new ArrayList<Plugin>(plugins.values());
+ public List<PluginWrapper> getPlugins() {
+ return new ArrayList<PluginWrapper>(plugins.values());
}
- public List<Plugin> getResolvedPlugins() {
+ public List<PluginWrapper> getResolvedPlugins() {
return resolvedPlugins;
}
- public Plugin getPlugin(String pluginId) {
+ public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
- public List<Plugin> getUnresolvedPlugins() {
+ public List<PluginWrapper> getUnresolvedPlugins() {
return unresolvedPlugins;
}
- public List<Plugin> getDisabledPlugins() {
+ public List<PluginWrapper> getDisabledPlugins() {
return disabledPlugins;
}
* Start all active plugins.
*/
public void startPlugins() {
- List<Plugin> resolvedPlugins = getResolvedPlugins();
- for (Plugin plugin : resolvedPlugins) {
+ List<PluginWrapper> resolvedPlugins = getResolvedPlugins();
+ for (PluginWrapper pluginWrapper : resolvedPlugins) {
try {
- plugin.start();
+ LOG.info("Start plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'");
+ pluginWrapper.getPlugin().start();
} catch (PluginException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ LOG.error(e.getMessage(), e);
}
}
}
* Stop all active plugins.
*/
public void stopPlugins() {
- List<Plugin> resolvedPlugins = getResolvedPlugins();
- for (Plugin plugin : resolvedPlugins) {
+ List<PluginWrapper> resolvedPlugins = getResolvedPlugins();
+ for (PluginWrapper pluginWrapper : resolvedPlugins) {
try {
- plugin.stop();
+ LOG.info("Stop plugin '" + pluginWrapper.getDescriptor().getPluginId() + "'");
+ pluginWrapper.getPlugin().stop();
} catch (PluginException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ LOG.error(e.getMessage(), e);
}
}
}
expandPluginArchive(zipFile);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
- e.printStackTrace();
}
}
for (String directory : directories) {
try {
loadPlugin(directory);
- } catch (Exception e) {
+ } catch (PluginException e) {
LOG.error(e.getMessage(), e);
- e.printStackTrace();
}
}
}
// resolve 'unresolvedPlugins'
- resolvePlugins();
+ try {
+ resolvePlugins();
+ } catch (PluginException e) {
+ LOG.error(e.getMessage(), e);
+ }
}
/**
return pluginClassLoaders.get(pluginId);
}
- public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type) {
- return extensionFinder.find(type);
+ public <T> List<T> getExtensions(Class<T> type) {
+ List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
+ List<T> extensions = new ArrayList<T>(extensionsWrapper.size());
+ for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
+ extensions.add(extensionWrapper.getInstance());
+ }
+
+ return extensions;
}
- private void loadPlugin(String fileName) throws Exception {
+ private void loadPlugin(String fileName) throws PluginException {
// test for plugin directory
File pluginDirectory = new File(pluginsDirectory, fileName);
if (!pluginDirectory.isDirectory()) {
// load plugin
LOG.debug("Loading plugin '" + pluginPath + "'");
- PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor);
- PluginLoader pluginLoader = new PluginLoader(this, pluginWrapper, pluginDirectory);
+ PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory);
pluginLoader.load();
LOG.debug("Loaded plugin '" + pluginPath + "'");
- // set some variables in plugin wrapper
- pluginWrapper.setPluginPath(pluginPath);
- pluginWrapper.setPluginClassLoader(pluginLoader.getPluginClassLoader());
-
- // create the plugin instance
- LOG.debug("Creating instance for plugin '" + pluginPath + "'");
- Plugin plugin = getPluginInstance(pluginWrapper, pluginLoader);
- LOG.debug("Created instance '" + plugin + "' for plugin '" + pluginPath + "'");
+ // create the plugin wrapper
+ LOG.debug("Creating wrapper for plugin '" + pluginPath + "'");
+ PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
+ LOG.debug("Created wrapper '" + pluginWrapper + "' for plugin '" + pluginPath + "'");
String pluginId = pluginDescriptor.getPluginId();
// add plugin to the list with plugins
- plugins.put(pluginId, plugin);
- unresolvedPlugins.add(plugin);
+ plugins.put(pluginId, pluginWrapper);
+ unresolvedPlugins.add(pluginWrapper);
// add plugin class loader to the list with class loaders
PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
}
}
- private Plugin getPluginInstance(PluginWrapper pluginWrapper, PluginLoader pluginLoader)
- throws Exception {
- String pluginClassName = pluginWrapper.getDescriptor().getPluginClass();
-
- ClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
- Class<?> pluginClass = pluginClassLoader.loadClass(pluginClassName);
-
- // once we have the class, we can do some checks on it to ensure
- // that it is a valid implementation of a plugin.
- int modifiers = pluginClass.getModifiers();
- if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)
- || (!Plugin.class.isAssignableFrom(pluginClass))) {
- throw new PluginException("The plugin class '" + pluginClassName
- + "' is not compatible.");
- }
-
- // create the plugin instance
- Constructor<?> constructor = pluginClass.getConstructor(new Class[] { PluginWrapper.class });
- Plugin plugin = (Plugin) constructor.newInstance(new Object[] { pluginWrapper });
-
- return plugin;
- }
-
- private void resolvePlugins() {
- resolveDependencies();
+ private void resolvePlugins() throws PluginException {
+ resolveDependencies();
}
- private void resolveDependencies() {
+ private void resolveDependencies() throws PluginException {
DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
- resolvedPlugins = dependencyResolver.getSortedDependencies();
- for (Plugin plugin : resolvedPlugins) {
- unresolvedPlugins.remove(plugin);
- uberClassLoader.addLoader(plugin.getWrapper().getPluginClassLoader());
+ resolvedPlugins = dependencyResolver.getSortedPlugins();
+ for (PluginWrapper pluginWrapper : resolvedPlugins) {
+ unresolvedPlugins.remove(pluginWrapper);
+ uberClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
+ LOG.info("Plugin '" + pluginWrapper.getDescriptor().getPluginId() + "' resolved");
}
}
private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class);
- private List<Plugin> plugins;
+ private List<PluginWrapper> plugins;
- public DependencyResolver(List<Plugin> plugins) {
+ public DependencyResolver(List<PluginWrapper> plugins) {
this.plugins = plugins;
}
/**
* Get the list of plugins in dependency sorted order.
*/
- public List<Plugin> getSortedDependencies() {
+ public List<PluginWrapper> getSortedPlugins() throws PluginException {
DirectedGraph<String> graph = new DirectedGraph<String>();
- for (Plugin plugin : plugins) {
- PluginDescriptor descriptor = plugin.getWrapper().getDescriptor();
+ for (PluginWrapper pluginWrapper : plugins) {
+ PluginDescriptor descriptor = pluginWrapper.getDescriptor();
String pluginId = descriptor.getPluginId();
- List<String> dependencies = descriptor.getDependencies();
+ List<PluginDependency> dependencies = descriptor.getDependencies();
if (!dependencies.isEmpty()) {
- for (String dependency : dependencies) {
- graph.addEdge(pluginId, dependency);
+ for (PluginDependency dependency : dependencies) {
+ graph.addEdge(pluginId, dependency.getPluginId());
}
} else {
graph.addVertex(pluginId);
List<String> pluginsId = graph.reverseTopologicalSort();
if (pluginsId == null) {
- LOG.error("Cyclic dependences !!!");
- return null;
+ throw new CyclicDependencyException("Cyclic dependences !!!" + graph.toString());
}
LOG.debug("Plugins order: " + pluginsId);
- List<Plugin> sortedPlugins = new ArrayList<Plugin>();
+ List<PluginWrapper> sortedPlugins = new ArrayList<PluginWrapper>();
for (String pluginId : pluginsId) {
sortedPlugins.add(getPlugin(pluginId));
}
return sortedPlugins;
}
- private Plugin getPlugin(String pluginId) {
- for (Plugin plugin : plugins) {
- if (pluginId.equals(plugin.getWrapper().getDescriptor().getPluginId())) {
- return plugin;
+ private PluginWrapper getPlugin(String pluginId) throws PluginNotFoundException {
+ for (PluginWrapper pluginWrapper : plugins) {
+ if (pluginId.equals(pluginWrapper.getDescriptor().getPluginId())) {
+ return pluginWrapper;
}
}
- return null;
+ throw new PluginNotFoundException(pluginId);
}
}
private static final String PLUGIN_PACKAGE_PREFIX = "ro.fortsoft.pf4j.";
private PluginManager pluginManager;
- private PluginWrapper pluginWrapper;
-
- public PluginClassLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, ClassLoader parent) {
+ private PluginDescriptor pluginDescriptor;
+
+ public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) {
super(new URL[0], parent);
this.pluginManager = pluginManager;
- this.pluginWrapper = pluginWrapper;
+ this.pluginDescriptor = pluginDescriptor;
}
@Override
}
// look in dependencies
- List<String> dependencies = pluginWrapper.getDescriptor().getDependencies();
- for (String dependency : dependencies) {
- PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency);
+ List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
+ for (PluginDependency dependency : dependencies) {
+ PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
--- /dev/null
+/*
+ * 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;
+
+/**
+ * @author Decebal Suiu
+ */
+public class PluginDependency {
+
+ private String pluginId;
+ private PluginVersion pluginVersion;
+
+ public PluginDependency(String dependency) {
+ /*
+ int index = dependency.indexOf(':');
+ if (index == -1) {
+ throw new IllegalArgumentException("Illegal dependency specifier "+ dependency);
+ }
+
+ this.pluginId = dependency.substring(0, index);
+ this.pluginVersion = PluginVersion.createVersion(dependency.substring(index + 1));
+ */
+ this.pluginId = dependency;
+ }
+
+ public String getPluginId() {
+ return pluginId;
+ }
+
+ public PluginVersion getPluginVersion() {
+ return pluginVersion;
+ }
+
+ @Override
+ public String toString() {
+ return "PluginDependency [pluginId=" + pluginId + ", pluginVersion=" + pluginVersion + "]";
+ }
+
+}
private String pluginClass;
private PluginVersion version;
private String provider;
- private String pluginPath;
- private List<String> dependencies;
+ private List<PluginDependency> dependencies;
private PluginClassLoader pluginClassLoader;
public PluginDescriptor() {
- dependencies = new ArrayList<String>();
+ dependencies = new ArrayList<PluginDependency>();
}
/**
return provider;
}
- /**
- * Returns the path of this plugin relative to plugins directory.
- */
- public String getPluginPath() {
- return pluginPath;
- }
-
/**
* Returns all dependencies declared by this plugin.
* Returns an empty array if this plugin does not declare any require.
*/
- public List<String> getDependencies() {
+ public List<PluginDependency> getDependencies() {
return dependencies;
}
.append("pluginClass", pluginClass)
.append("version", version)
.append("provider", provider)
- .append("pluginPath", pluginPath)
.append("dependencies", dependencies)
.toString();
}
void setProvider(String provider) {
this.provider = provider;
}
-
- void setPluginPath(String pluginPath) {
- this.pluginPath = pluginPath;
- }
void setDependencies(String dependencies) {
if (dependencies != null) {
- this.dependencies = Arrays.asList(StringUtils.split(dependencies, ','));
+ this.dependencies = new ArrayList<PluginDependency>();
+ List<String> tokens = Arrays.asList(StringUtils.split(dependencies, ','));
+ for (String dependency : tokens) {
+ this.dependencies.add(new PluginDependency(dependency));
+ }
} else {
this.dependencies = Collections.emptyList();
}
import ro.fortsoft.pf4j.util.DirectoryFilter;
import ro.fortsoft.pf4j.util.JarFilter;
-
/**
* Load all informations needed by a plugin.
* This means add all jar files from 'lib' directory, 'classes'
private PluginClassLoader pluginClassLoader;
- public PluginLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, File pluginRepository) {
+ public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository) {
this.pluginRepository = pluginRepository;
classesDirectory = new File(pluginRepository, "classes");
libDirectory = new File(pluginRepository, "lib");
ClassLoader parent = getClass().getClassLoader();
- pluginClassLoader = new PluginClassLoader(pluginManager, pluginWrapper, parent);
+ pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent);
LOG.debug("Created class loader " + pluginClassLoader);
}
/**
* Retrieves all plugins.
*/
- public List<Plugin> getPlugins();
+ public List<PluginWrapper> getPlugins();
/**
* Load plugins.
public PluginClassLoader getPluginClassLoader(String pluginId);
- public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type);
+ public <T> List<T> getExtensions(Class<T> type);
}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * @author Decebal Suiu
+ */
+class PluginNotFoundException extends PluginException {
+
+ private static final long serialVersionUID = 1L;
+
+ private String pluginId;
+
+ public PluginNotFoundException(String pluginId) {
+ super("Plugin '" + pluginId + "' not found.");
+
+ this.pluginId = pluginId;
+ }
+
+ public String getPluginId() {
+ return pluginId;
+ }
+
+}
*/
package ro.fortsoft.pf4j;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
/**
* A wrapper over plugin instance.
*
PluginDescriptor descriptor;
String pluginPath;
PluginClassLoader pluginClassLoader;
+ Plugin plugin;
- public PluginWrapper(PluginDescriptor descriptor) {
+ public PluginWrapper(PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) {
this.descriptor = descriptor;
+ this.pluginPath = pluginPath;
+ this.pluginClassLoader = pluginClassLoader;
+
+ // TODO
+ try {
+ plugin = createPluginInstance();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
}
/**
return pluginClassLoader;
}
- void setPluginPath(String pluginPath) {
- this.pluginPath = pluginPath;
+ public Plugin getPlugin() {
+ return plugin;
}
- void setPluginClassLoader(PluginClassLoader pluginClassLoader) {
- this.pluginClassLoader = pluginClassLoader;
- }
+ private Plugin createPluginInstance() throws Exception {
+ String pluginClassName = descriptor.getPluginClass();
+ Class<?> pluginClass = pluginClassLoader.loadClass(pluginClassName);
+
+ // once we have the class, we can do some checks on it to ensure
+ // that it is a valid implementation of a plugin.
+ int modifiers = pluginClass.getModifiers();
+ if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)
+ || (!Plugin.class.isAssignableFrom(pluginClass))) {
+ throw new PluginException("The plugin class '" + pluginClassName + "' is not compatible.");
+ }
+
+ // create the plugin instance
+ Constructor<?> constructor = pluginClass.getConstructor(new Class[] { PluginWrapper.class });
+ Plugin plugin = (Plugin) constructor.newInstance(new Object[] { this });
+
+ return plugin;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+ .append("descriptor", descriptor)
+ .append("pluginPath", pluginPath)
+ .append("plugin", plugin)
+ .toString();
+ }
}