diff options
Diffstat (limited to 'pf4j/src/main/java/org')
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); + } + +} |