diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | demo/maven/pom.xml | 2 | ||||
-rw-r--r-- | pf4j/src/main/java/org/pf4j/AbstractPluginManager.java | 1 | ||||
-rw-r--r-- | pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java | 14 | ||||
-rw-r--r-- | pf4j/src/main/java/org/pf4j/DefaultPluginManager.java | 2 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java | 4 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java | 12 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java | 97 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java | 12 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/test/ManifestUtils.java | 40 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/test/PluginJar.java | 46 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/test/PluginZip.java | 84 | ||||
-rw-r--r-- | pf4j/src/test/java/org/pf4j/test/PropertiesUtils.java | 35 |
13 files changed, 260 insertions, 95 deletions
@@ -159,3 +159,9 @@ Quickstart (call to action) 1. Read this file to have an overview about what this project does 2. Read [Getting started](https://pf4j.org/doc/getting-started.html) section of documentation to understand the basic concepts 3. Read [Quickstart](https://pf4j.org/dev/quickstart.html) section of documentation to create your first PF4J-based modular application + +Credits +------- +Many thanks to: +* [JetBrains](https://www.jetbrains.com) for their free OpenSource license +* [JProfiler](https://www.ej-technologies.com/jprofiler) for their free OpenSource license diff --git a/demo/maven/pom.xml b/demo/maven/pom.xml index 743ce31..61033f0 100644 --- a/demo/maven/pom.xml +++ b/demo/maven/pom.xml @@ -5,7 +5,7 @@ <groupId>org.pf4j</groupId> <artifactId>pf4j-parent</artifactId> <version>3.14.0-SNAPSHOT</version> - <relativePath>../..</relativePath> + <relativePath>../../pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java b/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java index 626dbcb..ac41519 100644 --- a/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java +++ b/pf4j/src/main/java/org/pf4j/AbstractPluginManager.java @@ -337,6 +337,7 @@ public abstract class AbstractPluginManager implements PluginManager { if (classLoader instanceof Closeable) { try { ((Closeable) classLoader).close(); + classLoader = null; // help GC to collect the classloader } catch (IOException e) { throw new PluginRuntimeException(e, "Cannot close classloader"); } diff --git a/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java index e8ff380..db74238 100644 --- a/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java +++ b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java @@ -134,13 +134,13 @@ public class DefaultPluginDescriptor implements PluginDescriptor { return this; } - protected PluginDescriptor setPluginDescription(String pluginDescription) { + protected DefaultPluginDescriptor setPluginDescription(String pluginDescription) { this.pluginDescription = pluginDescription; return this; } - protected PluginDescriptor setPluginClass(String pluginClassName) { + protected DefaultPluginDescriptor setPluginClass(String pluginClassName) { this.pluginClass = pluginClassName; return this; @@ -152,19 +152,19 @@ public class DefaultPluginDescriptor implements PluginDescriptor { return this; } - protected PluginDescriptor setProvider(String provider) { + protected DefaultPluginDescriptor setProvider(String provider) { this.provider = provider; return this; } - protected PluginDescriptor setRequires(String requires) { + protected DefaultPluginDescriptor setRequires(String requires) { this.requires = requires; return this; } - protected PluginDescriptor setDependencies(String dependencies) { + protected DefaultPluginDescriptor setDependencies(String dependencies) { this.dependencies = new ArrayList<>(); if (dependencies != null) { @@ -177,7 +177,7 @@ public class DefaultPluginDescriptor implements PluginDescriptor { return this; } - protected PluginDescriptor setDependencies(String... dependencies) { + protected DefaultPluginDescriptor setDependencies(String... dependencies) { for (String dependency : dependencies) { dependency = dependency.trim(); if (!dependency.isEmpty()) { @@ -188,7 +188,7 @@ public class DefaultPluginDescriptor implements PluginDescriptor { return this; } - public PluginDescriptor setLicense(String license) { + public DefaultPluginDescriptor setLicense(String license) { this.license = license; return this; diff --git a/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java b/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java index e2667d4..a47c892 100644 --- a/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java +++ b/pf4j/src/main/java/org/pf4j/DefaultPluginManager.java @@ -25,7 +25,7 @@ import java.util.List; /** * Default implementation of the {@link PluginManager} interface. - * In essence it is a {@link ZipPluginManager} plus a {@link JarPluginManager}. + * In essence, it is a {@link ZipPluginManager} plus a {@link JarPluginManager}. * So, it can load plugins from jar and zip, simultaneous. * * <p>This class is not thread-safe. diff --git a/pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java b/pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java index f80c19e..9823882 100644 --- a/pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java +++ b/pf4j/src/test/java/org/pf4j/CompoundPluginDescriptorFinderTest.java @@ -18,8 +18,8 @@ package org.pf4j; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.pf4j.test.PluginJar; -import org.pf4j.test.PluginZip; import org.pf4j.test.TestPlugin; +import org.pf4j.test.PropertiesUtils; import java.io.FileOutputStream; import java.io.IOException; @@ -113,7 +113,7 @@ public class CompoundPluginDescriptorFinderTest { map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, ">=1"); map.put(PropertiesPluginDescriptorFinder.PLUGIN_LICENSE, "Apache-2.0"); - return PluginZip.createProperties(map); + return PropertiesUtils.createProperties(map); } private void storePropertiesToPath(Properties properties, Path pluginPath) throws IOException { diff --git a/pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java b/pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java index 079d8e2..d34a77d 100644 --- a/pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java +++ b/pf4j/src/test/java/org/pf4j/ManifestPluginDescriptorFinderTest.java @@ -18,7 +18,7 @@ package org.pf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.pf4j.test.PluginJar; +import org.pf4j.test.ManifestUtils; import java.io.FileOutputStream; import java.io.IOException; @@ -121,7 +121,7 @@ public class ManifestPluginDescriptorFinderTest { map.put(ManifestPluginDescriptorFinder.PLUGIN_REQUIRES, "*"); map.put(ManifestPluginDescriptorFinder.PLUGIN_LICENSE, "Apache-2.0"); - return PluginJar.createManifest(map); + return ManifestUtils.createManifest(map); } private Manifest getPlugin2Manifest() { @@ -132,7 +132,7 @@ public class ManifestPluginDescriptorFinderTest { map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu"); map.put(ManifestPluginDescriptorFinder.PLUGIN_DEPENDENCIES, ""); - return PluginJar.createManifest(map); + return ManifestUtils.createManifest(map); } private Manifest getPlugin4Manifest() { @@ -141,7 +141,7 @@ public class ManifestPluginDescriptorFinderTest { map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, "0.0.1"); map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu"); - return PluginJar.createManifest(map); + return ManifestUtils.createManifest(map); } private Manifest getPlugin5Manifest() { @@ -150,7 +150,7 @@ public class ManifestPluginDescriptorFinderTest { map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, "org.pf4j.plugin.TestPlugin"); map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu"); - return PluginJar.createManifest(map); + return ManifestUtils.createManifest(map); } private Manifest getPlugin6Manifest() { @@ -158,7 +158,7 @@ public class ManifestPluginDescriptorFinderTest { map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, "org.pf4j.plugin.TestPlugin"); map.put(ManifestPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu"); - return PluginJar.createManifest(map); + return ManifestUtils.createManifest(map); } private void storeManifestToPath(Manifest manifest, Path pluginPath) throws IOException { diff --git a/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java b/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java index b19bd3b..b43d6b9 100644 --- a/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java +++ b/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java @@ -21,20 +21,27 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.pf4j.processor.LegacyExtensionStorage; +import org.pf4j.test.JavaFileObjectUtils; +import org.pf4j.test.JavaSources; import org.pf4j.test.PluginZip; import org.pf4j.util.FileUtils; +import javax.tools.JavaFileObject; import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.AbstractMap; import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -58,6 +65,8 @@ class PluginClassLoaderTest { private PluginClassLoader parentLastPluginDependencyClassLoader; private PluginClassLoader parentFirstPluginDependencyClassLoader; + private PluginZip pluginDependencyZip; + @TempDir Path pluginsPath; @@ -65,16 +74,17 @@ class PluginClassLoaderTest { static void setUpGlobal() throws IOException, URISyntaxException { Path parentClassPathBase = Paths.get(PluginClassLoaderTest.class.getClassLoader().getResource(".").toURI()); - File metaInfFile = parentClassPathBase.resolve("META-INF").toFile(); + Path metaInfPath = parentClassPathBase.resolve("META-INF"); + File metaInfFile = metaInfPath.toFile(); if (metaInfFile.mkdir()) { // Only delete the directory if this test created it, guarding for any future usages of the directory. metaInfFile.deleteOnExit(); } - createFile(parentClassPathBase.resolve("META-INF").resolve("file-only-in-parent")); - createFile(parentClassPathBase.resolve("META-INF").resolve("file-in-both-parent-and-dependency-and-plugin")); - createFile(parentClassPathBase.resolve("META-INF").resolve("file-in-both-parent-and-dependency")); - createFile(parentClassPathBase.resolve("META-INF").resolve("file-in-both-parent-and-plugin")); + createFile(metaInfPath.resolve("file-only-in-parent")); + createFile(metaInfPath.resolve("file-in-both-parent-and-dependency-and-plugin")); + createFile(metaInfPath.resolve("file-in-both-parent-and-dependency")); + createFile(metaInfPath.resolve("file-in-both-parent-and-plugin")); createFile(parentClassPathBase.resolve(LegacyExtensionStorage.EXTENSIONS_RESOURCE)); } @@ -93,25 +103,37 @@ class PluginClassLoaderTest { pluginManager = new TestPluginManager(pluginsPath); pluginManagerParentFirst = new TestPluginManager(pluginsPath); - pluginDependencyDescriptor = new DefaultPluginDescriptor(); - pluginDependencyDescriptor.setPluginId("myDependency"); - pluginDependencyDescriptor.setPluginVersion("1.2.3"); - pluginDependencyDescriptor.setPluginDescription("My plugin"); - pluginDependencyDescriptor.setDependencies(""); - pluginDependencyDescriptor.setProvider("Me"); - pluginDependencyDescriptor.setRequires("5.0.0"); + pluginDependencyDescriptor = new DefaultPluginDescriptor() + .setPluginId("myDependency") + .setPluginVersion("1.2.3") + .setPluginDescription("My plugin") + .setDependencies("") + .setProvider("Me") + .setRequires("5.0.0"); + + Map<String, JavaFileObject> generatedClasses = JavaSources.compileAll(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING) + .stream() + .map(javaFileObject -> new AbstractMap.SimpleEntry<>(JavaFileObjectUtils.getClassName(javaFileObject), javaFileObject)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Path classesPath = Paths.get("classes"); + Path metaInfPath = classesPath.resolve("META-INF"); + + Path greetingClassPath = classesPath.resolve(JavaSources.GREETING_CLASS_NAME.replace('.', '/') + ".class"); + Path whaszzupGreetingClassPath = classesPath.resolve(JavaSources.WHAZZUP_GREETING_CLASS_NAME.replace('.', '/') + ".class"); Path pluginDependencyPath = pluginsPath.resolve(pluginDependencyDescriptor.getPluginId() + "-" + pluginDependencyDescriptor.getVersion() + ".zip"); - PluginZip pluginDependencyZip = new PluginZip.Builder(pluginDependencyPath, pluginDependencyDescriptor.getPluginId()) + pluginDependencyZip = new PluginZip.Builder(pluginDependencyPath, pluginDependencyDescriptor.getPluginId()) .pluginVersion(pluginDependencyDescriptor.getVersion()) - .addFile(Paths.get("classes/META-INF/dependency-file"), "dependency") - .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-dependency-and-plugin"), "dependency") - .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-dependency"), "dependency") - .addFile(Paths.get("classes/" + LegacyExtensionStorage.EXTENSIONS_RESOURCE), "dependency") + .addFile(metaInfPath.resolve("dependency-file"), "dependency") + .addFile(metaInfPath.resolve("file-in-both-parent-and-dependency-and-plugin"), "dependency") + .addFile(metaInfPath.resolve("file-in-both-parent-and-dependency"), "dependency") + .addFile(classesPath.resolve(LegacyExtensionStorage.EXTENSIONS_RESOURCE), "dependency") + .addFile(greetingClassPath, JavaFileObjectUtils.getAllBytes(generatedClasses.get(JavaSources.GREETING_CLASS_NAME))) + .addFile(whaszzupGreetingClassPath, JavaFileObjectUtils.getAllBytes(generatedClasses.get(JavaSources.WHAZZUP_GREETING_CLASS_NAME))) .build(); - FileUtils.expandIfZip(pluginDependencyZip.path()); + pluginDependencyZip.unzip(); PluginClasspath pluginDependencyClasspath = new DefaultPluginClasspath(); @@ -121,7 +143,6 @@ class PluginClassLoaderTest { pluginManager.addClassLoader(pluginDependencyDescriptor.getPluginId(), parentLastPluginDependencyClassLoader); pluginManagerParentFirst.addClassLoader(pluginDependencyDescriptor.getPluginId(), parentFirstPluginDependencyClassLoader); - for (String classesDirectory : pluginDependencyClasspath.getClassesDirectories()) { File classesDirectoryFile = pluginDependencyZip.unzippedPath().resolve(classesDirectory).toFile(); parentLastPluginDependencyClassLoader.addFile(classesDirectoryFile); @@ -148,13 +169,13 @@ class PluginClassLoaderTest { Path pluginPath = pluginsPath.resolve(pluginDescriptor.getPluginId() + "-" + pluginDescriptor.getVersion() + ".zip"); PluginZip pluginZip = new PluginZip.Builder(pluginPath, pluginDescriptor.getPluginId()) .pluginVersion(pluginDescriptor.getVersion()) - .addFile(Paths.get("classes/META-INF/plugin-file"), "plugin") - .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-dependency-and-plugin"), "plugin") - .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-plugin"), "plugin") - .addFile(Paths.get("classes/" + LegacyExtensionStorage.EXTENSIONS_RESOURCE), "plugin") + .addFile(metaInfPath.resolve("plugin-file"), "plugin") + .addFile(metaInfPath.resolve("file-in-both-parent-and-dependency-and-plugin"), "plugin") + .addFile(metaInfPath.resolve("file-in-both-parent-and-plugin"), "plugin") + .addFile(classesPath.resolve(LegacyExtensionStorage.EXTENSIONS_RESOURCE), "plugin") .build(); - FileUtils.expandIfZip(pluginZip.path()); + pluginZip.unzip(); PluginClasspath pluginClasspath = new DefaultPluginClasspath(); @@ -347,6 +368,34 @@ class PluginClassLoaderTest { assertTrue(parentLastPluginClassLoader.isClosed()); } + @Test + void collectClassLoader() throws IOException, ClassNotFoundException, InterruptedException { + // Create a new classloader + PluginClassLoader classLoader = new PluginClassLoader(pluginManager, pluginDependencyDescriptor, PluginClassLoaderTest.class.getClassLoader()); + PluginClasspath pluginDependencyClasspath = new DefaultPluginClasspath(); + for (String classesDirectory : pluginDependencyClasspath.getClassesDirectories()) { + File classesDirectoryFile = pluginDependencyZip.unzippedPath().resolve(classesDirectory).toFile(); + classLoader.addFile(classesDirectoryFile); + } + + WeakReference<PluginClassLoader> weakRef = new WeakReference<>(classLoader); + + // Use the classloader + classLoader.loadClass(JavaSources.GREETING_CLASS_NAME); + + // Clear strong reference + classLoader.close(); + classLoader = null; + + // Try to force GC + System.gc(); + System.runFinalization(); + Thread.sleep(100); // Give GC a chance to run + + // Check if ClassLoader was collected + assertNull(weakRef.get(), "ClassLoader was not garbage collected"); + } + private static void assertFirstLine(String expected, URL resource) throws URISyntaxException, IOException { assertNotNull(resource); assertEquals(expected, Files.readAllLines(Paths.get(resource.toURI())).get(0)); diff --git a/pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java b/pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java index 4d73a4f..987ee13 100644 --- a/pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java +++ b/pf4j/src/test/java/org/pf4j/PropertiesPluginDescriptorFinderTest.java @@ -18,8 +18,8 @@ package org.pf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.pf4j.test.PluginZip; import org.pf4j.test.TestPlugin; +import org.pf4j.test.PropertiesUtils; import java.io.FileOutputStream; import java.io.IOException; @@ -118,7 +118,7 @@ public class PropertiesPluginDescriptorFinderTest { map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, ">=1"); map.put(PropertiesPluginDescriptorFinder.PLUGIN_LICENSE, "Apache-2.0"); - return PluginZip.createProperties(map); + return PropertiesUtils.createProperties(map); } private Properties getPlugin2Properties() { @@ -129,7 +129,7 @@ public class PropertiesPluginDescriptorFinderTest { map.put(PropertiesPluginDescriptorFinder.PLUGIN_PROVIDER, "Decebal Suiu"); map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, ""); - return PluginZip.createProperties(map); + return PropertiesUtils.createProperties(map); } private Properties getPlugin4Properties() { @@ -140,7 +140,7 @@ public class PropertiesPluginDescriptorFinderTest { map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, ""); map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, "*"); - return PluginZip.createProperties(map); + return PropertiesUtils.createProperties(map); } private Properties getPlugin5Properties() { @@ -151,7 +151,7 @@ public class PropertiesPluginDescriptorFinderTest { map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, ""); map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, "*"); - return PluginZip.createProperties(map); + return PropertiesUtils.createProperties(map); } private Properties getPlugin6Properties() { @@ -162,7 +162,7 @@ public class PropertiesPluginDescriptorFinderTest { map.put(PropertiesPluginDescriptorFinder.PLUGIN_DEPENDENCIES, ""); map.put(PropertiesPluginDescriptorFinder.PLUGIN_REQUIRES, "*"); - return PluginZip.createProperties(map); + return PropertiesUtils.createProperties(map); } private void storePropertiesToPath(Properties properties, Path pluginPath) throws IOException { diff --git a/pf4j/src/test/java/org/pf4j/test/ManifestUtils.java b/pf4j/src/test/java/org/pf4j/test/ManifestUtils.java new file mode 100644 index 0000000..e4dd33f --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/ManifestUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * 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 org.pf4j.test; + +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +public class ManifestUtils { + + private ManifestUtils() {} + + /** + * Creates a {@link Manifest} object from the given map. + */ + public static Manifest createManifest(Map<String, String> map) { + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0.0"); + for (Map.Entry<String, String> entry : map.entrySet()) { + attributes.put(new Attributes.Name(entry.getKey()), entry.getValue()); + } + + return manifest; + } + +} diff --git a/pf4j/src/test/java/org/pf4j/test/PluginJar.java b/pf4j/src/test/java/org/pf4j/test/PluginJar.java index 034b10f..1ad4d21 100644 --- a/pf4j/src/test/java/org/pf4j/test/PluginJar.java +++ b/pf4j/src/test/java/org/pf4j/test/PluginJar.java @@ -16,6 +16,7 @@ package org.pf4j.test; import org.pf4j.ManifestPluginDescriptorFinder; +import org.pf4j.processor.LegacyExtensionStorage; import java.io.ByteArrayOutputStream; import java.io.File; @@ -28,7 +29,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; @@ -53,46 +53,50 @@ public class PluginJar { this.pluginVersion = builder.pluginVersion; } + /** + * Returns the {@code jar} file path. + */ public Path path() { return path; } + /** + * Returns the {@code jar} file. + */ public File file() { return path.toFile(); } + /** + * Returns the plugin class. + */ public String pluginClass() { return pluginClass; } + /** + * Returns the plugin id. + */ public String pluginId() { return pluginId; } + /** + * Returns the plugin version. + */ public String pluginVersion() { return pluginVersion; } - public static Manifest createManifest(Map<String, String> map) { - Manifest manifest = new Manifest(); - Attributes attributes = manifest.getMainAttributes(); - attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0.0"); - for (Map.Entry<String, String> entry : map.entrySet()) { - attributes.put(new Attributes.Name(entry.getKey()), entry.getValue()); - } - - return manifest; - } - public static class Builder { private final Path path; private final String pluginId; + private final Map<String, String> manifestAttributes = new LinkedHashMap<>(); + private final Set<String> extensions = new LinkedHashSet<>(); private String pluginClass; private String pluginVersion; - private Map<String, String> manifestAttributes = new LinkedHashMap<>(); - private Set<String> extensions = new LinkedHashSet<>(); private ClassDataProvider classDataProvider = new DefaultClassDataProvider(); public Builder(Path path, String pluginId) { @@ -144,13 +148,15 @@ public class PluginJar { return this; } + /** + * Builds the {@link PluginJar} instance. + */ public PluginJar build() throws IOException { - Manifest manifest = createManifest(); try (OutputStream outputStream = new FileOutputStream(path.toFile()); - JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) { + JarOutputStream jarOutputStream = new JarOutputStream(outputStream, createManifest())) { if (!extensions.isEmpty()) { // add extensions.idx - JarEntry jarEntry = new JarEntry("META-INF/extensions.idx"); + JarEntry jarEntry = new JarEntry(LegacyExtensionStorage.EXTENSIONS_RESOURCE); jarOutputStream.putNextEntry(jarEntry); jarOutputStream.write(extensionsAsByteArray()); jarOutputStream.closeEntry(); @@ -175,11 +181,9 @@ public class PluginJar { if (pluginClass != null) { map.put(ManifestPluginDescriptorFinder.PLUGIN_CLASS, pluginClass); } - if (manifestAttributes != null) { - map.putAll(manifestAttributes); - } + map.putAll(manifestAttributes); - return PluginJar.createManifest(map); + return ManifestUtils.createManifest(map); } private byte[] extensionsAsByteArray() throws IOException { diff --git a/pf4j/src/test/java/org/pf4j/test/PluginZip.java b/pf4j/src/test/java/org/pf4j/test/PluginZip.java index 4b9a0c9..45fb3b2 100644 --- a/pf4j/src/test/java/org/pf4j/test/PluginZip.java +++ b/pf4j/src/test/java/org/pf4j/test/PluginZip.java @@ -16,6 +16,7 @@ package org.pf4j.test; import org.pf4j.PropertiesPluginDescriptorFinder; +import org.pf4j.util.FileUtils; import java.io.File; import java.io.FileOutputStream; @@ -49,52 +50,79 @@ public class PluginZip { this.pluginDependencies = builder.pluginDependencies; } + /** + * Returns the path of the {@code zip} file. + */ public Path path() { return path; } + /** + * Returns the {@code zip} file. + */ public File file() { return path.toFile(); } + /** + * Returns the plugin id. + */ public String pluginId() { return pluginId; } + /** + * Returns the plugin class. + */ public String pluginClass() { return pluginClass; } + /** + * Returns the plugin version. + */ public String pluginVersion() { return pluginVersion; } + /** + * Returns the plugin dependencies. + */ public String pluginDependencies() { return pluginDependencies; } + /** + * Returns the path where the {@code zip} file will be unzipped. + */ public Path unzippedPath() { - Path path = path(); - String fileName = path.getFileName().toString(); + Path zipPath = path(); + String fileName = zipPath.getFileName().toString(); - return path.getParent().resolve(fileName.substring(0, fileName.length() - 4)); // without ".zip" suffix + return zipPath.getParent().resolve(fileName.substring(0, fileName.length() - 4)); // without ".zip" suffix } - public static Properties createProperties(Map<String, String> map) { - Properties properties = new Properties(); - properties.putAll(map); - - return properties; + /** + * Unzips the {@code zip} file. + */ + public Path unzip() throws IOException { + return FileUtils.expandIfZip(path()); } + /** + * Builder for {@link PluginZip}. + * The {@code plugin.properties} file is created on the fly from the information supplied in this builder. + * The {@code plugin.properties} file is created in the root of the {@code zip} file. + * The {@code zip} file can contain extra files. + */ public static class Builder { private final Path path; private final String pluginId; + private final Map<String, String> properties = new LinkedHashMap<>(); + private final Map<Path, byte[]> files = new LinkedHashMap<>(); private String pluginClass; private String pluginVersion; private String pluginDependencies; - private Map<String, String> properties = new LinkedHashMap<>(); - private Map<Path, byte[]> files = new LinkedHashMap<>(); public Builder(Path path, String pluginId) { this.path = path; @@ -163,13 +191,28 @@ public class PluginZip { return this; } + /** + * Builds the {@link PluginZip} instance. + */ public PluginZip build() throws IOException { - createPropertiesFile(); + try (ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(path.toFile()))) { + ZipEntry propertiesFile = new ZipEntry(PropertiesPluginDescriptorFinder.DEFAULT_PROPERTIES_FILE_NAME); + outputStream.putNextEntry(propertiesFile); + createProperties().store(outputStream, ""); + outputStream.closeEntry(); + + for (Map.Entry<Path, byte[]> fileEntry : files.entrySet()) { + ZipEntry file = new ZipEntry(fileEntry.getKey().toString()); + outputStream.putNextEntry(file); + outputStream.write(fileEntry.getValue()); + outputStream.closeEntry(); + } + } return new PluginZip(this); } - protected void createPropertiesFile() throws IOException { + private Properties createProperties() { Map<String, String> map = new LinkedHashMap<>(); map.put(PropertiesPluginDescriptorFinder.PLUGIN_ID, pluginId); map.put(PropertiesPluginDescriptorFinder.PLUGIN_VERSION, pluginVersion); @@ -179,23 +222,10 @@ public class PluginZip { if (pluginClass != null) { map.put(PropertiesPluginDescriptorFinder.PLUGIN_CLASS, pluginClass); } - if (properties != null) { - map.putAll(properties); - } - try (ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(path.toFile()))) { - ZipEntry propertiesFile = new ZipEntry(PropertiesPluginDescriptorFinder.DEFAULT_PROPERTIES_FILE_NAME); - outputStream.putNextEntry(propertiesFile); - createProperties(map).store(outputStream, ""); - outputStream.closeEntry(); + map.putAll(properties); - for (Map.Entry<Path, byte[]> fileEntry : files.entrySet()) { - ZipEntry file = new ZipEntry(fileEntry.getKey().toString()); - outputStream.putNextEntry(file); - outputStream.write(fileEntry.getValue()); - outputStream.closeEntry(); - } - } + return PropertiesUtils.createProperties(map); } } diff --git a/pf4j/src/test/java/org/pf4j/test/PropertiesUtils.java b/pf4j/src/test/java/org/pf4j/test/PropertiesUtils.java new file mode 100644 index 0000000..72b3751 --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/test/PropertiesUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * 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 org.pf4j.test; + +import java.util.Map; +import java.util.Properties; + +public class PropertiesUtils { + + private PropertiesUtils() {} + + /** + * Creates a {@link Properties} object from the given map. + */ + public static Properties createProperties(Map<String, String> map) { + Properties properties = new Properties(); + properties.putAll(map); + + return properties; + } + +} |