Browse Source

Merge pull request #128 from decebals/jar_plugin_manager

Add JarPluginManager, PluginLoader, AbstractPluginManager
pull/129/head
Decebal Suiu 8 years ago committed by GitHub
parent
commit
002f1e3957
  1. 2
      demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java
  2. 2
      demo/app/src/main/resources/log4j.properties
  3. 800
      pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java
  4. 74
      pf4j/src/main/java/ro/fortsoft/pf4j/BasePluginRepository.java
  5. 14
      pf4j/src/main/java/ro/fortsoft/pf4j/CompoundPluginRepository.java
  6. 15
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginClasspath.java
  7. 41
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java
  8. 4
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginFactory.java
  9. 80
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginLoader.java
  10. 861
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  11. 110
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java
  12. 31
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginStatusProvider.java
  13. 2
      pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java
  14. 15
      pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java
  15. 115
      pf4j/src/main/java/ro/fortsoft/pf4j/JarPluginManager.java
  16. 53
      pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java
  17. 44
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java
  18. 29
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java
  19. 10
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptorFinder.java
  20. 113
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java
  21. 9
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginManager.java
  22. 10
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginRepository.java
  23. 23
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginWrapper.java
  24. 35
      pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java
  25. 26
      pf4j/src/main/java/ro/fortsoft/pf4j/util/FileUtils.java
  26. 4
      pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java
  27. 57
      pf4j/src/test/java/ro/fortsoft/pf4j/DefaultPluginRepositoryTest.java
  28. 28
      pf4j/src/test/java/ro/fortsoft/pf4j/DefaultPluginStatusProviderTest.java
  29. 122
      pf4j/src/test/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinderTest.java
  30. 20
      pf4j/src/test/resources/log4j.properties

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

