From 628bc9d92d483aef7318d02df97ca33d897de8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20L=C3=B6vdahl?= Date: Wed, 4 Sep 2019 12:45:07 +0300 Subject: [PATCH] Implement PluginClassLoader.getResources (#336) (#337) --- .../main/java/org/pf4j/PluginClassLoader.java | 20 ++ .../java/org/pf4j/PluginClassLoaderTest.java | 230 ++++++++++++++++++ .../test/java/org/pf4j/plugin/PluginZip.java | 44 +++- 3 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java diff --git a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java index 022b61c..755921a 100644 --- a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java +++ b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java @@ -22,6 +22,9 @@ import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; import java.util.List; /** @@ -189,6 +192,23 @@ public class PluginClassLoader extends URLClassLoader { } } + @Override + public Enumeration getResources(String name) throws IOException { + if (!parentFirst) { + List resources = new ArrayList<>(); + + resources.addAll(Collections.list(findResources(name))); + + if (getParent() != null) { + resources.addAll(Collections.list(getParent().getResources(name))); + } + + return Collections.enumeration(resources); + } else { + return super.getResources(name); + } + } + private Class loadClassFromDependencies(String className) { log.trace("Search in dependencies for class '{}'", className); List dependencies = pluginDescriptor.getDependencies(); diff --git a/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java b/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java new file mode 100644 index 0000000..64adbc1 --- /dev/null +++ b/pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java @@ -0,0 +1,230 @@ +/* + * 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; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.pf4j.plugin.PluginZip; +import org.pf4j.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +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.Collections; +import java.util.Enumeration; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Sebastian Lövdahl + */ +public class PluginClassLoaderTest { + + private DefaultPluginManager pluginManager; + private DefaultPluginDescriptor pluginDescriptor; + + private PluginClassLoader parentLastPluginClassLoader; + private PluginClassLoader parentFirstPluginClassLoader; + + @TempDir + Path pluginsPath; + + @BeforeAll + static void setUpGlobal() throws IOException, URISyntaxException { + Path parentClassPathBase = Paths.get(PluginClassLoaderTest.class.getClassLoader().getResource(".").toURI()); + + File metaInfFile = parentClassPathBase.resolve("META-INF").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-plugin")); + } + + private static void createFile(Path pathToFile) throws IOException { + File file = pathToFile.toFile(); + + file.deleteOnExit(); + assertTrue(file.createNewFile(), "failed to create '" + pathToFile + "'"); + try (PrintWriter printWriter = new PrintWriter(file)) { + printWriter.write("parent"); + } + } + + @BeforeEach + void setUp() throws IOException { + pluginManager = new DefaultPluginManager(pluginsPath); + + pluginDescriptor = new DefaultPluginDescriptor(); + pluginDescriptor.setPluginId("myPlugin"); + pluginDescriptor.setPluginVersion("1.2.3"); + pluginDescriptor.setPluginDescription("My plugin"); + pluginDescriptor.setDependencies("bar, baz"); + pluginDescriptor.setProvider("Me"); + pluginDescriptor.setRequires("5.0.0"); + + 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-plugin"), "plugin") + .build(); + + FileUtils.expandIfZip(pluginZip.path()); + + PluginClasspath pluginClasspath = new DefaultPluginClasspath(); + + parentLastPluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, PluginClassLoaderTest.class.getClassLoader()); + parentFirstPluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, PluginClassLoaderTest.class.getClassLoader(), true); + + for (String classesDirectory : pluginClasspath.getClassesDirectories()) { + File classesDirectoryFile = pluginZip.unzippedPath().resolve(classesDirectory).toFile(); + parentLastPluginClassLoader.addFile(classesDirectoryFile); + parentFirstPluginClassLoader.addFile(classesDirectoryFile); + } + + for (String jarsDirectory : pluginClasspath.getJarsDirectories()) { + Path jarsDirectoryPath = pluginZip.unzippedPath().resolve(jarsDirectory); + List jars = FileUtils.getJars(jarsDirectoryPath); + for (File jar : jars) { + parentLastPluginClassLoader.addFile(jar); + parentFirstPluginClassLoader.addFile(jar); + } + } + } + + @AfterEach + void tearDown() { + pluginManager = null; + pluginDescriptor = null; + } + + @Test + void parentLastGetResourceNonExisting() { + assertNull(parentLastPluginClassLoader.getResource("META-INF/non-existing-file")); + } + + @Test + void parentFirstGetResourceNonExisting() { + assertNull(parentFirstPluginClassLoader.getResource("META-INF/non-existing-file")); + } + + @Test + void parentLastGetResourceExistsInParent() throws IOException, URISyntaxException { + URL resource = parentLastPluginClassLoader.getResource("META-INF/file-only-in-parent"); + assertFirstLine("parent", resource); + } + + @Test + void parentFirstGetResourceExistsInParent() throws IOException, URISyntaxException { + URL resource = parentFirstPluginClassLoader.getResource("META-INF/file-only-in-parent"); + assertFirstLine("parent", resource); + } + + @Test + void parentLastGetResourceExistsOnlyInPlugin() throws IOException, URISyntaxException { + URL resource = parentLastPluginClassLoader.getResource("META-INF/plugin-file"); + assertFirstLine("plugin", resource); + } + + @Test + void parentFirstGetResourceExistsOnlyInPlugin() throws IOException, URISyntaxException { + URL resource = parentFirstPluginClassLoader.getResource("META-INF/plugin-file"); + assertFirstLine("plugin", resource); + } + + @Test + void parentLastGetResourceExistsInBothParentAndPlugin() throws URISyntaxException, IOException { + URL resource = parentLastPluginClassLoader.getResource("META-INF/file-in-both-parent-and-plugin"); + assertFirstLine("plugin", resource); + } + + @Test + void parentFirstGetResourceExistsInBothParentAndPlugin() throws URISyntaxException, IOException { + URL resource = parentFirstPluginClassLoader.getResource("META-INF/file-in-both-parent-and-plugin"); + assertFirstLine("parent", resource); + } + + @Test + void parentLastGetResourcesNonExisting() throws IOException { + assertFalse(parentLastPluginClassLoader.getResources("META-INF/non-existing-file").hasMoreElements()); + } + + @Test + void parentFirstGetResourcesNonExisting() throws IOException { + assertFalse(parentFirstPluginClassLoader.getResources("META-INF/non-existing-file").hasMoreElements()); + } + + @Test + void parentLastGetResourcesExistsInParent() throws IOException, URISyntaxException { + Enumeration resources = parentLastPluginClassLoader.getResources("META-INF/file-only-in-parent"); + assertNumberOfResourcesAndFirstLineOfFirstElement(1, "parent", resources); + } + + @Test + void parentFirstGetResourcesExistsInParent() throws IOException, URISyntaxException { + Enumeration resources = parentFirstPluginClassLoader.getResources("META-INF/file-only-in-parent"); + assertNumberOfResourcesAndFirstLineOfFirstElement(1, "parent", resources); + } + + @Test + void parentLastGetResourcesExistsOnlyInPlugin() throws IOException, URISyntaxException { + Enumeration resources = parentLastPluginClassLoader.getResources("META-INF/plugin-file"); + assertNumberOfResourcesAndFirstLineOfFirstElement(1, "plugin", resources); + } + + @Test + void parentFirstGetResourcesExistsOnlyInPlugin() throws IOException, URISyntaxException { + Enumeration resources = parentFirstPluginClassLoader.getResources("META-INF/plugin-file"); + assertNumberOfResourcesAndFirstLineOfFirstElement(1, "plugin", resources); + } + + @Test + void parentLastGetResourcesExistsInBothParentAndPlugin() throws URISyntaxException, IOException { + Enumeration resources = parentLastPluginClassLoader.getResources("META-INF/file-in-both-parent-and-plugin"); + assertNumberOfResourcesAndFirstLineOfFirstElement(2, "plugin", resources); + } + + @Test + void parentFirstGetResourcesExistsInBothParentAndPlugin() throws URISyntaxException, IOException { + Enumeration resources = parentFirstPluginClassLoader.getResources("META-INF/file-in-both-parent-and-plugin"); + assertNumberOfResourcesAndFirstLineOfFirstElement(2, "parent", resources); + } + + private static void assertFirstLine(String expected, URL resource) throws URISyntaxException, IOException { + assertNotNull(resource); + assertEquals(expected, Files.readAllLines(Paths.get(resource.toURI())).get(0)); + } + + private static void assertNumberOfResourcesAndFirstLineOfFirstElement(int expectedCount, String expectedFirstLine, Enumeration resources) throws URISyntaxException, IOException { + List list = Collections.list(resources); + assertEquals(expectedCount, list.size()); + + URL firstResource = list.get(0); + assertEquals(expectedFirstLine, Files.readAllLines(Paths.get(firstResource.toURI())).get(0)); + } +} diff --git a/pf4j/src/test/java/org/pf4j/plugin/PluginZip.java b/pf4j/src/test/java/org/pf4j/plugin/PluginZip.java index f34d84a..c2da257 100644 --- a/pf4j/src/test/java/org/pf4j/plugin/PluginZip.java +++ b/pf4j/src/test/java/org/pf4j/plugin/PluginZip.java @@ -89,6 +89,7 @@ public class PluginZip { private String pluginClass; private String pluginVersion; private Map properties = new LinkedHashMap<>(); + private Map files = new LinkedHashMap<>(); public Builder(Path path, String pluginId) { this.path = path; @@ -127,6 +128,30 @@ public class PluginZip { return this; } + /** + * Adds a file to the archive. + * + * @param path the relative path of the file + * @param content the content of the file + */ + public Builder addFile(Path path, byte[] content) { + files.put(path, content.clone()); + + return this; + } + + /** + * Adds a file to the archive. + * + * @param path the relative path of the file + * @param content the content of the file + */ + public Builder addFile(Path path, String content) { + files.put(path, content.getBytes()); + + return this; + } + public PluginZip build() throws IOException { createPropertiesFile(); @@ -144,12 +169,19 @@ public class PluginZip { map.putAll(properties); } - 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(); - outputStream.close(); + 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(); + + for (Map.Entry fileEntry : files.entrySet()) { + ZipEntry file = new ZipEntry(fileEntry.getKey().toString()); + outputStream.putNextEntry(file); + outputStream.write(fileEntry.getValue()); + outputStream.closeEntry(); + } + } } }