diff options
author | Decebal Suiu <decebal.suiu@gmail.com> | 2014-04-03 16:08:57 +0300 |
---|---|---|
committer | Decebal Suiu <decebal.suiu@gmail.com> | 2014-04-03 16:08:57 +0300 |
commit | eef2ee1cbaa3bf7f2e7fe8ff176d889f9b21abd6 (patch) | |
tree | 16aa24a12fcd5ee98b5110ea81080eafd2564239 /pf4j | |
parent | 5c223d79b2a479f163520c915afd345a0502e306 (diff) | |
parent | 4fefdcf76cb7b9c5df414fc870ad2bfebb96e5bb (diff) | |
download | pf4j-eef2ee1cbaa3bf7f2e7fe8ff176d889f9b21abd6.tar.gz pf4j-eef2ee1cbaa3bf7f2e7fe8ff176d889f9b21abd6.zip |
Merge pull request #3 from gitblit/stop_plugin
Add support for starting, stopping, and unloading a plugin
Diffstat (limited to 'pf4j')
-rw-r--r-- | pf4j/pom.xml | 6 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java | 192 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java | 34 | ||||
-rw-r--r-- | pf4j/src/main/java/ro/fortsoft/pf4j/util/CompoundClassLoader.java | 20 |
4 files changed, 192 insertions, 60 deletions
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 @@ <artifactId>slf4j-api</artifactId> <version>1.6.4</version> </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.8.1</version> + <scope>test</scope> + </dependency> </dependencies> </project> 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<PluginWrapper> resolvedPlugins;
-
+
/**
* A list with started plugins.
*/
private List<PluginWrapper> startedPlugins;
-
+
private List<String> enabledPlugins;
private List<String> 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<PluginWrapper> 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,11 +199,15 @@ public class DefaultPluginManager implements PluginManager { public void stopPlugins() {
// stop started plugins in reverse order
Collections.reverse(startedPlugins);
- for (PluginWrapper pluginWrapper : startedPlugins) {
+ Iterator<PluginWrapper> 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);
}
@@ -182,6 +215,34 @@ public class DefaultPluginManager implements PluginManager { }
/**
+ * 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.
*/
@Override
@@ -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<PluginDependency> 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<T> 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<String, PluginWrapper>();
pluginClassLoaders = new HashMap<String, PluginClassLoader>();
@@ -358,7 +458,7 @@ public class DefaultPluginManager implements PluginManager { startedPlugins = new ArrayList<PluginWrapper>();
disabledPlugins = new ArrayList<String>();
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<PluginWrapper> getUnresolvedPlugins(); - + /** * Retrieves all started plugins. */ @@ -53,17 +53,39 @@ 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 <T> List<T> getExtensions(Class<T> 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); } - + } |