]> source.dussan.org Git - pf4j.git/commitdiff
Improve PluginJar, add ClassDataProvider concept
authorDecebal Suiu <decebal.suiu@gmail.com>
Fri, 31 May 2019 17:02:09 +0000 (20:02 +0300)
committerDecebal Suiu <decebal.suiu@gmail.com>
Fri, 31 May 2019 17:02:09 +0000 (20:02 +0300)
pf4j/src/test/java/org/pf4j/JarPluginManagerTest.java
pf4j/src/test/java/org/pf4j/plugin/ClassDataProvider.java [new file with mode: 0644]
pf4j/src/test/java/org/pf4j/plugin/DefaultClassDataProvider.java [new file with mode: 0644]
pf4j/src/test/java/org/pf4j/plugin/FailTestExtension.java
pf4j/src/test/java/org/pf4j/plugin/PluginJar.java
pf4j/src/test/java/org/pf4j/plugin/TestExtension.java
pf4j/src/test/java/org/pf4j/plugin/TestExtensionPoint.java

index 45c3999d48a102c48063c52b96743220b3ce2cec..d9ca90a78981e737ed569fb79c3b94ea4c9333d8 100644 (file)
@@ -20,10 +20,13 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.pf4j.plugin.PluginJar;
+import org.pf4j.plugin.TestExtension;
+import org.pf4j.plugin.TestExtensionPoint;
 import org.pf4j.plugin.TestPlugin;
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -42,6 +45,7 @@ public class JarPluginManagerTest {
         pluginJar = new PluginJar.Builder(pluginsPath.resolve("test-plugin.jar"), "test-plugin")
             .pluginClass(TestPlugin.class.getName())
             .pluginVersion("1.2.3")
+            .extension(TestExtension.class.getName())
             .build();
 
         pluginManager = new JarPluginManager(pluginsPath);
@@ -53,6 +57,18 @@ public class JarPluginManagerTest {
         pluginManager = null;
     }
 
+    @Test
+    public void getExtensions() {
+        pluginManager.loadPlugins();
+        pluginManager.startPlugins();
+
+        List<TestExtensionPoint> extensions = pluginManager.getExtensions(TestExtensionPoint.class);
+        assertEquals(1, extensions.size());
+
+        String something = extensions.get(0).saySomething();
+        assertEquals(new TestExtension().saySomething(), something);
+    }
+
     @Test
     public void unloadPlugin() throws Exception {
         pluginManager.loadPlugins();
diff --git a/pf4j/src/test/java/org/pf4j/plugin/ClassDataProvider.java b/pf4j/src/test/java/org/pf4j/plugin/ClassDataProvider.java
new file mode 100644 (file)
index 0000000..ff55967
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.plugin;
+
+/**
+ * Defines the interface for classes that know to supply class data for a class name.
+ * The idea is to have the possibility to retrieve the data for a class from different sources:
+ * <ul>
+ * <li>Class path - the class is already loaded by the class loader</li>
+ * <li>String - the string (the source code) is compiled dynamically via {@link javax.tools.JavaCompiler}</>
+ * <li>Generate the source code programmatically using something like {@code https://github.com/square/javapoet}</li>
+ * </ul>
+ *
+ * @author Decebal Suiu
+ */
+public interface ClassDataProvider {
+
+    byte[] getClassData(String className);
+
+}
diff --git a/pf4j/src/test/java/org/pf4j/plugin/DefaultClassDataProvider.java b/pf4j/src/test/java/org/pf4j/plugin/DefaultClassDataProvider.java
new file mode 100644 (file)
index 0000000..ef0eaf9
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.plugin;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Get class data from the class path.
+ *
+ * @author Decebal Suiu
+ */
+public class DefaultClassDataProvider implements ClassDataProvider {
+
+    @Override
+    public byte[] getClassData(String className) {
+        String path = className.replace('.', '/') + ".class";
+        InputStream classDataStream = getClass().getClassLoader().getResourceAsStream(path);
+        if (classDataStream == null) {
+            throw new RuntimeException("Cannot find class data");
+        }
+
+        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+            copyStream(classDataStream, outputStream);
+            return outputStream.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    private void copyStream(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+
+        int bytesRead;
+        while ((bytesRead = in.read(buffer)) != -1) {
+            out.write(buffer, 0, bytesRead);
+        }
+    }
+
+}
index 9cc37e065d6ac3ef0a0f2594143d9133d0b54564..13b51eebd752984f09edbc2adc06cb812b2a5053 100644 (file)
@@ -26,4 +26,9 @@ public class FailTestExtension implements TestExtensionPoint {
     public FailTestExtension(String name) {
     }
 
+    @Override
+    public String saySomething() {
+        return "I am a fail test extension";
+    }
+
 }
index 366c22ade64c4152e6683de5ea80d24c74885c41..a75b68fa21841df4708fbe1d30144fc1c4b10943 100644 (file)
@@ -17,18 +17,23 @@ package org.pf4j.plugin;
 
 import org.pf4j.ManifestPluginDescriptorFinder;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.nio.file.Path;
 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;
 
 /**
- /**
  * Represents a plugin {@code jar} file.
  * The {@code MANIFEST.MF} file is created on the fly from the information supplied in {@link Builder}.
  *
@@ -87,6 +92,8 @@ public class PluginJar {
         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) {
             this.path = path;
@@ -125,13 +132,44 @@ public class PluginJar {
             return this;
         }
 
+        public Builder extension(String extensionClassName) {
+            extensions.add(extensionClassName);
+
+            return this;
+        }
+
+        public Builder classDataProvider(ClassDataProvider classDataProvider) {
+             this.classDataProvider = classDataProvider;
+
+             return this;
+        }
+
         public PluginJar build() throws IOException {
-            createManifestFile();
+            Manifest manifest = createManifest();
+            try (OutputStream outputStream = new FileOutputStream(path.toFile())) {
+                JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest);
+                if (!extensions.isEmpty()) {
+                    // add extensions.idx
+                    JarEntry jarEntry = new JarEntry("META-INF/extensions.idx");
+                    jarOutputStream.putNextEntry(jarEntry);
+                    jarOutputStream.write(extensionsAsByteArray());
+                    jarOutputStream.closeEntry();
+                    // add extensions classes
+                    for (String extension : extensions) {
+                        String extensionPath = extension.replace('.', '/') + ".class";
+                        JarEntry classEntry = new JarEntry(extensionPath);
+                        jarOutputStream.putNextEntry(classEntry);
+                        jarOutputStream.write(classDataProvider.getClassData(extension));
+                        jarOutputStream.closeEntry();
+                    }
+                }
+                jarOutputStream.close();
+            }
 
             return new PluginJar(this);
         }
 
-        protected void createManifestFile() throws IOException {
+        private Manifest createManifest() {
             Map<String, String> map = new LinkedHashMap<>();
             map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, pluginId);
             map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, pluginVersion);
@@ -142,9 +180,19 @@ public class PluginJar {
                 map.putAll(manifestAttributes);
             }
 
-            Manifest manifest = createManifest(map);
-            JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(path.toFile()), manifest);
-            outputStream.close();
+            return PluginJar.createManifest(map);
+        }
+
+        private byte[] extensionsAsByteArray() throws IOException {
+            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+                PrintWriter writer = new PrintWriter(outputStream);
+                for (String extension : extensions) {
+                    writer.println(extension);
+                }
+                writer.flush();
+
+                return outputStream.toByteArray();
+            }
         }
 
     }
index 5f48c9aacaa3231040b77b4b87a65a9b6d61df21..83bc0b5e94b6c5d713c878650f3aa075fa741d28 100644 (file)
@@ -23,4 +23,9 @@ import org.pf4j.Extension;
 @Extension
 public class TestExtension implements TestExtensionPoint {
 
+    @Override
+    public String saySomething() {
+        return "I am a test extension";
+    }
+
 }
index a33ac402cc42f97925653d87b568e27af7b49daa..d29a7ab5d0cc7ee905bbd935427224bf1b6bb32b 100644 (file)
@@ -22,4 +22,6 @@ import org.pf4j.ExtensionPoint;
  */
 public interface TestExtensionPoint extends ExtensionPoint {
 
+    String saySomething();
+
 }