@ -21,6 +21,7 @@ import java.util.Set;
import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager;
import ro.fortsoft.pf4j.JarPluginManager;
import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.PluginWrapper;
import ro.fortsoft.pf4j.demo.api.Greeting;
@ -38,6 +39,7 @@ public class Boot {
// create the plugin manager
final PluginManager pluginManager = new DefaultPluginManager();
// final PluginManager pluginManager = new JarPluginManager();
// load the plugins
pluginManager.loadPlugins();

2
demo/app/src/main/resources/log4j.properties

@ -5,7 +5,7 @@ log4j.rootLogger=DEBUG, Console
#
log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
# !!! Put the bellow classes on level TRACE when you are in trouble
log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=WARN, Console
log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=DEBUG, Console
log4j.logger.ro.fortsoft.pf4j.AbstractExtensionFinder=DEBUG, Console
log4j.additivity.ro.fortsoft.pf4j=false
log4j.additivity.ro.fortsoft.pf4j.PluginClassLoader=false

800
pf4j/src/main/java/ro/fortsoft/pf4j/AbstractPluginManager.java

@ -0,0 +1,800 @@
/*
* Copyright 2016 Decebal Suiu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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 ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
import com.github.zafarkhaja.semver.expr.Expression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
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 java.util.Set;
/**
* This class implements the boilerplate plugin code that any {@link PluginManager}
* implementation would have to support.
* It helps cut the noise out of the subclass that handles plugin management.
*
* @author Decebal Suiu
*/
public abstract class AbstractPluginManager implements PluginManager {
private static final Logger log = LoggerFactory.getLogger(AbstractPluginManager.class);
private Path pluginsRoot;
private ExtensionFinder extensionFinder;
private PluginDescriptorFinder pluginDescriptorFinder;
/*
* A map of plugins this manager is responsible for (the key is the 'pluginId').
*/
protected Map<String, PluginWrapper> plugins;
/*
* A map of plugin class loaders (he key is the 'pluginId').
*/
private Map<String, ClassLoader> pluginClassLoaders;
/*
* A list with unresolved plugins (unresolved dependency).
*/
private List<PluginWrapper> unresolvedPlugins;
/**
* A list with resolved plugins (resolved dependency).
*/
private List<PluginWrapper> resolvedPlugins;
/*
* A list with started plugins.
*/
private List<PluginWrapper> startedPlugins;
/*
* The registered {@link PluginStateListener}s.
*/
private List<PluginStateListener> pluginStateListeners;
/*
* Cache value for the runtime mode.
* No need to re-read it because it wont change at runtime.
*/
private RuntimeMode runtimeMode;
/*
* The system version used for comparisons to the plugin requires attribute.
*/
private Version systemVersion = Version.forIntegers(0, 0, 0);
/**
* A relation between 'pluginPath' and 'pluginId'
*/
private Map<Path, String> pathToIdMap;
private PluginRepository pluginRepository;
private PluginFactory pluginFactory;
private ExtensionFactory extensionFactory;
private PluginStatusProvider pluginStatusProvider;
private DependencyResolver dependencyResolver;
private PluginLoader pluginLoader;
/**
* The plugins root is supplied by {@code System.getProperty("pf4j.pluginsDir", "plugins")}.
*/
public AbstractPluginManager() {
initialize();
}
/**
* Constructs {@code DefaultPluginManager} which the given plugins root.
*
* @param pluginsRoot the root to search for plugins
*/
public AbstractPluginManager(Path pluginsRoot) {
this.pluginsRoot = pluginsRoot;
initialize();
}
@Override
public void setSystemVersion(Version version) {
systemVersion = version;
}
@Override
public Version getSystemVersion() {
return systemVersion;
}
/**
* Returns a copy of plugins.
*
* @return
*/
@Override
public List<PluginWrapper> getPlugins() {
return new ArrayList<>(plugins.values());
}
/**
* Returns a copy of plugins with that state.
*
* @param pluginState
* @return
*/
@Override
public List<PluginWrapper> getPlugins(PluginState pluginState) {
List<PluginWrapper> plugins = new ArrayList<>();
for (PluginWrapper plugin : getPlugins()) {
if (pluginState.equals(plugin.getPluginState())) {
plugins.add(plugin);
}
}
return plugins;
}
@Override
public List<PluginWrapper> getResolvedPlugins() {
return resolvedPlugins;
}
@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return unresolvedPlugins;
}
@Override
public List<PluginWrapper> getStartedPlugins() {
return startedPlugins;
}
@Override
public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
@Override
public String loadPlugin(Path pluginPath) {
if ((pluginPath == null) || Files.notExists(pluginPath)) {
throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginPath));
}
log.debug("Loading plugin from '{}'", pluginPath);
try {
PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
// TODO uninstalled plugin dependencies?
getUnresolvedPlugins().remove(pluginWrapper);
getResolvedPlugins().add(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, null));
return pluginWrapper.getDescriptor().getPluginId();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return null;
}
/**
* Load plugins.
*/
@Override
public void loadPlugins() {
log.debug("Lookup plugins in '{}'", pluginsRoot);
// check for plugins root
if (Files.notExists(pluginsRoot) || !Files.isDirectory(pluginsRoot)) {
log.error("No '{}' root", pluginsRoot);
return;
}
// get all plugin paths from repository
List<Path> pluginPaths = pluginRepository.getPluginPaths();
// check for no plugins
if (pluginPaths.isEmpty()) {
log.info("No plugins");
return;
}
log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
// load plugins from plugin paths
// TODO
for (Path pluginPath : pluginPaths) {
try {
loadPluginFromPath(pluginPath);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
// resolve 'unresolvedPlugins'
try {
resolvePlugins();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
@Override
public boolean unloadPlugin(String pluginId) {
try {
PluginState pluginState = stopPlugin(pluginId);
if (PluginState.STARTED == pluginState) {
return false;
}
PluginWrapper pluginWrapper = getPlugin(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);
getResolvedPlugins().remove(pluginWrapper);
pathToIdMap.remove(pluginWrapper.getPluginPath());
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
// remove the classloader
Map<String, ClassLoader> pluginClassLoaders = getPluginClassLoaders();
if (pluginClassLoaders.containsKey(pluginId)) {
ClassLoader classLoader = pluginClassLoaders.remove(pluginId);
if (classLoader instanceof Closeable) {
try {
((Closeable) classLoader).close();
} catch (IOException e) {
log.error("Cannot close classloader", e);
}
}
}
return true;
} catch (IllegalArgumentException e) {
// ignore not found exceptions because this method is recursive
}
return false;
}
@Override
public boolean deletePlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginState pluginState = stopPlugin(pluginId);
if (PluginState.STARTED == pluginState) {
log.error("Failed to stop plugin '{}' on delete", pluginId);
return false;
}
if (!unloadPlugin(pluginId)) {
log.error("Failed to unload plugin '{}' on delete", pluginId);
return false;
}
Path pluginPath = pluginWrapper.getPluginPath();
pluginRepository.deletePluginPath(pluginPath);
return true;
}
/**
* Start all active plugins.
*/
@Override
public void startPlugins() {
for (PluginWrapper pluginWrapper : resolvedPlugins) {
PluginState pluginState = pluginWrapper.getPluginState();
if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
try {
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().start();
pluginWrapper.setPluginState(PluginState.STARTED);
startedPlugins.add(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* 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 = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STARTED == pluginState) {
log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STARTED;
}
if (PluginState.DISABLED == pluginState) {
// automatically enable plugin on manual plugin start
if (!enablePlugin(pluginId)) {
return pluginState;
}
}
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);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState();
}
/**
* Stop all active plugins.
*/
@Override
public void stopPlugins() {
// stop started plugins in reverse order
Collections.reverse(startedPlugins);
Iterator<PluginWrapper> itr = startedPlugins.iterator();
while (itr.hasNext()) {
PluginWrapper pluginWrapper = itr.next();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STARTED == pluginState) {
try {
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
itr.remove();
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* Stop the specified plugin and it's dependencies.
*/
@Override
public PluginState stopPlugin(String pluginId) {
return stopPlugin(pluginId, true);
}
private PluginState stopPlugin(String pluginId, boolean stopDependents) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STOPPED == pluginState) {
log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STOPPED;
}
// test for disabled plugin
if (PluginState.DISABLED == pluginState) {
// do nothing
return pluginState;
}
if (stopDependents) {
List<String> dependents = dependencyResolver.getDependents(pluginId);
while (!dependents.isEmpty()) {
String dependent = dependents.remove(0);
stopPlugin(dependent, false);
dependents.addAll(0, dependencyResolver.getDependents(dependent));
}
}
try {
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
startedPlugins.remove(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState();
}
@Override
public boolean disablePlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.DISABLED == pluginState) {
log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
if (PluginState.STOPPED == stopPlugin(pluginId)) {
pluginWrapper.setPluginState(PluginState.DISABLED);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
if (!pluginStatusProvider.disablePlugin(pluginId)) {
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 = getPlugin(pluginId);
if (!isPluginValid(pluginWrapper)) {
log.warn("Plugin '{}:{}' can not be enabled", pluginWrapper.getPluginId(),
pluginWrapper.getDescriptor().getVersion());
return false;
}
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.DISABLED != pluginState) {
log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
if (!pluginStatusProvider.enablePlugin(pluginId)) {
return false;
}
pluginWrapper.setPluginState(PluginState.CREATED);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
/**
* Get plugin class loader for this path.
*/
@Override
public ClassLoader getPluginClassLoader(String pluginId) {
return pluginClassLoaders.get(pluginId);
}
@Override
public <T> List<T> getExtensions(Class<T> type) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
}
@Override
public <T> List<T> getExtensions(Class<T> type, String pluginId) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId);
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
}
@Override
@SuppressWarnings("unchecked")
public List getExtensions(String pluginId) {
List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
List extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
}
@Override
public Set<String> getExtensionClassNames(String pluginId) {
return extensionFinder.findClassNames(pluginId);
}
@Override
public ExtensionFactory getExtensionFactory() {
return extensionFactory;
}
// TODO remove
public PluginLoader getPluginLoader() {
return pluginLoader;
}
public Path getPluginsRoot() {
return pluginsRoot;
}
@Override
public RuntimeMode getRuntimeMode() {
if (runtimeMode == null) {
// retrieves the runtime mode from system
String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
runtimeMode = RuntimeMode.byName(modeAsString);
}
return runtimeMode;
}
@Override
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
for (PluginWrapper plugin : resolvedPlugins) {
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
}
return null;
}
@Override
public synchronized void addPluginStateListener(PluginStateListener listener) {
pluginStateListeners.add(listener);
}
@Override
public synchronized void removePluginStateListener(PluginStateListener listener) {
pluginStateListeners.remove(listener);
}
public Version getVersion() {
String version = null;
Package pf4jPackage = getClass().getPackage();
if (pf4jPackage != null) {
version = pf4jPackage.getImplementationVersion();
if (version == null) {
version = pf4jPackage.getSpecificationVersion();
}
}
return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0);
}
protected abstract PluginRepository createPluginRepository();
protected abstract PluginFactory createPluginFactory();
protected abstract ExtensionFactory createExtensionFactory();
protected abstract PluginDescriptorFinder createPluginDescriptorFinder();
protected abstract ExtensionFinder createExtensionFinder();
protected abstract PluginStatusProvider createPluginStatusProvider();
protected abstract PluginLoader createPluginLoader();
protected PluginDescriptorFinder getPluginDescriptorFinder() {
return pluginDescriptorFinder;
}
protected PluginFactory getPluginFactory() {
return pluginFactory;
}
protected Map<String, ClassLoader> getPluginClassLoaders() {
return pluginClassLoaders;
}
protected void initialize() {
plugins = new HashMap<>();
pluginClassLoaders = new HashMap<>();
unresolvedPlugins = new ArrayList<>();
resolvedPlugins = new ArrayList<>();
startedPlugins = new ArrayList<>();
pluginStateListeners = new ArrayList<>();
pathToIdMap = new HashMap<>();
if (pluginsRoot == null) {
pluginsRoot = createPluginsRoot();
}
System.setProperty("pf4j.pluginsDir", pluginsRoot.toString());
dependencyResolver = new DependencyResolver();
pluginRepository = createPluginRepository();
pluginFactory = createPluginFactory();
extensionFactory = createExtensionFactory();
pluginDescriptorFinder = createPluginDescriptorFinder();
extensionFinder = createExtensionFinder();
pluginStatusProvider = createPluginStatusProvider();
pluginLoader = createPluginLoader();
}
/**
* Add the possibility to override the plugins root.
* If a "pf4j.pluginsDir" system property is defined than this method returns
* that root.
* If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
* DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
* DEFAULT_PLUGINS_DIRECTORY ("plugins").
* @return
*/
protected Path createPluginsRoot() {
String pluginsDir = System.getProperty("pf4j.pluginsDir");
if (pluginsDir == null) {
if (isDevelopment()) {
pluginsDir = "../plugins";
} else {
pluginsDir = "plugins";
}
}
return Paths.get(pluginsDir);
}
protected boolean isPluginValid(PluginWrapper pluginWrapper) {
Expression requires = pluginWrapper.getDescriptor().getRequires();
Version system = getSystemVersion();
if (requires.interpret(system)) {
return true;
}
log.warn("Plugin '{}:{}' requires a minimum system version of {}",
pluginWrapper.getPluginId(),
pluginWrapper.getDescriptor().getVersion(),
requires);
return false;
}
protected boolean isPluginDisabled(String pluginId) {
return pluginStatusProvider.isPluginDisabled(pluginId);
}
protected void resolvePlugins() throws PluginException {
resolveDependencies();
}
protected void resolveDependencies() throws PluginException {
dependencyResolver.resolve(unresolvedPlugins);
resolvedPlugins = dependencyResolver.getSortedPlugins();
for (PluginWrapper pluginWrapper : resolvedPlugins) {
unresolvedPlugins.remove(pluginWrapper);
log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
}
}
protected synchronized void firePluginStateEvent(PluginStateEvent event) {
for (PluginStateListener listener : pluginStateListeners) {
log.debug("Fire '{}' to '{}'", event, listener);
listener.pluginStateChanged(event);
}
}
protected PluginWrapper loadPluginFromPath(Path pluginPath) throws PluginException {
// test for plugin duplication
if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
return null;
}
// retrieves the plugin descriptor
log.debug("Find plugin descriptor '{}'", pluginPath);
PluginDescriptor pluginDescriptor = getPluginDescriptorFinder().find(pluginPath);
log.debug("Descriptor {}", pluginDescriptor);
String pluginClassName = pluginDescriptor.getPluginClass();
log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
// load plugin
log.debug("Loading plugin '{}'", pluginPath);
ClassLoader pluginClassLoader = getPluginLoader().loadPlugin(pluginPath, pluginDescriptor);
log.debug("Loaded plugin '{}' with class loader '{}'", pluginPath, pluginClassLoader);
// create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
pluginWrapper.setPluginFactory(getPluginFactory());
pluginWrapper.setRuntimeMode(getRuntimeMode());
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) {
log.info("Plugin '{}' is disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
// validate the plugin
if (!isPluginValid(pluginWrapper)) {
log.info("Plugin '{}' is disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
String pluginId = pluginDescriptor.getPluginId();
// add plugin to the list with plugins
plugins.put(pluginId, pluginWrapper);
getUnresolvedPlugins().add(pluginWrapper);
// add plugin class loader to the list with class loaders
getPluginClassLoaders().put(pluginId, pluginClassLoader);
return pluginWrapper;
}
// TODO add this method in PluginManager as default method for Java 8.
protected boolean isDevelopment() {
return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode());
}
}

74
pf4j/src/main/java/ro/fortsoft/pf4j/BasePluginRepository.java

@ -0,0 +1,74 @@
/*
* Copyright 2012 Decebal Suiu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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 ro.fortsoft.pf4j;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Decebal Suiu
* @author Mário Franco
*/
public class BasePluginRepository implements PluginRepository {
protected final Path pluginsRoot;
protected FileFilter filter;
public BasePluginRepository(Path pluginsRoot) {
this.pluginsRoot = pluginsRoot;
}
public BasePluginRepository(Path pluginsRoot, FileFilter filter) {
this.pluginsRoot = pluginsRoot;
this.filter = filter;
}
public void setFilter(FileFilter filter) {
this.filter = filter;
}
@Override
public List<Path> getPluginPaths() {
File[] files = pluginsRoot.toFile().listFiles(filter);
if ((files == null) || files.length == 0) {
return Collections.emptyList();
}
List<Path> paths = new ArrayList<>(files.length);
for (File file : files) {
paths.add(file.toPath());
}
return paths;
}
@Override
public boolean deletePluginPath(Path pluginPath) {
try {
return Files.deleteIfExists(pluginPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

14
pf4j/src/main/java/ro/fortsoft/pf4j/CompoundPluginRepository.java

@ -15,7 +15,7 @@
*/
package ro.fortsoft.pf4j;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@ -32,19 +32,19 @@ public class CompoundPluginRepository implements PluginRepository {
}
@Override
public List<File> getPluginArchives() {
List<File> file = new ArrayList<>();
public List<Path> getPluginPaths() {
List<Path> paths = new ArrayList<>();
for (PluginRepository repository : repositories) {
file.addAll(repository.getPluginArchives());
paths.addAll(repository.getPluginPaths());
}
return file;
return paths;
}
@Override
public boolean deletePluginArchive(String pluginPath) {
public boolean deletePluginPath(Path pluginPath) {
for (PluginRepository repository : repositories) {
if (repository.deletePluginArchive(pluginPath)) {
if (repository.deletePluginPath(pluginPath)) {
return true;
}
}

15
pf4j/src/main/java/ro/fortsoft/pf4j/CyclicDependencyException.java → pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginClasspath.java

@ -1,5 +1,5 @@
/*
* Copyright 2012 Decebal Suiu
* Copyright 2016 Decebal Suiu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,16 +16,17 @@
package ro.fortsoft.pf4j;
/**
* CyclicDependencyException will be thrown if a cyclic dependency is detected.
* The default values are {@code classes} and {@code lib}.
*
* @author Decebal Suiu
*/
class CyclicDependencyException extends PluginException {
public class DefaultPluginClasspath extends PluginClasspath {
private static final long serialVersionUID = 1L;
public DefaultPluginClasspath() {
super();
public CyclicDependencyException(String message) {
super(message);
}
addClassesDirectories("classes");
addLibDirectories("lib");
}
}

41
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java

@ -15,16 +15,47 @@
*/
package ro.fortsoft.pf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.jar.Manifest;
/**
* The default implementation for PluginDescriptorFinder.
* Now, this class it's a "link" to {@link ro.fortsoft.pf4j.ManifestPluginDescriptorFinder}.
* The default implementation for {@link PluginDescriptorFinder}.
* Now, this class it's a "link" to {@link ManifestPluginDescriptorFinder}.
*
* @author Decebal Suiu
*/
public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
super(pluginClasspath);
}
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
private PluginClasspath pluginClasspath;
public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
this.pluginClasspath = pluginClasspath;
}
@Override
public Manifest readManifest(Path pluginPath) throws PluginException {
// TODO it's ok with first classes root? Another idea is to specify in PluginClasspath the folder.
String classes = pluginClasspath.getClassesDirectories().get(0);
Path manifestPath = pluginPath.resolve(Paths.get(classes,"/META-INF/MANIFEST.MF"));
log.debug("Lookup plugin descriptor in '{}'", manifestPath);
if (Files.notExists(manifestPath)) {
throw new PluginException("Cannot find '" + manifestPath + "' path");
}
try (InputStream input = Files.newInputStream(manifestPath)) {
return new Manifest(input);
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
}
}
}

4
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginFactory.java

@ -22,8 +22,8 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* The default implementation for PluginFactory.
* It uses Class.newInstance() method.
* The default implementation for {@link PluginFactory}.
* It uses {@link Class#newInstance()} method.
*
* @author Decebal Suiu
*/

80
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginLoader.java

@ -0,0 +1,80 @@
/*
* Copyright 2016 Decebal Suiu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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 ro.fortsoft.pf4j;
import ro.fortsoft.pf4j.util.FileUtils;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
/**
* Load all information needed by a plugin.
* This means add to classpath all jar files from {@code lib} directory
* and all class files from {@code classes}.
*
* @author Decebal Suiu
*/
public class DefaultPluginLoader implements PluginLoader {
protected PluginManager pluginManager;
protected PluginClasspath pluginClasspath;
public DefaultPluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) {
this.pluginManager = pluginManager;
this.pluginClasspath = pluginClasspath;
}
@Override
public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) {
PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginPath, pluginDescriptor);
loadClasses(pluginPath, pluginClassLoader);
loadJars(pluginPath, pluginClassLoader);
return pluginClassLoader;
}
protected PluginClassLoader createPluginClassLoader(Path pluginPath, PluginDescriptor pluginDescriptor) {
return new PluginClassLoader(pluginManager, pluginDescriptor, getClass().getClassLoader());
}
/**
* Add all {@code *.class} files from {@code classes} directories to plugin class loader.
*/
protected void loadClasses(Path pluginPath, PluginClassLoader pluginClassLoader) {
for (String directory : pluginClasspath.getClassesDirectories()) {
File file = pluginPath.resolve(directory).toFile();
if (file.exists() && file.isDirectory()) {
pluginClassLoader.addFile(file);
}
}
}
/**
* Add all {@code *.jar} files from {@code lib} directories to plugin class loader.
*/
protected void loadJars(Path pluginPath, PluginClassLoader pluginClassLoader) {
for (String libDirectory : pluginClasspath.getLibDirectories()) {
File file = pluginPath.resolve(libDirectory).toFile();
List<File> jars = FileUtils.getJars(file);
for (File jar : jars) {
pluginClassLoader.addFile(jar);
}
}
}
}

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

@ -15,882 +15,95 @@
*/
package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
import com.github.zafarkhaja.semver.expr.Expression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.AndFileFilter;
import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.FileUtils;
import ro.fortsoft.pf4j.util.HiddenFilter;
import ro.fortsoft.pf4j.util.NameFileFilter;
import ro.fortsoft.pf4j.util.NotFileFilter;
import ro.fortsoft.pf4j.util.OrFileFilter;
import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFileFilter;
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 java.util.Set;
import java.nio.file.Path;
/**
* Default implementation of the PluginManager interface.
* Default implementation of the {@link PluginManager} interface.
*
* @author Decebal Suiu
*/
public class DefaultPluginManager implements PluginManager {
public class DefaultPluginManager extends AbstractPluginManager {
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";
protected File pluginsDirectory;
protected ExtensionFinder extensionFinder;
protected PluginDescriptorFinder pluginDescriptorFinder;
protected PluginClasspath pluginClasspath;
/**
* A map of plugins this manager is responsible for (the key is the 'pluginId').
*/
protected Map<String, PluginWrapper> plugins;
/**
* A map of plugin class loaders (he key is the 'pluginId').
*/
protected Map<String, PluginClassLoader> pluginClassLoaders;
/**
* A relation between 'pluginPath' and 'pluginId'
*/
protected Map<String, String> pathToIdMap;
/**
* A list with unresolved plugins (unresolved dependency).
*/
protected List<PluginWrapper> unresolvedPlugins;
/**
* A list with resolved plugins (resolved dependency).
*/
protected List<PluginWrapper> resolvedPlugins;
/**
* A list with started plugins.
*/
protected List<PluginWrapper> startedPlugins;
/**
* The registered {@link PluginStateListener}s.
*/
protected List<PluginStateListener> pluginStateListeners;
/**
* Cache value for the runtime mode. No need to re-read it because it wont change at
* runtime.
*/
protected RuntimeMode runtimeMode;
/**
* The system version used for comparisons to the plugin requires attribute.
*/
protected Version systemVersion = Version.forIntegers(0, 0, 0);
protected PluginFactory pluginFactory;
protected ExtensionFactory extensionFactory;
protected PluginStatusProvider pluginStatusProvider;
protected DependencyResolver dependencyResolver;
/**
* The plugins repository.
*/
protected PluginRepository pluginRepository;
/**
* The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
*/
public DefaultPluginManager() {
this.pluginsDirectory = createPluginsDirectory();
initialize();
}
/**
* Constructs DefaultPluginManager which the given plugins directory.
*
* @param pluginsDirectory the directory to search for plugins
*/
public DefaultPluginManager(File pluginsDirectory) {
this.pluginsDirectory = pluginsDirectory;
initialize();
}
@Override
public void setSystemVersion(Version version) {
systemVersion = version;
}
@Override
public Version getSystemVersion() {
return systemVersion;
}
@Override
public List<PluginWrapper> getPlugins() {
return new ArrayList<>(plugins.values());
}
@Override
public List<PluginWrapper> getPlugins(PluginState pluginState) {
List<PluginWrapper> plugins= new ArrayList<>();
for (PluginWrapper plugin : getPlugins()) {
if (pluginState.equals(plugin.getPluginState())) {
plugins.add(plugin);
}
}
return plugins;
}
@Override
public List<PluginWrapper> getResolvedPlugins() {
return resolvedPlugins;
}
@Override
public List<PluginWrapper> getUnresolvedPlugins() {
return unresolvedPlugins;
}
@Override
public List<PluginWrapper> getStartedPlugins() {
return startedPlugins;
}
@Override
public PluginWrapper getPlugin(String pluginId) {
return plugins.get(pluginId);
}
@Override
public String loadPlugin(File pluginArchiveFile) {
if ((pluginArchiveFile == null) || !pluginArchiveFile.exists()) {
throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginArchiveFile));
}
log.debug("Loading plugin from '{}'", pluginArchiveFile);
File pluginDirectory = null;
try {
pluginDirectory = expandPluginArchive(pluginArchiveFile);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
if ((pluginDirectory == null) || !pluginDirectory.exists()) {
throw new IllegalArgumentException(String.format("Failed to expand %s", pluginArchiveFile));
}
try {
PluginWrapper pluginWrapper = loadPluginDirectory(pluginDirectory);
// TODO uninstalled plugin dependencies?
unresolvedPlugins.remove(pluginWrapper);
resolvedPlugins.add(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, null));
return pluginWrapper.getDescriptor().getPluginId();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return null;
}
/**
* Start all active plugins.
*/
@Override
public void startPlugins() {
for (PluginWrapper pluginWrapper : resolvedPlugins) {
PluginState pluginState = pluginWrapper.getPluginState();
if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
try {
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().start();
pluginWrapper.setPluginState(PluginState.STARTED);
startedPlugins.add(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* 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 = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STARTED == pluginState) {
log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STARTED;
}
if (PluginState.DISABLED == pluginState) {
// automatically enable plugin on manual plugin start
if (!enablePlugin(pluginId)) {
return pluginState;
}
}
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);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState();
}
/**
* Stop all active plugins.
*/
@Override
public void stopPlugins() {
// stop started plugins in reverse order
Collections.reverse(startedPlugins);
Iterator<PluginWrapper> itr = startedPlugins.iterator();
while (itr.hasNext()) {
PluginWrapper pluginWrapper = itr.next();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STARTED == pluginState) {
try {
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
itr.remove();
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* Stop the specified plugin and it's dependencies.
*/
@Override
public PluginState stopPlugin(String pluginId) {
return stopPlugin(pluginId, true);
super();
}
private PluginState stopPlugin(String pluginId, boolean stopDependents) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STOPPED == pluginState) {
log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return PluginState.STOPPED;
}
// test for disabled plugin
if (PluginState.DISABLED == pluginState) {
// do nothing
return pluginState;
}
if (stopDependents) {
List<String> dependents = dependencyResolver.getDependents(pluginId);
while (!dependents.isEmpty()) {
String dependent = dependents.remove(0);
stopPlugin(dependent, false);
dependents.addAll(0, dependencyResolver.getDependents(dependent));
}
}
try {
log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
startedPlugins.remove(pluginWrapper);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
return pluginWrapper.getPluginState();
}
/**
* Load plugins.
*/
@Override
public void loadPlugins() {
log.debug("Lookup plugins in '{}'", pluginsDirectory.getAbsolutePath());
// check for plugins directory
if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
log.error("No '{}' directory", pluginsDirectory.getAbsolutePath());
return;
}
// expand all plugin archives
List<File> pluginArchives = pluginRepository.getPluginArchives();
for (File archiveFile : pluginArchives) {
try {
expandPluginArchive(archiveFile);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
// check for no plugins
AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
File[] directories = pluginsDirectory.listFiles(pluginsFilter);
if (directories == null) {
directories = new File[0];
}
log.debug("Found {} possible plugins: {}", directories.length, directories);
if (directories.length == 0) {
log.info("No plugins");
return;
}
// load any plugin from plugins directory
for (File directory : directories) {
try {
loadPluginDirectory(directory);
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
// resolve 'unresolvedPlugins'
try {
resolvePlugins();
} catch (PluginException e) {
log.error(e.getMessage(), e);
}
}
@Override
public boolean unloadPlugin(String pluginId) {
try {
PluginState pluginState = stopPlugin(pluginId);
if (PluginState.STARTED == pluginState) {
return false;
}
PluginWrapper pluginWrapper = getPlugin(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());
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
// remove the classloader
if (pluginClassLoaders.containsKey(pluginId)) {
PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);
classLoader.dispose();
}
return true;
} catch (IllegalArgumentException e) {
// ignore not found exceptions because this method is recursive
}
return false;
}
@Override
public boolean disablePlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.DISABLED == pluginState) {
log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
if (PluginState.STOPPED == stopPlugin(pluginId)) {
pluginWrapper.setPluginState(PluginState.DISABLED);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
if (!pluginStatusProvider.disablePlugin(pluginId)) {
return false;
}
log.info("Disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
return false;
@Deprecated
public DefaultPluginManager(File pluginsDir) {
this(pluginsDir.toPath());
}
@Override
public boolean enablePlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
if (!isPluginValid(pluginWrapper)) {
log.warn("Plugin '{}:{}' can not be enabled", pluginWrapper.getPluginId(),
pluginWrapper.getDescriptor().getVersion());
return false;
}
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.DISABLED != pluginState) {
log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
}
if (!pluginStatusProvider.enablePlugin(pluginId)) {
return false;
}
pluginWrapper.setPluginState(PluginState.CREATED);
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
return true;
public DefaultPluginManager(Path pluginsRoot) {
super(pluginsRoot);
}
@Override
public boolean deletePlugin(String pluginId) {
if (!plugins.containsKey(pluginId)) {
throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
}
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginState pluginState = stopPlugin(pluginId);
if (PluginState.STARTED == pluginState) {
log.error("Failed to stop plugin {} on delete", pluginId);
return false;
}
if (!unloadPlugin(pluginId)) {
log.error("Failed to unload plugin {} on delete", pluginId);
return false;
}
File pluginFolder = new File(pluginsDirectory, pluginWrapper.getPluginPath());
if (pluginFolder.exists()) {
FileUtils.delete(pluginFolder);
}
pluginRepository.deletePluginArchive(pluginWrapper.getPluginPath());
return true;
}
/**
* Get plugin class loader for this path.
*/
@Override
public PluginClassLoader getPluginClassLoader(String pluginId) {
return pluginClassLoaders.get(pluginId);
}
@Override
public <T> List<T> getExtensions(Class<T> type) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
}
* By default if {@link DefaultPluginManager#isDevelopment()} returns true
* than a {@link PropertiesPluginDescriptorFinder} is returned
* else this method returns {@link DefaultPluginDescriptorFinder}.
*/
@Override
public <T> List<T> getExtensions(Class<T> type, String pluginId) {
List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId);
List<T> extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
return extensions;
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new DefaultPluginDescriptorFinder(pluginClasspath);
}
@Override
@SuppressWarnings("unchecked")
public List getExtensions(String pluginId) {
List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
List extensions = new ArrayList<>(extensionsWrapper.size());
for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
extensions.add(extensionWrapper.getExtension());
}
protected ExtensionFinder createExtensionFinder() {
DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
addPluginStateListener(extensionFinder);
return extensions;
return extensionFinder;
}
@Override
public Set<String> getExtensionClassNames(String pluginId) {
return extensionFinder.findClassNames(pluginId);
protected PluginFactory createPluginFactory() {
return new DefaultPluginFactory();
}
@Override
public ExtensionFactory getExtensionFactory() {
return extensionFactory;
protected ExtensionFactory createExtensionFactory() {
return new DefaultExtensionFactory();
}
@Override
public RuntimeMode getRuntimeMode() {
if (runtimeMode == null) {
// retrieves the runtime mode from system
String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
runtimeMode = RuntimeMode.byName(modeAsString);
}
return runtimeMode;
}
@Override
public PluginWrapper whichPlugin(Class<?> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
for (PluginWrapper plugin : resolvedPlugins) {
if (plugin.getPluginClassLoader() == classLoader) {
return plugin;
}
}
return null;
protected PluginStatusProvider createPluginStatusProvider() {
return new DefaultPluginStatusProvider(getPluginsRoot());
}
@Override
public synchronized void addPluginStateListener(PluginStateListener listener) {
pluginStateListeners.add(listener);
protected PluginRepository createPluginRepository() {
return new DefaultPluginRepository(getPluginsRoot(), isDevelopment());
}
@Override
public synchronized void removePluginStateListener(PluginStateListener listener) {
pluginStateListeners.remove(listener);
}
public Version getVersion() {
String version = null;
Package pf4jPackage = getClass().getPackage();
if (pf4jPackage != null) {
version = pf4jPackage.getImplementationVersion();
if (version == null) {
version = pf4jPackage.getSpecificationVersion();
}
}
return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0);
protected PluginLoader createPluginLoader() {
return new DefaultPluginLoader(this, pluginClasspath);
}
/**
* 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.
*/
protected ExtensionFinder createExtensionFinder() {
DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
addPluginStateListener(extensionFinder);
return extensionFinder;
}
/**
* Add the possibility to override the PluginClassPath.
* By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
* DevelopmentPluginClasspath is returned else this method returns
* PluginClasspath.
* By default if {@link DefaultPluginManager#isDevelopment()} returns true
* than a {@link DevelopmentPluginClasspath} is returned
* else this method returns {@link DefaultPluginClasspath}.
*/
protected PluginClasspath createPluginClasspath() {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
return new DevelopmentPluginClasspath();
}
return new PluginClasspath();
}
protected PluginStatusProvider createPluginStatusProvider() {
return new DefaultPluginStatusProvider(pluginsDirectory);
}
protected PluginRepository createPluginRepository() {
return new DefaultPluginRepository(pluginsDirectory, new ZipFileFilter());
}
protected boolean isPluginDisabled(String pluginId) {
return pluginStatusProvider.isPluginDisabled(pluginId);
}
protected boolean isPluginValid(PluginWrapper pluginWrapper) {
Expression requires = pluginWrapper.getDescriptor().getRequires();
Version system = getSystemVersion();
if (requires.interpret(system)) {
return true;
}
log.warn("Plugin '{}:{}' requires a minimum system version of {}",
pluginWrapper.getPluginId(),
pluginWrapper.getDescriptor().getVersion(),
requires);
return false;
}
protected FileFilter createHiddenPluginFilter() {
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
}
return hiddenPluginFilter;
}
/**
* 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
* DEFAULT_PLUGINS_DIRECTORY ("plugins").
* @return
*/
protected File createPluginsDirectory() {
String pluginsDir = System.getProperty("pf4j.pluginsDir");
if (pluginsDir == null) {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
pluginsDir = DEVELOPMENT_PLUGINS_DIRECTORY;
} else {
pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
}
}
return new File(pluginsDir);
}
/**
* Add the possibility to override the PluginFactory..
*/
protected PluginFactory createPluginFactory() {
return new DefaultPluginFactory();
}
/**
* Add the possibility to override the ExtensionFactory.
*/
protected ExtensionFactory createExtensionFactory() {
return new DefaultExtensionFactory();
}
/**
* Add the possibility to override the PluginClassLoader.
*/
protected PluginClassLoader createPluginClassLoader(PluginDescriptor pluginDescriptor) {
return new PluginClassLoader(this, pluginDescriptor, getClass().getClassLoader());
return isDevelopment() ? new DevelopmentPluginClasspath() : new DefaultPluginClasspath();
}
private void initialize() {
plugins = new HashMap<>();
pluginClassLoaders = new HashMap<>();
pathToIdMap = new HashMap<>();
unresolvedPlugins = new ArrayList<>();
resolvedPlugins = new ArrayList<>();
startedPlugins = new ArrayList<>();
pluginStateListeners = new ArrayList<>();
dependencyResolver = new DependencyResolver();
log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
@Override
protected void initialize() {
pluginClasspath = createPluginClasspath();
pluginFactory = createPluginFactory();
extensionFactory = createExtensionFactory();
pluginDescriptorFinder = createPluginDescriptorFinder();
extensionFinder = createExtensionFinder();
pluginStatusProvider = createPluginStatusProvider();
pluginRepository = createPluginRepository();
try {
pluginsDirectory = pluginsDirectory.getCanonicalFile();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath());
}
private PluginWrapper loadPluginDirectory(File pluginDirectory) throws PluginException {
// try to load the plugin
String pluginName = pluginDirectory.getName();
String pluginPath = "/".concat(pluginName);
// test for plugin duplication
if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
return null;
}
// 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 '{}' for plugin '{}'", pluginClassName, pluginPath);
// load plugin
log.debug("Loading plugin '{}'", pluginPath);
PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginDescriptor);
log.debug("Created class loader '{}'", pluginClassLoader);
PluginLoader pluginLoader = new PluginLoader(pluginDirectory, pluginClassLoader, pluginClasspath);
pluginLoader.load();
log.debug("Loaded plugin '{}'", pluginPath);
// create the plugin wrapper
log.debug("Creating wrapper for plugin '{}'", pluginPath);
PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
pluginWrapper.setPluginFactory(pluginFactory);
pluginWrapper.setRuntimeMode(getRuntimeMode());
// test for disabled plugin
if (isPluginDisabled(pluginDescriptor.getPluginId())) {
log.info("Plugin '{}' is disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
// validate the plugin
if (!isPluginValid(pluginWrapper)) {
log.info("Plugin '{}' is disabled", pluginPath);
pluginWrapper.setPluginState(PluginState.DISABLED);
}
log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
super.initialize();
String pluginId = pluginDescriptor.getPluginId();
// add plugin to the list with plugins
plugins.put(pluginId, pluginWrapper);
unresolvedPlugins.add(pluginWrapper);
// add plugin class loader to the list with class loaders
pluginClassLoaders.put(pluginId, pluginClassLoader);
return pluginWrapper;
}
private File expandPluginArchive(File pluginArchiveFile) throws IOException {
String fileName = pluginArchiveFile.getName();
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 '{}' in '{}'", pluginArchiveFile, pluginDirectory);
// do not overwrite an old version, remove it
if (pluginDirectory.exists()) {
FileUtils.delete(pluginDirectory);
}
// create directory for plugin
pluginDirectory.mkdirs();
// expand '.zip' file
Unzip unzip = new Unzip();
unzip.setSource(pluginArchiveFile);
unzip.setDestination(pluginDirectory);
unzip.extract();
}
return pluginDirectory;
}
private void resolvePlugins() throws PluginException {
resolveDependencies();
}
private void resolveDependencies() throws PluginException {
dependencyResolver.resolve(unresolvedPlugins);
resolvedPlugins = dependencyResolver.getSortedPlugins();
for (PluginWrapper pluginWrapper : resolvedPlugins) {
unresolvedPlugins.remove(pluginWrapper);
log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
}
log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
}
private synchronized void firePluginStateEvent(PluginStateEvent event) {
for (PluginStateListener listener : pluginStateListeners) {
log.debug("Fire '{}' to '{}'", event, listener);
listener.pluginStateChanged(event);
}
}
}

110
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginRepository.java

@ -15,56 +15,108 @@
*/
package ro.fortsoft.pf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.AndFileFilter;
import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.FileUtils;
import ro.fortsoft.pf4j.util.HiddenFilter;
import ro.fortsoft.pf4j.util.NameFileFilter;
import ro.fortsoft.pf4j.util.NotFileFilter;
import ro.fortsoft.pf4j.util.OrFileFilter;
import ro.fortsoft.pf4j.util.Unzip;
import ro.fortsoft.pf4j.util.ZipFileFilter;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Collections;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
/**
* @author Decebal Suiu
* @author Mário Franco
*/
public class DefaultPluginRepository implements PluginRepository {
public class DefaultPluginRepository extends BasePluginRepository {
private final File directory;
private final FileFilter filter;
private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class);
public DefaultPluginRepository(File directory, FileFilter filter) {
this.directory = directory;
this.filter = filter;
public DefaultPluginRepository(Path pluginsRoot, boolean development) {
super(pluginsRoot);
AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development)));
setFilter(pluginsFilter);
}
@Override
public List<File> getPluginArchives() {
File[] files = directory.listFiles(filter);
public List<Path> getPluginPaths() {
// expand plugins zip files
File[] pluginZips = pluginsRoot.toFile().listFiles(new ZipFileFilter());
if ((pluginZips != null) && pluginZips.length > 0) {
for (File pluginZip : pluginZips) {
try {
expandPluginZip(pluginZip);
} catch (IOException e) {
log.error("Cannot expand plugin zip '{}'", pluginZip);
log.error(e.getMessage(), e);
}
}
}
return (files != null) ? Arrays.asList(files) : Collections.<File>emptyList();
return super.getPluginPaths();
}
@Override
public boolean deletePluginArchive(String pluginPath) {
File[] files = directory.listFiles(filter);
if (files != null) {
File pluginArchive = null;
// strip prepended "/" from the plugin path
String dirName = pluginPath.substring(1);
// find the zip file that matches the plugin path
for (File archive : files) {
String name = archive.getName().substring(0, archive.getName().lastIndexOf('.'));
if (name.equals(dirName)) {
pluginArchive = archive;
break;
}
}
if (pluginArchive != null && pluginArchive.exists()) {
return FileUtils.delete(pluginArchive);
public boolean deletePluginPath(Path pluginPath) {
// TODO remove plugin zip ?
return super.deletePluginPath(pluginPath);
}
protected FileFilter createHiddenPluginFilter(boolean development) {
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
if (development) {
hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
}
return hiddenPluginFilter;
}
/**
* Unzip a plugin zip file in a directory that has the same name as the zip file
* and it's relative to {@code pluginsRoot}.
* For example if the zip file is {@code my-plugin.zip} then the resulted directory
* is {@code my-plugin}.
*
* @param pluginZip
* @return
* @throws IOException
*/
private File expandPluginZip(File pluginZip) throws IOException {
String fileName = pluginZip.getName();
long pluginZipDate = pluginZip.lastModified();
String pluginName = fileName.substring(0, fileName.length() - 4);
File pluginDirectory = pluginsRoot.resolve(pluginName).toFile();
// check if exists root or the '.zip' file is "newer" than root
if (!pluginDirectory.exists() || (pluginZipDate > pluginDirectory.lastModified())) {
log.debug("Expand plugin zip '{}' in '{}'", pluginZip, pluginDirectory);
// do not overwrite an old version, remove it
if (pluginDirectory.exists()) {
FileUtils.delete(pluginDirectory);
}
// create root for plugin
pluginDirectory.mkdirs();
// expand '.zip' file
Unzip unzip = new Unzip();
unzip.setSource(pluginZip);
unzip.setDestination(pluginDirectory);
unzip.extract();
}
return false;
return pluginDirectory;
}
}

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

@ -15,16 +15,16 @@
*/
package ro.fortsoft.pf4j;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.FileUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
/**
* The default implementation for PluginStatusProvider.
* The default implementation for {@link PluginStatusProvider}.
*
* @author Decebal Suiu
* @author Mário Franco
@ -33,24 +33,25 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginStatusProvider.class);
private final File pluginsDirectory;
private final Path pluginsRoot;
private List<String> enabledPlugins;
private List<String> disabledPlugins;
private List<String> enabledPlugins = new ArrayList<>();
private List<String> disabledPlugins = new ArrayList<>();
public DefaultPluginStatusProvider(Path pluginsRoot) {
this.pluginsRoot = pluginsRoot;
public DefaultPluginStatusProvider(File pluginsDirectory) {
this.pluginsDirectory = pluginsDirectory;
initialize();
}
private void initialize() {
try {
// 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);
enabledPlugins = FileUtils.readLines(pluginsRoot.resolve("enabled.txt").toFile(), 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);
disabledPlugins = FileUtils.readLines(pluginsRoot.resolve("disabled.txt").toFile(), true);
log.info("Disabled plugins: {}", disabledPlugins);
} catch (IOException e) {
log.error(e.getMessage(), e);
@ -70,12 +71,13 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
public boolean disablePlugin(String pluginId) {
if (disabledPlugins.add(pluginId)) {
try {
FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
} catch (IOException e) {
log.error("Failed to disable plugin {}", pluginId, e);
return false;
}
}
return true;
}
@ -83,12 +85,13 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
public boolean enablePlugin(String pluginId) {
try {
if (disabledPlugins.remove(pluginId)) {
FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
}
} catch (IOException e) {
log.error("Failed to enable plugin {}", pluginId, e);
return false;
}
return true;
}

2
pf4j/src/main/java/ro/fortsoft/pf4j/DependencyResolver.java

@ -71,7 +71,7 @@ public class DependencyResolver {
List<String> pluginsId = dependenciesGraph.reverseTopologicalSort();
if (pluginsId == null) {
throw new CyclicDependencyException("Cyclic dependencies !!!" + dependenciesGraph.toString());
throw new PluginException("Cyclic dependencies !!!" + dependenciesGraph.toString());
}
log.debug("Plugins order: {}", pluginsId);

15
pf4j/src/main/java/ro/fortsoft/pf4j/DevelopmentPluginClasspath.java

@ -16,24 +16,17 @@
package ro.fortsoft.pf4j;
/**
* Overwrite classes directories to "target/classes" and lib directories to "target/lib".
* Overwrite classes directories to {@code target/classes} and lib directories to {@code target/lib}.
*
* @author Decebal Suiu
*/
public class DevelopmentPluginClasspath extends PluginClasspath {
private static final String DEVELOPMENT_CLASSES_DIRECTORY = "target/classes";
private static final String DEVELOPMENT_LIB_DIRECTORY = "target/lib";
public DevelopmentPluginClasspath() {
super();
}
@Override
protected void addResources() {
classesDirectories.add(DEVELOPMENT_CLASSES_DIRECTORY);
libDirectories.add(DEVELOPMENT_LIB_DIRECTORY);
}
addClassesDirectories("target/classes");
addLibDirectories("target/lib");
}
}

115
pf4j/src/main/java/ro/fortsoft/pf4j/JarPluginManager.java

@ -0,0 +1,115 @@
/*
* Copyright 2016 Decebal Suiu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License 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 ro.fortsoft.pf4j;
import ro.fortsoft.pf4j.util.AndFileFilter;
import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.HiddenFilter;
import ro.fortsoft.pf4j.util.JarFileFilter;
import ro.fortsoft.pf4j.util.NameFileFilter;
import ro.fortsoft.pf4j.util.NotFileFilter;
import ro.fortsoft.pf4j.util.OrFileFilter;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.Path;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* It's a {@link PluginManager} that load plugin from a jar file.
* Actually, a plugin is a fat jar, a jar which contains classes from all the libraries,
* on which your project depends and, of course, the classes of current project.
*
* @author Decebal Suiu
*/
public class JarPluginManager extends DefaultPluginManager {
@Override
protected PluginRepository createPluginRepository() {
return new JarPluginRepository(getPluginsRoot(), isDevelopment());
}
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new JarPluginDescriptorFinder();
}
@Override
protected PluginLoader createPluginLoader() {
return new JarPluginLoader(this, pluginClasspath);
}
class JarPluginRepository extends BasePluginRepository {
public JarPluginRepository(Path pluginsRoot, boolean development) {
super(pluginsRoot);
if (development) {
AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development)));
setFilter(pluginsFilter);
} else {
setFilter(new JarFileFilter());
}
}
protected FileFilter createHiddenPluginFilter(boolean development) {
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
if (development) {
hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
}
return hiddenPluginFilter;
}
}
class JarPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
@Override
public Manifest readManifest(Path pluginPath) throws PluginException {
try {
return new JarFile(pluginPath.toFile()).getManifest();
} catch (IOException e) {
throw new PluginException(e);
}
}
}
class JarPluginLoader extends DefaultPluginLoader {
public JarPluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) {
super(pluginManager, pluginClasspath);
}
@Override
public ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor) {
if (isDevelopment()) {
return super.loadPlugin(pluginPath, pluginDescriptor);
}
PluginClassLoader pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, getClass().getClassLoader());
pluginClassLoader.addFile(pluginPath.toFile());
return pluginClassLoader;
}
}
}

53
pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java

@ -16,14 +16,9 @@
package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
@ -32,19 +27,11 @@ import java.util.jar.Manifest;
*
* @author Decebal Suiu
*/
public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
private PluginClasspath pluginClasspath;
public ManifestPluginDescriptorFinder(PluginClasspath pluginClasspath) {
this.pluginClasspath = pluginClasspath;
}
public abstract class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
@Override
public PluginDescriptor find(File pluginRepository) throws PluginException {
Manifest manifest = readManifest(pluginRepository);
public PluginDescriptor find(Path pluginPath) throws PluginException {
Manifest manifest = readManifest(pluginPath);
PluginDescriptor pluginDescriptor = createPluginDescriptor(manifest);
validatePluginDescriptor(pluginDescriptor);
@ -52,37 +39,7 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
return pluginDescriptor;
}
protected Manifest readManifest(File pluginRepository) throws PluginException {
// TODO it's ok with first classes directory? Another idea is to specify in PluginClasspath the folder.
String classes = pluginClasspath.getClassesDirectories().get(0);
File manifestFile = new File(pluginRepository, classes + "/META-INF/MANIFEST.MF");
log.debug("Lookup plugin descriptor in '{}'", manifestFile);
if (!manifestFile.exists()) {
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);
}
}
return manifest;
}
public abstract Manifest readManifest(Path pluginPath) throws PluginException;
protected PluginDescriptor createPluginDescriptor(Manifest manifest) {
PluginDescriptor pluginDescriptor = createPluginDescriptorInstance();

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

@ -18,6 +18,7 @@ package ro.fortsoft.pf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
@ -25,8 +26,8 @@ import java.util.List;
/**
* One instance of this class should be created by plugin manager for every available plug-in.
* This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars before delegating
* to the parent class loader.
* This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars
* before delegating to the parent class loader.
*
* @author Decebal Suiu
*/
@ -48,13 +49,23 @@ public class PluginClassLoader extends URLClassLoader {
@Override
public void addURL(URL url) {
log.debug("Add '{}'", url);
super.addURL(url);
}
public void addFile(File file) {
try {
addURL(file.getCanonicalFile().toURI().toURL());
} catch (IOException e) {
// throw new RuntimeException(e);
log.error(e.getMessage(), e);
}
}
/**
* This implementation of loadClass uses a child first delegation model rather than the standard parent first.
* Uses a child first delegation model rather than the standard parent first.
* If the requested class cannot be found in this class loader, the parent class loader will be consulted
* via the standard ClassLoader.loadClass(String) mechanism.
* via the standard {@link ClassLoader#loadClass(String)} mechanism.
*/
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
@ -67,9 +78,6 @@ public class PluginClassLoader extends URLClassLoader {
return getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
// try next step
// TODO if I uncomment below lines (the correct approach) I received ClassNotFoundException for demo (ro.fortsoft.pf4j.demo)
// log.error(e.getMessage(), e);
// throw e;
}
}
@ -93,7 +101,7 @@ public class PluginClassLoader extends URLClassLoader {
log.trace("Search in dependencies for class '{}'", className);
List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
for (PluginDependency dependency : dependencies) {
PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
@ -103,14 +111,14 @@ public class PluginClassLoader extends URLClassLoader {
log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className);
// use the standard URLClassLoader (which follows normal parent delegation)
// use the standard ClassLoader (which follows normal parent delegation)
return super.loadClass(className);
}
}
/**
* Load the named resource from this plugin. This implementation checks the plugin's classpath first
* then delegates to the parent.
* Load the named resource from this plugin.
* This implementation checks the plugin's classpath first then delegates to the parent.
*
* @param name the name of the resource.
* @return the URL to the resource, <code>null</code> if the resource was not found.
@ -134,18 +142,4 @@ public class PluginClassLoader extends URLClassLoader {
return super.findResource(name);
}
/**
* Release all resources acquired by this class loader.
* The current implementation is incomplete.
* For now, this instance can no longer be used to load
* new classes or resources that are defined by this loader.
*/
public void dispose() {
try {
close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}

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

@ -16,50 +16,39 @@
package ro.fortsoft.pf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The classpath of the plugin after it was unpacked.
* It contains classes directories and lib directories (directories that contains jars).
* All directories are relative to plugin repository.
* The default values are "classes" and "lib".
* The classpath of the plugin.
* It contains {@code classes} directories and {@code lib} directories (directories that contains jars).
*
* @author Decebal Suiu
*/
public class PluginClasspath {
private static final String DEFAULT_CLASSES_DIRECTORY = "classes";
private static final String DEFAULT_LIB_DIRECTORY = "lib";
protected List<String> classesDirectories;
protected List<String> libDirectories;
private List<String> classesDirectories;
private List<String> libDirectories;
public PluginClasspath() {
classesDirectories = new ArrayList<>();
libDirectories = new ArrayList<>();
addResources();
}
public List<String> getClassesDirectories() {
return classesDirectories;
}
public void setClassesDirectories(List<String> classesDirectories) {
this.classesDirectories = classesDirectories;
public void addClassesDirectories(String... classesDirectories) {
this.classesDirectories.addAll(Arrays.asList(classesDirectories));
}
public List<String> getLibDirectories() {
return libDirectories;
}
public void setLibDirectories(List<String> libDirectories) {
this.libDirectories = libDirectories;
}
protected void addResources() {
classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
libDirectories.add(DEFAULT_LIB_DIRECTORY);
public void addLibDirectories(String... libDirectories) {
this.libDirectories.addAll(Arrays.asList(libDirectories));
}
}

10
pf4j/src/main/java/ro/fortsoft/pf4j/PluginDescriptorFinder.java

@ -15,17 +15,17 @@
*/
package ro.fortsoft.pf4j;
import java.io.File;
import java.nio.file.Path;
/**
* 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.
* Find a plugin descriptor for a plugin path.
* You can find in manifest file {@link DefaultPluginDescriptorFinder},
* xml file, properties file, java services (with {@link java.util.ServiceLoader}), etc.
*
* @author Decebal Suiu
*/
public interface PluginDescriptorFinder {
PluginDescriptor find(File pluginRepository) throws PluginException;
PluginDescriptor find(Path pluginPath) throws PluginException;
}

113
pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java

@ -15,120 +15,15 @@
*/
package ro.fortsoft.pf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.JarFileFilter;
import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Vector;
import java.nio.file.Path;
/**
* Load all information 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.
* Load all information (classes) needed by a plugin.
*
* @author Decebal Suiu
*/
class PluginLoader {
private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
/*
* The plugin repository.
*/
private File pluginRepository;
private PluginClasspath pluginClasspath;
private PluginClassLoader pluginClassLoader;
public PluginLoader(File pluginRepository, PluginClassLoader pluginClassLoader, PluginClasspath pluginClasspath) {
this.pluginRepository = pluginRepository;
this.pluginClassLoader = pluginClassLoader;
this.pluginClasspath = pluginClasspath;
}
public boolean load() {
return loadClassesAndJars();
}
private boolean loadClassesAndJars() {
return loadClasses() && loadJars();
}
private boolean loadClasses() {
List<String> classesDirectories = pluginClasspath.getClassesDirectories();
// add each classes directory to plugin class loader
for (String classesDirectory : classesDirectories) {
// make 'classesDirectory' absolute
File file = new File(pluginRepository, classesDirectory).getAbsoluteFile();
if (file.exists() && file.isDirectory()) {
log.debug("Found '{}' directory", file.getPath());
try {
pluginClassLoader.addURL(file.toURI().toURL());
log.debug("Added '{}' to the class loader path", file);
} catch (MalformedURLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
return false;
}
}
}
return true;
}
/**
* Add all *.jar files from lib directories to class loader.
*/
private boolean loadJars() {
List<String> libDirectories = pluginClasspath.getLibDirectories();
// add each jars directory to plugin class loader
for (String libDirectory : libDirectories) {
// make 'libDirectory' absolute
File file = new File(pluginRepository, libDirectory).getAbsoluteFile();
// collect all jars from current lib directory in jars variable
Vector<File> jars = new Vector<>();
getJars(jars, file);
for (File jar : jars) {
try {
pluginClassLoader.addURL(jar.toURI().toURL());
log.debug("Added '{}' to the class loader path", jar);
} catch (MalformedURLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
return false;
}
}
}
return true;
}
private void getJars(Vector<File> bucket, File file) {
FileFilter jarFilter = new JarFileFilter();
FileFilter directoryFilter = new DirectoryFileFilter();
if (file.exists() && file.isDirectory() && file.isAbsolute()) {
File[] jars = file.listFiles(jarFilter);
for (int i = 0; (jars != null) && (i < jars.length); ++i) {
bucket.addElement(jars[i]);
}
public interface PluginLoader {
File[] directories = file.listFiles(directoryFilter);
for (int i = 0; (directories != null) && (i < directories.length); ++i) {
File directory = directories[i];
getJars(bucket, directory);
}
}
}
ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor);
}

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

@ -16,7 +16,8 @@
package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
@ -69,10 +70,10 @@ public interface PluginManager {
/**
* Load a plugin.
*
* @param pluginArchiveFile
* @param pluginPath
* @return the pluginId of the installed plugin or null
*/
String loadPlugin(File pluginArchiveFile);
String loadPlugin(Path pluginPath);
/**
* Start all active plugins.
@ -130,7 +131,7 @@ public interface PluginManager {
*/
boolean deletePlugin(String pluginId);
PluginClassLoader getPluginClassLoader(String pluginId);
ClassLoader getPluginClassLoader(String pluginId);
<T> List<T> getExtensions(Class<T> type);

10
pf4j/src/main/java/ro/fortsoft/pf4j/PluginRepository.java

@ -15,11 +15,11 @@
*/
package ro.fortsoft.pf4j;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
/**
* Directory whose contents are .zip files used as plugins.
* Directory that contains plugins. A plugin could be a zip file.
*
* @author Decebal Suiu
* @author Mário Franco
@ -27,11 +27,11 @@ import java.util.List;
public interface PluginRepository {
/**
* List all plugin archive filed.
* List all plugin paths.
*
* @return a list of files
*/
List<File> getPluginArchives();
List<Path> getPluginPaths();
/**
* Removes a plugin from the repository.
@ -39,6 +39,6 @@ public interface PluginRepository {
* @param pluginPath the plugin path
* @return true if deleted
*/
boolean deletePluginArchive(String pluginPath);
boolean deletePluginPath(Path pluginPath);
}

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

@ -15,6 +15,8 @@
*/
package ro.fortsoft.pf4j;
import java.nio.file.Path;
/**
* A wrapper over plugin instance.
*
@ -22,16 +24,17 @@ package ro.fortsoft.pf4j;
*/
public class PluginWrapper {
PluginManager pluginManager;
PluginDescriptor descriptor;
String pluginPath;
PluginClassLoader pluginClassLoader;
PluginFactory pluginFactory;
PluginState pluginState;
RuntimeMode runtimeMode;
private PluginManager pluginManager;
private PluginDescriptor descriptor;
private Path pluginPath;
private ClassLoader pluginClassLoader;
private PluginFactory pluginFactory;
private PluginState pluginState;
private RuntimeMode runtimeMode;
Plugin plugin; // cache
public PluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) {
public PluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor, Path pluginPath, ClassLoader pluginClassLoader) {
this.pluginManager = pluginManager;
this.descriptor = descriptor;
this.pluginPath = pluginPath;
@ -55,9 +58,9 @@ public class PluginWrapper {
}
/**
* Returns the path of this plugin relative to plugins directory.
* Returns the path of this plugin.
*/
public String getPluginPath() {
public Path getPluginPath() {
return pluginPath;
}

35
pf4j/src/main/java/ro/fortsoft/pf4j/PropertiesPluginDescriptorFinder.java

@ -20,11 +20,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
/**
@ -49,8 +49,8 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
}
@Override
public PluginDescriptor find(File pluginRepository) throws PluginException {
Properties properties = readProperties(pluginRepository);
public PluginDescriptor find(Path pluginPath) throws PluginException {
Properties properties = readProperties(pluginPath);
PluginDescriptor pluginDescriptor = createPluginDescriptor(properties);
validatePluginDescriptor(pluginDescriptor);
@ -58,31 +58,18 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
return pluginDescriptor;
}
protected Properties readProperties(File pluginRepository) throws PluginException {
File propertiesFile = new File(pluginRepository, propertiesFileName);
log.debug("Lookup plugin descriptor in '{}'", propertiesFile);
if (!propertiesFile.exists()) {
throw new PluginException("Cannot find '" + propertiesFile + "' file");
}
InputStream input = null;
try {
input = new FileInputStream(propertiesFile);
} catch (FileNotFoundException e) {
// not happening
protected Properties readProperties(Path pluginPath) throws PluginException {
Path propertiesPath = pluginPath.resolve(Paths.get(propertiesFileName));
log.debug("Lookup plugin descriptor in '{}'", propertiesPath);
if (Files.notExists(propertiesPath)) {
throw new PluginException("Cannot find '" + pluginPath + "' path");
}
Properties properties = new Properties();
try {
try (InputStream input = Files.newInputStream(propertiesPath)) {
properties.load(input);
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
} finally {
try {
input.close();
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
}
}
return properties;

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

@ -18,6 +18,7 @@ package ro.fortsoft.pf4j.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
@ -95,4 +96,29 @@ public class FileUtils {
return success;
}
public static List<File> getJars(File folder) {
List<File> bucket = new ArrayList<>();
getJars(bucket, folder);
return bucket;
}
private static void getJars(List<File> bucket, File folder) {
FileFilter jarFilter = new JarFileFilter();
FileFilter directoryFilter = new DirectoryFileFilter();
if (folder.exists() && folder.isDirectory() && folder.isAbsolute()) {
File[] jars = folder.listFiles(jarFilter);
for (int i = 0; (jars != null) && (i < jars.length); ++i) {
bucket.add(jars[i]);
}
File[] directories = folder.listFiles(directoryFilter);
for (int i = 0; (directories != null) && (i < directories.length); ++i) {
File directory = directories[i];
getJars(bucket, directory);
}
}
}
}

4
pf4j/src/main/java/ro/fortsoft/pf4j/util/Unzip.java

@ -27,7 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class extracts the containt of the plugin archive into a directory.
* This class extracts the content of the plugin zip into a directory.
* It's a class for only the internal use.
*
* @author Decebal Suiu
@ -70,8 +70,8 @@ public class Unzip {
removeDirectory(destination);
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source));
ZipEntry zipEntry = null;
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
try {
File file = new File(destination, zipEntry.getName());

57
pf4j/src/test/java/ro/fortsoft/pf4j/DefaultPluginRepositoryTest.java

@ -19,10 +19,9 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import ro.fortsoft.pf4j.util.ZipFileFilter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import static org.junit.Assert.assertEquals;
@ -31,6 +30,7 @@ import static org.junit.Assert.assertTrue;
/**
* @author Mario Franco
* @author Decebal Suiu
*/
public class DefaultPluginRepositoryTest {
@ -39,52 +39,53 @@ public class DefaultPluginRepositoryTest {
@Before
public void setUp() throws IOException {
testFolder.newFile("plugin-1.zip");
testFolder.newFile("plugin-2.zip");
testFolder.newFile("plugin-3.zi_");
testFolder.newFolder("plugin-1");
testFolder.newFolder("plugin-2");
testFolder.newFolder("plugin-3");
}
/**
* Test of getPluginArchives method, of class DefaultPluginRepository.
* Test of {@link DefaultPluginRepository#getPluginPaths()} method.
*/
@Test
public void testGetPluginArchives() {
DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter());
Path pluginsRoot = getPluginsRoot();
List<File> result = instance.getPluginArchives();
PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
assertEquals(2, result.size());
assertFileExists(result, "plugin-1.zip");
assertFileExists(result, "plugin-2.zip");
List<Path> result = instance.getPluginPaths();
assertEquals(3, result.size());
assertPathExists(result, pluginsRoot.resolve("plugin-1"));
assertPathExists(result, pluginsRoot.resolve("plugin-2"));
assertPathExists(result, pluginsRoot.resolve("plugin-3"));
}
/**
* Test of deletePluginArchive method, of class DefaultPluginRepository.
* Test of {@link DefaultPluginRepository#deletePluginPath(Path)} method.
*/
@Test
public void testDeletePluginArchive() {
DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter());
public void testDeletePluginPath() {
Path pluginsRoot = getPluginsRoot();
PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
assertTrue(instance.deletePluginArchive("/plugin-1"));
assertFalse(instance.deletePluginArchive("/plugin-3"));
assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-1")));
assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-3")));
assertFalse(instance.deletePluginPath(pluginsRoot.resolve("plugin-4")));
List<File> result = instance.getPluginArchives();
List<Path> result = instance.getPluginPaths();
assertEquals(1, result.size());
assertEquals(result.get(0).getName(), "plugin-2.zip");
assertEquals(pluginsRoot.relativize(result.get(0)).toString(), "plugin-2");
}
public static void assertFileExists(List<File> files, String file) {
boolean found = false;
for (File f : files) {
if (f.getName().equals(file)) {
found = true;
break;
}
}
private void assertPathExists(List<Path> paths, Path path) {
assertTrue("The directory must contains the file " + path, paths.contains(path));
}
assertTrue("The directory must contains the file " + file, found);
private Path getPluginsRoot() {
return testFolder.getRoot().toPath();
}
}

28
pf4j/src/test/java/ro/fortsoft/pf4j/DefaultPluginStatusProviderTest.java

@ -22,13 +22,16 @@ import ro.fortsoft.pf4j.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Mario Franco
* @author Decebal Suiu
*/
public class DefaultPluginStatusProviderTest {
@ -42,7 +45,8 @@ public class DefaultPluginStatusProviderTest {
public void testIsPluginDisabled() throws IOException {
createEnabledFile();
createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-2"));
@ -55,7 +59,8 @@ public class DefaultPluginStatusProviderTest {
@Test
public void testIsPluginDisabledWithEnableEmpty() throws IOException {
createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-2"));
@ -69,7 +74,8 @@ public class DefaultPluginStatusProviderTest {
public void testDisablePlugin() throws IOException {
createEnabledFile();
createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.disablePlugin("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-1"));
@ -83,7 +89,8 @@ public class DefaultPluginStatusProviderTest {
@Test
public void testDisablePluginWithEnableEmpty() throws IOException {
createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.disablePlugin("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-1"));
@ -97,7 +104,8 @@ public class DefaultPluginStatusProviderTest {
@Test
public void testEnablePlugin() throws IOException {
createEnabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.enablePlugin("plugin-2"));
assertFalse(instance.isPluginDisabled("plugin-1"));
@ -110,7 +118,7 @@ public class DefaultPluginStatusProviderTest {
*/
@Test
public void testEnablePluginWithEnableEmpty() {
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.enablePlugin("plugin-2"));
assertFalse(instance.isPluginDisabled("plugin-1"));
@ -123,7 +131,7 @@ public class DefaultPluginStatusProviderTest {
*/
@Test
public void testDisablePluginWithoutDisabledFile() throws IOException {
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.disablePlugin("plugin-1"));
@ -150,4 +158,8 @@ public class DefaultPluginStatusProviderTest {
FileUtils.writeLines(lines, file);
}
private Path getPluginsRoot() {
return testFolder.getRoot().toPath();
}
}

122
pf4j/src/test/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinderTest.java

@ -21,18 +21,19 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Mario Franco
* @author Decebal Suiu
*/
public class ManifestPluginDescriptorFinderTest {
@ -43,43 +44,42 @@ public class ManifestPluginDescriptorFinderTest {
public void setUp() throws IOException {
Charset charset = Charset.forName("UTF-8");
File plugin = testFolder.newFolder("test-plugin-1", "classes", "META-INF");
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin1Manifest(), charset);
Path pluginPath = testFolder.newFolder("test-plugin-1", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin1Manifest(), charset);
plugin = testFolder.newFolder("test-plugin-2", "classes", "META-INF");
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin2Manifest(), charset);
pluginPath = testFolder.newFolder("test-plugin-2", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin2Manifest(), charset);
// Empty Plugin
// empty plugin
testFolder.newFolder("test-plugin-3");
// No Plugin Class
plugin = testFolder.newFolder("test-plugin-4", "classes", "META-INF");
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin4Manifest(), charset);
// no plugin class
pluginPath = testFolder.newFolder("test-plugin-4", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin4Manifest(), charset);
// No Plugin Version
plugin = testFolder.newFolder("test-plugin-5", "classes", "META-INF");
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin5Manifest(), charset);
// no plugin version
pluginPath = testFolder.newFolder("test-plugin-5", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin5Manifest(), charset);
// No Plugin Id
plugin = testFolder.newFolder("test-plugin-6", "classes", "META-INF");
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin6Manifest(), charset);
// no plugin id
pluginPath = testFolder.newFolder("test-plugin-6", "classes", "META-INF").toPath();
Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin6Manifest(), charset);
}
/**
* Test of find method, of class ManifestPluginDescriptorFinder.
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test
public void testFind() throws Exception {
DefaultPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
PluginDescriptor plugin1 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-1").toFile());
PluginDescriptor plugin2 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-2").toFile());
PluginDescriptor plugin1 = instance.find(getPluginsRoot().resolve("test-plugin-1"));
PluginDescriptor plugin2 = instance.find(getPluginsRoot().resolve("test-plugin-2"));
assertEquals("test-plugin-1", plugin1.getPluginId());
assertEquals("Test Plugin 1", plugin1.getPluginDescription());
@ -102,48 +102,44 @@ public class ManifestPluginDescriptorFinderTest {
}
/**
* Test of find method, of class ManifestPluginDescriptorFinder.
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindNotFound() throws Exception {
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-3").toFile());
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
instance.find(getPluginsRoot().resolve("test-plugin-3"));
}
/**
* Test of find method, of class ManifestPluginDescriptorFinder.
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindMissingPluginClass() throws Exception {
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-4").toFile());
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
instance.find(getPluginsRoot().resolve("test-plugin-4"));
}
/**
* Test of find method, of class ManifestPluginDescriptorFinder.
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindMissingPluginVersion() throws Exception {
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-5").toFile());
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
instance.find(getPluginsRoot().resolve("test-plugin-5"));
}
/**
* Test of find method, of class ManifestPluginDescriptorFinder.
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindMissingPluginId() throws Exception {
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-6").toFile());
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
instance.find(getPluginsRoot().resolve("test-plugin-6"));
}
private List<String> getPlugin1Manifest() {
String[] lines = new String[]{"Manifest-Version: 1.0\n"
String[] lines = new String[] {
"Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #1\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
@ -161,14 +157,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""};
+ ""
};
return Arrays.asList(lines);
}
private List<String> getPlugin2Manifest() {
String[] lines = new String[]{"Manifest-Version: 1.0\n"
String[] lines = new String[] {
"Manifest-Version: 1.0\n"
+ "Plugin-Dependencies: \n"
+ "Implementation-Title: Test Plugin #2\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
@ -184,14 +181,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""};
+ ""
};
return Arrays.asList(lines);
}
private List<String> getPlugin4Manifest() {
String[] lines = new String[]{"Manifest-Version: 1.0\n"
String[] lines = new String[] {
"Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #4\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
@ -205,14 +203,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""};
+ ""
};
return Arrays.asList(lines);
}
private List<String> getPlugin5Manifest() {
String[] lines = new String[]{"Manifest-Version: 1.0\n"
String[] lines = new String[] {
"Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #5\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
@ -226,14 +225,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""};
+ ""
};
return Arrays.asList(lines);
}
private List<String> getPlugin6Manifest() {
String[] lines = new String[]{"Manifest-Version: 1.0\n"
String[] lines = new String[] {
"Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #6\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
@ -246,8 +246,14 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
+ ""};
+ ""
};
return Arrays.asList(lines);
}
private Path getPluginsRoot() {
return testFolder.getRoot().toPath();
}
}

20
pf4j/src/test/resources/log4j.properties

@ -0,0 +1,20 @@
log4j.rootLogger=DEBUG, Console
#
# PF4J log
#
log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
# !!! Put the bellow classes on level TRACE when you are in trouble
log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=WARN, Console
log4j.logger.ro.fortsoft.pf4j.AbstractExtensionFinder=DEBUG, Console
log4j.additivity.ro.fortsoft.pf4j=false
log4j.additivity.ro.fortsoft.pf4j.PluginClassLoader=false
log4j.additivity.ro.fortsoft.pf4j.AbstractExtensionFinder=false
#
# Appenders
#
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
#log4j.appender.Console.layout.conversionPattern=%-5p - %-32.32c{1} - %m\n
log4j.appender.Console.layout.ConversionPattern=%d %p %c - %m%n
Loading…
Cancel
Save