All Downloads are FREE. Search and download functionalities are using the official Maven repository.

opwvhk.plugin.Plugins Maven / Gradle / Ivy

Go to download

A simple plugin loader, loading isolated plugins with their own classpath. The application and the plugins share access to platform and service classes -- without a proxy or reflection.

The newest version!
package opwvhk.plugin;

import org.jetbrains.annotations.VisibleForTesting;

import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;

import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.INFO;

/**
 * 

Entry point to use plugins.

* *

Loads plugins for the specified service classes (and only those service classes) using the context classloader from the specified plugin path.

* *

Uses a {@link FilteringClassLoader} on the context class loader to allow the classpath entries for the specified service classes (this acts like a * firewall, keeping your application code off limits), and allows only those service classes to be loaded (to guarantee they are available).

* * @see Plugin * @see FilteringClassLoader */ public class Plugins { private static final System.Logger LOG = System.getLogger(Plugins.class.getCanonicalName()); private final Collection plugins; /** * Load plugins for the specified service classes (and only those service classes) using the context classloader from the specified plugin path. * * @param pluginPath the plugin path * @param serviceClasses the service classes to expose * @throws IOException when the plugins could not be loaded */ public Plugins(Path pluginPath, Class... serviceClasses) throws IOException { this(Collections.singleton(pluginPath), serviceClasses); } /** * Load plugins for the specified service classes (and only those service classes) using the context classloader from the specified plugin path. * * @param pluginPath the plugin path * @param serviceClasses the service classes to expose * @throws IOException when the plugins could not be loaded */ public Plugins(Set pluginPath, Class... serviceClasses) throws IOException { this(pluginPath, createPluginParentClassLoader(serviceClasses)); } private static ClassLoader createPluginParentClassLoader(Class[] serviceClasses) { ClassLoader classLoader = Optional.ofNullable(Thread.currentThread().getContextClassLoader()).orElseGet(ClassLoader::getSystemClassLoader); return FilteringClassLoader.using(classLoader).withCodeSourcesOf(serviceClasses).build(); } @VisibleForTesting Plugins(Set pluginPath, ClassLoader pluginParentClassLoader) throws IOException { LOG.log(INFO, "Loading plugins from {0}", pluginPath); plugins = new ArrayList<>(); for (Path pluginPathElement : pluginPath) { Files.walkFileTree(pluginPathElement, EnumSet.noneOf(FileVisitOption.class), 2, new SimpleFileVisitor<>() { private boolean startedInDirectory = false; private Path pluginBasePath; private List classpath; @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (!startedInDirectory) { startedInDirectory = true; } else { LOG.log(DEBUG, "Found directory plugin at {0}", dir); pluginBasePath = dir; classpath = new ArrayList<>(); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { if (!startedInDirectory) { throw new IOException("A plugin directory must be a directory"); } else if (isFileOrDirectory(attrs)) { if (classpath != null) { LOG.log(DEBUG, "Found classpath entry: {0}", path); if (attrs.isDirectory() || path.getFileName().toString().endsWith(".jar")) { classpath.add(path); } } else if (path.getFileName().toString().endsWith(".jar")){ LOG.log(DEBUG, "Found .jar-only plugin at {0}", path); Plugin plugin = new Plugin(pluginParentClassLoader, path); LOG.log(INFO, "Created plugin {0}", plugin.getName()); plugins.add(plugin); } } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { // classpath is null when terminating at the pluginPathElement if (classpath != null) { Plugin plugin = new Plugin(pluginParentClassLoader, pluginBasePath, classpath); LOG.log(INFO, "Created plugin {0}", plugin.getName()); plugins.add(plugin); pluginBasePath = null; classpath = null; } return FileVisitResult.CONTINUE; } }); } } @VisibleForTesting boolean isFileOrDirectory(BasicFileAttributes attrs) { return attrs.isRegularFile() || attrs.isDirectory(); } /** * Get all implementations of the service class across all plugins. * * @param serviceClass a service class * @return all services provided by the plugins */ public Iterable getServices(Class serviceClass) { return plugins.stream() .flatMap(p -> p.getServiceLoader(serviceClass).stream()) .map(ServiceLoader.Provider::get) .toList(); } /** * Return all loaded plugins. * * @return all loaded plugins */ public Collection getPlugins() { return Collections.unmodifiableCollection(plugins); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy