MINOR: Move plugin path parsing from DelegatingClassLoader to PluginUtils (#13334)

Reviewers: Chaitanya Mukka <chaitanya.mvs2007@gmail.com>, Chris Egerton <chrise@aiven.io>
This commit is contained in:
Greg Harris 2023-05-26 09:36:00 -07:00 committed by GitHub
parent 2ec6b5e1e2
commit 9aac5ff1fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 85 additions and 77 deletions

View File

@ -38,7 +38,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import static org.apache.kafka.common.config.ConfigDef.Range.atLeast; import static org.apache.kafka.common.config.ConfigDef.Range.atLeast;
import static org.apache.kafka.common.config.ConfigDef.ValidString.in; import static org.apache.kafka.common.config.ConfigDef.ValidString.in;
@ -50,8 +49,6 @@ import static org.apache.kafka.connect.runtime.SourceConnectorConfig.TOPIC_CREAT
public class WorkerConfig extends AbstractConfig { public class WorkerConfig extends AbstractConfig {
private static final Logger log = LoggerFactory.getLogger(WorkerConfig.class); private static final Logger log = LoggerFactory.getLogger(WorkerConfig.class);
private static final Pattern COMMA_WITH_WHITESPACE = Pattern.compile("\\s*,\\s*");
public static final String BOOTSTRAP_SERVERS_CONFIG = "bootstrap.servers"; public static final String BOOTSTRAP_SERVERS_CONFIG = "bootstrap.servers";
public static final String BOOTSTRAP_SERVERS_DOC public static final String BOOTSTRAP_SERVERS_DOC
= "A list of host/port pairs to use for establishing the initial connection to the Kafka " = "A list of host/port pairs to use for establishing the initial connection to the Kafka "
@ -400,11 +397,8 @@ public class WorkerConfig extends AbstractConfig {
return CommonClientConfigs.postProcessReconnectBackoffConfigs(this, parsedValues); return CommonClientConfigs.postProcessReconnectBackoffConfigs(this, parsedValues);
} }
public static List<String> pluginLocations(Map<String, String> props) { public static String pluginPath(Map<String, String> props) {
String locationList = props.get(WorkerConfig.PLUGIN_PATH_CONFIG); return props.get(WorkerConfig.PLUGIN_PATH_CONFIG);
return locationList == null
? new ArrayList<>()
: Arrays.asList(COMMA_WITH_WHITESPACE.split(locationList.trim(), -1));
} }
public WorkerConfig(ConfigDef definition, Map<String, String> props) { public WorkerConfig(ConfigDef definition, Map<String, String> props) {

View File

@ -41,10 +41,8 @@ import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.sql.Driver; import java.sql.Driver;
@ -77,7 +75,6 @@ import java.util.concurrent.ConcurrentMap;
*/ */
public class DelegatingClassLoader extends URLClassLoader { public class DelegatingClassLoader extends URLClassLoader {
private static final Logger log = LoggerFactory.getLogger(DelegatingClassLoader.class); private static final Logger log = LoggerFactory.getLogger(DelegatingClassLoader.class);
private static final String CLASSPATH_NAME = "classpath";
public static final String UNDEFINED_VERSION = "undefined"; public static final String UNDEFINED_VERSION = "undefined";
private final ConcurrentMap<String, SortedMap<PluginDesc<?>, ClassLoader>> pluginLoaders; private final ConcurrentMap<String, SortedMap<PluginDesc<?>, ClassLoader>> pluginLoaders;
@ -91,7 +88,7 @@ public class DelegatingClassLoader extends URLClassLoader {
private final SortedSet<PluginDesc<ConfigProvider>> configProviders; private final SortedSet<PluginDesc<ConfigProvider>> configProviders;
private final SortedSet<PluginDesc<ConnectRestExtension>> restExtensions; private final SortedSet<PluginDesc<ConnectRestExtension>> restExtensions;
private final SortedSet<PluginDesc<ConnectorClientConfigOverridePolicy>> connectorClientConfigPolicies; private final SortedSet<PluginDesc<ConnectorClientConfigOverridePolicy>> connectorClientConfigPolicies;
private final List<String> pluginPaths; private final List<Path> pluginLocations;
// Although this classloader does not load classes directly but rather delegates loading to a // Although this classloader does not load classes directly but rather delegates loading to a
// PluginClassLoader or its parent through its base class, because of the use of inheritance in // PluginClassLoader or its parent through its base class, because of the use of inheritance in
@ -101,9 +98,9 @@ public class DelegatingClassLoader extends URLClassLoader {
ClassLoader.registerAsParallelCapable(); ClassLoader.registerAsParallelCapable();
} }
public DelegatingClassLoader(List<String> pluginPaths, ClassLoader parent) { public DelegatingClassLoader(List<Path> pluginLocations, ClassLoader parent) {
super(new URL[0], parent); super(new URL[0], parent);
this.pluginPaths = pluginPaths; this.pluginLocations = pluginLocations;
this.pluginLoaders = new ConcurrentHashMap<>(); this.pluginLoaders = new ConcurrentHashMap<>();
this.aliases = new ConcurrentHashMap<>(); this.aliases = new ConcurrentHashMap<>();
this.sinkConnectors = new TreeSet<>(); this.sinkConnectors = new TreeSet<>();
@ -117,12 +114,12 @@ public class DelegatingClassLoader extends URLClassLoader {
this.connectorClientConfigPolicies = new TreeSet<>(); this.connectorClientConfigPolicies = new TreeSet<>();
} }
public DelegatingClassLoader(List<String> pluginPaths) { public DelegatingClassLoader(List<Path> pluginLocations) {
// Use as parent the classloader that loaded this class. In most cases this will be the // Use as parent the classloader that loaded this class. In most cases this will be the
// System classloader. But this choice here provides additional flexibility in managed // System classloader. But this choice here provides additional flexibility in managed
// environments that control classloading differently (OSGi, Spring and others) and don't // environments that control classloading differently (OSGi, Spring and others) and don't
// depend on the System classloader to load Connect's classes. // depend on the System classloader to load Connect's classes.
this(pluginPaths, DelegatingClassLoader.class.getClassLoader()); this(pluginLocations, DelegatingClassLoader.class.getClassLoader());
} }
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
@ -226,42 +223,23 @@ public class DelegatingClassLoader extends URLClassLoader {
} }
protected void initLoaders() { protected void initLoaders() {
for (String configPath : pluginPaths) { for (Path pluginLocation : pluginLocations) {
initPluginLoader(configPath); try {
registerPlugin(pluginLocation);
} catch (InvalidPathException | MalformedURLException e) {
log.error("Invalid path in plugin path: {}. Ignoring.", pluginLocation, e);
} catch (IOException e) {
log.error("Could not get listing for plugin path: {}. Ignoring.", pluginLocation, e);
}
} }
// Finally add parent/system loader. // Finally add parent/system loader.
initPluginLoader(CLASSPATH_NAME); scanUrlsAndAddPlugins(
getParent(),
ClasspathHelper.forJavaClassPath().toArray(new URL[0])
);
addAllAliases(); addAllAliases();
} }
private void initPluginLoader(String path) {
try {
if (CLASSPATH_NAME.equals(path)) {
scanUrlsAndAddPlugins(
getParent(),
ClasspathHelper.forJavaClassPath().toArray(new URL[0])
);
} else {
Path pluginPath = Paths.get(path).toAbsolutePath();
// Update for exception handling
path = pluginPath.toString();
// Currently 'plugin.paths' property is a list of top-level directories
// containing plugins
if (Files.isDirectory(pluginPath)) {
for (Path pluginLocation : PluginUtils.pluginLocations(pluginPath)) {
registerPlugin(pluginLocation);
}
} else if (PluginUtils.isArchive(pluginPath)) {
registerPlugin(pluginPath);
}
}
} catch (InvalidPathException | MalformedURLException e) {
log.error("Invalid path in plugin path: {}. Ignoring.", path, e);
} catch (IOException e) {
log.error("Could not get listing for plugin path: {}. Ignoring.", path, e);
}
}
private void registerPlugin(Path pluginLocation) private void registerPlugin(Path pluginLocation)
throws IOException { throws IOException {
log.info("Loading plugin from: {}", pluginLocation); log.info("Loading plugin from: {}", pluginLocation);

View File

@ -23,7 +23,9 @@ import java.io.IOException;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -142,6 +144,8 @@ public class PluginUtils {
+ "|common\\.config\\.provider\\.(?!ConfigProvider$).*" + "|common\\.config\\.provider\\.(?!ConfigProvider$).*"
+ ")$"); + ")$");
private static final Pattern COMMA_WITH_WHITESPACE = Pattern.compile("\\s*,\\s*");
private static final DirectoryStream.Filter<Path> PLUGIN_PATH_FILTER = path -> private static final DirectoryStream.Filter<Path> PLUGIN_PATH_FILTER = path ->
Files.isDirectory(path) || isArchive(path) || isClassFile(path); Files.isDirectory(path) || isArchive(path) || isClassFile(path);
@ -188,11 +192,34 @@ public class PluginUtils {
return path.toString().toLowerCase(Locale.ROOT).endsWith(".class"); return path.toString().toLowerCase(Locale.ROOT).endsWith(".class");
} }
public static List<Path> pluginLocations(Path topPath) throws IOException { public static List<Path> pluginLocations(String pluginPath) {
if (pluginPath == null) {
return Collections.emptyList();
}
String[] pluginPathElements = COMMA_WITH_WHITESPACE.split(pluginPath.trim(), -1);
List<Path> pluginLocations = new ArrayList<>();
for (String path : pluginPathElements) {
try {
Path pluginPathElement = Paths.get(path).toAbsolutePath();
// Currently 'plugin.paths' property is a list of top-level directories
// containing plugins
if (Files.isDirectory(pluginPathElement)) {
pluginLocations.addAll(pluginLocations(pluginPathElement));
} else if (isArchive(pluginPathElement)) {
pluginLocations.add(pluginPathElement);
}
} catch (InvalidPathException | IOException e) {
log.error("Could not get listing for plugin path: {}. Ignoring.", path, e);
}
}
return pluginLocations;
}
private static List<Path> pluginLocations(Path pluginPathElement) throws IOException {
List<Path> locations = new ArrayList<>(); List<Path> locations = new ArrayList<>();
try ( try (
DirectoryStream<Path> listing = Files.newDirectoryStream( DirectoryStream<Path> listing = Files.newDirectoryStream(
topPath, pluginPathElement,
PLUGIN_PATH_FILTER PLUGIN_PATH_FILTER
) )
) { ) {

View File

@ -37,6 +37,7 @@ import org.apache.kafka.connect.transforms.predicates.Predicate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.nio.file.Path;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.ArrayList; import java.util.ArrayList;
@ -61,15 +62,16 @@ public class Plugins {
// VisibleForTesting // VisibleForTesting
Plugins(Map<String, String> props, ClassLoader parent) { Plugins(Map<String, String> props, ClassLoader parent) {
List<String> pluginLocations = WorkerConfig.pluginLocations(props); String pluginPath = WorkerConfig.pluginPath(props);
List<Path> pluginLocations = PluginUtils.pluginLocations(pluginPath);
delegatingLoader = newDelegatingClassLoader(pluginLocations, parent); delegatingLoader = newDelegatingClassLoader(pluginLocations, parent);
delegatingLoader.initLoaders(); delegatingLoader.initLoaders();
} }
// VisibleForTesting // VisibleForTesting
protected DelegatingClassLoader newDelegatingClassLoader(final List<String> paths, ClassLoader parent) { protected DelegatingClassLoader newDelegatingClassLoader(final List<Path> pluginLocations, ClassLoader parent) {
return AccessController.doPrivileged( return AccessController.doPrivileged(
(PrivilegedAction<DelegatingClassLoader>) () -> new DelegatingClassLoader(paths, parent) (PrivilegedAction<DelegatingClassLoader>) () -> new DelegatingClassLoader(pluginLocations, parent)
); );
} }

View File

@ -21,7 +21,6 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
@ -64,7 +63,7 @@ public class DelegatingClassLoaderTest {
pluginDir.newFile("invalid.jar"); pluginDir.newFile("invalid.jar");
DelegatingClassLoader classLoader = new DelegatingClassLoader( DelegatingClassLoader classLoader = new DelegatingClassLoader(
Collections.singletonList(pluginDir.getRoot().getAbsolutePath()), Collections.singletonList(pluginDir.getRoot().toPath().toAbsolutePath()),
DelegatingClassLoader.class.getClassLoader() DelegatingClassLoader.class.getClassLoader()
); );
classLoader.initLoaders(); classLoader.initLoaders();
@ -76,7 +75,7 @@ public class DelegatingClassLoaderTest {
pluginDir.newFile("my-plugin/invalid.jar"); pluginDir.newFile("my-plugin/invalid.jar");
DelegatingClassLoader classLoader = new DelegatingClassLoader( DelegatingClassLoader classLoader = new DelegatingClassLoader(
Collections.singletonList(pluginDir.getRoot().getAbsolutePath()), Collections.singletonList(pluginDir.getRoot().toPath().toAbsolutePath()),
DelegatingClassLoader.class.getClassLoader() DelegatingClassLoader.class.getClassLoader()
); );
classLoader.initLoaders(); classLoader.initLoaders();
@ -85,7 +84,7 @@ public class DelegatingClassLoaderTest {
@Test @Test
public void testLoadingNoPlugins() { public void testLoadingNoPlugins() {
DelegatingClassLoader classLoader = new DelegatingClassLoader( DelegatingClassLoader classLoader = new DelegatingClassLoader(
Collections.singletonList(pluginDir.getRoot().getAbsolutePath()), Collections.singletonList(pluginDir.getRoot().toPath().toAbsolutePath()),
DelegatingClassLoader.class.getClassLoader() DelegatingClassLoader.class.getClassLoader()
); );
classLoader.initLoaders(); classLoader.initLoaders();
@ -96,7 +95,7 @@ public class DelegatingClassLoaderTest {
pluginDir.newFolder("my-plugin"); pluginDir.newFolder("my-plugin");
DelegatingClassLoader classLoader = new DelegatingClassLoader( DelegatingClassLoader classLoader = new DelegatingClassLoader(
Collections.singletonList(pluginDir.getRoot().getAbsolutePath()), Collections.singletonList(pluginDir.getRoot().toPath().toAbsolutePath()),
DelegatingClassLoader.class.getClassLoader() DelegatingClassLoader.class.getClassLoader()
); );
classLoader.initLoaders(); classLoader.initLoaders();
@ -109,13 +108,12 @@ public class DelegatingClassLoaderTest {
pluginDir.newFile("my-plugin/invalid.jar"); pluginDir.newFile("my-plugin/invalid.jar");
Path pluginPath = this.pluginDir.getRoot().toPath(); Path pluginPath = this.pluginDir.getRoot().toPath();
for (String sourceJar : TestPlugins.pluginPath()) { for (Path source : TestPlugins.pluginPath()) {
Path source = new File(sourceJar).toPath();
Files.copy(source, pluginPath.resolve(source.getFileName())); Files.copy(source, pluginPath.resolve(source.getFileName()));
} }
DelegatingClassLoader classLoader = new DelegatingClassLoader( DelegatingClassLoader classLoader = new DelegatingClassLoader(
Collections.singletonList(pluginDir.getRoot().getAbsolutePath()), Collections.singletonList(pluginDir.getRoot().toPath().toAbsolutePath()),
DelegatingClassLoader.class.getClassLoader() DelegatingClassLoader.class.getClassLoader()
); );
classLoader.initLoaders(); classLoader.initLoaders();

View File

@ -21,6 +21,7 @@ import java.io.File;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -81,7 +82,7 @@ public class PluginsTest {
Map<String, String> pluginProps = new HashMap<>(); Map<String, String> pluginProps = new HashMap<>();
// Set up the plugins with some test plugins to test isolation // Set up the plugins with some test plugins to test isolation
pluginProps.put(WorkerConfig.PLUGIN_PATH_CONFIG, String.join(",", TestPlugins.pluginPath())); pluginProps.put(WorkerConfig.PLUGIN_PATH_CONFIG, TestPlugins.pluginPathJoined());
plugins = new Plugins(pluginProps); plugins = new Plugins(pluginProps);
props = new HashMap<>(pluginProps); props = new HashMap<>(pluginProps);
props.put(WorkerConfig.KEY_CONVERTER_CLASS_CONFIG, TestConverter.class.getName()); props.put(WorkerConfig.KEY_CONVERTER_CLASS_CONFIG, TestConverter.class.getName());
@ -466,7 +467,7 @@ public class PluginsTest {
TestPlugin parentResource, TestPlugin childResource, String className, String... expectedVersions) throws MalformedURLException { TestPlugin parentResource, TestPlugin childResource, String className, String... expectedVersions) throws MalformedURLException {
URL[] systemPath = TestPlugins.pluginPath(parentResource) URL[] systemPath = TestPlugins.pluginPath(parentResource)
.stream() .stream()
.map(File::new) .map(Path::toFile)
.map(File::toURI) .map(File::toURI)
.map(uri -> { .map(uri -> {
try { try {
@ -482,7 +483,7 @@ public class PluginsTest {
// to simulate the situation where jars exist on both system classpath and plugin path. // to simulate the situation where jars exist on both system classpath and plugin path.
Map<String, String> pluginProps = Collections.singletonMap( Map<String, String> pluginProps = Collections.singletonMap(
WorkerConfig.PLUGIN_PATH_CONFIG, WorkerConfig.PLUGIN_PATH_CONFIG,
String.join(",", TestPlugins.pluginPath(childResource)) TestPlugins.pluginPathJoined(childResource)
); );
plugins = new Plugins(pluginProps, parent); plugins = new Plugins(pluginProps, parent);

View File

@ -24,6 +24,7 @@ import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo; import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo; import java.lang.management.ThreadInfo;
import java.net.URL; import java.net.URL;
import java.nio.file.Path;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.Arrays; import java.util.Arrays;
@ -72,7 +73,7 @@ public class SynchronizationTest {
public void setup() { public void setup() {
Map<String, String> pluginProps = Collections.singletonMap( Map<String, String> pluginProps = Collections.singletonMap(
WorkerConfig.PLUGIN_PATH_CONFIG, WorkerConfig.PLUGIN_PATH_CONFIG,
String.join(",", TestPlugins.pluginPath()) TestPlugins.pluginPathJoined()
); );
threadPrefix = SynchronizationTest.class.getSimpleName() threadPrefix = SynchronizationTest.class.getSimpleName()
+ "." + testName.getMethodName() + "-"; + "." + testName.getMethodName() + "-";
@ -80,10 +81,10 @@ public class SynchronizationTest {
pclBreakpoint = new Breakpoint<>(); pclBreakpoint = new Breakpoint<>();
plugins = new Plugins(pluginProps) { plugins = new Plugins(pluginProps) {
@Override @Override
protected DelegatingClassLoader newDelegatingClassLoader(List<String> paths, ClassLoader parent) { protected DelegatingClassLoader newDelegatingClassLoader(List<Path> pluginLocations, ClassLoader parent) {
return AccessController.doPrivileged( return AccessController.doPrivileged(
(PrivilegedAction<DelegatingClassLoader>) () -> (PrivilegedAction<DelegatingClassLoader>) () ->
new SynchronizedDelegatingClassLoader(paths, parent) new SynchronizedDelegatingClassLoader(pluginLocations, parent)
); );
} }
}; };
@ -171,8 +172,8 @@ public class SynchronizationTest {
ClassLoader.registerAsParallelCapable(); ClassLoader.registerAsParallelCapable();
} }
public SynchronizedDelegatingClassLoader(List<String> pluginPaths, ClassLoader parent) { public SynchronizedDelegatingClassLoader(List<Path> pluginLocations, ClassLoader parent) {
super(pluginPaths, parent); super(pluginLocations, parent);
} }
@Override @Override

View File

@ -204,12 +204,12 @@ public class TestPlugins {
} }
private static final Logger log = LoggerFactory.getLogger(TestPlugins.class); private static final Logger log = LoggerFactory.getLogger(TestPlugins.class);
private static final Map<String, File> PLUGIN_JARS; private static final Map<String, Path> PLUGIN_JARS;
private static final Throwable INITIALIZATION_EXCEPTION; private static final Throwable INITIALIZATION_EXCEPTION;
static { static {
Throwable err = null; Throwable err = null;
Map<String, File> pluginJars = new HashMap<>(); Map<String, Path> pluginJars = new HashMap<>();
try { try {
for (TestPlugin testPlugin : TestPlugin.values()) { for (TestPlugin testPlugin : TestPlugin.values()) {
if (pluginJars.containsKey(testPlugin.resourceDir())) { if (pluginJars.containsKey(testPlugin.resourceDir())) {
@ -240,27 +240,34 @@ public class TestPlugins {
* @return A list of plugin jar filenames * @return A list of plugin jar filenames
* @throws AssertionError if any plugin failed to load, or no plugins were loaded. * @throws AssertionError if any plugin failed to load, or no plugins were loaded.
*/ */
public static List<String> pluginPath() { public static List<Path> pluginPath() {
return pluginPath(defaultPlugins()); return pluginPath(defaultPlugins());
} }
public static String pluginPathJoined() {
return pluginPath().stream().map(Path::toString).collect(Collectors.joining(","));
}
/** /**
* Assemble a plugin path containing some TestPlugin instances * Assemble a plugin path containing some TestPlugin instances
* @param plugins One or more plugins which should be included on the plugin path. * @param plugins One or more plugins which should be included on the plugin path.
* @return A list of plugin jar filenames containing the specified test plugins * @return A list of plugin jar filenames containing the specified test plugins
* @throws AssertionError if any plugin failed to load, or no plugins were loaded. * @throws AssertionError if any plugin failed to load, or no plugins were loaded.
*/ */
public static List<String> pluginPath(TestPlugin... plugins) { public static List<Path> pluginPath(TestPlugin... plugins) {
assertAvailable(); assertAvailable();
return Arrays.stream(plugins) return Arrays.stream(plugins)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(TestPlugin::resourceDir) .map(TestPlugin::resourceDir)
.distinct() .distinct()
.map(PLUGIN_JARS::get) .map(PLUGIN_JARS::get)
.map(File::getPath)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public static String pluginPathJoined(TestPlugin... plugins) {
return pluginPath(plugins).stream().map(Path::toString).collect(Collectors.joining(","));
}
/** /**
* Get all plugin classes which are included on the default classpath * Get all plugin classes which are included on the default classpath
* @return A list of plugin class names * @return A list of plugin class names
@ -291,17 +298,17 @@ public class TestPlugins {
.toArray(TestPlugin[]::new); .toArray(TestPlugin[]::new);
} }
private static File createPluginJar(String resourceDir, Predicate<String> removeRuntimeClasses) throws IOException { private static Path createPluginJar(String resourceDir, Predicate<String> removeRuntimeClasses) throws IOException {
Path inputDir = resourceDirectoryPath("test-plugins/" + resourceDir); Path inputDir = resourceDirectoryPath("test-plugins/" + resourceDir);
Path binDir = Files.createTempDirectory(resourceDir + ".bin."); Path binDir = Files.createTempDirectory(resourceDir + ".bin.");
compileJavaSources(inputDir, binDir); compileJavaSources(inputDir, binDir);
File jarFile = Files.createTempFile(resourceDir + ".", ".jar").toFile(); Path jarFile = Files.createTempFile(resourceDir + ".", ".jar");
try (JarOutputStream jar = openJarFile(jarFile)) { try (JarOutputStream jar = openJarFile(jarFile.toFile())) {
writeJar(jar, inputDir, removeRuntimeClasses); writeJar(jar, inputDir, removeRuntimeClasses);
writeJar(jar, binDir, removeRuntimeClasses); writeJar(jar, binDir, removeRuntimeClasses);
} }
removeDirectory(binDir); removeDirectory(binDir);
jarFile.deleteOnExit(); jarFile.toFile().deleteOnExit();
return jarFile; return jarFile;
} }