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. 13
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginClasspath.java
  7. 37
      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. 859
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  11. 106
      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. 13
      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. 55
      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 org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager; import ro.fortsoft.pf4j.DefaultPluginManager;
import ro.fortsoft.pf4j.JarPluginManager;
import ro.fortsoft.pf4j.PluginManager; import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.PluginWrapper; import ro.fortsoft.pf4j.PluginWrapper;
import ro.fortsoft.pf4j.demo.api.Greeting; import ro.fortsoft.pf4j.demo.api.Greeting;
@ -38,6 +39,7 @@ public class Boot {
// create the plugin manager // create the plugin manager
final PluginManager pluginManager = new DefaultPluginManager(); final PluginManager pluginManager = new DefaultPluginManager();
// final PluginManager pluginManager = new JarPluginManager();
// load the plugins // load the plugins
pluginManager.loadPlugins(); 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 log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
# !!! Put the bellow classes on level TRACE when you are in trouble # !!! 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.logger.ro.fortsoft.pf4j.AbstractExtensionFinder=DEBUG, Console
log4j.additivity.ro.fortsoft.pf4j=false log4j.additivity.ro.fortsoft.pf4j=false
log4j.additivity.ro.fortsoft.pf4j.PluginClassLoader=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; package ro.fortsoft.pf4j;
import java.io.File; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -32,19 +32,19 @@ public class CompoundPluginRepository implements PluginRepository {
} }
@Override @Override
public List<File> getPluginArchives() { public List<Path> getPluginPaths() {
List<File> file = new ArrayList<>(); List<Path> paths = new ArrayList<>();
for (PluginRepository repository : repositories) { for (PluginRepository repository : repositories) {
file.addAll(repository.getPluginArchives()); paths.addAll(repository.getPluginPaths());
} }
return file; return paths;
} }
@Override @Override
public boolean deletePluginArchive(String pluginPath) { public boolean deletePluginPath(Path pluginPath) {
for (PluginRepository repository : repositories) { for (PluginRepository repository : repositories) {
if (repository.deletePluginArchive(pluginPath)) { if (repository.deletePluginPath(pluginPath)) {
return true; return true;
} }
} }

13
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,16 +16,17 @@
package ro.fortsoft.pf4j; 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 * @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) { addClassesDirectories("classes");
super(message); addLibDirectories("lib");
} }
} }

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

