Browse Source

Merge pull request #9 from decebals/enable-disable-plugin

Enable disable plugin
pull/10/head
Decebal Suiu 11 years ago
parent
commit
5287be5438
  1. 55
      demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java
  2. 2
      demo/plugins/disabled.txt
  3. 2
      demo/plugins/enabled.txt
  4. 129
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
  5. 211
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  6. 13
      pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionFinder.java
  7. 37
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java
  8. 28
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java
  9. 38
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java
  10. 31
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginState.java
  11. 33
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java
  12. 22
      pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java

55
demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * specific language governing permissions and limitations under the License.
@ -13,11 +13,13 @@
package ro.fortsoft.pf4j.demo; package ro.fortsoft.pf4j.demo;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager; import ro.fortsoft.pf4j.DefaultPluginManager;
import ro.fortsoft.pf4j.PluginManager; import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.PluginWrapper;
import ro.fortsoft.pf4j.demo.api.Greeting; import ro.fortsoft.pf4j.demo.api.Greeting;
/** /**
@ -26,24 +28,41 @@ import ro.fortsoft.pf4j.demo.api.Greeting;
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class Boot { public class Boot {
public static void main(String[] args) { public static void main(String[] args) {
// print logo // print logo
printLogo(); printLogo();
// create the plugin manager // create the plugin manager
final PluginManager pluginManager = new DefaultPluginManager(); final PluginManager pluginManager = new DefaultPluginManager();
// load and start (active/resolved) the plugins // load the plugins
pluginManager.loadPlugins(); pluginManager.loadPlugins();
// enable a disabled plugin
// pluginManager.enablePlugin("welcome-plugin");
// start (active/resolved) the plugins
pluginManager.startPlugins(); pluginManager.startPlugins();
// retrieves the extensions for Greeting extension point // retrieves the extensions for Greeting extension point
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class); List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
System.out.println(String.format("Found %d extensions for extension point '%s'", greetings.size(), Greeting.class.getName()));
for (Greeting greeting : greetings) { for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting()); System.out.println(">>> " + greeting.getGreeting());
}
// print extensions for each started plugin
List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();
for (PluginWrapper plugin : startedPlugins) {
String pluginId = plugin.getDescriptor().getPluginId();
System.out.println(String.format("Extensions added by plugin '%s':", pluginId));
Set<String> extensionClassNames = pluginManager.getExtensionClassNames(pluginId);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
}
} }
// stop the plugins // stop the plugins
pluginManager.stopPlugins(); pluginManager.stopPlugins();
/* /*
@ -53,15 +72,15 @@ public class Boot {
public void run() { public void run() {
pluginManager.stopPlugins(); pluginManager.stopPlugins();
} }
}); });
*/ */
} }
private static void printLogo() { private static void printLogo() {
System.out.println(StringUtils.repeat("#", 40)); System.out.println(StringUtils.repeat("#", 40));
System.out.println(StringUtils.center("PF4J-DEMO", 40)); System.out.println(StringUtils.center("PF4J-DEMO", 40));
System.out.println(StringUtils.repeat("#", 40)); System.out.println(StringUtils.repeat("#", 40));
} }
} }

2
demo/disabled.txt → demo/plugins/disabled.txt

@ -3,4 +3,4 @@
# - add one plugin id on each line # - add one plugin id on each line
# - put this file in plugins folder # - put this file in plugins folder
######################################## ########################################
welcome-plugin #welcome-plugin

2
demo/enabled.txt → demo/plugins/enabled.txt

@ -3,4 +3,4 @@
# - add one plugin id on each line # - add one plugin id on each line
# - put this file in plugins folder # - put this file in plugins folder
######################################## ########################################
welcome-plugin #welcome-plugin

129
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java

