]> source.dussan.org Git - pf4j.git/commitdiff
Implement PluginClassLoader.getResources (#336) (#337)
authorSebastian Lövdahl <slovdahl@hibox.fi>
Wed, 4 Sep 2019 09:45:07 +0000 (12:45 +0300)
committerDecebal Suiu <decebal.suiu@gmail.com>
Wed, 4 Sep 2019 09:45:07 +0000 (12:45 +0300)
pf4j/src/main/java/org/pf4j/PluginClassLoader.java
pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java [new file with mode: 0644]
pf4j/src/test/java/org/pf4j/plugin/PluginZip.java

index 022b61cf06e34536b9c8a6d3a1296f1fa0c9cb07..755921a388c6b8674a5b990e8f4f676132ac4ac7 100644 (file)
@@ -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<URL> getResources(String name) throws IOException {
+        if (!parentFirst) {
+            List<URL> 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<PluginDependency> 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 (file)
index 0000000..64adbc1
--- /dev/null
@@ -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<File> 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<URL> resources = parentLastPluginClassLoader.getResources("META-INF/file-only-in-parent");
+        assertNumberOfResourcesAndFirstLineOfFirstElement(1, "parent", resources);
+    }
+
+    @Test
+    void parentFirstGetResourcesExistsInParent() throws IOException, URISyntaxException {
+        Enumeration<URL> resources = parentFirstPluginClassLoader.getResources("META-INF/file-only-in-parent");
+        assertNumberOfResourcesAndFirstLineOfFirstElement(1, "parent", resources);
+    }
+
+    @Test
+    void parentLastGetResourcesExistsOnlyInPlugin() throws IOException, URISyntaxException {
+        Enumeration<URL> resources = parentLastPluginClassLoader.getResources("META-INF/plugin-file");
+        assertNumberOfResourcesAndFirstLineOfFirstElement(1, "plugin", resources);
+    }
+
+    @Test
+    void parentFirstGetResourcesExistsOnlyInPlugin() throws IOException, URISyntaxException {
+        Enumeration<URL> resources = parentFirstPluginClassLoader.getResources("META-INF/plugin-file");
+        assertNumberOfResourcesAndFirstLineOfFirstElement(1, "plugin", resources);
+    }
+
+    @Test
+    void parentLastGetResourcesExistsInBothParentAndPlugin() throws URISyntaxException, IOException {
+        Enumeration<URL> resources = parentLastPluginClassLoader.getResources("META-INF/file-in-both-parent-and-plugin");
+        assertNumberOfResourcesAndFirstLineOfFirstElement(2, "plugin", resources);
+    }
+
+    @Test
+    void parentFirstGetResourcesExistsInBothParentAndPlugin() throws URISyntaxException, IOException {
+        Enumeration<URL> 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<URL> resources) throws URISyntaxException, IOException {
+        List<URL> list = Collections.list(resources);
+        assertEquals(expectedCount, list.size());
+
+        URL firstResource = list.get(0);
+        assertEquals(expectedFirstLine, Files.readAllLines(Paths.get(firstResource.toURI())).get(0));
+    }
+}
index f34d84a3c19b76d6a683ba6d4b242bdee0b5237a..c2da257afcbb589b1b9d8d62f3069a8d3fb42a77 100644 (file)
@@ -89,6 +89,7 @@ public class PluginZip {
         private String pluginClass;
         private String pluginVersion;
         private Map<String, String> properties = new LinkedHashMap<>();
+        private Map<Path, byte[]> 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<Path, byte[]> fileEntry : files.entrySet()) {
+                    ZipEntry file = new ZipEntry(fileEntry.getKey().toString());
+                    outputStream.putNextEntry(file);
+                    outputStream.write(fileEntry.getValue());
+                    outputStream.closeEntry();
+                }
+            }
         }
 
     }