@ -15,16 +15,47 @@
*/ */
package ro.fortsoft.pf4j; 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. * The default implementation for {@link PluginDescriptorFinder}.
* Now, this class it's a "link" to {@link ro.fortsoft.pf4j.ManifestPluginDescriptorFinder}. * Now, this class it's a "link" to {@link ManifestPluginDescriptorFinder}.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder { public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
private PluginClasspath pluginClasspath;
public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) { public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
super(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; import java.lang.reflect.Modifier;
/** /**
* The default implementation for PluginFactory. * The default implementation for {@link PluginFactory}.
* It uses Class.newInstance() method. * It uses {@link Class#newInstance()} method.
* *
* @author Decebal Suiu * @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);
}
}
}
}

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

@ -15,649 +15,47 @@
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
import com.github.zafarkhaja.semver.expr.Expression;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.File;
import java.io.FileFilter; import java.nio.file.Path;
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;
/** /**
* Default implementation of the PluginManager interface. * Default implementation of the {@link PluginManager} interface.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultPluginManager implements PluginManager { public class DefaultPluginManager extends AbstractPluginManager {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class); 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; 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() { public DefaultPluginManager() {
this.pluginsDirectory = createPluginsDirectory(); super();
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);
}
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 DefaultPluginManager(Path pluginsRoot) {
public boolean enablePlugin(String pluginId) { super(pluginsRoot);
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;
}
@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. * By default if {@link DefaultPluginManager#isDevelopment()} returns true
* than a {@link PropertiesPluginDescriptorFinder} is returned
* else this method returns {@link DefaultPluginDescriptorFinder}.
*/ */
@Override @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;
}
@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;
}
@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);
}
/**
* 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() { protected PluginDescriptorFinder createPluginDescriptorFinder() {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) { return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new DefaultPluginDescriptorFinder(pluginClasspath);
return new PropertiesPluginDescriptorFinder();
}
return new DefaultPluginDescriptorFinder(pluginClasspath);
} }
/** @Override
* Add the possibility to override the ExtensionFinder.
*/
protected ExtensionFinder createExtensionFinder() { protected ExtensionFinder createExtensionFinder() {
DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this); DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
addPluginStateListener(extensionFinder); addPluginStateListener(extensionFinder);
@ -665,232 +63,47 @@ public class DefaultPluginManager implements PluginManager {
return extensionFinder; return extensionFinder;
} }
/** @Override
* Add the possibility to override the PluginClassPath. protected PluginFactory createPluginFactory() {
* By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a return new DefaultPluginFactory();
* DevelopmentPluginClasspath is returned else this method returns
* PluginClasspath.
*/
protected PluginClasspath createPluginClasspath() {
if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
return new DevelopmentPluginClasspath();
} }
return new PluginClasspath(); @Override
protected ExtensionFactory createExtensionFactory() {
return new DefaultExtensionFactory();
} }
@Override
protected PluginStatusProvider createPluginStatusProvider() { protected PluginStatusProvider createPluginStatusProvider() {
return new DefaultPluginStatusProvider(pluginsDirectory); return new DefaultPluginStatusProvider(getPluginsRoot());
} }
@Override
protected PluginRepository createPluginRepository() { protected PluginRepository createPluginRepository() {
return new DefaultPluginRepository(pluginsDirectory, new ZipFileFilter()); return new DefaultPluginRepository(getPluginsRoot(), isDevelopment());
}
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() { @Override
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter()); protected PluginLoader createPluginLoader() {
return new DefaultPluginLoader(this, pluginClasspath);
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. * By default if {@link DefaultPluginManager#isDevelopment()} returns true
* than a {@link DevelopmentPluginClasspath} is returned
* else this method returns {@link DefaultPluginClasspath}.
*/ */
protected PluginClassLoader createPluginClassLoader(PluginDescriptor pluginDescriptor) { protected PluginClasspath createPluginClasspath() {
return new PluginClassLoader(this, pluginDescriptor, getClass().getClassLoader()); return isDevelopment() ? new DevelopmentPluginClasspath() : new DefaultPluginClasspath();
} }
private void initialize() { @Override
plugins = new HashMap<>(); protected void initialize() {
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());
pluginClasspath = createPluginClasspath(); 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);
String pluginId = pluginDescriptor.getPluginId();
// add plugin to the list with plugins super.initialize();
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 { log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
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());
}
}
private synchronized void firePluginStateEvent(PluginStateEvent event) {
for (PluginStateListener listener : pluginStateListeners) {
log.debug("Fire '{}' to '{}'", event, listener);
listener.pluginStateChanged(event);
}
} }
} }

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