@ -16,12 +16,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -37,21 +32,16 @@ public class DefaultExtensionFinder implements ExtensionFinder {
private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class); private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class);
private ClassLoader classLoader; private PluginManager pluginManager;
private ExtensionFactory extensionFactory; private ExtensionFactory extensionFactory;
private volatile Set<String> entries; private volatile Map<String, Set<String>> entries; // cache by pluginId
public DefaultExtensionFinder(ClassLoader classLoader) { public DefaultExtensionFinder(PluginManager pluginManager) {
this.classLoader = classLoader; this.pluginManager = pluginManager;
this.extensionFactory = createExtensionFactory(); this.extensionFactory = createExtensionFactory();
} }
@Override @Override
public void reset() {
entries = null;
}
@Override
public <T> List<ExtensionWrapper<T>> find(Class<T> type) { public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
log.debug("Checking extension point '{}'", type.getName()); log.debug("Checking extension point '{}'", type.getName());
if (!isExtensionPoint(type)) { if (!isExtensionPoint(type)) {
@ -61,28 +51,31 @@ public class DefaultExtensionFinder implements ExtensionFinder {
} }
log.debug("Finding extensions for extension point '{}'", type.getName()); log.debug("Finding extensions for extension point '{}'", type.getName());
List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>(); readIndexFiles();
if (entries == null) {
entries = readIndexFiles();
}
for (String entry : entries) { List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
try { for (Map.Entry<String, Set<String>> entry : entries.entrySet()) {
Class<?> extensionType = classLoader.loadClass(entry); String pluginId = entry.getKey();
log.debug("Checking extension type '{}'", extensionType.getName()); Set<String> extensionClassNames = entry.getValue();
if (type.isAssignableFrom(extensionType)) {
Object instance = extensionFactory.create(extensionType); for (String className : extensionClassNames) {
if (instance != null) { try {
Extension extension = extensionType.getAnnotation(Extension.class); Class<?> extensionType = pluginManager.getPluginClassLoader(pluginId).loadClass(className);
log.debug("Added extension '{}' with ordinal {}", extensionType.getName(), extension.ordinal()); log.debug("Checking extension type '{}'", extensionType.getName());
result.add(new ExtensionWrapper<T>(type.cast(instance), extension.ordinal())); if (type.isAssignableFrom(extensionType)) {
Object instance = extensionFactory.create(extensionType);
if (instance != null) {
Extension extension = extensionType.getAnnotation(Extension.class);
log.debug("Added extension '{}' with ordinal {}", extensionType.getName(), extension.ordinal());
result.add(new ExtensionWrapper<T>(type.cast(instance), extension.ordinal()));
}
} else {
log.warn("'{}' is not an extension for extension point '{}'", extensionType.getName(), type.getName());
} }
} else { } catch (ClassNotFoundException e) {
log.warn("'{}' is not an extension for extension point '{}'", extensionType.getName(), type.getName()); log.error(e.getMessage(), e);
} }
} catch (ClassNotFoundException e) { }
log.error(e.getMessage(), e);
}
} }
if (entries.isEmpty()) { if (entries.isEmpty()) {
@ -97,7 +90,18 @@ public class DefaultExtensionFinder implements ExtensionFinder {
return result; return result;
} }
/** @Override
public Set<String> findClassNames(String pluginId) {
return entries.get(pluginId);
}
@Override
public void reset() {
// clear cache
entries = null;
}
/**
* Add the possibility to override the ExtensionFactory. * Add the possibility to override the ExtensionFactory.
* The default implementation uses Class.newInstance() method. * The default implementation uses Class.newInstance() method.
*/ */
@ -122,28 +126,43 @@ public class DefaultExtensionFinder implements ExtensionFinder {
}; };
} }
private Set<String> readIndexFiles() { private Map<String, Set<String>> readIndexFiles() {
log.debug("Reading extensions index files"); // checking cache
Set<String> entries = new HashSet<String>(); if (entries != null) {
return entries;
}
try { entries = new HashMap<String, Set<String>>();
Enumeration<URL> indexFiles = classLoader.getResources(ExtensionsIndexer.EXTENSIONS_RESOURCE);
while (indexFiles.hasMoreElements()) { List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();
Reader reader = new InputStreamReader(indexFiles.nextElement().openStream(), "UTF-8"); for (PluginWrapper plugin : startedPlugins) {
ExtensionsIndexer.readIndex(reader, entries); String pluginId = plugin.getDescriptor().getPluginId();
} log.debug("Reading extensions index file for plugin '{}'", pluginId);
} catch (IOException e) { Set<String> entriesPerPlugin = new HashSet<String>();
log.error(e.getMessage(), e);
} try {
URL url = plugin.getPluginClassLoader().getResource(ExtensionsIndexer.EXTENSIONS_RESOURCE);
log.debug("Read '{}'", url.getFile());
Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
ExtensionsIndexer.readIndex(reader, entriesPerPlugin);
if (entriesPerPlugin.isEmpty()) {
log.debug("No extensions found");
} else {
log.debug("Found possible {} extensions:", entriesPerPlugin.size());
for (String entry : entriesPerPlugin) {
log.debug(" " + entry);
}
}
if (entries.isEmpty()) { entries.put(pluginId, entriesPerPlugin);
log.debug("No extensions found"); } catch (IOException e) {
} else { log.error(e.getMessage(), e);
log.debug("Found possible {} extensions", entries.size()); }
} }
return entries; return entries;
} }
private boolean isExtensionPoint(Class type) { private boolean isExtensionPoint(Class type) {
return ExtensionPoint.class.isAssignableFrom(type); return ExtensionPoint.class.isAssignableFrom(type);

211
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java

@ -12,27 +12,14 @@
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.*;
import ro.fortsoft.pf4j.util.AndFileFilter; import java.io.File;
import ro.fortsoft.pf4j.util.CompoundClassLoader; import java.io.FileFilter;
import ro.fortsoft.pf4j.util.DirectoryFileFilter; import java.io.IOException;
import ro.fortsoft.pf4j.util.FileUtils; import java.util.*;
import ro.fortsoft.pf4j.util.HiddenFilter;
import ro.fortsoft.pf4j.util.NotFileFilter;
import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFileFilter;
/** /**
* Default implementation of the PluginManager interface. * Default implementation of the PluginManager interface.
@ -90,11 +77,6 @@ public class DefaultPluginManager implements PluginManager {
private List<String> enabledPlugins; private List<String> enabledPlugins;
private List<String> disabledPlugins; private List<String> disabledPlugins;
/**
* 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 * Cache value for the runtime mode. No need to re-read it because it wont change at
* runtime. * runtime.
@ -127,15 +109,23 @@ public class DefaultPluginManager implements PluginManager {
return new ArrayList<PluginWrapper>(plugins.values()); return new ArrayList<PluginWrapper>(plugins.values());
} }
@Override
public List<PluginWrapper> getPlugins(PluginState pluginState) {
List<PluginWrapper> plugins= new ArrayList<PluginWrapper>();
for (PluginWrapper plugin : getPlugins()) {
if (pluginState.equals(plugin.getPluginState())) {
plugins.add(plugin);
}
}
return plugins;
}
@Override @Override
public List<PluginWrapper> getResolvedPlugins() { public List<PluginWrapper> getResolvedPlugins() {
return resolvedPlugins; return resolvedPlugins;
} }
public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
@Override @Override
public List<PluginWrapper> getUnresolvedPlugins() { public List<PluginWrapper> getUnresolvedPlugins() {
return unresolvedPlugins; return unresolvedPlugins;
@ -146,6 +136,11 @@ public class DefaultPluginManager implements PluginManager {
return startedPlugins; return startedPlugins;
} }
@Override
public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
@Override @Override
public String loadPlugin(File pluginArchiveFile) { public String loadPlugin(File pluginArchiveFile) {
if (pluginArchiveFile == null || !pluginArchiveFile.exists()) { if (pluginArchiveFile == null || !pluginArchiveFile.exists()) {
@ -167,12 +162,12 @@ public class DefaultPluginManager implements PluginManager {
// TODO uninstalled plugin dependencies? // TODO uninstalled plugin dependencies?
unresolvedPlugins.remove(pluginWrapper); unresolvedPlugins.remove(pluginWrapper);
resolvedPlugins.add(pluginWrapper); resolvedPlugins.add(pluginWrapper);
compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
extensionFinder.reset(); extensionFinder.reset();
return pluginWrapper.getDescriptor().getPluginId(); return pluginWrapper.getDescriptor().getPluginId();
} catch (PluginException e) { } catch (PluginException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
return null; return null;
} }
@ -182,14 +177,18 @@ public class DefaultPluginManager implements PluginManager {
@Override @Override
public void startPlugins() { public void startPlugins() {
for (PluginWrapper pluginWrapper : resolvedPlugins) { for (PluginWrapper pluginWrapper : resolvedPlugins) {
try { PluginState pluginState = pluginWrapper.getPluginState();
log.info("Start plugin '{}'", pluginWrapper.getDescriptor().getPluginId()); if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
pluginWrapper.getPlugin().start(); try {
pluginWrapper.setPluginState(PluginState.STARTED); PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
startedPlugins.add(pluginWrapper); log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
} catch (PluginException e) { pluginWrapper.getPlugin().start();
log.error(e.getMessage(), e); pluginWrapper.setPluginState(PluginState.STARTED);
} startedPlugins.add(pluginWrapper);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
} }
} }
@ -201,15 +200,25 @@ public class DefaultPluginManager implements PluginManager {
if (!plugins.containsKey(pluginId)) { if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
} }
PluginWrapper pluginWrapper = plugins.get(pluginId); PluginWrapper pluginWrapper = plugins.get(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
if (pluginWrapper.getPluginState().equals(PluginState.STARTED)) { if (PluginState.STARTED == pluginWrapper.getPluginState()) {
log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STARTED; return PluginState.STARTED;
} }
for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
if (PluginState.DISABLED == pluginWrapper.getPluginState()) {
// automatically enable plugin on manual plugin start
if (!enablePlugin(pluginId)) {
return pluginWrapper.getPluginState();
}
}
for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
startPlugin(dependency.getPluginId()); startPlugin(dependency.getPluginId());
} }
try { try {
log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().start(); pluginWrapper.getPlugin().start();
@ -218,6 +227,7 @@ public class DefaultPluginManager implements PluginManager {
} catch (PluginException e) { } catch (PluginException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
return pluginWrapper.getPluginState(); return pluginWrapper.getPluginState();
} }
@ -231,15 +241,17 @@ public class DefaultPluginManager implements PluginManager {
Iterator<PluginWrapper> itr = startedPlugins.iterator(); Iterator<PluginWrapper> itr = startedPlugins.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
PluginWrapper pluginWrapper = itr.next(); PluginWrapper pluginWrapper = itr.next();
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); if (PluginState.STARTED == pluginWrapper.getPluginState()) {
try { try {
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
pluginWrapper.getPlugin().stop(); log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.setPluginState(PluginState.STOPPED); pluginWrapper.getPlugin().stop();
itr.remove(); pluginWrapper.setPluginState(PluginState.STOPPED);
} catch (PluginException e) { itr.remove();
log.error(e.getMessage(), e); } catch (PluginException e) {
} log.error(e.getMessage(), e);
}
}
} }
} }
@ -251,15 +263,24 @@ public class DefaultPluginManager implements PluginManager {
if (!plugins.containsKey(pluginId)) { if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId)); throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
} }
PluginWrapper pluginWrapper = plugins.get(pluginId); PluginWrapper pluginWrapper = plugins.get(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
if (pluginWrapper.getPluginState().equals(PluginState.STOPPED)) { if (PluginState.STOPPED == pluginWrapper.getPluginState()) {
log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STOPPED; return PluginState.STOPPED;
} }
// test for disabled plugin
if (PluginState.DISABLED == pluginWrapper.getPluginState()) {
// do nothing
return pluginWrapper.getPluginState();
}
for (PluginDependency dependency : pluginDescriptor.getDependencies()) { for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
stopPlugin(dependency.getPluginId()); stopPlugin(dependency.getPluginId());
} }
try { try {
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion()); log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop(); pluginWrapper.getPlugin().stop();
@ -268,6 +289,7 @@ public class DefaultPluginManager implements PluginManager {
} catch (PluginException e) { } catch (PluginException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
return pluginWrapper.getPluginState(); return pluginWrapper.getPluginState();
} }
@ -332,7 +354,7 @@ public class DefaultPluginManager implements PluginManager {
public boolean unloadPlugin(String pluginId) { public boolean unloadPlugin(String pluginId) {
try { try {
PluginState state = stopPlugin(pluginId); PluginState state = stopPlugin(pluginId);
if (!PluginState.STOPPED.equals(state)) { if (PluginState.STOPPED != state) {
return false; return false;
} }
@ -354,20 +376,87 @@ public class DefaultPluginManager implements PluginManager {
// remove the classloader // remove the classloader
if (pluginClassLoaders.containsKey(pluginId)) { if (pluginClassLoaders.containsKey(pluginId)) {
PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId); PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);
compoundClassLoader.removeLoader(classLoader);
try { try {
classLoader.close(); classLoader.close();
} catch (IOException e) { } catch (IOException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
} }
} }
return true; return true;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// ignore not found exceptions because this method is recursive // ignore not found exceptions because this method is recursive
} }
return false; return false;
} }
@Override
public boolean disablePlugin(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 (PluginState.DISABLED == getPlugin(pluginId).getPluginState()) {
log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
if (PluginState.STOPPED == stopPlugin(pluginId)) {
getPlugin(pluginId).setPluginState(PluginState.DISABLED);
extensionFinder.reset();
if (disabledPlugins.add(pluginId)) {
try {
FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
} catch (IOException e) {
log.error("Failed to disable plugin {}", pluginId, e);
return false;
}
}
log.info("Disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
return false;
}
@Override
public boolean enablePlugin(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 (PluginState.DISABLED != getPlugin(pluginId).getPluginState()) {
log.debug("Plugin plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
try {
if (disabledPlugins.remove(pluginId)) {
FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
}
} catch (IOException e) {
log.error("Failed to enable plugin {}", pluginId, e);
return false;
}
getPlugin(pluginId).setPluginState(PluginState.CREATED);
extensionFinder.reset();
log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
@Override @Override
public boolean deletePlugin(String pluginId) { public boolean deletePlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) { if (!plugins.containsKey(pluginId)) {
@ -432,6 +521,11 @@ public class DefaultPluginManager implements PluginManager {
return extensions; return extensions;
} }
@Override
public Set<String> getExtensionClassNames(String pluginId) {
return extensionFinder.findClassNames(pluginId);
}
@Override @Override
public RuntimeMode getRuntimeMode() { public RuntimeMode getRuntimeMode() {
if (runtimeMode == null) { if (runtimeMode == null) {
@ -478,7 +572,7 @@ public class DefaultPluginManager implements PluginManager {
* Add the possibility to override the ExtensionFinder. * Add the possibility to override the ExtensionFinder.
*/ */
protected ExtensionFinder createExtensionFinder() { protected ExtensionFinder createExtensionFinder() {
return new DefaultExtensionFinder(compoundClassLoader); return new DefaultExtensionFinder(this);
} }
/** /**
@ -537,7 +631,6 @@ public class DefaultPluginManager implements PluginManager {
resolvedPlugins = new ArrayList<PluginWrapper>(); resolvedPlugins = new ArrayList<PluginWrapper>();
startedPlugins = new ArrayList<PluginWrapper>(); startedPlugins = new ArrayList<PluginWrapper>();
disabledPlugins = new ArrayList<String>(); disabledPlugins = new ArrayList<String>();
compoundClassLoader = new CompoundClassLoader();
pluginClasspath = createPluginClasspath(); pluginClasspath = createPluginClasspath();
pluginDescriptorFinder = createPluginDescriptorFinder(); pluginDescriptorFinder = createPluginDescriptorFinder();
@ -575,12 +668,6 @@ public class DefaultPluginManager implements PluginManager {
String pluginClassName = pluginDescriptor.getPluginClass(); String pluginClassName = pluginDescriptor.getPluginClass();
log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath); log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) {
log.info("Plugin '{}' is disabled", pluginPath);
return null;
}
// load plugin // load plugin
log.debug("Loading plugin '{}'", pluginPath); log.debug("Loading plugin '{}'", pluginPath);
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath); PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
@ -591,6 +678,13 @@ public class DefaultPluginManager implements PluginManager {
log.debug("Creating wrapper for plugin '{}'", pluginPath); log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader()); PluginWrapper pluginWrapper = new PluginWrapper(pluginDescriptor, pluginPath, pluginLoader.getPluginClassLoader());
pluginWrapper.setRuntimeMode(getRuntimeMode()); pluginWrapper.setRuntimeMode(getRuntimeMode());
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) {
log.info("Plugin '{}' is disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath); log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
String pluginId = pluginDescriptor.getPluginId(); String pluginId = pluginDescriptor.getPluginId();
@ -641,7 +735,6 @@ public class DefaultPluginManager implements PluginManager {
resolvedPlugins = dependencyResolver.getSortedPlugins(); resolvedPlugins = dependencyResolver.getSortedPlugins();
for (PluginWrapper pluginWrapper : resolvedPlugins) { for (PluginWrapper pluginWrapper : resolvedPlugins) {
unresolvedPlugins.remove(pluginWrapper); unresolvedPlugins.remove(pluginWrapper);
compoundClassLoader.addLoader(pluginWrapper.getPluginClassLoader());
log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId()); log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
} }
} }

13
pf4j/src/main/java/ro/fortsoft/pf4j/ExtensionFinder.java

@ -13,14 +13,23 @@
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public interface ExtensionFinder { public interface ExtensionFinder {
public <T> List<ExtensionWrapper<T>> find(Class<T> type); /**
* Retrieves a list with all extensions found for an extension point.
*/
public <T> List<ExtensionWrapper<T>> find(Class<T> type);
public void reset(); /**
* Retrieves a list with all extension class names found for a plugin.
*/
public Set<String> findClassNames(String pluginId);
public void reset();
} }

