import org.apache.commons.lang.StringUtils;
import ro.fortsoft.pf4j.DefaultPluginManager;
+import ro.fortsoft.pf4j.JarPluginManager;
import ro.fortsoft.pf4j.PluginManager;
import ro.fortsoft.pf4j.PluginWrapper;
import ro.fortsoft.pf4j.demo.api.Greeting;
// create the plugin manager
final PluginManager pluginManager = new DefaultPluginManager();
+// final PluginManager pluginManager = new JarPluginManager();
// load the plugins
pluginManager.loadPlugins();
#
log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
# !!! Put the bellow classes on level TRACE when you are in trouble
-log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=WARN, Console
+log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=DEBUG, Console
log4j.logger.ro.fortsoft.pf4j.AbstractExtensionFinder=DEBUG, Console
log4j.additivity.ro.fortsoft.pf4j=false
log4j.additivity.ro.fortsoft.pf4j.PluginClassLoader=false
--- /dev/null
+/*
+ * 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());
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
*/
package ro.fortsoft.pf4j;
-import java.io.File;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
}
@Override
- public List<File> getPluginArchives() {
- List<File> file = new ArrayList<>();
+ public List<Path> getPluginPaths() {
+ List<Path> paths = new ArrayList<>();
for (PluginRepository repository : repositories) {
- file.addAll(repository.getPluginArchives());
+ paths.addAll(repository.getPluginPaths());
}
- return file;
+ return paths;
}
@Override
- public boolean deletePluginArchive(String pluginPath) {
+ public boolean deletePluginPath(Path pluginPath) {
for (PluginRepository repository : repositories) {
- if (repository.deletePluginArchive(pluginPath)) {
+ if (repository.deletePluginPath(pluginPath)) {
return true;
}
}
+++ /dev/null
-/*
- * 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;
-
-/**
- * CyclicDependencyException will be thrown if a cyclic dependency is detected.
- *
- * @author Decebal Suiu
- */
-class CyclicDependencyException extends PluginException {
-
- private static final long serialVersionUID = 1L;
-
- public CyclicDependencyException(String message) {
- super(message);
- }
-
-}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * The default values are {@code classes} and {@code lib}.
+ *
+ * @author Decebal Suiu
+ */
+public class DefaultPluginClasspath extends PluginClasspath {
+
+ public DefaultPluginClasspath() {
+ super();
+
+ addClassesDirectories("classes");
+ addLibDirectories("lib");
+ }
+
+}
*/
package ro.fortsoft.pf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.jar.Manifest;
+
/**
- * The default implementation for PluginDescriptorFinder.
- * Now, this class it's a "link" to {@link ro.fortsoft.pf4j.ManifestPluginDescriptorFinder}.
+ * The default implementation for {@link PluginDescriptorFinder}.
+ * Now, this class it's a "link" to {@link ManifestPluginDescriptorFinder}.
*
* @author Decebal Suiu
*/
public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
- public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
- super(pluginClasspath);
- }
+ private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
+
+ private PluginClasspath pluginClasspath;
+
+ public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
+ this.pluginClasspath = pluginClasspath;
+ }
+
+ @Override
+ public Manifest readManifest(Path pluginPath) throws PluginException {
+ // TODO it's ok with first classes root? Another idea is to specify in PluginClasspath the folder.
+ String classes = pluginClasspath.getClassesDirectories().get(0);
+ Path manifestPath = pluginPath.resolve(Paths.get(classes,"/META-INF/MANIFEST.MF"));
+ log.debug("Lookup plugin descriptor in '{}'", manifestPath);
+ if (Files.notExists(manifestPath)) {
+ throw new PluginException("Cannot find '" + manifestPath + "' path");
+ }
+
+ try (InputStream input = Files.newInputStream(manifestPath)) {
+ return new Manifest(input);
+ } catch (IOException e) {
+ throw new PluginException(e.getMessage(), e);
+ }
+ }
}
import java.lang.reflect.Modifier;
/**
- * The default implementation for PluginFactory.
- * It uses Class.newInstance() method.
+ * The default implementation for {@link PluginFactory}.
+ * It uses {@link Class#newInstance()} method.
*
* @author Decebal Suiu
*/
--- /dev/null
+/*
+ * 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);
+ }
+ }
+ }
+
+}
*/
package ro.fortsoft.pf4j;
-import com.github.zafarkhaja.semver.Version;
-import com.github.zafarkhaja.semver.expr.Expression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import ro.fortsoft.pf4j.util.AndFileFilter;
-import ro.fortsoft.pf4j.util.DirectoryFileFilter;
-import ro.fortsoft.pf4j.util.FileUtils;
-import ro.fortsoft.pf4j.util.HiddenFilter;
-import ro.fortsoft.pf4j.util.NameFileFilter;
-import ro.fortsoft.pf4j.util.NotFileFilter;
-import ro.fortsoft.pf4j.util.OrFileFilter;
-import ro.fortsoft.pf4j.util.Unzip;
-import ro.fortsoft.pf4j.util.ZipFileFilter;
import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.nio.file.Path;
/**
- * Default implementation of the PluginManager interface.
+ * Default implementation of the {@link PluginManager} interface.
*
* @author Decebal Suiu
*/
-public class DefaultPluginManager implements PluginManager {
+public class DefaultPluginManager extends AbstractPluginManager {
private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
- public static final String DEFAULT_PLUGINS_DIRECTORY = "plugins";
- public static final String DEVELOPMENT_PLUGINS_DIRECTORY = "../plugins";
-
- protected File pluginsDirectory;
-
- protected ExtensionFinder extensionFinder;
-
- protected PluginDescriptorFinder pluginDescriptorFinder;
-
protected PluginClasspath pluginClasspath;
- /**
- * A map of plugins this manager is responsible for (the key is the 'pluginId').
- */
- protected Map<String, PluginWrapper> plugins;
-
- /**
- * A map of plugin class loaders (he key is the 'pluginId').
- */
- protected Map<String, PluginClassLoader> pluginClassLoaders;
-
- /**
- * A relation between 'pluginPath' and 'pluginId'
- */
- protected Map<String, String> pathToIdMap;
-
- /**
- * A list with unresolved plugins (unresolved dependency).
- */
- protected List<PluginWrapper> unresolvedPlugins;
-
- /**
- * A list with resolved plugins (resolved dependency).
- */
- protected List<PluginWrapper> resolvedPlugins;
-
- /**
- * A list with started plugins.
- */
- protected List<PluginWrapper> startedPlugins;
-
- /**
- * The registered {@link PluginStateListener}s.
- */
- protected List<PluginStateListener> pluginStateListeners;
-
- /**
- * Cache value for the runtime mode. No need to re-read it because it wont change at
- * runtime.
- */
- protected RuntimeMode runtimeMode;
-
- /**
- * The system version used for comparisons to the plugin requires attribute.
- */
- protected Version systemVersion = Version.forIntegers(0, 0, 0);
-
- protected PluginFactory pluginFactory;
- protected ExtensionFactory extensionFactory;
- protected PluginStatusProvider pluginStatusProvider;
- protected DependencyResolver dependencyResolver;
-
- /**
- * The plugins repository.
- */
- protected PluginRepository pluginRepository;
-
- /**
- * The plugins directory is supplied by System.getProperty("pf4j.pluginsDir", "plugins").
- */
public DefaultPluginManager() {
- this.pluginsDirectory = createPluginsDirectory();
-
- initialize();
- }
-
- /**
- * Constructs DefaultPluginManager which the given plugins directory.
- *
- * @param pluginsDirectory the directory to search for plugins
- */
- public DefaultPluginManager(File pluginsDirectory) {
- this.pluginsDirectory = pluginsDirectory;
-
- initialize();
- }
-
- @Override
- public void setSystemVersion(Version version) {
- systemVersion = version;
- }
-
- @Override
- public Version getSystemVersion() {
- return systemVersion;
- }
-
- @Override
- public List<PluginWrapper> getPlugins() {
- return new ArrayList<>(plugins.values());
- }
-
- @Override
- public List<PluginWrapper> getPlugins(PluginState pluginState) {
- List<PluginWrapper> plugins= new ArrayList<>();
- for (PluginWrapper plugin : getPlugins()) {
- if (pluginState.equals(plugin.getPluginState())) {
- plugins.add(plugin);
- }
- }
-
- return plugins;
- }
-
- @Override
- public List<PluginWrapper> getResolvedPlugins() {
- return resolvedPlugins;
- }
-
- @Override
- public List<PluginWrapper> getUnresolvedPlugins() {
- return unresolvedPlugins;
- }
-
- @Override
- public List<PluginWrapper> getStartedPlugins() {
- return startedPlugins;
- }
-
- @Override
- public PluginWrapper getPlugin(String pluginId) {
- return plugins.get(pluginId);
- }
-
- @Override
- public String loadPlugin(File pluginArchiveFile) {
- if ((pluginArchiveFile == null) || !pluginArchiveFile.exists()) {
- throw new IllegalArgumentException(String.format("Specified plugin %s does not exist!", pluginArchiveFile));
- }
-
- log.debug("Loading plugin from '{}'", pluginArchiveFile);
-
- File pluginDirectory = null;
- try {
- pluginDirectory = expandPluginArchive(pluginArchiveFile);
- } catch (IOException e) {
- log.error(e.getMessage(), e);
- }
- if ((pluginDirectory == null) || !pluginDirectory.exists()) {
- throw new IllegalArgumentException(String.format("Failed to expand %s", pluginArchiveFile));
- }
-
- try {
- PluginWrapper pluginWrapper = loadPluginDirectory(pluginDirectory);
- // TODO uninstalled plugin dependencies?
- unresolvedPlugins.remove(pluginWrapper);
- resolvedPlugins.add(pluginWrapper);
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, null));
-
- return pluginWrapper.getDescriptor().getPluginId();
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
-
- return null;
- }
-
- /**
- * Start all active plugins.
- */
- @Override
- public void startPlugins() {
- for (PluginWrapper pluginWrapper : resolvedPlugins) {
- PluginState pluginState = pluginWrapper.getPluginState();
- if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
- try {
- PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
- log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- pluginWrapper.getPlugin().start();
- pluginWrapper.setPluginState(PluginState.STARTED);
- startedPlugins.add(pluginWrapper);
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
- }
- }
- }
-
- /**
- * Start the specified plugin and it's dependencies.
- */
- @Override
- public PluginState startPlugin(String pluginId) {
- if (!plugins.containsKey(pluginId)) {
- throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
- }
-
- PluginWrapper pluginWrapper = getPlugin(pluginId);
- PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
- PluginState pluginState = pluginWrapper.getPluginState();
- if (PluginState.STARTED == pluginState) {
- log.debug("Already started plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- return PluginState.STARTED;
- }
-
- if (PluginState.DISABLED == pluginState) {
- // automatically enable plugin on manual plugin start
- if (!enablePlugin(pluginId)) {
- return pluginState;
- }
- }
-
- for (PluginDependency dependency : pluginDescriptor.getDependencies()) {
- startPlugin(dependency.getPluginId());
- }
-
- try {
- log.info("Start plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- pluginWrapper.getPlugin().start();
- pluginWrapper.setPluginState(PluginState.STARTED);
- startedPlugins.add(pluginWrapper);
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
-
- return pluginWrapper.getPluginState();
- }
-
- /**
- * Stop all active plugins.
- */
- @Override
- public void stopPlugins() {
- // stop started plugins in reverse order
- Collections.reverse(startedPlugins);
- Iterator<PluginWrapper> itr = startedPlugins.iterator();
- while (itr.hasNext()) {
- PluginWrapper pluginWrapper = itr.next();
- PluginState pluginState = pluginWrapper.getPluginState();
- if (PluginState.STARTED == pluginState) {
- try {
- PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
- log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- pluginWrapper.getPlugin().stop();
- pluginWrapper.setPluginState(PluginState.STOPPED);
- itr.remove();
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
- }
- }
- }
-
- /**
- * Stop the specified plugin and it's dependencies.
- */
- @Override
- public PluginState stopPlugin(String pluginId) {
- return stopPlugin(pluginId, true);
+ super();
}
- private PluginState stopPlugin(String pluginId, boolean stopDependents) {
- if (!plugins.containsKey(pluginId)) {
- throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
- }
-
- PluginWrapper pluginWrapper = getPlugin(pluginId);
- PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
- PluginState pluginState = pluginWrapper.getPluginState();
- if (PluginState.STOPPED == pluginState) {
- log.debug("Already stopped plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- return PluginState.STOPPED;
- }
-
- // test for disabled plugin
- if (PluginState.DISABLED == pluginState) {
- // do nothing
- return pluginState;
- }
-
- if (stopDependents) {
- List<String> dependents = dependencyResolver.getDependents(pluginId);
- while (!dependents.isEmpty()) {
- String dependent = dependents.remove(0);
- stopPlugin(dependent, false);
- dependents.addAll(0, dependencyResolver.getDependents(dependent));
- }
- }
-
- try {
- log.info("Stop plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- pluginWrapper.getPlugin().stop();
- pluginWrapper.setPluginState(PluginState.STOPPED);
- startedPlugins.remove(pluginWrapper);
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
-
- return pluginWrapper.getPluginState();
- }
-
- /**
- * Load plugins.
- */
- @Override
- public void loadPlugins() {
- log.debug("Lookup plugins in '{}'", pluginsDirectory.getAbsolutePath());
- // check for plugins directory
- if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
- log.error("No '{}' directory", pluginsDirectory.getAbsolutePath());
- return;
- }
-
- // expand all plugin archives
- List<File> pluginArchives = pluginRepository.getPluginArchives();
- for (File archiveFile : pluginArchives) {
- try {
- expandPluginArchive(archiveFile);
- } catch (IOException e) {
- log.error(e.getMessage(), e);
- }
- }
-
- // check for no plugins
- AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
- pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter()));
- File[] directories = pluginsDirectory.listFiles(pluginsFilter);
- if (directories == null) {
- directories = new File[0];
- }
- log.debug("Found {} possible plugins: {}", directories.length, directories);
- if (directories.length == 0) {
- log.info("No plugins");
- return;
- }
-
- // load any plugin from plugins directory
- for (File directory : directories) {
- try {
- loadPluginDirectory(directory);
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
- }
-
- // resolve 'unresolvedPlugins'
- try {
- resolvePlugins();
- } catch (PluginException e) {
- log.error(e.getMessage(), e);
- }
- }
-
- @Override
- public boolean unloadPlugin(String pluginId) {
- try {
- PluginState pluginState = stopPlugin(pluginId);
- if (PluginState.STARTED == pluginState) {
- return false;
- }
-
- PluginWrapper pluginWrapper = getPlugin(pluginId);
- PluginDescriptor descriptor = pluginWrapper.getDescriptor();
- List<PluginDependency> dependencies = descriptor.getDependencies();
- for (PluginDependency dependency : dependencies) {
- if (!unloadPlugin(dependency.getPluginId())) {
- return false;
- }
- }
-
- // remove the plugin
- plugins.remove(pluginId);
- resolvedPlugins.remove(pluginWrapper);
- pathToIdMap.remove(pluginWrapper.getPluginPath());
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-
- // remove the classloader
- if (pluginClassLoaders.containsKey(pluginId)) {
- PluginClassLoader classLoader = pluginClassLoaders.remove(pluginId);
- classLoader.dispose();
- }
-
- return true;
- } catch (IllegalArgumentException e) {
- // ignore not found exceptions because this method is recursive
- }
-
- return false;
- }
-
- @Override
- public boolean disablePlugin(String pluginId) {
- if (!plugins.containsKey(pluginId)) {
- throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
- }
-
- PluginWrapper pluginWrapper = getPlugin(pluginId);
- PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
- PluginState pluginState = pluginWrapper.getPluginState();
- if (PluginState.DISABLED == pluginState) {
- log.debug("Already disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- return true;
- }
-
- if (PluginState.STOPPED == stopPlugin(pluginId)) {
- pluginWrapper.setPluginState(PluginState.DISABLED);
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
-
- if (!pluginStatusProvider.disablePlugin(pluginId)) {
- return false;
- }
-
- log.info("Disabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-
- return true;
- }
-
- return false;
+ @Deprecated
+ public DefaultPluginManager(File pluginsDir) {
+ this(pluginsDir.toPath());
}
- @Override
- public boolean enablePlugin(String pluginId) {
- if (!plugins.containsKey(pluginId)) {
- throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
- }
-
- PluginWrapper pluginWrapper = getPlugin(pluginId);
- if (!isPluginValid(pluginWrapper)) {
- log.warn("Plugin '{}:{}' can not be enabled", pluginWrapper.getPluginId(),
- pluginWrapper.getDescriptor().getVersion());
- return false;
- }
-
- PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
- PluginState pluginState = pluginWrapper.getPluginState();
- if (PluginState.DISABLED != pluginState) {
- log.debug("Plugin '{}:{}' is not disabled", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
- return true;
- }
-
- if (!pluginStatusProvider.enablePlugin(pluginId)) {
- return false;
- }
-
- pluginWrapper.setPluginState(PluginState.CREATED);
-
- firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
-
- log.info("Enabled plugin '{}:{}'", pluginDescriptor.getPluginId(), pluginDescriptor.getVersion());
-
- return true;
+ public DefaultPluginManager(Path pluginsRoot) {
+ super(pluginsRoot);
}
- @Override
- public boolean deletePlugin(String pluginId) {
- if (!plugins.containsKey(pluginId)) {
- throw new IllegalArgumentException(String.format("Unknown pluginId %s", pluginId));
- }
-
- PluginWrapper pluginWrapper = getPlugin(pluginId);
- PluginState pluginState = stopPlugin(pluginId);
- if (PluginState.STARTED == pluginState) {
- log.error("Failed to stop plugin {} on delete", pluginId);
- return false;
- }
-
- if (!unloadPlugin(pluginId)) {
- log.error("Failed to unload plugin {} on delete", pluginId);
- return false;
- }
-
- File pluginFolder = new File(pluginsDirectory, pluginWrapper.getPluginPath());
-
- if (pluginFolder.exists()) {
- FileUtils.delete(pluginFolder);
- }
-
- pluginRepository.deletePluginArchive(pluginWrapper.getPluginPath());
-
- return true;
- }
-
/**
- * Get plugin class loader for this path.
- */
- @Override
- public PluginClassLoader getPluginClassLoader(String pluginId) {
- return pluginClassLoaders.get(pluginId);
- }
-
- @Override
- public <T> List<T> getExtensions(Class<T> type) {
- List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type);
- List<T> extensions = new ArrayList<>(extensionsWrapper.size());
- for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
- extensions.add(extensionWrapper.getExtension());
- }
-
- return extensions;
- }
-
+ * By default if {@link DefaultPluginManager#isDevelopment()} returns true
+ * than a {@link PropertiesPluginDescriptorFinder} is returned
+ * else this method returns {@link DefaultPluginDescriptorFinder}.
+ */
@Override
- public <T> List<T> getExtensions(Class<T> type, String pluginId) {
- List<ExtensionWrapper<T>> extensionsWrapper = extensionFinder.find(type, pluginId);
- List<T> extensions = new ArrayList<>(extensionsWrapper.size());
- for (ExtensionWrapper<T> extensionWrapper : extensionsWrapper) {
- extensions.add(extensionWrapper.getExtension());
- }
-
- return extensions;
+ protected PluginDescriptorFinder createPluginDescriptorFinder() {
+ return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new DefaultPluginDescriptorFinder(pluginClasspath);
}
@Override
- @SuppressWarnings("unchecked")
- public List getExtensions(String pluginId) {
- List<ExtensionWrapper> extensionsWrapper = extensionFinder.find(pluginId);
- List extensions = new ArrayList<>(extensionsWrapper.size());
- for (ExtensionWrapper extensionWrapper : extensionsWrapper) {
- extensions.add(extensionWrapper.getExtension());
- }
+ protected ExtensionFinder createExtensionFinder() {
+ DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
+ addPluginStateListener(extensionFinder);
- return extensions;
+ return extensionFinder;
}
@Override
- public Set<String> getExtensionClassNames(String pluginId) {
- return extensionFinder.findClassNames(pluginId);
+ protected PluginFactory createPluginFactory() {
+ return new DefaultPluginFactory();
}
@Override
- public ExtensionFactory getExtensionFactory() {
- return extensionFactory;
+ protected ExtensionFactory createExtensionFactory() {
+ return new DefaultExtensionFactory();
}
@Override
- public RuntimeMode getRuntimeMode() {
- if (runtimeMode == null) {
- // retrieves the runtime mode from system
- String modeAsString = System.getProperty("pf4j.mode", RuntimeMode.DEPLOYMENT.toString());
- runtimeMode = RuntimeMode.byName(modeAsString);
- }
-
- return runtimeMode;
- }
-
- @Override
- public PluginWrapper whichPlugin(Class<?> clazz) {
- ClassLoader classLoader = clazz.getClassLoader();
- for (PluginWrapper plugin : resolvedPlugins) {
- if (plugin.getPluginClassLoader() == classLoader) {
- return plugin;
- }
- }
-
- return null;
+ protected PluginStatusProvider createPluginStatusProvider() {
+ return new DefaultPluginStatusProvider(getPluginsRoot());
}
@Override
- public synchronized void addPluginStateListener(PluginStateListener listener) {
- pluginStateListeners.add(listener);
+ protected PluginRepository createPluginRepository() {
+ return new DefaultPluginRepository(getPluginsRoot(), isDevelopment());
}
@Override
- public synchronized void removePluginStateListener(PluginStateListener listener) {
- pluginStateListeners.remove(listener);
- }
-
- public Version getVersion() {
- String version = null;
-
- Package pf4jPackage = getClass().getPackage();
- if (pf4jPackage != null) {
- version = pf4jPackage.getImplementationVersion();
- if (version == null) {
- version = pf4jPackage.getSpecificationVersion();
- }
- }
-
- return (version != null) ? Version.valueOf(version) : Version.forIntegers(0, 0, 0);
+ protected PluginLoader createPluginLoader() {
+ return new DefaultPluginLoader(this, pluginClasspath);
}
/**
- * Add the possibility to override the PluginDescriptorFinder.
- * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
- * PropertiesPluginDescriptorFinder is returned else this method returns
- * DefaultPluginDescriptorFinder.
- */
- protected PluginDescriptorFinder createPluginDescriptorFinder() {
- if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
- return new PropertiesPluginDescriptorFinder();
- }
-
- return new DefaultPluginDescriptorFinder(pluginClasspath);
- }
-
- /**
- * Add the possibility to override the ExtensionFinder.
- */
- protected ExtensionFinder createExtensionFinder() {
- DefaultExtensionFinder extensionFinder = new DefaultExtensionFinder(this);
- addPluginStateListener(extensionFinder);
-
- return extensionFinder;
- }
-
- /**
- * Add the possibility to override the PluginClassPath.
- * By default if getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
- * DevelopmentPluginClasspath is returned else this method returns
- * PluginClasspath.
+ * By default if {@link DefaultPluginManager#isDevelopment()} returns true
+ * than a {@link DevelopmentPluginClasspath} is returned
+ * else this method returns {@link DefaultPluginClasspath}.
*/
protected PluginClasspath createPluginClasspath() {
- if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
- return new DevelopmentPluginClasspath();
- }
-
- return new PluginClasspath();
- }
-
- protected PluginStatusProvider createPluginStatusProvider() {
- return new DefaultPluginStatusProvider(pluginsDirectory);
- }
-
- protected PluginRepository createPluginRepository() {
- return new DefaultPluginRepository(pluginsDirectory, new ZipFileFilter());
- }
-
- protected boolean isPluginDisabled(String pluginId) {
- return pluginStatusProvider.isPluginDisabled(pluginId);
- }
-
- protected boolean isPluginValid(PluginWrapper pluginWrapper) {
- Expression requires = pluginWrapper.getDescriptor().getRequires();
- Version system = getSystemVersion();
- if (requires.interpret(system)) {
- return true;
- }
-
- log.warn("Plugin '{}:{}' requires a minimum system version of {}",
- pluginWrapper.getPluginId(),
- pluginWrapper.getDescriptor().getVersion(),
- requires);
-
- return false;
- }
-
- protected FileFilter createHiddenPluginFilter() {
- OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
-
- if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
- hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
- }
-
- return hiddenPluginFilter;
- }
-
- /**
- * Add the possibility to override the plugins directory.
- * If a "pf4j.pluginsDir" system property is defined than this method returns
- * that directory.
- * If getRuntimeMode() returns RuntimeMode.DEVELOPMENT than a
- * DEVELOPMENT_PLUGINS_DIRECTORY ("../plugins") is returned else this method returns
- * DEFAULT_PLUGINS_DIRECTORY ("plugins").
- * @return
- */
- protected File createPluginsDirectory() {
- String pluginsDir = System.getProperty("pf4j.pluginsDir");
- if (pluginsDir == null) {
- if (RuntimeMode.DEVELOPMENT.equals(getRuntimeMode())) {
- pluginsDir = DEVELOPMENT_PLUGINS_DIRECTORY;
- } else {
- pluginsDir = DEFAULT_PLUGINS_DIRECTORY;
- }
- }
-
- return new File(pluginsDir);
- }
-
- /**
- * Add the possibility to override the PluginFactory..
- */
- protected PluginFactory createPluginFactory() {
- return new DefaultPluginFactory();
- }
-
- /**
- * Add the possibility to override the ExtensionFactory.
- */
- protected ExtensionFactory createExtensionFactory() {
- return new DefaultExtensionFactory();
- }
-
- /**
- * Add the possibility to override the PluginClassLoader.
- */
- protected PluginClassLoader createPluginClassLoader(PluginDescriptor pluginDescriptor) {
- return new PluginClassLoader(this, pluginDescriptor, getClass().getClassLoader());
+ return isDevelopment() ? new DevelopmentPluginClasspath() : new DefaultPluginClasspath();
}
- private void initialize() {
- plugins = new HashMap<>();
- pluginClassLoaders = new HashMap<>();
- pathToIdMap = new HashMap<>();
- unresolvedPlugins = new ArrayList<>();
- resolvedPlugins = new ArrayList<>();
- startedPlugins = new ArrayList<>();
-
- pluginStateListeners = new ArrayList<>();
-
- dependencyResolver = new DependencyResolver();
-
- log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
-
+ @Override
+ protected void initialize() {
pluginClasspath = createPluginClasspath();
- pluginFactory = createPluginFactory();
- extensionFactory = createExtensionFactory();
- pluginDescriptorFinder = createPluginDescriptorFinder();
- extensionFinder = createExtensionFinder();
- pluginStatusProvider = createPluginStatusProvider();
- pluginRepository = createPluginRepository();
-
- try {
- pluginsDirectory = pluginsDirectory.getCanonicalFile();
- } catch (IOException e) {
- log.error(e.getMessage(), e);
- }
- System.setProperty("pf4j.pluginsDir", pluginsDirectory.getAbsolutePath());
- }
-
- private PluginWrapper loadPluginDirectory(File pluginDirectory) throws PluginException {
- // try to load the plugin
- String pluginName = pluginDirectory.getName();
- String pluginPath = "/".concat(pluginName);
-
- // test for plugin duplication
- if (plugins.get(pathToIdMap.get(pluginPath)) != null) {
- return null;
- }
-
- // retrieves the plugin descriptor
- log.debug("Find plugin descriptor '{}'", pluginPath);
- PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginDirectory);
- log.debug("Descriptor " + pluginDescriptor);
- String pluginClassName = pluginDescriptor.getPluginClass();
- log.debug("Class '{}' for plugin '{}'", pluginClassName, pluginPath);
-
- // load plugin
- log.debug("Loading plugin '{}'", pluginPath);
- PluginClassLoader pluginClassLoader = createPluginClassLoader(pluginDescriptor);
- log.debug("Created class loader '{}'", pluginClassLoader);
- PluginLoader pluginLoader = new PluginLoader(pluginDirectory, pluginClassLoader, pluginClasspath);
- pluginLoader.load();
- log.debug("Loaded plugin '{}'", pluginPath);
-
- // create the plugin wrapper
- log.debug("Creating wrapper for plugin '{}'", pluginPath);
- PluginWrapper pluginWrapper = new PluginWrapper(this, pluginDescriptor, pluginPath, pluginClassLoader);
- pluginWrapper.setPluginFactory(pluginFactory);
- pluginWrapper.setRuntimeMode(getRuntimeMode());
-
- // test for disabled plugin
- if (isPluginDisabled(pluginDescriptor.getPluginId())) {
- log.info("Plugin '{}' is disabled", pluginPath);
- pluginWrapper.setPluginState(PluginState.DISABLED);
- }
-
- // validate the plugin
- if (!isPluginValid(pluginWrapper)) {
- log.info("Plugin '{}' is disabled", pluginPath);
- pluginWrapper.setPluginState(PluginState.DISABLED);
- }
- log.debug("Created wrapper '{}' for plugin '{}'", pluginWrapper, pluginPath);
+ super.initialize();
- String pluginId = pluginDescriptor.getPluginId();
-
- // add plugin to the list with plugins
- plugins.put(pluginId, pluginWrapper);
- unresolvedPlugins.add(pluginWrapper);
-
- // add plugin class loader to the list with class loaders
- pluginClassLoaders.put(pluginId, pluginClassLoader);
-
- return pluginWrapper;
- }
-
- private File expandPluginArchive(File pluginArchiveFile) throws IOException {
- String fileName = pluginArchiveFile.getName();
- long pluginArchiveDate = pluginArchiveFile.lastModified();
- String pluginName = fileName.substring(0, fileName.length() - 4);
- File pluginDirectory = new File(pluginsDirectory, pluginName);
- // check if exists directory or the '.zip' file is "newer" than directory
- if (!pluginDirectory.exists() || (pluginArchiveDate > pluginDirectory.lastModified())) {
- log.debug("Expand plugin archive '{}' in '{}'", pluginArchiveFile, pluginDirectory);
-
- // do not overwrite an old version, remove it
- if (pluginDirectory.exists()) {
- FileUtils.delete(pluginDirectory);
- }
-
- // create directory for plugin
- pluginDirectory.mkdirs();
-
- // expand '.zip' file
- Unzip unzip = new Unzip();
- unzip.setSource(pluginArchiveFile);
- unzip.setDestination(pluginDirectory);
- unzip.extract();
- }
-
- return pluginDirectory;
- }
-
- private void resolvePlugins() throws PluginException {
- resolveDependencies();
- }
-
- private void resolveDependencies() throws PluginException {
- dependencyResolver.resolve(unresolvedPlugins);
- resolvedPlugins = dependencyResolver.getSortedPlugins();
- for (PluginWrapper pluginWrapper : resolvedPlugins) {
- unresolvedPlugins.remove(pluginWrapper);
- log.info("Plugin '{}' resolved", pluginWrapper.getDescriptor().getPluginId());
- }
+ log.info("PF4J version {} in '{}' mode", getVersion(), getRuntimeMode());
}
- private synchronized void firePluginStateEvent(PluginStateEvent event) {
- for (PluginStateListener listener : pluginStateListeners) {
- log.debug("Fire '{}' to '{}'", event, listener);
- listener.pluginStateChanged(event);
- }
- }
-
}
*/
package ro.fortsoft.pf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ro.fortsoft.pf4j.util.AndFileFilter;
+import ro.fortsoft.pf4j.util.DirectoryFileFilter;
import ro.fortsoft.pf4j.util.FileUtils;
+import ro.fortsoft.pf4j.util.HiddenFilter;
+import ro.fortsoft.pf4j.util.NameFileFilter;
+import ro.fortsoft.pf4j.util.NotFileFilter;
+import ro.fortsoft.pf4j.util.OrFileFilter;
+import ro.fortsoft.pf4j.util.Unzip;
+import ro.fortsoft.pf4j.util.ZipFileFilter;
import java.io.File;
import java.io.FileFilter;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.IOException;
+import java.nio.file.Path;
import java.util.List;
/**
* @author Decebal Suiu
- * @author Mário Franco
*/
-public class DefaultPluginRepository implements PluginRepository {
+public class DefaultPluginRepository extends BasePluginRepository {
- private final File directory;
- private final FileFilter filter;
+ private static final Logger log = LoggerFactory.getLogger(DefaultPluginRepository.class);
- public DefaultPluginRepository(File directory, FileFilter filter) {
- this.directory = directory;
- this.filter = filter;
+ public DefaultPluginRepository(Path pluginsRoot, boolean development) {
+ super(pluginsRoot);
+
+ AndFileFilter pluginsFilter = new AndFileFilter(new DirectoryFileFilter());
+ pluginsFilter.addFileFilter(new NotFileFilter(createHiddenPluginFilter(development)));
+ setFilter(pluginsFilter);
}
@Override
- public List<File> getPluginArchives() {
- File[] files = directory.listFiles(filter);
+ public List<Path> getPluginPaths() {
+ // expand plugins zip files
+ File[] pluginZips = pluginsRoot.toFile().listFiles(new ZipFileFilter());
+ if ((pluginZips != null) && pluginZips.length > 0) {
+ for (File pluginZip : pluginZips) {
+ try {
+ expandPluginZip(pluginZip);
+ } catch (IOException e) {
+ log.error("Cannot expand plugin zip '{}'", pluginZip);
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
- return (files != null) ? Arrays.asList(files) : Collections.<File>emptyList();
+ return super.getPluginPaths();
}
@Override
- public boolean deletePluginArchive(String pluginPath) {
- File[] files = directory.listFiles(filter);
- if (files != null) {
- File pluginArchive = null;
- // strip prepended "/" from the plugin path
- String dirName = pluginPath.substring(1);
- // find the zip file that matches the plugin path
- for (File archive : files) {
- String name = archive.getName().substring(0, archive.getName().lastIndexOf('.'));
- if (name.equals(dirName)) {
- pluginArchive = archive;
- break;
- }
- }
- if (pluginArchive != null && pluginArchive.exists()) {
- return FileUtils.delete(pluginArchive);
+ public boolean deletePluginPath(Path pluginPath) {
+ // TODO remove plugin zip ?
+ return super.deletePluginPath(pluginPath);
+ }
+
+ protected FileFilter createHiddenPluginFilter(boolean development) {
+ OrFileFilter hiddenPluginFilter = new OrFileFilter(new HiddenFilter());
+
+ if (development) {
+ hiddenPluginFilter.addFileFilter(new NameFileFilter("target"));
+ }
+
+ return hiddenPluginFilter;
+ }
+
+ /**
+ * Unzip a plugin zip file in a directory that has the same name as the zip file
+ * and it's relative to {@code pluginsRoot}.
+ * For example if the zip file is {@code my-plugin.zip} then the resulted directory
+ * is {@code my-plugin}.
+ *
+ * @param pluginZip
+ * @return
+ * @throws IOException
+ */
+ private File expandPluginZip(File pluginZip) throws IOException {
+ String fileName = pluginZip.getName();
+ long pluginZipDate = pluginZip.lastModified();
+ String pluginName = fileName.substring(0, fileName.length() - 4);
+ File pluginDirectory = pluginsRoot.resolve(pluginName).toFile();
+ // check if exists root or the '.zip' file is "newer" than root
+ if (!pluginDirectory.exists() || (pluginZipDate > pluginDirectory.lastModified())) {
+ log.debug("Expand plugin zip '{}' in '{}'", pluginZip, pluginDirectory);
+
+ // do not overwrite an old version, remove it
+ if (pluginDirectory.exists()) {
+ FileUtils.delete(pluginDirectory);
}
+
+ // create root for plugin
+ pluginDirectory.mkdirs();
+
+ // expand '.zip' file
+ Unzip unzip = new Unzip();
+ unzip.setSource(pluginZip);
+ unzip.setDestination(pluginDirectory);
+ unzip.extract();
}
- return false;
+ return pluginDirectory;
}
}
*/
package ro.fortsoft.pf4j;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.FileUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+
/**
- * The default implementation for PluginStatusProvider.
+ * The default implementation for {@link PluginStatusProvider}.
*
* @author Decebal Suiu
* @author Mário Franco
private static final Logger log = LoggerFactory.getLogger(DefaultPluginStatusProvider.class);
- private final File pluginsDirectory;
+ private final Path pluginsRoot;
+
+ private List<String> enabledPlugins;
+ private List<String> disabledPlugins;
- private List<String> enabledPlugins = new ArrayList<>();
- private List<String> disabledPlugins = new ArrayList<>();
+ public DefaultPluginStatusProvider(Path pluginsRoot) {
+ this.pluginsRoot = pluginsRoot;
- public DefaultPluginStatusProvider(File pluginsDirectory) {
- this.pluginsDirectory = pluginsDirectory;
initialize();
}
private void initialize() {
try {
// create a list with plugin identifiers that should be only accepted by this manager (whitelist from plugins/enabled.txt file)
- enabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "enabled.txt"), true);
+ enabledPlugins = FileUtils.readLines(pluginsRoot.resolve("enabled.txt").toFile(), true);
log.info("Enabled plugins: {}", enabledPlugins);
// create a list with plugin identifiers that should not be accepted by this manager (blacklist from plugins/disabled.txt file)
- disabledPlugins = FileUtils.readLines(new File(pluginsDirectory, "disabled.txt"), true);
+ disabledPlugins = FileUtils.readLines(pluginsRoot.resolve("disabled.txt").toFile(), true);
log.info("Disabled plugins: {}", disabledPlugins);
} catch (IOException e) {
log.error(e.getMessage(), e);
public boolean disablePlugin(String pluginId) {
if (disabledPlugins.add(pluginId)) {
try {
- FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
+ FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
} catch (IOException e) {
log.error("Failed to disable plugin {}", pluginId, e);
return false;
}
}
+
return true;
}
public boolean enablePlugin(String pluginId) {
try {
if (disabledPlugins.remove(pluginId)) {
- FileUtils.writeLines(disabledPlugins, new File(pluginsDirectory, "disabled.txt"));
+ FileUtils.writeLines(disabledPlugins, pluginsRoot.resolve("disabled.txt").toFile());
}
} catch (IOException e) {
log.error("Failed to enable plugin {}", pluginId, e);
return false;
}
+
return true;
}
List<String> pluginsId = dependenciesGraph.reverseTopologicalSort();
if (pluginsId == null) {
- throw new CyclicDependencyException("Cyclic dependencies !!!" + dependenciesGraph.toString());
+ throw new PluginException("Cyclic dependencies !!!" + dependenciesGraph.toString());
}
log.debug("Plugins order: {}", pluginsId);
package ro.fortsoft.pf4j;
/**
- * Overwrite classes directories to "target/classes" and lib directories to "target/lib".
+ * Overwrite classes directories to {@code target/classes} and lib directories to {@code target/lib}.
*
* @author Decebal Suiu
*/
public class DevelopmentPluginClasspath extends PluginClasspath {
- private static final String DEVELOPMENT_CLASSES_DIRECTORY = "target/classes";
- private static final String DEVELOPMENT_LIB_DIRECTORY = "target/lib";
-
public DevelopmentPluginClasspath() {
super();
- }
-
- @Override
- protected void addResources() {
- classesDirectories.add(DEVELOPMENT_CLASSES_DIRECTORY);
- libDirectories.add(DEVELOPMENT_LIB_DIRECTORY);
- }
+ addClassesDirectories("target/classes");
+ addLibDirectories("target/lib");
+ }
}
--- /dev/null
+/*
+ * 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;
+ }
+
+ }
+
+}
package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
+import java.nio.file.Path;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
*
* @author Decebal Suiu
*/
-public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
-
- private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
-
- private PluginClasspath pluginClasspath;
-
- public ManifestPluginDescriptorFinder(PluginClasspath pluginClasspath) {
- this.pluginClasspath = pluginClasspath;
- }
+public abstract class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
@Override
- public PluginDescriptor find(File pluginRepository) throws PluginException {
- Manifest manifest = readManifest(pluginRepository);
+ public PluginDescriptor find(Path pluginPath) throws PluginException {
+ Manifest manifest = readManifest(pluginPath);
PluginDescriptor pluginDescriptor = createPluginDescriptor(manifest);
validatePluginDescriptor(pluginDescriptor);
return pluginDescriptor;
}
- protected Manifest readManifest(File pluginRepository) throws PluginException {
- // TODO it's ok with first classes directory? Another idea is to specify in PluginClasspath the folder.
- String classes = pluginClasspath.getClassesDirectories().get(0);
- File manifestFile = new File(pluginRepository, classes + "/META-INF/MANIFEST.MF");
- log.debug("Lookup plugin descriptor in '{}'", manifestFile);
- if (!manifestFile.exists()) {
- throw new PluginException("Cannot find '" + manifestFile + "' file");
- }
-
- FileInputStream input = null;
- try {
- input = new FileInputStream(manifestFile);
- } catch (FileNotFoundException e) {
- // not happening
- }
-
- Manifest manifest = null;
- try {
- manifest = new Manifest(input);
- } catch (IOException e) {
- throw new PluginException(e.getMessage(), e);
- } finally {
- try {
- input.close();
- } catch (IOException e) {
- throw new PluginException(e.getMessage(), e);
- }
- }
-
- return manifest;
- }
+ public abstract Manifest readManifest(Path pluginPath) throws PluginException;
protected PluginDescriptor createPluginDescriptor(Manifest manifest) {
PluginDescriptor pluginDescriptor = createPluginDescriptorInstance();
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* One instance of this class should be created by plugin manager for every available plug-in.
- * This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars before delegating
- * to the parent class loader.
+ * This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars
+ * before delegating to the parent class loader.
*
* @author Decebal Suiu
*/
@Override
public void addURL(URL url) {
+ log.debug("Add '{}'", url);
super.addURL(url);
}
+ public void addFile(File file) {
+ try {
+ addURL(file.getCanonicalFile().toURI().toURL());
+ } catch (IOException e) {
+// throw new RuntimeException(e);
+ log.error(e.getMessage(), e);
+ }
+ }
+
/**
- * This implementation of loadClass uses a child first delegation model rather than the standard parent first.
+ * Uses a child first delegation model rather than the standard parent first.
* If the requested class cannot be found in this class loader, the parent class loader will be consulted
- * via the standard ClassLoader.loadClass(String) mechanism.
+ * via the standard {@link ClassLoader#loadClass(String)} mechanism.
*/
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
return getClass().getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
// try next step
- // TODO if I uncomment below lines (the correct approach) I received ClassNotFoundException for demo (ro.fortsoft.pf4j.demo)
- // log.error(e.getMessage(), e);
- // throw e;
}
}
log.trace("Search in dependencies for class '{}'", className);
List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
for (PluginDependency dependency : dependencies) {
- PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
+ ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className);
- // use the standard URLClassLoader (which follows normal parent delegation)
+ // use the standard ClassLoader (which follows normal parent delegation)
return super.loadClass(className);
}
}
/**
- * Load the named resource from this plugin. This implementation checks the plugin's classpath first
- * then delegates to the parent.
+ * Load the named resource from this plugin.
+ * This implementation checks the plugin's classpath first then delegates to the parent.
*
* @param name the name of the resource.
* @return the URL to the resource, <code>null</code> if the resource was not found.
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);
- }
- }
-
}
package ro.fortsoft.pf4j;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
- * The classpath of the plugin after it was unpacked.
- * It contains classes directories and lib directories (directories that contains jars).
- * All directories are relative to plugin repository.
- * The default values are "classes" and "lib".
+ * The classpath of the plugin.
+ * It contains {@code classes} directories and {@code lib} directories (directories that contains jars).
*
* @author Decebal Suiu
*/
public class PluginClasspath {
- private static final String DEFAULT_CLASSES_DIRECTORY = "classes";
- private static final String DEFAULT_LIB_DIRECTORY = "lib";
-
- protected List<String> classesDirectories;
- protected List<String> libDirectories;
+ private List<String> classesDirectories;
+ private List<String> libDirectories;
public PluginClasspath() {
classesDirectories = new ArrayList<>();
libDirectories = new ArrayList<>();
-
- addResources();
}
public List<String> getClassesDirectories() {
return classesDirectories;
}
- public void setClassesDirectories(List<String> classesDirectories) {
- this.classesDirectories = classesDirectories;
+ public void addClassesDirectories(String... classesDirectories) {
+ this.classesDirectories.addAll(Arrays.asList(classesDirectories));
}
public List<String> getLibDirectories() {
return libDirectories;
}
- public void setLibDirectories(List<String> libDirectories) {
- this.libDirectories = libDirectories;
- }
-
- protected void addResources() {
- classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
- libDirectories.add(DEFAULT_LIB_DIRECTORY);
+ public void addLibDirectories(String... libDirectories) {
+ this.libDirectories.addAll(Arrays.asList(libDirectories));
}
}
*/
package ro.fortsoft.pf4j;
-import java.io.File;
+import java.nio.file.Path;
/**
- * Find a plugin descriptor in a directory (plugin repository).
- * You can find in manifest file @see DefaultPluginDescriptorFinder,
- * xml file, properties file, java services (with ServiceLoader), etc.
+ * Find a plugin descriptor for a plugin path.
+ * You can find in manifest file {@link DefaultPluginDescriptorFinder},
+ * xml file, properties file, java services (with {@link java.util.ServiceLoader}), etc.
*
* @author Decebal Suiu
*/
public interface PluginDescriptorFinder {
- PluginDescriptor find(File pluginRepository) throws PluginException;
+ PluginDescriptor find(Path pluginPath) throws PluginException;
}
*/
package ro.fortsoft.pf4j;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import ro.fortsoft.pf4j.util.DirectoryFileFilter;
-import ro.fortsoft.pf4j.util.JarFileFilter;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.net.MalformedURLException;
-import java.util.List;
-import java.util.Vector;
+import java.nio.file.Path;
/**
- * Load all information needed by a plugin.
- * This means add all jar files from 'lib' directory, 'classes' to classpath.
- * It's a class for only the internal use.
+ * Load all information (classes) needed by a plugin.
*
* @author Decebal Suiu
*/
-class PluginLoader {
-
- private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
-
- /*
- * The plugin repository.
- */
- private File pluginRepository;
-
- private PluginClasspath pluginClasspath;
- private PluginClassLoader pluginClassLoader;
-
- public PluginLoader(File pluginRepository, PluginClassLoader pluginClassLoader, PluginClasspath pluginClasspath) {
- this.pluginRepository = pluginRepository;
- this.pluginClassLoader = pluginClassLoader;
- this.pluginClasspath = pluginClasspath;
- }
-
- public boolean load() {
- return loadClassesAndJars();
- }
-
- private boolean loadClassesAndJars() {
- return loadClasses() && loadJars();
- }
-
- private boolean loadClasses() {
- List<String> classesDirectories = pluginClasspath.getClassesDirectories();
-
- // add each classes directory to plugin class loader
- for (String classesDirectory : classesDirectories) {
- // make 'classesDirectory' absolute
- File file = new File(pluginRepository, classesDirectory).getAbsoluteFile();
-
- if (file.exists() && file.isDirectory()) {
- log.debug("Found '{}' directory", file.getPath());
-
- try {
- pluginClassLoader.addURL(file.toURI().toURL());
- log.debug("Added '{}' to the class loader path", file);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- log.error(e.getMessage(), e);
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Add all *.jar files from lib directories to class loader.
- */
- private boolean loadJars() {
- List<String> libDirectories = pluginClasspath.getLibDirectories();
-
- // add each jars directory to plugin class loader
- for (String libDirectory : libDirectories) {
- // make 'libDirectory' absolute
- File file = new File(pluginRepository, libDirectory).getAbsoluteFile();
-
- // collect all jars from current lib directory in jars variable
- Vector<File> jars = new Vector<>();
- getJars(jars, file);
- for (File jar : jars) {
- try {
- pluginClassLoader.addURL(jar.toURI().toURL());
- log.debug("Added '{}' to the class loader path", jar);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- log.error(e.getMessage(), e);
- return false;
- }
- }
- }
-
- return true;
- }
-
- private void getJars(Vector<File> bucket, File file) {
- FileFilter jarFilter = new JarFileFilter();
- FileFilter directoryFilter = new DirectoryFileFilter();
-
- if (file.exists() && file.isDirectory() && file.isAbsolute()) {
- File[] jars = file.listFiles(jarFilter);
- for (int i = 0; (jars != null) && (i < jars.length); ++i) {
- bucket.addElement(jars[i]);
- }
+public interface PluginLoader {
- File[] directories = file.listFiles(directoryFilter);
- for (int i = 0; (directories != null) && (i < directories.length); ++i) {
- File directory = directories[i];
- getJars(bucket, directory);
- }
- }
- }
+ ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor);
}
package ro.fortsoft.pf4j;
import com.github.zafarkhaja.semver.Version;
-import java.io.File;
+
+import java.nio.file.Path;
import java.util.List;
import java.util.Set;
/**
* Load a plugin.
*
- * @param pluginArchiveFile
+ * @param pluginPath
* @return the pluginId of the installed plugin or null
*/
- String loadPlugin(File pluginArchiveFile);
+ String loadPlugin(Path pluginPath);
/**
* Start all active plugins.
*/
boolean deletePlugin(String pluginId);
- PluginClassLoader getPluginClassLoader(String pluginId);
+ ClassLoader getPluginClassLoader(String pluginId);
<T> List<T> getExtensions(Class<T> type);
*/
package ro.fortsoft.pf4j;
-import java.io.File;
+import java.nio.file.Path;
import java.util.List;
/**
- * Directory whose contents are .zip files used as plugins.
+ * Directory that contains plugins. A plugin could be a zip file.
*
* @author Decebal Suiu
* @author Mário Franco
public interface PluginRepository {
/**
- * List all plugin archive filed.
+ * List all plugin paths.
*
* @return a list of files
*/
- List<File> getPluginArchives();
+ List<Path> getPluginPaths();
/**
* Removes a plugin from the repository.
* @param pluginPath the plugin path
* @return true if deleted
*/
- boolean deletePluginArchive(String pluginPath);
+ boolean deletePluginPath(Path pluginPath);
}
*/
package ro.fortsoft.pf4j;
+import java.nio.file.Path;
+
/**
* A wrapper over plugin instance.
*
*/
public class PluginWrapper {
- PluginManager pluginManager;
- PluginDescriptor descriptor;
- String pluginPath;
- PluginClassLoader pluginClassLoader;
- PluginFactory pluginFactory;
- PluginState pluginState;
- RuntimeMode runtimeMode;
+ private PluginManager pluginManager;
+ private PluginDescriptor descriptor;
+ private Path pluginPath;
+ private ClassLoader pluginClassLoader;
+ private PluginFactory pluginFactory;
+ private PluginState pluginState;
+ private RuntimeMode runtimeMode;
+
Plugin plugin; // cache
- public PluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor, String pluginPath, PluginClassLoader pluginClassLoader) {
+ public PluginWrapper(PluginManager pluginManager, PluginDescriptor descriptor, Path pluginPath, ClassLoader pluginClassLoader) {
this.pluginManager = pluginManager;
this.descriptor = descriptor;
this.pluginPath = pluginPath;
}
/**
- * Returns the path of this plugin relative to plugins directory.
+ * Returns the path of this plugin.
*/
- public String getPluginPath() {
+ public Path getPluginPath() {
return pluginPath;
}
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Properties;
/**
}
@Override
- public PluginDescriptor find(File pluginRepository) throws PluginException {
- Properties properties = readProperties(pluginRepository);
+ public PluginDescriptor find(Path pluginPath) throws PluginException {
+ Properties properties = readProperties(pluginPath);
PluginDescriptor pluginDescriptor = createPluginDescriptor(properties);
validatePluginDescriptor(pluginDescriptor);
return pluginDescriptor;
}
- protected Properties readProperties(File pluginRepository) throws PluginException {
- File propertiesFile = new File(pluginRepository, propertiesFileName);
- log.debug("Lookup plugin descriptor in '{}'", propertiesFile);
- if (!propertiesFile.exists()) {
- throw new PluginException("Cannot find '" + propertiesFile + "' file");
- }
-
- InputStream input = null;
- try {
- input = new FileInputStream(propertiesFile);
- } catch (FileNotFoundException e) {
- // not happening
+ protected Properties readProperties(Path pluginPath) throws PluginException {
+ Path propertiesPath = pluginPath.resolve(Paths.get(propertiesFileName));
+ log.debug("Lookup plugin descriptor in '{}'", propertiesPath);
+ if (Files.notExists(propertiesPath)) {
+ throw new PluginException("Cannot find '" + pluginPath + "' path");
}
Properties properties = new Properties();
- try {
+ try (InputStream input = Files.newInputStream(propertiesPath)) {
properties.load(input);
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
- } finally {
- try {
- input.close();
- } catch (IOException e) {
- throw new PluginException(e.getMessage(), e);
- }
}
return properties;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileFilter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
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);
+ }
+ }
+ }
+
}
import org.slf4j.LoggerFactory;
/**
- * This class extracts the containt of the plugin archive into a directory.
+ * This class extracts the content of the plugin zip into a directory.
* It's a class for only the internal use.
*
* @author Decebal Suiu
removeDirectory(destination);
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(source));
- ZipEntry zipEntry = null;
+ ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
try {
File file = new File(destination, zipEntry.getName());
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import ro.fortsoft.pf4j.util.ZipFileFilter;
-import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* @author Mario Franco
+ * @author Decebal Suiu
*/
public class DefaultPluginRepositoryTest {
@Before
public void setUp() throws IOException {
- testFolder.newFile("plugin-1.zip");
- testFolder.newFile("plugin-2.zip");
- testFolder.newFile("plugin-3.zi_");
+ testFolder.newFolder("plugin-1");
+ testFolder.newFolder("plugin-2");
+ testFolder.newFolder("plugin-3");
}
/**
- * Test of getPluginArchives method, of class DefaultPluginRepository.
+ * Test of {@link DefaultPluginRepository#getPluginPaths()} method.
*/
@Test
public void testGetPluginArchives() {
- DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter());
+ Path pluginsRoot = getPluginsRoot();
- List<File> result = instance.getPluginArchives();
+ PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
- assertEquals(2, result.size());
- assertFileExists(result, "plugin-1.zip");
- assertFileExists(result, "plugin-2.zip");
+ List<Path> result = instance.getPluginPaths();
+
+ assertEquals(3, result.size());
+ assertPathExists(result, pluginsRoot.resolve("plugin-1"));
+ assertPathExists(result, pluginsRoot.resolve("plugin-2"));
+ assertPathExists(result, pluginsRoot.resolve("plugin-3"));
}
/**
- * Test of deletePluginArchive method, of class DefaultPluginRepository.
+ * Test of {@link DefaultPluginRepository#deletePluginPath(Path)} method.
*/
@Test
- public void testDeletePluginArchive() {
- DefaultPluginRepository instance = new DefaultPluginRepository(testFolder.getRoot(), new ZipFileFilter());
+ public void testDeletePluginPath() {
+ Path pluginsRoot = getPluginsRoot();
+
+ PluginRepository instance = new DefaultPluginRepository(pluginsRoot, false);
- assertTrue(instance.deletePluginArchive("/plugin-1"));
- assertFalse(instance.deletePluginArchive("/plugin-3"));
+ assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-1")));
+ assertTrue(instance.deletePluginPath(pluginsRoot.resolve("plugin-3")));
+ assertFalse(instance.deletePluginPath(pluginsRoot.resolve("plugin-4")));
- List<File> result = instance.getPluginArchives();
+ List<Path> result = instance.getPluginPaths();
assertEquals(1, result.size());
- assertEquals(result.get(0).getName(), "plugin-2.zip");
+ assertEquals(pluginsRoot.relativize(result.get(0)).toString(), "plugin-2");
}
- public static void assertFileExists(List<File> files, String file) {
- boolean found = false;
-
- for (File f : files) {
- if (f.getName().equals(file)) {
- found = true;
- break;
- }
- }
+ private void assertPathExists(List<Path> paths, Path path) {
+ assertTrue("The directory must contains the file " + path, paths.contains(path));
+ }
- assertTrue("The directory must contains the file " + file, found);
+ private Path getPluginsRoot() {
+ return testFolder.getRoot().toPath();
}
}
import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* @author Mario Franco
+ * @author Decebal Suiu
*/
public class DefaultPluginStatusProviderTest {
public void testIsPluginDisabled() throws IOException {
createEnabledFile();
createDisabledFile();
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-2"));
@Test
public void testIsPluginDisabledWithEnableEmpty() throws IOException {
createDisabledFile();
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-2"));
public void testDisablePlugin() throws IOException {
createEnabledFile();
createDisabledFile();
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.disablePlugin("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-1"));
@Test
public void testDisablePluginWithEnableEmpty() throws IOException {
createDisabledFile();
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.disablePlugin("plugin-1"));
assertTrue(instance.isPluginDisabled("plugin-1"));
@Test
public void testEnablePlugin() throws IOException {
createEnabledFile();
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.enablePlugin("plugin-2"));
assertFalse(instance.isPluginDisabled("plugin-1"));
*/
@Test
public void testEnablePluginWithEnableEmpty() {
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertTrue(instance.enablePlugin("plugin-2"));
assertFalse(instance.isPluginDisabled("plugin-1"));
*/
@Test
public void testDisablePluginWithoutDisabledFile() throws IOException {
- DefaultPluginStatusProvider instance = new DefaultPluginStatusProvider(testFolder.getRoot());
+ PluginStatusProvider instance = new DefaultPluginStatusProvider(getPluginsRoot());
assertFalse(instance.isPluginDisabled("plugin-1"));
assertTrue(instance.disablePlugin("plugin-1"));
FileUtils.writeLines(lines, file);
}
+ private Path getPluginsRoot() {
+ return testFolder.getRoot().toPath();
+ }
+
}
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* @author Mario Franco
+ * @author Decebal Suiu
*/
public class ManifestPluginDescriptorFinderTest {
public void setUp() throws IOException {
Charset charset = Charset.forName("UTF-8");
- File plugin = testFolder.newFolder("test-plugin-1", "classes", "META-INF");
- Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
- Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin1Manifest(), charset);
+ Path pluginPath = testFolder.newFolder("test-plugin-1", "classes", "META-INF").toPath();
+ Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+ Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin1Manifest(), charset);
- plugin = testFolder.newFolder("test-plugin-2", "classes", "META-INF");
- Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
- Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin2Manifest(), charset);
+ pluginPath = testFolder.newFolder("test-plugin-2", "classes", "META-INF").toPath();
+ Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+ Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin2Manifest(), charset);
- // Empty Plugin
+ // empty plugin
testFolder.newFolder("test-plugin-3");
- // No Plugin Class
- plugin = testFolder.newFolder("test-plugin-4", "classes", "META-INF");
- Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
- Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin4Manifest(), charset);
+ // no plugin class
+ pluginPath = testFolder.newFolder("test-plugin-4", "classes", "META-INF").toPath();
+ Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+ Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin4Manifest(), charset);
- // No Plugin Version
- plugin = testFolder.newFolder("test-plugin-5", "classes", "META-INF");
- Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
- Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin5Manifest(), charset);
+ // no plugin version
+ pluginPath = testFolder.newFolder("test-plugin-5", "classes", "META-INF").toPath();
+ Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+ Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin5Manifest(), charset);
- // No Plugin Id
- plugin = testFolder.newFolder("test-plugin-6", "classes", "META-INF");
- Files.write(Paths.get(plugin.getPath(), "extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
- Files.write(Paths.get(plugin.getPath(), "MANIFEST.MF"), getPlugin6Manifest(), charset);
+ // no plugin id
+ pluginPath = testFolder.newFolder("test-plugin-6", "classes", "META-INF").toPath();
+ Files.write(pluginPath.resolve("extensions.idx"), "ro.fortsoft.pf4j.demo.hello.HelloPlugin$HelloGreeting".getBytes());
+ Files.write(pluginPath.resolve("MANIFEST.MF"), getPlugin6Manifest(), charset);
}
/**
- * Test of find method, of class ManifestPluginDescriptorFinder.
+ * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test
public void testFind() throws Exception {
- DefaultPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
+ PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
- PluginDescriptor plugin1 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-1").toFile());
-
- PluginDescriptor plugin2 = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-2").toFile());
+ PluginDescriptor plugin1 = instance.find(getPluginsRoot().resolve("test-plugin-1"));
+ PluginDescriptor plugin2 = instance.find(getPluginsRoot().resolve("test-plugin-2"));
assertEquals("test-plugin-1", plugin1.getPluginId());
assertEquals("Test Plugin 1", plugin1.getPluginDescription());
}
/**
- * Test of find method, of class ManifestPluginDescriptorFinder.
+ * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindNotFound() throws Exception {
-
- ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
- PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-3").toFile());
+ PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+ instance.find(getPluginsRoot().resolve("test-plugin-3"));
}
/**
- * Test of find method, of class ManifestPluginDescriptorFinder.
+ * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindMissingPluginClass() throws Exception {
-
- ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
- PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-4").toFile());
+ PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+ instance.find(getPluginsRoot().resolve("test-plugin-4"));
}
/**
- * Test of find method, of class ManifestPluginDescriptorFinder.
+ * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindMissingPluginVersion() throws Exception {
-
- ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
- PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-5").toFile());
+ PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+ instance.find(getPluginsRoot().resolve("test-plugin-5"));
}
/**
- * Test of find method, of class ManifestPluginDescriptorFinder.
+ * Test of {@link DefaultPluginDescriptorFinder#find(Path)} method.
*/
@Test(expected = PluginException.class)
public void testFindMissingPluginId() throws Exception {
-
- ManifestPluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new PluginClasspath());
- PluginDescriptor result = instance.find(Paths.get(testFolder.getRoot().getPath(),"test-plugin-6").toFile());
+ PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath());
+ instance.find(getPluginsRoot().resolve("test-plugin-6"));
}
private List<String> getPlugin1Manifest() {
-
- String[] lines = new String[]{"Manifest-Version: 1.0\n"
+ String[] lines = new String[] {
+ "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #1\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
- + ""};
+ + ""
+ };
return Arrays.asList(lines);
}
private List<String> getPlugin2Manifest() {
-
- String[] lines = new String[]{"Manifest-Version: 1.0\n"
+ String[] lines = new String[] {
+ "Manifest-Version: 1.0\n"
+ "Plugin-Dependencies: \n"
+ "Implementation-Title: Test Plugin #2\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
- + ""};
+ + ""
+ };
return Arrays.asList(lines);
}
private List<String> getPlugin4Manifest() {
-
- String[] lines = new String[]{"Manifest-Version: 1.0\n"
+ String[] lines = new String[] {
+ "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #4\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
- + ""};
+ + ""
+ };
return Arrays.asList(lines);
}
private List<String> getPlugin5Manifest() {
-
- String[] lines = new String[]{"Manifest-Version: 1.0\n"
+ String[] lines = new String[] {
+ "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #5\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
- + ""};
+ + ""
+ };
return Arrays.asList(lines);
}
private List<String> getPlugin6Manifest() {
-
- String[] lines = new String[]{"Manifest-Version: 1.0\n"
+ String[] lines = new String[] {
+ "Manifest-Version: 1.0\n"
+ "Implementation-Title: Test Plugin #6\n"
+ "Implementation-Version: 0.10.0-SNAPSHOT\n"
+ "Archiver-Version: Plexus Archiver\n"
+ "Build-Jdk: 1.8.0_45\n"
+ "Specification-Version: 0.10.0-SNAPSHOT\n"
+ "\n"
- + ""};
+ + ""
+ };
return Arrays.asList(lines);
}
+
+ private Path getPluginsRoot() {
+ return testFolder.getRoot().toPath();
+ }
+
}
--- /dev/null
+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