@ -15,56 +15,108 @@
*/ */
package ro.fortsoft.pf4j; 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.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.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.util.Arrays; import java.io.IOException;
import java.util.Collections; import java.nio.file.Path;
import java.util.List; import java.util.List;
/** /**
* @author Decebal Suiu * @author Decebal Suiu
* @author Mário Franco
*/ */
public class DefaultPluginRepository implements PluginRepository { public class DefaultPluginRepository extends BasePluginRepository {
private final File directory; private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class);
private final FileFilter filter;
public DefaultPluginRepository(File directory, FileFilter filter) { public DefaultPluginRepository(Path pluginsRoot, boolean development) {
this.directory = directory; super(pluginsRoot);
this.filter = filter;
AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development)));
setFilter(pluginsFilter);
} }
@Override @Override
public List<File> getPluginArchives() { public List<Path> getPluginPaths() {
File[] files = directory.listFiles(filter); // 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 @Override
public boolean deletePluginArchive(String pluginPath) { public boolean deletePluginPath(Path pluginPath) {
File[] files = directory.listFiles(filter); // TODO remove plugin zip ?
if (files != null) { return super.deletePluginPath(pluginPath);
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;
} }
protected FileFilter createHiddenPluginFilter(boolean development) {
OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
if (development) {
hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
} }
if (pluginArchive != null && pluginArchive.exists()) {
return FileUtils.delete(pluginArchive); 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; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.FileUtils; 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 Decebal Suiu
* @author Mário Franco * @author Mário Franco
@ -33,24 +33,25 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginStatusProvider.class); 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<>(); public DefaultPluginStatusProvider(Path pluginsRoot) {
private List<String> disabledPlugins = new ArrayList<>(); this.pluginsRoot = pluginsRoot;
public DefaultPluginStatusProvider(File pluginsDirectory) {
this.pluginsDirectory = pluginsDirectory;
initialize(); initialize();
} }
private void initialize() { private void initialize() {
try { try {
// create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file) // 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); 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) // 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); log.info("Disabled plugins: {}", disabledPlugins);
} catch (IOException e) { } catch (IOException e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
@ -70,12 +71,13 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
public boolean disablePlugin(String pluginId) { public boolean disablePlugin(String pluginId) {
if (disabledPlugins.add(pluginId)) { if (disabledPlugins.add(pluginId)) {
try { try {
FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt")); FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to disable plugin {}", pluginId, e); log.error("Failed to disable plugin {}", pluginId, e);
return false; return false;
} }
} }
return true; return true;
} }
@ -83,12 +85,13 @@ public class DefaultPluginStatusProvider implements PluginStatusProvider {
public boolean enablePlugin(String pluginId) { public boolean enablePlugin(String pluginId) {
try { try {
if (disabledPlugins.remove(pluginId)) { if (disabledPlugins.remove(pluginId)) {
FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt")); FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
} }
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to enable plugin {}", pluginId, e); log.error("Failed to enable plugin {}", pluginId, e);
return false; return false;
} }
return true; return true;
} }

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

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

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

@ -16,24 +16,17 @@
package ro.fortsoft.pf4j; 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 * @author Decebal Suiu
*/ */
public class DevelopmentPluginClasspath extends PluginClasspath { 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() { public DevelopmentPluginClasspath() {
super(); super();
}
@Override addClassesDirectories("target/classes");
protected void addResources() { addLibDirectories("target/lib");
classesDirectories.add(DEVELOPMENT_CLASSES_DIRECTORY);
libDirectories.add(DEVELOPMENT_LIB_DIRECTORY);
} }
} }

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; package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version; import com.github.zafarkhaja.semver.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils; import ro.fortsoft.pf4j.util.StringUtils;
import java.io.File; import java.nio.file.Path;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
@ -32,19 +27,11 @@ import java.util.jar.Manifest;
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder { public abstract class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
private PluginClasspath pluginClasspath;
public ManifestPluginDescriptorFinder(PluginClasspath pluginClasspath) {
this.pluginClasspath = pluginClasspath;
}
@Override @Override
public PluginDescriptor find(File pluginRepository) throws PluginException { public PluginDescriptor find(Path pluginPath) throws PluginException {
Manifest manifest = readManifest(pluginRepository); Manifest manifest = readManifest(pluginPath);
PluginDescriptor pluginDescriptor = createPluginDescriptor(manifest); PluginDescriptor pluginDescriptor = createPluginDescriptor(manifest);
validatePluginDescriptor(pluginDescriptor); validatePluginDescriptor(pluginDescriptor);
@ -52,37 +39,7 @@ public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
return pluginDescriptor; return pluginDescriptor;
} }
protected Manifest readManifest(File pluginRepository) throws PluginException { public abstract Manifest readManifest(Path pluginPath) 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;
}
protected PluginDescriptor createPluginDescriptor(Manifest manifest) { protected PluginDescriptor createPluginDescriptor(Manifest manifest) {
PluginDescriptor pluginDescriptor = createPluginDescriptorInstance(); 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; 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. * 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 * This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars
* to the parent class loader. * before delegating to the parent class loader.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
@ -48,13 +49,23 @@ public class PluginClassLoader extends URLClassLoader {
@Override @Override
public void addURL(URL url) { public void addURL(URL url) {
log.debug("Add '{}'", url);
super.addURL(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 * 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 @Override
public Class<?> loadClass(String className) throws ClassNotFoundException { public Class<?> loadClass(String className) throws ClassNotFoundException {
@ -67,9 +78,6 @@ public class PluginClassLoader extends URLClassLoader {
return getClass().getClassLoader().loadClass(className); return getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// try next step // 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); log.trace("Search in dependencies for class '{}'", className);
List<PluginDependency> dependencies = pluginDescriptor.getDependencies(); List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
for (PluginDependency dependency : dependencies) { for (PluginDependency dependency : dependencies) {
PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId()); ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
try { try {
return classLoader.loadClass(className); return classLoader.loadClass(className);
} catch (ClassNotFoundException e) { } 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); 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); return super.loadClass(className);
} }
} }
/** /**
* Load the named resource from this plugin. This implementation checks the plugin's classpath first * Load the named resource from this plugin.
* then delegates to the parent. * This implementation checks the plugin's classpath first then delegates to the parent.
* *
* @param name the name of the resource. * @param name the name of the resource.
* @return the URL to the resource, <code>null</code> if the resource was not found. * @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); 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; package ro.fortsoft.pf4j;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
* The classpath of the plugin after it was unpacked. * The classpath of the plugin.
* It contains classes directories and lib directories (directories that contains jars). * It contains {@code classes} directories and {@code lib} directories (directories that contains jars).
* All directories are relative to plugin repository.
* The default values are "classes" and "lib".
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class PluginClasspath { public class PluginClasspath {
private static final String DEFAULT_CLASSES_DIRECTORY = "classes"; private List<String> classesDirectories;
private static final String DEFAULT_LIB_DIRECTORY = "lib"; private List<String> libDirectories;
protected List<String> classesDirectories;
protected List<String> libDirectories;
public PluginClasspath() { public PluginClasspath() {
classesDirectories = new ArrayList<>(); classesDirectories = new ArrayList<>();
libDirectories = new ArrayList<>(); libDirectories = new ArrayList<>();
addResources();
} }
public List<String> getClassesDirectories() { public List<String> getClassesDirectories() {
return classesDirectories; return classesDirectories;
} }
public void setClassesDirectories(List<String> classesDirectories) { public void addClassesDirectories(String... classesDirectories) {
this.classesDirectories = classesDirectories; this.classesDirectories.addAll(Arrays.asList(classesDirectories));
} }
public List<String> getLibDirectories() { public List<String> getLibDirectories() {
return libDirectories; return libDirectories;
} }
public void setLibDirectories(List<String> libDirectories) { public void addLibDirectories(String... libDirectories) {
this.libDirectories = libDirectories; this.libDirectories.addAll(Arrays.asList(libDirectories));
}
protected void addResources() {
classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
libDirectories.add(DEFAULT_LIB_DIRECTORY);
} }
} }

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

@ -15,17 +15,17 @@
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import java.io.File; import java.nio.file.Path;
/** /**
* Find a plugin descriptor in a directory (plugin repository). * Find a plugin descriptor for a plugin path.
* You can find in manifest file @see DefaultPluginDescriptorFinder, * You can find in manifest file {@link DefaultPluginDescriptorFinder},
* xml file, properties file, java services (with ServiceLoader), etc. * xml file, properties file, java services (with {@link java.util.ServiceLoader}), etc.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public interface PluginDescriptorFinder { 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; package ro.fortsoft.pf4j;
import org.slf4j.Logger; import java.nio.file.Path;
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;
/** /**
* Load all information needed by a plugin. * Load all information (classes) needed by a plugin.
* This means add all jar files from 'lib' directory, 'classes' to classpath.
* It's a class for only the internal use.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
class PluginLoader { public interface 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; ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor);
}
/**
* 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]);
}
File[] directories = file.listFiles(directoryFilter);
for (int i = 0; (directories != null) && (i < directories.length); ++i) {
File directory = directories[i];
getJars(bucket, directory);
}
}
}
} }

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

@ -16,7 +16,8 @@
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version; import com.github.zafarkhaja.semver.Version;
import java.io.File;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -69,10 +70,10 @@ public interface PluginManager {
/** /**
* Load a plugin. * Load a plugin.
* *
* @param pluginArchiveFile * @param pluginPath
* @return the pluginId of the installed plugin or null * @return the pluginId of the installed plugin or null
*/ */
String loadPlugin(File pluginArchiveFile); String loadPlugin(Path pluginPath);
/** /**
* Start all active plugins. * Start all active plugins.
@ -130,7 +131,7 @@ public interface PluginManager {
*/ */
boolean deletePlugin(String pluginId); boolean deletePlugin(String pluginId);
PluginClassLoader getPluginClassLoader(String pluginId); ClassLoader getPluginClassLoader(String pluginId);
<T> List<T> getExtensions(Class<T> type); <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; package ro.fortsoft.pf4j;
import java.io.File; import java.nio.file.Path;
import java.util.List; 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 Decebal Suiu
* @author Mário Franco * @author Mário Franco
@ -27,11 +27,11 @@ import java.util.List;
public interface PluginRepository { public interface PluginRepository {
/** /**
* List all plugin archive filed. * List all plugin paths.
* *
* @return a list of files * @return a list of files
*/ */
List<File> getPluginArchives(); List<Path> getPluginPaths();
/** /**
* Removes a plugin from the repository. * Removes a plugin from the repository.
@ -39,6 +39,6 @@ public interface PluginRepository {
* @param pluginPath the plugin path * @param pluginPath the plugin path
* @return true if deleted * @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; package ro.fortsoft.pf4j;
import java.nio.file.Path;
/** /**
* A wrapper over plugin instance. * A wrapper over plugin instance.
* *
@ -22,16 +24,17 @@ package ro.fortsoft.pf4j;
*/ */
public class PluginWrapper { public class PluginWrapper {
PluginManager pluginManager; private PluginManager pluginManager;
PluginDescriptor descriptor; private PluginDescriptor descriptor;
String pluginPath; private Path pluginPath;
PluginClassLoader pluginClassLoader; private ClassLoader pluginClassLoader;
PluginFactory pluginFactory; private PluginFactory pluginFactory;
PluginState pluginState; private PluginState pluginState;
RuntimeMode runtimeMode; private RuntimeMode runtimeMode;
Plugin plugin; // cache 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.pluginManager = pluginManager;
this.descriptor = descriptor; this.descriptor = descriptor;
this.pluginPath = pluginPath; 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; 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 org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties; import java.util.Properties;
/** /**
@ -49,8 +49,8 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
} }
@Override @Override
public PluginDescriptor find(File pluginRepository) throws PluginException { public PluginDescriptor find(Path pluginPath) throws PluginException {
Properties properties = readProperties(pluginRepository); Properties properties = readProperties(pluginPath);
PluginDescriptor pluginDescriptor = createPluginDescriptor(properties); PluginDescriptor pluginDescriptor = createPluginDescriptor(properties);
validatePluginDescriptor(pluginDescriptor); validatePluginDescriptor(pluginDescriptor);
@ -58,31 +58,18 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder
return pluginDescriptor; return pluginDescriptor;
} }
protected Properties readProperties(File pluginRepository) throws PluginException { protected Properties readProperties(Path pluginPath) throws PluginException {
File propertiesFile = new File(pluginRepository, propertiesFileName); Path propertiesPath = pluginPath.resolve(Paths.get(propertiesFileName));
log.debug("Lookup plugin descriptor in '{}'", propertiesFile); log.debug("Lookup plugin descriptor in '{}'", propertiesPath);
if (!propertiesFile.exists()) { if (Files.notExists(propertiesPath)) {
throw new PluginException("Cannot find '" + propertiesFile + "' file"); throw new PluginException("Cannot find '" + pluginPath + "' path");
}
InputStream input = null;
try {
input = new FileInputStream(propertiesFile);
} catch (FileNotFoundException e) {
// not happening
} }
Properties properties = new Properties(); Properties properties = new Properties();
try { try (InputStream input = Files.newInputStream(propertiesPath)) {
properties.load(input); properties.load(input);
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e.getMessage(), e); throw new PluginException(e.getMessage(), e);
} finally {
try {
input.close();
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
}
} }
return properties; 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.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
@ -95,4 +96,29 @@ public class FileUtils {
return success; 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; 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. * It's a class for only the internal use.
* *
* @author Decebal Suiu * @author Decebal Suiu
@ -70,8 +70,8 @@ public class Unzip {
removeDirectory(destination); removeDirectory(destination);
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source)); ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source));
ZipEntry zipEntry = null;
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) { while ((zipEntry = zipInputStream.getNextEntry()) != null) {
try { try {
File file = new File(destination, zipEntry.getName()); File file = new File(destination, zipEntry.getName());

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

@ -19,10 +19,9 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import ro.fortsoft.pf4j.util.ZipFileFilter;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -31,6 +30,7 @@ import static org.junit.Assert.assertTrue;
/** /**
* @author Mario Franco * @author Mario Franco
* @author Decebal Suiu
*/ */
public class DefaultPluginRepositoryTest { public class DefaultPluginRepositoryTest {
@ -39,52 +39,53 @@ public class DefaultPluginRepositoryTest {
@Before @Before
public void setUp() throws IOException { public void setUp() throws IOException {
testFolder.newFile("plugin-1.zip"); testFolder.newFolder("plugin-1");
testFolder.newFile("plugin-2.zip"); testFolder.newFolder("plugin-2");
testFolder.newFile("plugin-3.zi_"); testFolder.newFolder("plugin-3");
} }
/** /**
* Test of getPluginArchives method, of class DefaultPluginRepository. * Test of {@link DefaultPluginRepository#getPluginPaths()} method.
*/ */
@Test @Test
public void testGetPluginArchives() { 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()); List<Path> result = instance.getPluginPaths();
assertFileExists(result, "plugin-1.zip");
assertFileExists(result, "plugin-2.zip"); 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 @Test
public void testDeletePluginArchive() { public void testDeletePluginPath() {
DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter()); Path pluginsRoot = getPluginsRoot();
PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
assertTrue(instance.deletePluginArchive("/plugin-1")); assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-1")));
assertFalse(instance.deletePluginArchive("/plugin-3")); 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(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) { private void assertPathExists(List<Path> paths, Path path) {
boolean found = false; assertTrue("The directory must contains the file " + path, paths.contains(path));
for (File f : files) {
if (f.getName().equals(file)) {
found = true;
break;
}
} }
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.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 Mario Franco
* @author Decebal Suiu
*/ */
public class DefaultPluginStatusProviderTest { public class DefaultPluginStatusProviderTest {
@ -42,7 +45,8 @@ public class DefaultPluginStatusProviderTest {
public void testIsPluginDisabled() throws IOException { public void testIsPluginDisabled() throws IOException {
createEnabledFile(); createEnabledFile();
createDisabledFile(); createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1")); assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-2")); assertTrue(instance.isPluginDisabled("plugin-2"));
@ -55,7 +59,8 @@ public class DefaultPluginStatusProviderTest {
@Test @Test
public void testIsPluginDisabledWithEnableEmpty() throws IOException { public void testIsPluginDisabledWithEnableEmpty() throws IOException {
createDisabledFile(); createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1")); assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-2")); assertTrue(instance.isPluginDisabled("plugin-2"));
@ -69,7 +74,8 @@ public class DefaultPluginStatusProviderTest {
public void testDisablePlugin() throws IOException { public void testDisablePlugin() throws IOException {
createEnabledFile(); createEnabledFile();
createDisabledFile(); createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.disablePlugin("plugin-1")); assertTrue(instance.disablePlugin("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-1")); assertTrue(instance.isPluginDisabled("plugin-1"));
@ -83,7 +89,8 @@ public class DefaultPluginStatusProviderTest {
@Test @Test
public void testDisablePluginWithEnableEmpty() throws IOException { public void testDisablePluginWithEnableEmpty() throws IOException {
createDisabledFile(); createDisabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.disablePlugin("plugin-1")); assertTrue(instance.disablePlugin("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-1")); assertTrue(instance.isPluginDisabled("plugin-1"));
@ -97,7 +104,8 @@ public class DefaultPluginStatusProviderTest {
@Test @Test
public void testEnablePlugin() throws IOException { public void testEnablePlugin() throws IOException {
createEnabledFile(); createEnabledFile();
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.enablePlugin("plugin-2")); assertTrue(instance.enablePlugin("plugin-2"));
assertFalse(instance.isPluginDisabled("plugin-1")); assertFalse(instance.isPluginDisabled("plugin-1"));
@ -110,7 +118,7 @@ public class DefaultPluginStatusProviderTest {
*/ */
@Test @Test
public void testEnablePluginWithEnableEmpty() { public void testEnablePluginWithEnableEmpty() {
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot()); PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.enablePlugin("plugin-2")); assertTrue(instance.enablePlugin("plugin-2"));
assertFalse(instance.isPluginDisabled("plugin-1")); assertFalse(instance.isPluginDisabled("plugin-1"));
@ -123,7 +131,7 @@ public class DefaultPluginStatusProviderTest {
*/ */
@Test @Test
public void testDisablePluginWithoutDisabledFile() throws IOException { public void testDisablePluginWithoutDisabledFile() throws IOException {
DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot()); PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1")); assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.disablePlugin("plugin-1")); assertTrue(instance.disablePlugin("plugin-1"));
@ -150,4 +158,8 @@ public class DefaultPluginStatusProviderTest {
FileUtils.writeLines(lines, file); 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.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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 Mario Franco
* @author Decebal Suiu
*/ */
public class ManifestPluginDescriptorFinderTest { public class ManifestPluginDescriptorFinderTest {
@ -43,43 +44,42 @@ public class ManifestPluginDescriptorFinderTest {
public void setUp() throws IOException { public void setUp() throws IOException {
Charset charset = Charset.forName("UTF-8"); Charset charset = Charset.forName("UTF-8");
File plugin = testFolder.newFolder("test-plugin-1", "classes", "META-INF"); Path pluginPath = testFolder.newFolder("test-plugin-1", "classes", "META-INF").toPath();
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin1Manifest(), charset); Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin1Manifest(), charset);
plugin = testFolder.newFolder("test-plugin-2", "classes", "META-INF"); pluginPath = testFolder.newFolder("test-plugin-2", "classes", "META-INF").toPath();
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin2Manifest(), charset); Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin2Manifest(), charset);
// Empty Plugin // empty plugin
testFolder.newFolder("test-plugin-3"); testFolder.newFolder("test-plugin-3");
// No Plugin Class // no plugin class
plugin = testFolder.newFolder("test-plugin-4", "classes", "META-INF"); pluginPath = testFolder.newFolder("test-plugin-4", "classes", "META-INF").toPath();
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin4Manifest(), charset); Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin4Manifest(), charset);
// No Plugin Version // no plugin version
plugin = testFolder.newFolder("test-plugin-5", "classes", "META-INF"); pluginPath = testFolder.newFolder("test-plugin-5", "classes", "META-INF").toPath();
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin5Manifest(), charset); Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin5Manifest(), charset);
// No Plugin Id // no plugin id
plugin = testFolder.newFolder("test-plugin-6", "classes", "META-INF"); pluginPath = testFolder.newFolder("test-plugin-6", "classes", "META-INF").toPath();
Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes()); Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin6Manifest(), charset); Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin6Manifest(), charset);
} }
/** /**
* Test of find method, of class ManifestPluginDescriptorFinder. * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/ */
@Test @Test
public void testFind() throws Exception { 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 plugin1 = instance.find(getPluginsRoot().resolve("test-plugin-1"));
PluginDescriptor plugin2 = instance.find(getPluginsRoot().resolve("test-plugin-2"));
PluginDescriptor plugin2 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-2").toFile());
assertEquals("test-plugin-1", plugin1.getPluginId()); assertEquals("test-plugin-1", plugin1.getPluginId());
assertEquals("Test Plugin 1", plugin1.getPluginDescription()); 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) @Test(expected = PluginException.class)
public void testFindNotFound() throws Exception { public void testFindNotFound() throws Exception {
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath()); instance.find(getPluginsRoot().resolve("test-plugin-3"));
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-3").toFile());
} }
/** /**
* Test of find method, of class ManifestPluginDescriptorFinder. * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/ */
@Test(expected = PluginException.class) @Test(expected = PluginException.class)
public void testFindMissingPluginClass() throws Exception { public void testFindMissingPluginClass() throws Exception {
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath()); instance.find(getPluginsRoot().resolve("test-plugin-4"));
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-4").toFile());
} }
/** /**
* Test of find method, of class ManifestPluginDescriptorFinder. * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/ */
@Test(expected = PluginException.class) @Test(expected = PluginException.class)
public void testFindMissingPluginVersion() throws Exception { public void testFindMissingPluginVersion() throws Exception {
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath()); instance.find(getPluginsRoot().resolve("test-plugin-5"));
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-5").toFile());
} }
/** /**
* Test of find method, of class ManifestPluginDescriptorFinder. * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/ */
@Test(expected = PluginException.class) @Test(expected = PluginException.class)
public void testFindMissingPluginId() throws Exception { public void testFindMissingPluginId() throws Exception {
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath()); instance.find(getPluginsRoot().resolve("test-plugin-6"));
PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-6").toFile());
} }
private List<String> getPlugin1Manifest() { private List<String> getPlugin1Manifest() {
String[] lines = new String[] {
String[] lines = new String[]{"Manifest-Version: 1.0\n" "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #1\n" + "Implementation-Title: Test Plugin #1\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" + "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n" + "Archiver-Version: Plexus Archiver\n"
@ -161,14 +157,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n" + "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n" + "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n" + "\n"
+ ""}; + ""
};
return Arrays.asList(lines); return Arrays.asList(lines);
} }
private List<String> getPlugin2Manifest() { private List<String> getPlugin2Manifest() {
String[] lines = new String[] {
String[] lines = new String[]{"Manifest-Version: 1.0\n" "Manifest-Version: 1.0\n"
+ "Plugin-Dependencies: \n" + "Plugin-Dependencies: \n"
+ "Implementation-Title: Test Plugin #2\n" + "Implementation-Title: Test Plugin #2\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" + "Implementation-Version: 0.10.0-SNAPSHOT\n"
@ -184,14 +181,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n" + "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n" + "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n" + "\n"
+ ""}; + ""
};
return Arrays.asList(lines); return Arrays.asList(lines);
} }
private List<String> getPlugin4Manifest() { private List<String> getPlugin4Manifest() {
String[] lines = new String[] {
String[] lines = new String[]{"Manifest-Version: 1.0\n" "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #4\n" + "Implementation-Title: Test Plugin #4\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" + "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n" + "Archiver-Version: Plexus Archiver\n"
@ -205,14 +203,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n" + "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n" + "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n" + "\n"
+ ""}; + ""
};
return Arrays.asList(lines); return Arrays.asList(lines);
} }
private List<String> getPlugin5Manifest() { private List<String> getPlugin5Manifest() {
String[] lines = new String[] {
String[] lines = new String[]{"Manifest-Version: 1.0\n" "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #5\n" + "Implementation-Title: Test Plugin #5\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" + "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n" + "Archiver-Version: Plexus Archiver\n"
@ -226,14 +225,15 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n" + "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n" + "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n" + "\n"
+ ""}; + ""
};
return Arrays.asList(lines); return Arrays.asList(lines);
} }
private List<String> getPlugin6Manifest() { private List<String> getPlugin6Manifest() {
String[] lines = new String[] {
String[] lines = new String[]{"Manifest-Version: 1.0\n" "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #6\n" + "Implementation-Title: Test Plugin #6\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n" + "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n" + "Archiver-Version: Plexus Archiver\n"
@ -246,8 +246,14 @@ public class ManifestPluginDescriptorFinderTest {
+ "Build-Jdk: 1.8.0_45\n" + "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n" + "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n" + "\n"
+ ""}; + ""
};
return Arrays.asList(lines); 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