*/
package org.pf4j;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
/**
+ * An extension is a class that extends an extension point.
+ * Use this annotation to mark a class as an extension.
+ * The extension class must implement the extension point interface or extend the extension point abstract class.
+ *
* @author Decebal Suiu
*/
@Retention(RUNTIME)
@Documented
public @interface Extension {
+ /**
+ * The order of the extension.
+ * The ordinal is used to sort the extensions.
+ *
+ * @return the order of the extension
+ */
int ordinal() default 0;
/**
package org.pf4j;
/**
+ * Describes an extension.
+ * The extension is described by the class and the ordinal (the order of the extension).
+
+ *
* @author Decebal Suiu
*/
public class ExtensionDescriptor {
*/
public interface ExtensionFactory {
+ /**
+ * Creates an extension instance.
+ *
+ * @param extensionClass the extension class
+ * @return the extension instance
+ */
<T> T create(Class<T> extensionClass);
}
import java.util.Set;
/**
+ * Provides the functionality for finding extensions.
+ *
* @author Decebal Suiu
*/
public interface ExtensionFinder {
<T> List<ExtensionWrapper<T>> find(Class<T> type, String pluginId);
/**
- * Retrieves a list with all extensions found for a plugin
+ * Retrieves a list with all extensions found for a plugin.
*/
List<ExtensionWrapper> find(String pluginId);
/**
* An extension point is a formal declaration in a plugin (or in application API) where customization is allowed.
+ * It's a place where custom code can be "plugged in".
+ * <p>
+ * An extension point is defined by an interface or an abstract class.
+ * The extension point is used by the application to discover and use the custom implementations.
*
* @author Decebal Suiu
*/
/**
* A wrapper over extension instance.
+ * It contains the extension descriptor and the extension instance.
*
* @author Decebal Suiu
*/
/**
* This class will be extended by all plugins and
* serve as the common class between a plugin and the application.
+ * <p>
+ * Create (it's optional) a Plugin class if you are interested in plugin's lifecycle events (start, stop, ...)
+ * or you want to pass some context to the plugin.
*
* @author Decebal Suiu
*/
import java.nio.file.Path;
/**
+ * Thrown when a plugin is already loaded.
+ *
* @author Decebal Suiu
*/
public class PluginAlreadyLoadedException extends PluginRuntimeException {
private final Path pluginPath;
public PluginAlreadyLoadedException(String pluginId, Path pluginPath) {
- super("Plugin '{}' already loaded with id '{}'", pluginPath, pluginId);
+ super("Plugin '{}' already loaded with id '{}'", pluginPath, pluginId);
this.pluginId = pluginId;
this.pluginPath = pluginPath;
import java.util.Objects;
/**
- * One instance of this class should be created by plugin manager for every available plug-in.
- * By default, this class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars
+ * One instance of this class should be created for every available plug-in.
+ * It's responsible for loading classes and resources from the plug-in.
+ * <p>
+ * By default, this {@link ClassLoader} is a Parent Last ClassLoader - it loads the classes from the plugin's jars
* before delegating to the parent class loader.
* Use {@link #classLoadingStrategy} to change the loading strategy.
*
private static final String JAVA_PACKAGE_PREFIX = "java.";
private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j.";
- private PluginManager pluginManager;
- private PluginDescriptor pluginDescriptor;
- private ClassLoadingStrategy classLoadingStrategy;
+ private final PluginManager pluginManager;
+ private final PluginDescriptor pluginDescriptor;
+ private final ClassLoadingStrategy classLoadingStrategy;
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) {
this(pluginManager, pluginDescriptor, parent, ClassLoadingStrategy.PDA);
}
/**
+ * Creates a new {@link PluginClassLoader} for the given plugin using parent first strategy.
+ *
* @deprecated Replaced by {@link #PluginClassLoader(PluginManager, PluginDescriptor, ClassLoader, ClassLoadingStrategy)}.
* If {@code parentFirst} is {@code true}, indicates that the parent {@link ClassLoader} should be consulted
- * before trying to load the a class through this loader.
+ * before trying to load a class through this loader.
*/
@Deprecated
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, boolean parentFirst) {
}
/**
- * classloading according to {@code classLoadingStrategy}
+ * Creates a new {@link PluginClassLoader} for the given plugin using the specified class loading strategy.
+ *
+ * @param pluginManager the plugin manager
+ * @param pluginDescriptor the plugin descriptor
+ * @param parent the parent class loader
+ * @param classLoadingStrategy the strategy to use for loading classes and resources
*/
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, ClassLoadingStrategy classLoadingStrategy) {
super(new URL[0], parent);
this.classLoadingStrategy = classLoadingStrategy;
}
+ /**
+ * Adds the specified URL to the search path for classes and resources.
+ *
+ * @param url the URL to be added to the search path of URLs
+ */
@Override
public void addURL(URL url) {
log.debug("Add '{}'", url);
super.addURL(url);
}
+ /**
+ * Adds the specified file to the search path for classes and resources.
+ *
+ * @param file the file to be added to the search path of URLs
+ */
public void addFile(File file) {
try {
addURL(file.getCanonicalFile().toURI().toURL());
}
/**
+ * Loads the class with the specified name.
+ * <p>
* By default, it 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 {@link ClassLoader#loadClass(String)} mechanism.
* Use {@link #classLoadingStrategy} to change the loading strategy.
+ *
+ * @param className the name of the class
+ * @return the loaded class
*/
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
}
/**
- * Load the named resource from this plugin.
+ * Loads the named resource from this plugin.
+ * <p>
* By default, this implementation checks the plugin's classpath first then delegates to the parent.
* Use {@link #classLoadingStrategy} to change the loading strategy.
*
return null;
}
+
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> resources = new ArrayList<>();
return Collections.enumeration(resources);
}
+ /**
+ * Loads the class with the specified name from the dependencies of the plugin.
+ *
+ * @param className the name of the class
+ * @return the loaded class
+ */
protected Class<?> loadClassFromDependencies(String className) {
log.trace("Search in dependencies for class '{}'", className);
List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
return null;
}
+ /**
+ * Finds the resource with the given name in the dependencies of the plugin.
+ *
+ * @param name the name of the resource
+ * @return the URL to the resource, {@code null} if the resource was not found
+ */
protected URL findResourceFromDependencies(String name) {
log.trace("Search in dependencies for resource '{}'", name);
List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
return null;
}
+ /**
+ * Finds all resources with the given name in the dependencies of the plugin.
+ *
+ * @param name the name of the resource
+ * @return an enumeration of {@link URL} objects for the resource
+ * @throws IOException if I/O errors occur
+ */
protected Collection<URL> findResourcesFromDependencies(String name) throws IOException {
log.trace("Search in dependencies for resources '{}'", name);
List<URL> results = new ArrayList<>();
/**
* The classpath of the plugin.
+ * <p>
* It contains {@code classes} directories (directories that contain classes files)
* and {@code jars} directories (directories that contain jars files).
+ * <p>
+ * The classpath is used to create the {@link ClassLoader} for the plugin.
*
* @author Decebal Suiu
*/
public class PluginClasspath {
- private Set<String> classesDirectories = new HashSet<>();
- private Set<String> jarsDirectories = new HashSet<>();
+ private final Set<String> classesDirectories = new HashSet<>();
+ private final Set<String> jarsDirectories = new HashSet<>();
+ /**
+ * Get the classes directories.
+ *
+ * @return a set of directories that contain classes files
+ */
public Set<String> getClassesDirectories() {
return classesDirectories;
}
+ /**
+ * Add classes directories.
+ *
+ * @param classesDirectories a set of directories that contain classes files
+ * @return this object for chaining
+ */
public PluginClasspath addClassesDirectories(String... classesDirectories) {
return addClassesDirectories(Arrays.asList(classesDirectories));
}
+ /**
+ * Add classes directories.
+ *
+ * @param classesDirectories a collection of directories that contain classes files
+ * @return this object for chaining
+ */
public PluginClasspath addClassesDirectories(Collection<String> classesDirectories) {
this.classesDirectories.addAll(classesDirectories);
return this;
}
+ /**
+ * Get the jars directories.
+ *
+ * @return a set of directories that contain jars files
+ */
public Set<String> getJarsDirectories() {
return jarsDirectories;
}
+ /**
+ * Add jars directories.
+ *
+ * @param jarsDirectories a set of directories that contain jars files
+ * @return this object for chaining
+ */
public PluginClasspath addJarsDirectories(String... jarsDirectories) {
return addJarsDirectories(Arrays.asList(jarsDirectories));
}
+ /**
+ * Add jars directories.
+ *
+ * @param jarsDirectories a collection of directories that contain jars files
+ * @return this object for chaining
+ */
public PluginClasspath addJarsDirectories(Collection<String> jarsDirectories) {
this.jarsDirectories.addAll(jarsDirectories);
public int hashCode() {
return Objects.hash(classesDirectories, jarsDirectories);
}
+
}
import java.util.Objects;
/**
+ * A plugin dependency is a dependency that the plugin has on another plugin.
+ * <p>
+ * The dependency is defined by the plugin id and the version of the plugin that is required.
+ * <p>
+ * A dependency is considered as optional, if the plugin id ends with a question mark.
+ * For example, the plugin id "my-plugin?" is considered as optional.
+ * <p>
+ * The plugin id and the version are separated by the '@' character.
+ * For example, the dependency "my-plugin@1.0.0" means that the plugin "my-plugin" with version "1.0.0" is required.
+ * If the version is not specified, then the plugin is required with any version.
+ * For example, the dependency "my-plugin" means that the plugin "my-plugin" with any version is required.
+ *
+ * @see VersionManager
* @author Decebal Suiu
*/
public class PluginDependency {
private String pluginId;
private String pluginVersionSupport = "*";
- private boolean optional;
+ private final boolean optional;
public PluginDependency(String dependency) {
int index = dependency.indexOf('@');
}
}
+ /**
+ * Returns the unique identifier of the plugin.
+ *
+ * @return the plugin id
+ */
public String getPluginId() {
return pluginId;
}
+ /**
+ * Returns the version of the plugin that is required.
+ *
+ * @return the version of the plugin that is required
+ */
public String getPluginVersionSupport() {
return pluginVersionSupport;
}
+ /**
+ * Returns {@code true} if the dependency is optional, {@code false} otherwise.
+ *
+ * @return {@code true} if the dependency is optional, {@code false} otherwise
+ */
public boolean isOptional() {
return optional;
}
public int hashCode() {
return Objects.hash(pluginId, pluginVersionSupport, optional);
}
+
}
*/
public interface PluginDescriptor {
+ /**
+ * The unique identifier of the plugin.
+ *
+ * @return the plugin id
+ */
String getPluginId();
+ /**
+ * Returns a description of the plugin.
+ *
+ * @return the plugin description
+ */
String getPluginDescription();
+ /**
+ * Returns the fully qualified class name of the plugin class.
+ * The plugin class must implement the {@link Plugin} interface.
+ *
+ * @return the plugin class
+ */
String getPluginClass();
+ /**
+ * Returns the plugin version.
+ * The version must be unique for each release of the plugin.
+ * The version is used to check if the plugin is compatible with the application.
+ *
+ * @see VersionManager
+ * @return the plugin version
+ */
String getVersion();
+ /**
+ * Returns the required version of the application.
+ *
+ * @return the required version of the application
+ */
String getRequires();
+ /**
+ * Returns the author of the plugin.
+ *
+ * @return the author of the plugin
+ */
String getProvider();
+ /**
+ * Returns the license of the plugin.
+ *
+ * @return the license of the plugin
+ */
String getLicense();
+ /**
+ * Returns the dependencies of the plugin.
+ * A dependency is represented by a {@link PluginDependency} object.
+ *
+ * @return the dependencies of the plugin
+ */
List<PluginDependency> getDependencies();
}
/**
* Find a plugin descriptor for a plugin path.
+ * <p>
* You can find the plugin descriptor in manifest file {@link ManifestPluginDescriptorFinder},
* properties file {@link PropertiesPluginDescriptorFinder}, xml file,
* java services (with {@link java.util.ServiceLoader}), etc.
public interface PluginDescriptorFinder {
/**
- * Returns true if this finder is applicable to the given {@link Path}.
+ * Returns {@code true} if this finder is applicable to the given {@code pluginPath}.
+ * This is used to select the appropriate finder for a given plugin path.
+ *
+ * @param pluginPath the plugin path
*/
boolean isApplicable(Path pluginPath);
+ /**
+ * Find the plugin descriptor for the given {@code pluginPath}.
+ *
+ * @param pluginPath the plugin path
+ * @return the plugin descriptor or {@code null} if not found
+ */
PluginDescriptor find(Path pluginPath);
}
package org.pf4j;
/**
- * Creates a plugin instance.
+ * It's responsible for creating a plugin instance.
*/
public interface PluginFactory {
+ /**
+ * Create a plugin instance.
+ *
+ * @param pluginWrapper the plugin wrapper
+ * @return a plugin instance
+ */
Plugin create(PluginWrapper pluginWrapper);
}
/**
* Load all information (classes) needed by a plugin.
+ * <p>
+ * The plugin loader is responsible for creating a class loader for a plugin
+ * and loading all classes/resources needed by the plugin.
*
* @author Decebal Suiu
*/
public interface PluginLoader {
/**
- * Returns true if this loader is applicable to the given {@link Path}.
+ * Returns {@code true} if this loader is applicable to the given plugin path.
+ * This is used to select the appropriate loader for a given plugin path.
*
- * @param pluginPath
- * @return
+ * @param pluginPath the plugin path
+ * @return true if this loader is applicable to the given {@link Path}
*/
boolean isApplicable(Path pluginPath);
+ /**
+ * Load all information (classes) needed by a plugin.
+ *
+ * @param pluginPath the plugin path
+ * @param pluginDescriptor the plugin descriptor
+ * @return the class loader for the plugin
+ */
ClassLoader loadPlugin(Path pluginPath, PluginDescriptor pluginDescriptor);
}
*/
boolean deletePlugin(String pluginId);
+ /**
+ * Retrieves the class loader for the specified plugin.
+ *
+ * @param pluginId the unique plugin identifier, specified in its metadata
+ * @return the class loader for the plugin
+ */
ClassLoader getPluginClassLoader(String pluginId);
List<Class<?>> getExtensionClasses(String pluginId);
<T> List<T> getExtensions(Class<T> type, String pluginId);
+ /**
+ * Retrieves the extensions for the specified plugin.
+ *
+ * @param pluginId the unique plugin identifier, specified in its metadata
+ * @return the extensions for the plugin
+ */
List getExtensions(String pluginId);
Set<String> getExtensionClassNames(String pluginId);
RuntimeMode getRuntimeMode();
/**
- * Returns {@code true} if the runtime mode is {@code RuntimeMode.DEVELOPMENT}.
+ * Returns {@code true} if the runtime mode is {@link RuntimeMode#DEVELOPMENT}.
*/
default boolean isDevelopment() {
return RuntimeMode.DEVELOPMENT.equals(getRuntimeMode());
}
/**
- * Returns {@code true} if the runtime mode is not {@code RuntimeMode.DEVELOPMENT}.
+ * Returns {@code true} if the runtime mode is not {@link RuntimeMode#DEVELOPMENT}.
*/
default boolean isNotDevelopment() {
return !isDevelopment();
* disables all version checking.
*
* @default 0.0.0
- * @param version
+ * @param version the system version
*/
void setSystemVersion(String version);
Path getPluginsRoot();
/**
- * Gets the a read-only list of all paths of the folders where plugins are installed.
+ * Gets a read-only list of all paths of the folders where plugins are installed.
*
* @return Paths of plugins roots
*/
import java.util.List;
/**
- * Directory that contains plugins. A plugin could be a {@code directory}, @code zip} or {@code jar} file.
+ * Directory that contains plugins. A plugin could be a {@code directory}, {@code zip} or {@code jar} file.
*
* @author Decebal Suiu
* @author Mário Franco
package org.pf4j;
/**
+ * The state of a plugin.
+ * <p>
+ * Lifecycle of a plugin:
+ * <pre>
+ * CREATED -> RESOLVED -> STARTED -> STOPPED
+ * CREATED -> DISABLED
+ * CREATED -> FAILED
+ *
* @author Decebal Suiu
*/
public enum PluginState {
*/
FAILED("FAILED");
- private String status;
+ private final String status;
- private PluginState(String status) {
+ PluginState(String status) {
this.status = status;
}
public boolean equals(String status) {
- return (status == null ? false : this.status.equalsIgnoreCase(status));
+ return (this.status.equalsIgnoreCase(status));
}
@Override
return status;
}
+ /**
+ * Parse a string to a {@link PluginState}.
+ *
+ * @param string the string to parse
+ * @return the {@link PluginState} or null if the string is not a valid state
+ */
public static PluginState parse(String string) {
- for (PluginState status : values()) {
- if (status.equals(string)) {
- return status;
- }
- }
+ for (PluginState status : values()) {
+ if (status.equals(string)) {
+ return status;
+ }
+ }
+
+ return null;
+ }
- return null;
- }
}
import java.util.EventObject;
/**
+ * Event object that indicates a change in the state of a plugin.
+ * The event is propagated to all registered listeners.
+ * The event source is the {@link PluginManager} that changed the state of the plugin.
+ * The event object contains the plugin that changed its state and the old state.
+ *
+ * @see PluginStateListener
* @author Decebal Suiu
*/
public class PluginStateEvent extends EventObject {
this.oldState = oldState;
}
+ /**
+ * The object on which the Event initially occurred.
+ *
+ * @return the PluginManager that changed the state of the plugin
+ */
@Override
public PluginManager getSource() {
return (PluginManager) super.getSource();
}
+ /**
+ * The plugin that changed its state.
+ *
+ * @return the plugin that changed its state
+ */
public PluginWrapper getPlugin() {
return plugin;
}
+ /**
+ * The new state of the plugin.
+ *
+ * @return the new state of the plugin
+ */
public PluginState getPluginState() {
return plugin.getPluginState();
}
+ /**
+ * The old state of the plugin.
+ *
+ * @return the old state of the plugin
+ */
public PluginState getOldState() {
return oldState;
}
import java.util.EventListener;
/**
- * PluginStateListener defines the interface for an object that listens to plugin state changes.
+ * Defines the interface for an object that listens to plugin state changes.
+ * <p>
+ * The class that is interested in processing a plugin state event implements this interface.
*
+ * @see PluginStateEvent
* @author Decebal Suiu
*/
public interface PluginStateListener extends EventListener {
/**
- * Invoked when a plugin's state (for example DISABLED, STARTED) is changed.
+ * Invoked when a plugin's state (for example {@link PluginState#DISABLED}, {@link PluginState#STARTED}) is changed.
+ *
+ * @param event the plugin state event
*/
void pluginStateChanged(PluginStateEvent event);
package org.pf4j;
/**
+ * Provides a way to check if a plugin is disabled and to disable/enable a plugin.
+ * <p>
+ * This is useful when you want to store the plugin status in a database or in a file.
+ *
+ * @see PluginState#DISABLED
+ *
* @author Decebal Suiu
* @author Mário Franco
*/
*/
public class PluginWrapper {
- private PluginManager pluginManager;
- private PluginDescriptor descriptor;
- private Path pluginPath;
- private ClassLoader pluginClassLoader;
+ private final PluginManager pluginManager;
+ private final PluginDescriptor descriptor;
+ private final Path pluginPath;
+ private final ClassLoader pluginClassLoader;
private PluginFactory pluginFactory;
private PluginState pluginState;
- private RuntimeMode runtimeMode;
+ private final RuntimeMode runtimeMode;
private Throwable failedException;
/**
* Returns the plugin manager.
+ *
+ * @return the plugin manager
*/
public PluginManager getPluginManager() {
return pluginManager;
/**
* Returns the plugin descriptor.
+ *
+ * @return the plugin descriptor
*/
public PluginDescriptor getDescriptor() {
return descriptor;
/**
* Returns the path of this plugin.
+ *
+ * @return the path of this plugin
*/
public Path getPluginPath() {
return pluginPath;
}
/**
- * Returns the plugin class loader used to load classes and resources
- * for this plug-in. The class loader can be used to directly access
- * plug-in resources and classes.
+ * Returns the plugin {@link ClassLoader} used to load classes and resources for this plug-in.
+ * <p>
+ * The class loader can be used to directly access plug-in resources and classes.
+ *
+ * @return the plugin class loader
*/
public ClassLoader getPluginClassLoader() {
return pluginClassLoader;
}
+ /**
+ * Returns the plugin instance.
+ *
+ * @return the plugin instance
+ */
public Plugin getPlugin() {
if (plugin == null) {
plugin = pluginFactory.create(this);
return plugin;
}
+ /**
+ * Returns the plugin factory.
+ *
+ * @return the plugin factory
+ */
public PluginState getPluginState() {
return pluginState;
}
+ /**
+ * Returns the runtime mode.
+ *
+ * @return the runtime mode
+ */
public RuntimeMode getRuntimeMode() {
return runtimeMode;
}
/**
- * Shortcut
+ * Shortcut for {@code getDescriptor().getPluginId()}.
*/
public String getPluginId() {
return getDescriptor().getPluginId();
return "PluginWrapper [descriptor=" + descriptor + ", pluginPath=" + pluginPath + "]";
}
+ /**
+ * Used internally by the framework to set the plugin factory.
+ *
+ * @param pluginState the plugin state
+ */
public void setPluginState(PluginState pluginState) {
this.pluginState = pluginState;
}
+ /**
+ * Used internally by the framework to set the plugin factory.
+ *
+ * @param pluginFactory the plugin factory
+ */
public void setPluginFactory(PluginFactory pluginFactory) {
this.pluginFactory = pluginFactory;
}
/**
* Returns the exception with which the plugin fails to start.
* See @{link PluginStatus#FAILED}.
+ *
+ * @return the exception with which the plugin fails to start
*/
public Throwable getFailedException() {
return failedException;
}
+ /**
+ * Used internally by the framework to set the exception with which the plugin fails to start.
+ *
+ * @param failedException the exception with which the plugin fails to start
+ */
public void setFailedException(Throwable failedException) {
this.failedException = failedException;
}
import java.util.NoSuchElementException;
/**
+ * The runtime mode of the PF4J application.
+ * <p>
+ * The runtime mode is used to determine the behavior of the application.
+ * For example, in development mode, the application may display detailed error messages,
+ * while in deployment mode, the application may display a generic error message.
+ *
* @author Decebal Suiu
*/
public enum RuntimeMode {
return name;
}
+ /**
+ * Returns the runtime mode with the specified name.
+ *
+ * @param name the name of the runtime mode
+ * @return the runtime mode with the specified name
+ * @throws NoSuchElementException if the runtime mode with the specified name is not found
+ */
public static RuntimeMode byName(String name) {
if (map.containsKey(name)) {
return map.get(name);
/**
* Manager responsible for versions of plugins.
+ * It's used to check if a version matches a constraint and to compare two versions.
*
* @author Decebal Suiu
*/
* Check if a {@code constraint} and a {@code version} match.
* A possible constrain can be {@code >=1.0.0 & <2.0.0}.
*
- * @param version
- * @param constraint
- * @return
+ * @param version the version to check
+ * @param constraint the constraint to check
+ * @return {@code true} if the version matches the constraint, {@code false} otherwise
*/
boolean checkVersionConstraint(String version, String constraint);
/**
* Compare two versions. It's similar with {@link Comparator#compare(Object, Object)}.
*
- * @param v1
- * @param v2
+ * @param v1 the first version to compare
+ * @param v2 the second version to compare
*/
int compareVersions(String v1, String v2);