diff --git a/pf4j/pom.xml b/pf4j/pom.xml index 10c4b05..183c10e 100644 --- a/pf4j/pom.xml +++ b/pf4j/pom.xml @@ -32,6 +32,12 @@ slf4j-api 1.6.4 + + junit + junit + 4.8.1 + test + diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java index 013eadf..1bca676 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java @@ -1,11 +1,11 @@ /* * 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. @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -42,7 +43,7 @@ import ro.fortsoft.pf4j.util.ZipFileFilter; public class DefaultPluginManager implements PluginManager { private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class); - + public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins"; public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins"; @@ -52,11 +53,11 @@ public class DefaultPluginManager implements PluginManager { private File pluginsDirectory; private ExtensionFinder extensionFinder; - + private PluginDescriptorFinder pluginDescriptorFinder; - + private PluginClasspath pluginClasspath; - + /** * A map of plugins this manager is responsible for (the key is the 'pluginId'). */ @@ -81,35 +82,35 @@ public class DefaultPluginManager implements PluginManager { * A list with resolved plugins (resolved dependency). */ private List resolvedPlugins; - + /** * A list with started plugins. */ private List startedPlugins; - + private List enabledPlugins; private List disabledPlugins; - + /** - * A compound class loader of resolved plugins. + * A compound class loader of resolved plugins. */ protected CompoundClassLoader compoundClassLoader; - + /** * Cache value for the runtime mode. No need to re-read it because it wont change at * runtime. */ private RuntimeMode runtimeMode; - + /** * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins"). */ public DefaultPluginManager() { this.pluginsDirectory = createPluginsDirectory(); - + initialize(); } - + /** * Constructs DefaultPluginManager which the given plugins directory. * @@ -118,7 +119,7 @@ public class DefaultPluginManager implements PluginManager { */ public DefaultPluginManager(File pluginsDirectory) { this.pluginsDirectory = pluginsDirectory; - + initialize(); } @@ -145,7 +146,7 @@ public class DefaultPluginManager implements PluginManager { public List getStartedPlugins() { return startedPlugins; } - + /** * Start all active plugins. */ @@ -163,6 +164,34 @@ public class DefaultPluginManager implements PluginManager { } } + /** + * Start the specified plugin and it's dependencies. + */ + @Override + public PluginState startPlugin(String pluginId) { + if (!plugins.containsKey(pluginId)) { + throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); + } + PluginWrapper pluginWrapper = plugins.get(pluginId); + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); + if (pluginWrapper.getPluginState().equals(PluginState.STARTED)) { + log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); + return PluginState.STARTED; + } + for (PluginDependency dependency : pluginDescriptor.getDependencies()) { + startPlugin(dependency.getPluginId()); + } + try { + log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); + pluginWrapper.getPlugin().start(); + pluginWrapper.setPluginState(PluginState.STARTED); + startedPlugins.add(pluginWrapper); + } catch (PluginException e) { + log.error(e.getMessage(), e); + } + return pluginWrapper.getPluginState(); + } + /** * Stop all active plugins. */ @@ -170,17 +199,49 @@ public class DefaultPluginManager implements PluginManager { public void stopPlugins() { // stop started plugins in reverse order Collections.reverse(startedPlugins); - for (PluginWrapper pluginWrapper : startedPlugins) { + Iterator itr = startedPlugins.iterator(); + while (itr.hasNext()) { + PluginWrapper pluginWrapper = itr.next(); + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); try { - log.info("Stop plugin '{}'", pluginWrapper.getDescriptor().getPluginId()); + log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); pluginWrapper.getPlugin().stop(); pluginWrapper.setPluginState(PluginState.STOPPED); + itr.remove(); } catch (PluginException e) { log.error(e.getMessage(), e); } } } + /** + * Stop the specified plugin and it's dependencies. + */ + @Override + public PluginState stopPlugin(String pluginId) { + if (!plugins.containsKey(pluginId)) { + throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); + } + PluginWrapper pluginWrapper = plugins.get(pluginId); + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); + if (pluginWrapper.getPluginState().equals(PluginState.STOPPED)) { + log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); + return PluginState.STOPPED; + } + for (PluginDependency dependency : pluginDescriptor.getDependencies()) { + stopPlugin(dependency.getPluginId()); + } + try { + log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); + pluginWrapper.getPlugin().stop(); + pluginWrapper.setPluginState(PluginState.STOPPED); + startedPlugins.remove(pluginWrapper); + } catch (PluginException e) { + log.error(e.getMessage(), e); + } + return pluginWrapper.getPluginState(); + } + /** * Load plugins. */ @@ -233,6 +294,45 @@ public class DefaultPluginManager implements PluginManager { } } + @Override + public boolean unloadPlugin(String pluginId) { + try { + PluginState state = stopPlugin(pluginId); + if (!PluginState.STOPPED.equals(state)) { + return false; + } + + PluginWrapper pluginWrapper = plugins.get(pluginId); + PluginDescriptor descriptor = pluginWrapper.getDescriptor(); + List dependencies = descriptor.getDependencies(); + for (PluginDependency dependency : dependencies) { + if (!unloadPlugin(dependency.getPluginId())) { + return false; + } + } + + // remove the plugin + plugins.remove(pluginId); + resolvedPlugins.remove(pluginWrapper); + pathToIdMap.remove(pluginWrapper.getPluginPath()); + + // remove the classloader + if (pluginClassLoaders.containsKey(pluginId)) { + PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId); + compoundClassLoader.removeLoader(classLoader); + try { + classLoader.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + return true; + } catch (IllegalArgumentException e) { + // ignore not found exceptions because this method is recursive + } + return false; + } + /** * Get plugin class loader for this path. */ @@ -248,24 +348,24 @@ public class DefaultPluginManager implements PluginManager { for (ExtensionWrapper extensionWrapper : extensionsWrapper) { extensions.add(extensionWrapper.getInstance()); } - + return extensions; } - + @Override public RuntimeMode getRuntimeMode() { if (runtimeMode == null) { - // retrieves the runtime mode from system + // retrieves the runtime mode from system String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString()); runtimeMode = RuntimeMode.byName(modeAsString); log.info("PF4J runtime mode is '{}'", runtimeMode); } - + return runtimeMode; } - + /** * Retrieves the {@link PluginWrapper} that loaded the given class 'clazz'. */ @@ -276,63 +376,63 @@ public class DefaultPluginManager implements PluginManager { return plugin; } } - + return null; } /** - * Add the possibility to override the PluginDescriptorFinder. - * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a - * PropertiesPluginDescriptorFinder is returned else this method returns + * Add the possibility to override the PluginDescriptorFinder. + * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a + * PropertiesPluginDescriptorFinder is returned else this method returns * DefaultPluginDescriptorFinder. */ protected PluginDescriptorFinder createPluginDescriptorFinder() { if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) { return new PropertiesPluginDescriptorFinder(); } - + return new DefaultPluginDescriptorFinder(pluginClasspath); } /** - * Add the possibility to override the ExtensionFinder. + * Add the possibility to override the ExtensionFinder. */ protected ExtensionFinder createExtensionFinder() { return new DefaultExtensionFinder(compoundClassLoader); } /** - * Add the possibility to override the PluginClassPath. - * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a - * DevelopmentPluginClasspath is returned else this method returns + * Add the possibility to override the PluginClassPath. + * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a + * DevelopmentPluginClasspath is returned else this method returns * PluginClasspath. */ protected PluginClasspath createPluginClasspath() { if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) { return new DevelopmentPluginClasspath(); } - + return new PluginClasspath(); } - + protected boolean isPluginDisabled(String pluginId) { if (enabledPlugins.isEmpty()) { return disabledPlugins.contains(pluginId); } - + return !enabledPlugins.contains(pluginId); } - + protected FileFilter createHiddenPluginFilter() { return new HiddenFilter(); } - + /** * Add the possibility to override the plugins directory. * If a "pf4j.pluginsDir" system property is defined than this method returns * that directory. - * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a - * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns + * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a + * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns * DEFAULT_PLUGINS_DIRECTORY ("plugins"). * @return */ @@ -345,10 +445,10 @@ public class DefaultPluginManager implements PluginManager { pluginsDir = DEFAULT_PLUGINS_DIRECTORY; } } - + return new File(pluginsDir); } - + private void initialize() { plugins = new HashMap(); pluginClassLoaders = new HashMap(); @@ -358,7 +458,7 @@ public class DefaultPluginManager implements PluginManager { startedPlugins = new ArrayList(); disabledPlugins = new ArrayList(); compoundClassLoader = new CompoundClassLoader(); - + pluginClasspath = createPluginClasspath(); pluginDescriptorFinder = createPluginDescriptorFinder(); extensionFinder = createExtensionFinder(); @@ -367,7 +467,7 @@ public class DefaultPluginManager implements PluginManager { // create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file) enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true); log.info("Enabled plugins: {}", enabledPlugins); - + // create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file) disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true); log.info("Disabled plugins: {}", disabledPlugins); @@ -406,7 +506,7 @@ public class DefaultPluginManager implements PluginManager { PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath); pluginLoader.load(); log.debug("Loaded plugin '{}'", pluginPath); - + // create the plugin wrapper log.debug("Creating wrapper for plugin '{}'", pluginPath); PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader()); @@ -456,5 +556,5 @@ public class DefaultPluginManager implements PluginManager { log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId()); } } - + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java index 7c78b89..6ffe4cf 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java @@ -1,11 +1,11 @@ /* * 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. @@ -36,7 +36,7 @@ public interface PluginManager { * Retrieves all unresolved plugins (with unresolved dependency). */ public List getUnresolvedPlugins(); - + /** * Retrieves all started plugins. */ @@ -52,18 +52,40 @@ public interface PluginManager { */ public void startPlugins(); + /** + * Start the specified plugin and it's dependencies. + * + * @return the plugin state + */ + public PluginState startPlugin(String pluginId); + /** * Stop all active plugins. */ public void stopPlugins(); + /** + * Stop the specified plugin and it's dependencies. + * + * @return the plugin state + */ + public PluginState stopPlugin(String pluginId); + + /** + * Unload a plugin. + * + * @param pluginId + * @return true if the plugin was unloaded + */ + public boolean unloadPlugin(String pluginId); + public PluginClassLoader getPluginClassLoader(String pluginId); public List getExtensions(Class type); - + /** * The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT. */ public RuntimeMode getRuntimeMode(); - + } diff --git a/pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java b/pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java index f5c5df4..426b2a3 100644 --- a/pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java +++ b/pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java @@ -1,11 +1,11 @@ /* * 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. @@ -23,7 +23,7 @@ import java.util.Set; /** * A class loader that has multiple loaders and uses them for loading classes and resources. - * + * * @author Decebal Suiu */ public class CompoundClassLoader extends ClassLoader { @@ -34,6 +34,10 @@ public class CompoundClassLoader extends ClassLoader { loaders.add(loader); } + public void removeLoader(ClassLoader loader) { + loaders.remove(loader); + } + @Override public Class findClass(String name) throws ClassNotFoundException { for (ClassLoader loader : loaders) { @@ -43,7 +47,7 @@ public class CompoundClassLoader extends ClassLoader { // try next } } - + throw new ClassNotFoundException(name); } @@ -55,7 +59,7 @@ public class CompoundClassLoader extends ClassLoader { return url; } } - + return null; } @@ -65,8 +69,8 @@ public class CompoundClassLoader extends ClassLoader { for (ClassLoader loader : loaders) { resources.addAll(Collections.list(loader.getResources(name))); } - + return Collections.enumeration(resources); } - + }