37
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java

@ -1,24 +1,27 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * specific language governing permissions and limitations under the License.
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List; import java.util.List;
/** /**
* One instance of this class should be created by plugin manager for every available plug-in. * One instance of this class should be created by plugin manager for every available plug-in.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginClassLoader extends URLClassLoader { public class PluginClassLoader extends URLClassLoader {
@ -29,10 +32,10 @@ public class PluginClassLoader extends URLClassLoader {
private PluginManager pluginManager; private PluginManager pluginManager;
private PluginDescriptor pluginDescriptor; private PluginDescriptor pluginDescriptor;
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) { public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) {
super(new URL[0], parent); super(new URL[0], parent);
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.pluginDescriptor = pluginDescriptor; this.pluginDescriptor = pluginDescriptor;
} }
@ -91,4 +94,26 @@ public class PluginClassLoader extends URLClassLoader {
return super.loadClass(className); return super.loadClass(className);
} }
@Override
public URL getResource(String name) {
if (PluginState.DISABLED == getPlugin().getPluginState()) {
return null;
}
return super.getResource(name);
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if (PluginState.DISABLED == getPlugin().getPluginState()) {
return Collections.emptyEnumeration();
}
return super.getResources(name);
}
private PluginWrapper getPlugin() {
return pluginManager.getPlugin(pluginDescriptor.getPluginId());
}
} }

28
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2013 Decebal Suiu * Copyright 2013 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * specific language governing permissions and limitations under the License.
@ -16,25 +16,25 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* The classpath of the plugin after it was unpacked. * The classpath of the plugin after it was unpacked.
* It contains classes directories and lib directories (directories that contains jars). * It contains classes directories and lib directories (directories that contains jars).
* All directories are relativ to plugin repository. * All directories are relative to plugin repository.
* The default values are "classes" and "lib". * The default values are "classes" and "lib".
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginClasspath { public class PluginClasspath {
private static final String DEFAULT_CLASSES_DIRECTORY = "classes"; private static final String DEFAULT_CLASSES_DIRECTORY = "classes";
private static final String DEFAULT_LIB_DIRECTORY = "lib"; private static final String DEFAULT_LIB_DIRECTORY = "lib";
protected List<String> classesDirectories; protected List<String> classesDirectories;
protected List<String> libDirectories; protected List<String> libDirectories;
public PluginClasspath() { public PluginClasspath() {
classesDirectories = new ArrayList<String>(); classesDirectories = new ArrayList<String>();
libDirectories = new ArrayList<String>(); libDirectories = new ArrayList<String>();
addResources(); addResources();
} }
@ -53,10 +53,10 @@ public class PluginClasspath {
public void setLibDirectories(List<String> libDirectories) { public void setLibDirectories(List<String> libDirectories) {
this.libDirectories = libDirectories; this.libDirectories = libDirectories;
} }
protected void addResources() { protected void addResources() {
classesDirectories.add(DEFAULT_CLASSES_DIRECTORY); classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
libDirectories.add(DEFAULT_LIB_DIRECTORY); libDirectories.add(DEFAULT_LIB_DIRECTORY);
} }
} }

38
pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java

@ -14,6 +14,7 @@ package ro.fortsoft.pf4j;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Provides the functionality for plugin management such as load, * Provides the functionality for plugin management such as load,
@ -28,21 +29,34 @@ public interface PluginManager {
*/ */
public List<PluginWrapper> getPlugins(); public List<PluginWrapper> getPlugins();
/**
* Retrieves all plugins with this state.
*/
public List<PluginWrapper> getPlugins(PluginState pluginState);
/** /**
* Retrieves all resolved plugins (with resolved dependency). * Retrieves all resolved plugins (with resolved dependency).
*/ */
public List<PluginWrapper> getResolvedPlugins(); public List<PluginWrapper> getResolvedPlugins();
/** /**
* Retrieves all unresolved plugins (with unresolved dependency). * Retrieves all unresolved plugins (with unresolved dependency).
*/ */
public List<PluginWrapper> getUnresolvedPlugins(); public List<PluginWrapper> getUnresolvedPlugins();
/** /**
* Retrieves all started plugins. * Retrieves all started plugins.
*/ */
public List<PluginWrapper> getStartedPlugins(); public List<PluginWrapper> getStartedPlugins();
/**
* Retrieves the plugin with this id.
*
* @param pluginId
* @return the plugin
*/
public PluginWrapper getPlugin(String pluginId);
/** /**
* Load plugins. * Load plugins.
*/ */
@ -88,6 +102,22 @@ public interface PluginManager {
*/ */
public boolean unloadPlugin(String pluginId); public boolean unloadPlugin(String pluginId);
/**
* Disables a plugin from being loaded.
*
* @param pluginId
* @return true if plugin is disabled
*/
public boolean disablePlugin(String pluginId);
/**
* Enables a plugin that has previously been disabled.
*
* @param pluginId
* @return true if plugin is enabled
*/
public boolean enablePlugin(String pluginId);
/** /**
* Deletes a plugin. * Deletes a plugin.
* *
@ -100,7 +130,9 @@ public interface PluginManager {
public <T> List<T> getExtensions(Class<T> type); public <T> List<T> getExtensions(Class<T> type);
/** public Set<String> getExtensionClassNames(String pluginId);
/**
* The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT. * The runtime mode. Must currently be either DEVELOPMENT or DEPLOYMENT.
*/ */
public RuntimeMode getRuntimeMode(); public RuntimeMode getRuntimeMode();

31
pf4j/src/main/java/ro/fortsoft/pf4j/PluginState.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * specific language governing permissions and limitations under the License.
@ -18,19 +18,34 @@ package ro.fortsoft.pf4j;
public class PluginState { public class PluginState {
public static final PluginState CREATED = new PluginState("CREATED"); public static final PluginState CREATED = new PluginState("CREATED");
public static final PluginState INITIALIZED = new PluginState("INITIALIZED"); public static final PluginState DISABLED = new PluginState("DISABLED");
public static final PluginState STARTED = new PluginState("STARTED"); public static final PluginState STARTED = new PluginState("STARTED");
public static final PluginState STOPPED = new PluginState("STOPPED"); public static final PluginState STOPPED = new PluginState("STOPPED");
public static final PluginState DESTROYED = new PluginState("DESTROYED");
public static final PluginState FAILED = new PluginState("FAILED");
private String status; private String status;
private PluginState(String status) { private PluginState(String status) {
this.status = status; this.status = status;
} }
@Override @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PluginState that = (PluginState) o;
if (!status.equals(that.status)) return false;
return true;
}
@Override
public int hashCode() {
return status.hashCode();
}
@Override
public String toString() { public String toString() {
return status; return status;
} }

33
pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java

@ -1,11 +1,11 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2012 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * specific language governing permissions and limitations under the License.
@ -28,22 +28,22 @@ public class PluginWrapper {
Plugin plugin; Plugin plugin;
PluginState pluginState; PluginState pluginState;
RuntimeMode runtimeMode; RuntimeMode runtimeMode;
public PluginWrapper(PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) { public PluginWrapper(PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) {
this.descriptor = descriptor; this.descriptor = descriptor;
this.pluginPath = pluginPath; this.pluginPath = pluginPath;
this.pluginClassLoader = pluginClassLoader; this.pluginClassLoader = pluginClassLoader;
// TODO // TODO
try { try {
plugin = createPluginInstance(); plugin = createPluginInstance();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
pluginState = PluginState.CREATED; pluginState = PluginState.CREATED;
} }
/** /**
* Returns the plugin descriptor. * Returns the plugin descriptor.
*/ */
@ -79,12 +79,19 @@ public class PluginWrapper {
return runtimeMode; return runtimeMode;
} }
/**
* Shortcut
*/
public String getPluginId() {
return getDescriptor().getPluginId();
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + descriptor.getPluginId().hashCode(); result = prime * result + descriptor.getPluginId().hashCode();
return result; return result;
} }
@ -93,20 +100,20 @@ public class PluginWrapper {
if (this == obj) { if (this == obj) {
return true; return true;
} }
if (obj == null) { if (obj == null) {
return false; return false;
} }
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass()) {
return false; return false;
} }
PluginWrapper other = (PluginWrapper) obj; PluginWrapper other = (PluginWrapper) obj;
if (!descriptor.getPluginId().equals(other.descriptor.getPluginId())) { if (!descriptor.getPluginId().equals(other.descriptor.getPluginId())) {
return false; return false;
} }
return true; return true;
} }
@ -119,7 +126,7 @@ public class PluginWrapper {
void setPluginState(PluginState pluginState) { void setPluginState(PluginState pluginState) {
this.pluginState = pluginState; this.pluginState = pluginState;
} }
void setRuntimeMode(RuntimeMode runtimeMode) { void setRuntimeMode(RuntimeMode runtimeMode) {
this.runtimeMode = runtimeMode; this.runtimeMode = runtimeMode;
} }

22
pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java

@ -12,11 +12,9 @@
*/ */
package ro.fortsoft.pf4j.util; package ro.fortsoft.pf4j.util;
import java.io.BufferedReader; import java.io.*;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -50,6 +48,21 @@ public class FileUtils {
return lines; return lines;
} }
public static void writeLines(Collection<String> lines, File file) throws IOException {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(file));
for (String line : lines) {
writer.write(line);
writer.write('\n');
}
} finally {
if (writer != null) {
writer.close();
}
}
}
/** /**
* Delete a file or recursively delete a folder. * Delete a file or recursively delete a folder.
* *
@ -71,6 +84,7 @@ public class FileUtils {
} }
} }
success |= fileOrFolder.delete(); success |= fileOrFolder.delete();
return success; return success;
} }
} }

Loading…
Cancel
Save