aboutsummaryrefslogtreecommitdiffstats
path: root/pf4j/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'pf4j/src/main/java/org')
-rw-r--r--pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java80
-rw-r--r--pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java90
-rw-r--r--pf4j/src/main/java/org/pf4j/DefaultPluginManager.java327
-rw-r--r--pf4j/src/main/java/org/pf4j/DependencyResolver.java81
-rw-r--r--pf4j/src/main/java/org/pf4j/Extension.java35
-rw-r--r--pf4j/src/main/java/org/pf4j/ExtensionFinder.java24
-rw-r--r--pf4j/src/main/java/org/pf4j/ExtensionPoint.java20
-rw-r--r--pf4j/src/main/java/org/pf4j/ExtensionWrapper.java41
-rw-r--r--pf4j/src/main/java/org/pf4j/Plugin.java68
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginClassLoader.java91
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginDescriptor.java140
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java28
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginException.java40
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginLoader.java142
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginManager.java49
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginVersion.java191
-rw-r--r--pf4j/src/main/java/org/pf4j/PluginWrapper.java61
-rw-r--r--pf4j/src/main/java/org/pf4j/util/DirectedGraph.java171
-rw-r--r--pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java35
-rw-r--r--pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java41
-rw-r--r--pf4j/src/main/java/org/pf4j/util/JarFilter.java32
-rw-r--r--pf4j/src/main/java/org/pf4j/util/UberClassLoader.java72
-rw-r--r--pf4j/src/main/java/org/pf4j/util/Unzip.java122
-rw-r--r--pf4j/src/main/java/org/pf4j/util/ZipFilter.java32
24 files changed, 2013 insertions, 0 deletions
diff --git a/pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java b/pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.java
new file mode 100644
index 0000000..e7a6dc9
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/DefaultExtensionFinder.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 org.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 DefaultExtensionFinder implements ExtensionFinder {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultExtensionFinder.class);
+
+ private volatile List<IndexItem<Extension, Object>> indices;
+ private ClassLoader classLoader;
+
+ public DefaultExtensionFinder(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
+ LOG.debug("Find extensions for " + type);
+ List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
+ getIndices();
+// System.out.println("indices = "+ indices);
+ for (IndexItem<Extension, Object> 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<T>(type.cast(instance), item.annotation().ordinal()));
+ }
+ }
+ } catch (InstantiationException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+
+ return result;
+ }
+
+ private List<IndexItem<Extension, Object>> getIndices() {
+ if (indices == null) {
+ indices = new ArrayList<IndexItem<Extension, Object>>();
+ Iterator<IndexItem<Extension, Object>> 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/org/pf4j/DefaultPluginDescriptorFinder.java b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java
new file mode 100644
index 0000000..0055e57
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptorFinder.java
@@ -0,0 +1,90 @@
+/*
+ * 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 org.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.apache.commons.lang.StringUtils;
+
+/**
+ * Read the plugin descriptor from the manifest file.
+ *
+ * @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()) {
+ // not found a 'plugin.xml' file for this plugin
+ 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/org/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java
new file mode 100644
index 0000000..9cb7ccd
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java
@@ -0,0 +1,327 @@
+/*
+ * 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 org.pf4j;
+
+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 java.util.Map;
+
+import org.pf4j.util.DirectoryFilter;
+import org.pf4j.util.UberClassLoader;
+import org.pf4j.util.Unzip;
+import org.pf4j.util.ZipFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Default implementation of the PluginManager interface.
+ *
+ * @author Decebal Suiu
+ */
+public class DefaultPluginManager implements PluginManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class);
+
+ /**
+ * The plugins repository.
+ */
+ private File pluginsDirectory;
+
+ private ExtensionFinder extensionFinder;
+
+ private PluginDescriptorFinder pluginDescriptorFinder;
+
+ /**
+ * A map of plugins this manager is responsible for (the key is the 'pluginId').
+ */
+ private Map<String, Plugin> plugins;
+
+ /**
+ * A map of plugin class loaders (he key is the 'pluginId').
+ */
+ private Map<String, PluginClassLoader> pluginClassLoaders;
+
+ /**
+ * A relation between 'pluginPath' and 'pluginId'
+ */
+ private Map<String, String> pathToIdMap;
+
+ /**
+ * A list with unresolved plugins (unresolved dependency).
+ */
+ private List<Plugin> unresolvedPlugins;
+
+ /**
+ * A list with resolved plugins (resolved dependency).
+ */
+ private List<Plugin> resolvedPlugins;
+
+ /**
+ * A list with disabled plugins.
+ */
+ private List<Plugin> disabledPlugins;
+
+ private UberClassLoader uberClassLoader;
+
+ /**
+ * Th plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
+ */
+ public DefaultPluginManager() {
+ this(new File(System.getProperty("pf4j.pluginsDir", "plugins")));
+ }
+
+ /**
+ * Constructs DefaultPluginManager which the given plugins directory.
+ *
+ * @param pluginsDirectory
+ * the directory to search for plugins
+ */
+ public DefaultPluginManager(File pluginsDirectory) {
+ this.pluginsDirectory = pluginsDirectory;
+ plugins = new HashMap<String, Plugin>();
+ pluginClassLoaders = new HashMap<String, PluginClassLoader>();
+ pathToIdMap = new HashMap<String, String>();
+ unresolvedPlugins = new ArrayList<Plugin>();
+ resolvedPlugins = new ArrayList<Plugin>();
+ disabledPlugins = new ArrayList<Plugin>();
+ 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<Plugin> getResolvedPlugins() {
+ return resolvedPlugins;
+ }
+
+ public Plugin getPlugin(String pluginId) {
+ return plugins.get(pluginId);
+ }
+
+ public List<Plugin> getUnresolvedPlugins() {
+ return unresolvedPlugins;
+ }
+
+ public List<Plugin> getDisabledPlugins() {
+ return disabledPlugins;
+ }
+
+ /**
+ * Start all active plugins.
+ */
+ public void startPlugins() {
+ List<Plugin> resolvedPlugins = getResolvedPlugins();
+ for (Plugin plugin : resolvedPlugins) {
+ try {
+ plugin.start();
+ } catch (PluginException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Stop all active plugins.
+ */
+ public void stopPlugins() {
+ List<Plugin> resolvedPlugins = getResolvedPlugins();
+ for (Plugin plugin : resolvedPlugins) {
+ try {
+ plugin.stop();
+ } catch (PluginException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Load plugins.
+ */
+ public void loadPlugins() {
+ // check for plugins directory
+ if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
+ LOG.error("No '" + pluginsDirectory + "' directory");
+ return;
+ }
+
+ // expand all plugin archives
+ FilenameFilter zipFilter = new ZipFilter();
+ String[] zipFiles = pluginsDirectory.list(zipFilter);
+ for (String zipFile : zipFiles) {
+ try {
+ expandPluginArchive(zipFile);
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ e.printStackTrace();
+ }
+ }
+
+ // load any plugin from plugins directory
+ FilenameFilter directoryFilter = new DirectoryFilter();
+ String[] directories = pluginsDirectory.list(directoryFilter);
+ for (String directory : directories) {
+ try {
+ loadPlugin(directory);
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ e.printStackTrace();
+ }
+ }
+
+ // check for no plugins
+ if (directories.length == 0) {
+ LOG.info("No plugins");
+ return;
+ }
+
+ // resolve 'unresolvedPlugins'
+ resolvePlugins();
+ }
+
+ /**
+ * Get plugin class loader for this path.
+ */
+ public PluginClassLoader getPluginClassLoader(String pluginId) {
+ return pluginClassLoaders.get(pluginId);
+ }
+
+ public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type) {
+ return extensionFinder.find(type);
+ }
+
+ private void loadPlugin(String fileName) throws Exception {
+ // test for plugin directory
+ File pluginDirectory = new File(pluginsDirectory, fileName);
+ if (!pluginDirectory.isDirectory()) {
+ return;
+ }
+
+ // try to load the plugin
+ String pluginPath = "/".concat(fileName);
+
+ // test for disabled plugin
+ if (disabledPlugins.contains(pluginPath)) {
+ return;
+ }
+
+ // it's a new plugin
+ if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
+ return;
+ }
+
+ // retrieves the plugin descriptor
+ LOG.debug("Find plugin descriptor '" + pluginPath + "'");
+ PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory);
+ LOG.debug("Descriptor " + pluginDescriptor);
+ String pluginClassName = pluginDescriptor.getPluginClass();
+ LOG.debug("Class '" + pluginClassName + "'" + " for plugin '" + pluginPath + "'");
+
+ // load plugin
+ LOG.debug("Loading plugin '" + pluginPath + "'");
+ PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor);
+ PluginLoader pluginLoader = new PluginLoader(this, pluginWrapper, 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 + "'");
+
+ String pluginId = pluginDescriptor.getPluginId();
+
+ // add plugin to the list with plugins
+ plugins.put(pluginId, plugin);
+ unresolvedPlugins.add(plugin);
+
+ // add plugin class loader to the list with class loaders
+ PluginClassLoader pluginClassLoader = pluginLoader.getPluginClassLoader();
+ pluginDescriptor.setPluginClassLoader(pluginClassLoader);
+ pluginClassLoaders.put(pluginId, pluginClassLoader);
+ }
+
+ private void expandPluginArchive(String fileName) throws IOException {
+ File pluginArchiveFile = new File(pluginsDirectory, fileName);
+ long pluginArchiveDate = pluginArchiveFile.lastModified();
+ String pluginName = fileName.substring(0, fileName.length() - 4);
+ File pluginDirectory = new File(pluginsDirectory, pluginName);
+ // check if exists directory or the '.zip' file is "newer" than directory
+ if (!pluginDirectory.exists() || pluginArchiveDate > pluginDirectory.lastModified()) {
+ LOG.debug("Expand plugin archive '" + pluginArchiveFile + "' in '" + pluginDirectory + "'");
+ // create directorie for plugin
+ pluginDirectory.mkdirs();
+
+ // expand '.zip' file
+ Unzip unzip = new Unzip();
+ unzip.setSource(pluginArchiveFile);
+ unzip.setDestination(pluginDirectory);
+ unzip.extract();
+ }
+ }
+
+ 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 resolveDependencies() {
+ DependencyResolver dependencyResolver = new DependencyResolver(unresolvedPlugins);
+ resolvedPlugins = dependencyResolver.getSortedDependencies();
+ for (Plugin plugin : resolvedPlugins) {
+ unresolvedPlugins.remove(plugin);
+ uberClassLoader.addLoader(plugin.getWrapper().getPluginClassLoader());
+ }
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/DependencyResolver.java b/pf4j/src/main/java/org/pf4j/DependencyResolver.java
new file mode 100644
index 0000000..2f5f601
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/DependencyResolver.java
@@ -0,0 +1,81 @@
+/*
+ * 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 org.pf4j;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.pf4j.util.DirectedGraph;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @author Decebal Suiu
+ */
+class DependencyResolver {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class);
+
+ private List<Plugin> plugins;
+
+ public DependencyResolver(List<Plugin> plugins) {
+ this.plugins = plugins;
+ }
+
+ /**
+ * Get the list of plugins in dependency sorted order.
+ */
+ public List<Plugin> getSortedDependencies() {
+ DirectedGraph<String> graph = new DirectedGraph<String>();
+ for (Plugin plugin : plugins) {
+ PluginDescriptor descriptor = plugin.getWrapper().getDescriptor();
+ String pluginId = descriptor.getPluginId();
+ List<String> dependencies = descriptor.getDependencies();
+ if (!dependencies.isEmpty()) {
+ for (String dependency : dependencies) {
+ graph.addEdge(pluginId, dependency);
+ }
+ } else {
+ graph.addVertex(pluginId);
+ }
+ }
+
+ LOG.debug("Graph: " + graph);
+ List<String> pluginsId = graph.reverseTopologicalSort();
+
+ if (pluginsId == null) {
+ LOG.error("Cyclic dependences !!!");
+ return null;
+ }
+
+ LOG.debug("Plugins order: " + pluginsId);
+ List<Plugin> sortedPlugins = new ArrayList<Plugin>();
+ 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;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/Extension.java b/pf4j/src/main/java/org/pf4j/Extension.java
new file mode 100644
index 0000000..6c499b6
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/Extension.java
@@ -0,0 +1,35 @@
+/*
+ * 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 org.pf4j;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import net.java.sezpoz.Indexable;
+
+/**
+ * @author Decebal Suiu
+ */
+@Indexable
+@Retention(RUNTIME)
+@Target(TYPE)
+@Documented
+public @interface Extension {
+
+ int ordinal() default 0;
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/ExtensionFinder.java b/pf4j/src/main/java/org/pf4j/ExtensionFinder.java
new file mode 100644
index 0000000..5fc466d
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/ExtensionFinder.java
@@ -0,0 +1,24 @@
+/*
+ * 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 org.pf4j;
+
+import java.util.List;
+
+/**
+ * @author Decebal Suiu
+ */
+public interface ExtensionFinder {
+
+ public <T> List<ExtensionWrapper<T>> find(Class<T> type);
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/ExtensionPoint.java b/pf4j/src/main/java/org/pf4j/ExtensionPoint.java
new file mode 100644
index 0000000..9113c1d
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/ExtensionPoint.java
@@ -0,0 +1,20 @@
+/*
+ * 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 org.pf4j;
+
+/**
+ * @author Decebal Suiu
+ */
+public interface ExtensionPoint {
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/ExtensionWrapper.java b/pf4j/src/main/java/org/pf4j/ExtensionWrapper.java
new file mode 100644
index 0000000..9ba320e
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/ExtensionWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.pf4j;
+
+/**
+ * @author Decebal Suiu
+ */
+public class ExtensionWrapper<T> implements Comparable<ExtensionWrapper<T>> {
+
+ private final T instance;
+ private final int ordinal;
+
+ public ExtensionWrapper(T instance, int ordinal) {
+ this.instance = instance;
+ this.ordinal = ordinal;
+ }
+
+ public T getInstance() {
+ return instance;
+ }
+
+ public int getOrdinal() {
+ return ordinal;
+ }
+
+ @Override
+ public int compareTo(ExtensionWrapper<T> o) {
+ return (ordinal - o.getOrdinal());
+ }
+
+} \ No newline at end of file
diff --git a/pf4j/src/main/java/org/pf4j/Plugin.java b/pf4j/src/main/java/org/pf4j/Plugin.java
new file mode 100644
index 0000000..bab55c1
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/Plugin.java
@@ -0,0 +1,68 @@
+/*
+ * 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 org.pf4j;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class will be extended by all plugins and
+ * serve as the common class between a plugin and the application.
+ *
+ * @author Decebal Suiu
+ */
+public abstract class Plugin {
+
+ /**
+ * Makes logging service available for descending classes.
+ */
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * Wrapper of the plugin.
+ */
+ PluginWrapper wrapper;
+
+ /**
+ * Constructor to be used by plugin manager for plugin instantiation.
+ * Your plugins have to provide constructor with this exact signature to
+ * be successfully loaded by manager.
+ */
+ public Plugin(final PluginWrapper wrapper) {
+ if (wrapper == null) {
+ throw new IllegalArgumentException("Wrapper cannot be null");
+ }
+
+ this.wrapper = wrapper;
+ }
+
+ /**
+ * Retrieves the wrapper of this plug-in.
+ */
+ public final PluginWrapper getWrapper() {
+ return wrapper;
+ }
+
+ /**
+ * Start method is called by the application when the plugin is loaded.
+ */
+ public void start() throws PluginException {
+ }
+
+ /**
+ * Stop method is called by the application when the plugin is unloaded.
+ */
+ public void stop() throws PluginException {
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java
new file mode 100644
index 0000000..884642f
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java
@@ -0,0 +1,91 @@
+/*
+ * 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 org.pf4j;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.List;
+
+/**
+ * One instance of this class should be created by plugin manager for every available plug-in.
+ *
+ * @author Decebal Suiu
+ */
+class PluginClassLoader extends URLClassLoader {
+
+ private static final String JAVA_PACKAGE_PREFIX = "java.";
+ private static final String JAVAX_PACKAGE_PREFIX = "javax.";
+ private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j.";
+
+ private PluginManager pluginManager;
+ private PluginWrapper pluginWrapper;
+
+ public PluginClassLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, ClassLoader parent) {
+ super(new URL[0], parent);
+
+ this.pluginManager = pluginManager;
+ this.pluginWrapper = pluginWrapper;
+ }
+
+ @Override
+ public void addURL(URL url) {
+ super.addURL(url);
+ }
+
+ @Override
+ public Class<?> loadClass(String className) throws ClassNotFoundException {
+// System.out.println(">>>" + className);
+
+ // first check whether it's a system class, delegate to the system loader
+ if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) {
+ return findSystemClass(className);
+ }
+
+ // second check whether it's already been loaded
+ Class<?> loadedClass = findLoadedClass(className);
+ if (loadedClass != null) {
+ return loadedClass;
+ }
+
+ // nope, try to load locally
+ try {
+ return findClass(className);
+ } catch (ClassNotFoundException e) {
+ // try next step
+ }
+
+ // if the class it's a part of the plugin engine use parent class loader
+ if (className.startsWith(PLUGIN_PACKAGE_PREFIX)) {
+ try {
+ return PluginClassLoader.class.getClassLoader().loadClass(className);
+ } catch (ClassNotFoundException e) {
+ // try next step
+ }
+ }
+
+ // look in dependencies
+ List<String> dependencies = pluginWrapper.getDescriptor().getDependencies();
+ for (String dependency : dependencies) {
+ PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency);
+ try {
+ return classLoader.loadClass(className);
+ } catch (ClassNotFoundException e) {
+ // try next dependency
+ }
+ }
+
+ // use the standard URLClassLoader (which follows normal parent delegation)
+ return super.loadClass(className);
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginDescriptor.java b/pf4j/src/main/java/org/pf4j/PluginDescriptor.java
new file mode 100644
index 0000000..8346e48
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginDescriptor.java
@@ -0,0 +1,140 @@
+/*
+ * 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 org.pf4j;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+/**
+ * A plugin descriptor contains information about a plug-in obtained
+ * from the manifest (META-INF) file.
+ *
+ * @author Decebal Suiu
+ */
+class PluginDescriptor {
+
+ private String pluginId;
+ private String pluginClass;
+ private PluginVersion version;
+ private String provider;
+ private String pluginPath;
+ private List<String> dependencies;
+ private PluginClassLoader pluginClassLoader;
+
+ public PluginDescriptor() {
+ dependencies = new ArrayList<String>();
+ }
+
+ /**
+ * Returns the unique identifier of this plugin.
+ */
+ public String getPluginId() {
+ return pluginId;
+ }
+
+ /**
+ * Returns the name of the class that implements Plugin interface.
+ */
+ public String getPluginClass() {
+ return pluginClass;
+ }
+
+ /**
+ * Returns the version of this plugin.
+ */
+ public PluginVersion getVersion() {
+ return version;
+ }
+
+ /**
+ * Returns the provider name of this plugin.
+ */
+ public String getProvider() {
+ 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() {
+ return dependencies;
+ }
+
+ /**
+ * Returns the plugin class loader used to load classes and resources
+ * for this plug-in. The class loader can be used to directly access
+ * plug-in resources and classes.
+ */
+ public PluginClassLoader getPluginClassLoader() {
+ return pluginClassLoader;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+ .append("pluginId", pluginId)
+ .append("pluginClass", pluginClass)
+ .append("version", version)
+ .append("provider", provider)
+ .append("pluginPath", pluginPath)
+ .append("dependencies", dependencies)
+ .toString();
+ }
+
+ void setPluginId(String pluginId) {
+ this.pluginId = pluginId;
+ }
+
+ void setPluginClass(String pluginClassName) {
+ this.pluginClass = pluginClassName;
+ }
+
+ void setPluginVersion(PluginVersion version) {
+ this.version = version;
+ }
+
+ 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, ','));
+ } else {
+ this.dependencies = Collections.emptyList();
+ }
+ }
+
+ void setPluginClassLoader(PluginClassLoader pluginClassLoader) {
+ this.pluginClassLoader = pluginClassLoader;
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java b/pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java
new file mode 100644
index 0000000..c59ec65
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginDescriptorFinder.java
@@ -0,0 +1,28 @@
+/*
+ * 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 org.pf4j;
+
+import java.io.File;
+
+/**
+ * Find a plugin descriptor in a directory (plugin repository).
+ * You can find in manifest file @see DefaultPluginDescriptorFinder,
+ * xml file, properties file, java services (with ServiceLoader), etc.
+ *
+ * @author Decebal Suiu
+ */
+public interface PluginDescriptorFinder {
+
+ public PluginDescriptor find(File pluginRepository) throws PluginException;
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginException.java b/pf4j/src/main/java/org/pf4j/PluginException.java
new file mode 100644
index 0000000..c341c70
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginException.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.pf4j;
+
+/**
+ * An exception used to indicate that a plugin problem occurred.
+ *
+ * @author Decebal Suiu
+ */
+class PluginException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public PluginException() {
+ super();
+ }
+
+ public PluginException(String message) {
+ super(message);
+ }
+
+ public PluginException(Throwable cause) {
+ super(cause);
+ }
+
+ public PluginException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginLoader.java b/pf4j/src/main/java/org/pf4j/PluginLoader.java
new file mode 100644
index 0000000..bd64d59
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginLoader.java
@@ -0,0 +1,142 @@
+/*
+ * 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 org.pf4j;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.net.MalformedURLException;
+import java.util.Vector;
+
+import org.pf4j.util.DirectoryFilter;
+import org.pf4j.util.JarFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Load all informations needed by a plugin.
+ * This means add all jar files from 'lib' directory, 'classes'
+ * to classpath.
+ * It's a class for only the internal use.
+ *
+ * @author Decebal Suiu
+ */
+class PluginLoader {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PluginLoader.class);
+
+ /*
+ * The plugin repository.
+ */
+ private File pluginRepository;
+
+ /*
+ * The directory with '.class' files.
+ */
+ private File classesDirectory;
+
+ /*
+ * The directory with '.jar' files.
+ */
+ private File libDirectory;
+
+ private PluginClassLoader pluginClassLoader;
+
+ public PluginLoader(PluginManager pluginManager, PluginWrapper pluginWrapper, 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);
+ LOG.debug("Created class loader " + pluginClassLoader);
+ }
+
+ public File getPluginRepository() {
+ return pluginRepository;
+ }
+
+ public boolean load() {
+ return loadClassesAndJars();
+ }
+
+ public PluginClassLoader getPluginClassLoader() {
+ return pluginClassLoader;
+ }
+
+ private boolean loadClassesAndJars() {
+ return loadClasses() && loadJars();
+ }
+
+ private void getJars(Vector<String> v, File file) {
+ FilenameFilter jarFilter = new JarFilter();
+ FilenameFilter directoryFilter = new DirectoryFilter();
+
+ if (file.exists() && file.isDirectory() && file.isAbsolute()) {
+ String[] jars = file.list(jarFilter);
+ for (int i = 0; (jars != null) && (i < jars.length); ++i) {
+ v.addElement(jars[i]);
+ }
+
+ String[] directoryList = file.list(directoryFilter);
+ for (int i = 0; (directoryList != null) && (i < directoryList.length); ++i) {
+ File directory = new File(file, directoryList[i]);
+ getJars(v, directory);
+ }
+ }
+ }
+
+ private boolean loadClasses() {
+ // make 'classesDirectory' absolute
+ classesDirectory = classesDirectory.getAbsoluteFile();
+
+ if (classesDirectory.exists() && classesDirectory.isDirectory()) {
+ LOG.debug("Found '" + classesDirectory.getPath() + "' directory");
+
+ try {
+ pluginClassLoader.addURL(classesDirectory.toURI().toURL());
+ LOG.debug("Added '" + classesDirectory + "' to the class loader path");
+ } 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<String> jars = new Vector<String>();
+ getJars(jars, libDirectory);
+ for (String jar : jars) {
+ File jarFile = new File(libDirectory, jar);
+ try {
+ pluginClassLoader.addURL(jarFile.toURI().toURL());
+ LOG.debug("Added '" + jarFile + "' to the class loader path");
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ LOG.error(e.getMessage(), e);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginManager.java b/pf4j/src/main/java/org/pf4j/PluginManager.java
new file mode 100644
index 0000000..cf0cff1
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginManager.java
@@ -0,0 +1,49 @@
+/*
+ * 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 org.pf4j;
+
+import java.util.List;
+
+/**
+ * Provides the functionality for plugin management such as load,
+ * start and stop the plugins.
+ *
+ * @author Decebal Suiu
+ */
+public interface PluginManager {
+
+ /**
+ * Retrieves all plugins.
+ */
+ public List<Plugin> getPlugins();
+
+ /**
+ * Load plugins.
+ */
+ public void loadPlugins();
+
+ /**
+ * Start all active plugins.
+ */
+ public void startPlugins();
+
+ /**
+ * Stop all active plugins.
+ */
+ public void stopPlugins();
+
+ public PluginClassLoader getPluginClassLoader(String pluginId);
+
+ public <T> List<ExtensionWrapper<T>> getExtensions(Class<T> type);
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginVersion.java b/pf4j/src/main/java/org/pf4j/PluginVersion.java
new file mode 100644
index 0000000..66267c9
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginVersion.java
@@ -0,0 +1,191 @@
+/*
+ * 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 org.pf4j;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Represents the version of a Plugin and allows versions to be compared.
+ * Version identifiers have five components.
+ *
+ * 1. Major version. A non-negative integer.
+ * 2. Minor version. A non-negative integer.
+ * 3. Release version. A non-negative integer.
+ * 4. Build version. A non-negative integer.
+ * 5. Qualifier. A text string.
+ *
+ * This class is immutable.
+ *
+ * @author Decebal Suiu
+ */
+public class PluginVersion implements Comparable<PluginVersion> {
+
+ private int major;
+ private int minor;
+ private int release;
+ private int build;
+ private String qualifier;
+
+ private PluginVersion() {
+ }
+
+ public PluginVersion(int major, int minor, int release) {
+ this.major = major;
+ this.minor = minor;
+ this.release = release;
+ }
+
+ public PluginVersion(int major, int minor, int release, int build) {
+ this.major = major;
+ this.minor = minor;
+ this.release = release;
+ this.build = build;
+ }
+
+ public PluginVersion(int major, int minor, int release, int build, String qualifier) {
+ this.major = major;
+ this.minor = minor;
+ this.release = release;
+ this.build = build;
+ this.qualifier = qualifier;
+ }
+
+ public static PluginVersion createVersion(String version) {
+ if (version == null) {
+ return new PluginVersion();
+ }
+
+ PluginVersion v = new PluginVersion();
+
+ StringTokenizer st = new StringTokenizer(version, ".");
+ List<String> tmp = new ArrayList<String>();
+ for (int i = 0; st.hasMoreTokens() && i < 4; i++) {
+ tmp.add(st.nextToken());
+ }
+
+ int n = tmp.size();
+ switch (n) {
+ case 0 :
+ break;
+ case 1 :
+ v.major = Integer.parseInt(tmp.get(0));
+ break;
+ case 2 :
+ v.major = Integer.parseInt(tmp.get(0));
+ v.minor = Integer.parseInt(tmp.get(1));
+ break;
+ case 3 :
+ v.major = Integer.parseInt(tmp.get(0));
+ v.minor = Integer.parseInt(tmp.get(1));
+ v.release = Integer.parseInt(tmp.get(2));
+ break;
+ case 4 :
+ v.major = Integer.parseInt(tmp.get(0));
+ v.minor = Integer.parseInt(tmp.get(1));
+ v.release = Integer.parseInt(tmp.get(2));
+ v.build = Integer.parseInt(tmp.get(3));
+ break;
+ }
+
+ return v;
+ }
+
+ public int getMajor() {
+ return this.major;
+ }
+
+ public int getMinor() {
+ return this.minor;
+ }
+
+ public int getRelease() {
+ return this.release;
+ }
+
+ public int getBuild() {
+ return this.build;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(50);
+ sb.append(major);
+ sb.append('.');
+ sb.append(minor);
+ sb.append('.');
+ sb.append(release);
+ sb.append('.');
+ sb.append(build);
+ if (qualifier != null) {
+ sb.append(qualifier);
+ }
+
+ return sb.toString();
+ }
+
+ public int compareTo(PluginVersion version) {
+ if (version.major > major) {
+ return 1;
+ } else if (version.major < major) {
+ return -1;
+ }
+
+ if (version.minor > minor) {
+ return 1;
+ } else if (version.minor < minor) {
+ return -1;
+ }
+
+ if (version.release > release) {
+ return 1;
+ } else if (version.release < release) {
+ return -1;
+ }
+
+ if (version.build > build) {
+ return 1;
+ } else if (version.build < build) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ /*
+ private String extractQualifier(String token) {
+ StringTokenizer st = new StringTokenizer(token, "-");
+ if (st.countTokens() == 2) {
+ return st.
+ }
+ }
+ */
+
+ // for test only
+ public static void main(String[] args) {
+ PluginVersion v = PluginVersion.createVersion("4.0.0.123");
+ System.out.println(v.toString());
+// v = PluginVersion.createVersion("4.0.0.123-alpha");
+// System.out.println(v.toString());
+ PluginVersion v1 = PluginVersion.createVersion("4.1.0");
+ System.out.println(v1.toString());
+ PluginVersion v2 = PluginVersion.createVersion("4.0.32");
+ System.out.println(v2.toString());
+ System.out.println(v1.compareTo(v2));
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/PluginWrapper.java b/pf4j/src/main/java/org/pf4j/PluginWrapper.java
new file mode 100644
index 0000000..46a0da1
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/PluginWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * 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 org.pf4j;
+
+/**
+ * A wrapper over plugin instance.
+ *
+ * @author Decebal Suiu
+ */
+public class PluginWrapper {
+
+ PluginDescriptor descriptor;
+ String pluginPath;
+ PluginClassLoader pluginClassLoader;
+
+ public PluginWrapper(PluginDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ /**
+ * Returns the plugin descriptor.
+ */
+ public PluginDescriptor getDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Returns the path of this plugin relative to plugins directory.
+ */
+ public String getPluginPath() {
+ return pluginPath;
+ }
+
+ /**
+ * Returns the plugin class loader used to load classes and resources
+ * for this plug-in. The class loader can be used to directly access
+ * plug-in resources and classes.
+ */
+ public PluginClassLoader getPluginClassLoader() {
+ return pluginClassLoader;
+ }
+
+ void setPluginPath(String pluginPath) {
+ this.pluginPath = pluginPath;
+ }
+
+ void setPluginClassLoader(PluginClassLoader pluginClassLoader) {
+ this.pluginClassLoader = pluginClassLoader;
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/DirectedGraph.java b/pf4j/src/main/java/org/pf4j/util/DirectedGraph.java
new file mode 100644
index 0000000..a5fa829
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/DirectedGraph.java
@@ -0,0 +1,171 @@
+/*
+ * 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 org.pf4j.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * @author Decebal Suiu
+ */
+public class DirectedGraph<V> {
+
+ /**
+ * The implementation here is basically an adjacency list, but instead
+ * of an array of lists, a Map is used to map each vertex to its list of
+ * adjacent vertices.
+ */
+ private Map<V, List<V>> neighbors = new HashMap<V, List<V>>();
+
+ /**
+ * Add a vertex to the graph. Nothing happens if vertex is already in graph.
+ */
+ public void addVertex(V vertex) {
+ if (neighbors.containsKey(vertex)) {
+ return;
+ }
+ neighbors.put(vertex, new ArrayList<V>());
+ }
+
+ /**
+ * True if graph contains vertex.
+ */
+ public boolean containsVertex(V vertex) {
+ return neighbors.containsKey(vertex);
+ }
+
+ /**
+ * Add an edge to the graph; if either vertex does not exist, it's added.
+ * This implementation allows the creation of multi-edges and self-loops.
+ */
+ public void addEdge(V from, V to) {
+ this.addVertex(from);
+ this.addVertex(to);
+ neighbors.get(from).add(to);
+ }
+
+ /**
+ * Remove an edge from the graph. Nothing happens if no such edge.
+ * @throws IllegalArgumentException if either vertex doesn't exist.
+ */
+ public void remove(V from, V to) {
+ if (!(this.containsVertex(from) && this.containsVertex(to))) {
+ throw new IllegalArgumentException("Nonexistent vertex");
+ }
+ neighbors.get(from).remove(to);
+ }
+
+ /**
+ * Report (as a Map) the out-degree of each vertex.
+ */
+ public Map<V, Integer> outDegree() {
+ Map<V, Integer> result = new HashMap<V, Integer>();
+ for (V vertex : neighbors.keySet()) {
+ result.put(vertex, neighbors.get(vertex).size());
+ }
+
+ return result;
+ }
+
+ /**
+ * Report (as a Map) the in-degree of each vertex.
+ */
+ public Map<V,Integer> inDegree() {
+ Map<V, Integer> result = new HashMap<V, Integer>();
+ for (V vertex : neighbors.keySet()) {
+ result.put(vertex, 0); // all in-degrees are 0
+ }
+ for (V from : neighbors.keySet()) {
+ for (V to : neighbors.get(from)) {
+ result.put(to, result.get(to) + 1); // increment in-degree
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Report (as a List) the topological sort of the vertices; null for no such sort.
+ */
+ public List<V> topologicalSort() {
+ Map<V, Integer> degree = inDegree();
+
+ // determine all vertices with zero in-degree
+ Stack<V> zeroVertices = new Stack<V>(); // stack as good as any here
+ for (V v : degree.keySet()) {
+ if (degree.get(v) == 0) {
+ zeroVertices.push(v);
+ }
+ }
+
+ // determine the topological order
+ List<V> result = new ArrayList<V>();
+ while (!zeroVertices.isEmpty()) {
+ V vertex = zeroVertices.pop(); // choose a vertex with zero in-degree
+ result.add(vertex); // vertex 'v' is next in topological order
+ // "remove" vertex 'v' by updating its neighbors
+ for (V neighbor : neighbors.get(vertex)) {
+ degree.put(neighbor, degree.get(neighbor) - 1);
+ // remember any vertices that now have zero in-degree
+ if (degree.get(neighbor) == 0) {
+ zeroVertices.push(neighbor);
+ }
+ }
+ }
+
+ // check that we have used the entire graph (if not, there was a cycle)
+ if (result.size() != neighbors.size()) {
+ return null;
+ }
+
+ return result;
+ }
+
+ /**
+ * Report (as a List) the reverse topological sort of the vertices; null for no such sort.
+ */
+ public List<V> reverseTopologicalSort() {
+ List<V> list = topologicalSort();
+ if (list == null) {
+ return null;
+ }
+ Collections.reverse(list);
+
+ return list;
+ }
+
+ /**
+ * True if graph is a dag (directed acyclic graph).
+ */
+ public boolean isDag () {
+ return topologicalSort() != null;
+ }
+
+ /**
+ * String representation of graph.
+ */
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ for (V vertex : neighbors.keySet()) {
+ sb.append("\n " + vertex + " -> " + neighbors.get(vertex));
+ }
+
+ return sb.toString();
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java b/pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java
new file mode 100644
index 0000000..c6a236b
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/DirectoryFilter.java
@@ -0,0 +1,35 @@
+/*
+ * 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 org.pf4j.util;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+
+/**
+ * @author Decebal Suiu
+ */
+public class DirectoryFilter implements FileFilter, FilenameFilter {
+
+ /**
+ * Accepts any file ending in .jar. The case of the filename is ignored.
+ */
+ public boolean accept(File file) {
+ return file.isDirectory();
+ }
+
+ public boolean accept(File dir, String name) {
+ return accept(new File(dir, name));
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java b/pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java
new file mode 100644
index 0000000..1252a4e
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/ExtensionFilter.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.pf4j.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * @author Decebal Suiu
+ */
+public class ExtensionFilter implements FilenameFilter {
+
+ private String extension;
+
+ public ExtensionFilter(String extension) {
+ this.extension = extension;
+ }
+
+ /**
+ * Accepts any file ending in extension. The case of the filename is ignored.
+ */
+ public boolean accept(File file) {
+ // perform a case insensitive check.
+ return file.getName().toUpperCase().endsWith(extension.toUpperCase());
+ }
+
+ public boolean accept(File dir, String name) {
+ return accept(new File(dir, name));
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/JarFilter.java b/pf4j/src/main/java/org/pf4j/util/JarFilter.java
new file mode 100644
index 0000000..09c322c
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/JarFilter.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.pf4j.util;
+
+/**
+ * File filter that accepts all files ending with .JAR.
+ * This filter is case insensitive.
+ *
+ * @author Decebal Suiu
+ */
+public class JarFilter extends ExtensionFilter {
+
+ /**
+ * The extension that this filter will search for.
+ */
+ private static final String JAR_EXTENSION = ".JAR";
+
+ public JarFilter() {
+ super(JAR_EXTENSION);
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/UberClassLoader.java b/pf4j/src/main/java/org/pf4j/util/UberClassLoader.java
new file mode 100644
index 0000000..4ecfe2c
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/UberClassLoader.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.pf4j.util;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class loader that has multiple loaders and uses them for loading classes and resources.
+ *
+ * @author Decebal Suiu
+ */
+public class UberClassLoader extends ClassLoader {
+
+ private Set<ClassLoader> loaders = new HashSet<ClassLoader>();
+
+ public void addLoader(ClassLoader loader) {
+ loaders.add(loader);
+ }
+
+ @Override
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ for (ClassLoader loader : loaders) {
+ try {
+ return loader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ // try next
+ }
+ }
+
+ throw new ClassNotFoundException(name);
+ }
+
+ @Override
+ public URL findResource(String name) {
+ for (ClassLoader loader : loaders) {
+ URL url = loader.getResource(name);
+ if (url != null) {
+ return url;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected Enumeration<URL> findResources(String name) throws IOException {
+ List<URL> resources = new ArrayList<URL>();
+ for (ClassLoader loader : loaders) {
+ resources.addAll(Collections.list(loader.getResources(name)));
+ }
+
+ return Collections.enumeration(resources);
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/Unzip.java b/pf4j/src/main/java/org/pf4j/util/Unzip.java
new file mode 100644
index 0000000..d5e6a04
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/Unzip.java
@@ -0,0 +1,122 @@
+/*
+ * 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 org.pf4j.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class extracts the containt of the plugin archive into a directory.
+ * It's a class for only the internal use.
+ *
+ * @author Decebal Suiu
+ */
+public class Unzip {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Unzip.class);
+
+ /**
+ * Holds the destination directory.
+ * File will be unzipped into the destination directory.
+ */
+ private File destination;
+
+ /**
+ * Holds path to zip file.
+ */
+ private File source;
+
+ public Unzip() {
+ }
+
+ public Unzip(File source, File destination) {
+ this.source = source;
+ this.destination = destination;
+ }
+
+ public void setSource(File source) {
+ this.source = source;
+ }
+
+ public void setDestination(File destination) {
+ this.destination = destination;
+ }
+
+ public void extract() throws IOException {
+ LOG.debug("Extract content of " + source + " to " + destination);
+
+ // delete destination file if exists
+ removeDirectory(destination);
+
+ ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source));
+ ZipEntry zipEntry = null;
+
+ while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+ try {
+ File file = new File(destination, zipEntry.getName());
+
+ // create intermediary directories - sometimes zip don't add them
+ File dir = new File(file.getParent());
+ dir.mkdirs();
+
+ if (zipEntry.isDirectory()) {
+ file.mkdirs();
+ } else {
+ byte[] buffer = new byte[1024];
+ int length = 0;
+ FileOutputStream fos = new FileOutputStream(file);
+
+ while ((length = zipInputStream.read(buffer)) >= 0) {
+ fos.write(buffer, 0, length);
+ }
+
+ fos.close();
+ }
+ } catch (FileNotFoundException e) {
+ LOG.error("File '" + zipEntry.getName() + "' not found");
+ }
+ }
+
+ zipInputStream.close();
+ }
+
+ private boolean removeDirectory(File directory) {
+ if (!directory.exists()) {
+ return true;
+ }
+
+ if (!directory.isDirectory()) {
+ return false;
+ }
+
+ File[] files = directory.listFiles();
+ for (File file : files) {
+ if (file.isDirectory()) {
+ removeDirectory(file);
+ } else {
+ file.delete();
+ }
+ }
+
+ return directory.delete();
+ }
+
+}
diff --git a/pf4j/src/main/java/org/pf4j/util/ZipFilter.java b/pf4j/src/main/java/org/pf4j/util/ZipFilter.java
new file mode 100644
index 0000000..4884e90
--- /dev/null
+++ b/pf4j/src/main/java/org/pf4j/util/ZipFilter.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.pf4j.util;
+
+/**
+ * File filter that accepts all files ending with .ZIP.
+ * This filter is case insensitive.
+ *
+ * @author Decebal Suiu
+ */
+public class ZipFilter extends ExtensionFilter {
+
+ /**
+ * The extension that this filter will search for.
+ */
+ private static final String ZIP_EXTENSION = ".ZIP";
+
+ public ZipFilter() {
+ super(ZIP_EXTENSION);
+ }
+
+}