]> source.dussan.org Git - pf4j.git/commitdiff
load extensions from classpath; before this commit only plugins can declare extensions
authorDecebal Suiu <decebal.suiu@gmail.com>
Wed, 11 Jun 2014 10:30:30 +0000 (13:30 +0300)
committerDecebal Suiu <decebal.suiu@gmail.com>
Wed, 11 Jun 2014 10:30:30 +0000 (13:30 +0300)
README.md
demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java
demo/app/src/main/java/ro/fortsoft/pf4j/demo/WhazzupGreeting.java [new file with mode: 0644]
demo/app/src/main/resources/log4j.properties
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java

index 9ac353bd0c653d9565b237f2e4d9008dc6addc61..cfa8a1365d244fa85a5ef804b6ed990bdcb34a73 100644 (file)
--- a/README.md
+++ b/README.md
@@ -138,6 +138,8 @@ The output is:
     >>> Welcome
     >>> Hello
 
+**NOTE:** Starting with version 0.9 you can define an extension directly in a jar (in classpath). See [WhazzupGreeting](https://github.com/decebals/pf4j/blob/master/demo/app/src/main/java/ro/fortsoft/pf4j/WhazzupGreeting.java) for a real example.
+
 You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder, PluginClasspath, ...) in DefaultPluginManager just override `create...` methods (factory method pattern).
 
 Example:
index 453e2c2fe00e3f2b324ff00f8de2ee8e48f2ceed..261953174891a7398834a9de67a001134c5069fe 100644 (file)
@@ -52,12 +52,19 @@ public class Boot {
             System.out.println(">>> " + greeting.getGreeting());\r
         }\r
 \r
+        // print extensions from classpath (non plugin)\r
+        System.out.println(String.format("Extensions added by classpath:"));\r
+        Set<String> extensionClassNames = pluginManager.getExtensionClassNames(null);\r
+        for (String extension : extensionClassNames) {\r
+            System.out.println("   " + extension);\r
+        }\r
+\r
         // print extensions for each started plugin\r
         List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();\r
         for (PluginWrapper plugin : startedPlugins) {\r
             String pluginId = plugin.getDescriptor().getPluginId();\r
             System.out.println(String.format("Extensions added by plugin '%s':", pluginId));\r
-            Set<String> extensionClassNames = pluginManager.getExtensionClassNames(pluginId);\r
+            extensionClassNames = pluginManager.getExtensionClassNames(pluginId);\r
             for (String extension : extensionClassNames) {\r
                 System.out.println("   " + extension);\r
             }\r
diff --git a/demo/app/src/main/java/ro/fortsoft/pf4j/demo/WhazzupGreeting.java b/demo/app/src/main/java/ro/fortsoft/pf4j/demo/WhazzupGreeting.java
new file mode 100644 (file)
index 0000000..9cf7e35
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 Decebal Suiu
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
+ * the License. You may obtain a copy of the License in the LICENSE file, or 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 ro.fortsoft.pf4j.demo;
+
+import ro.fortsoft.pf4j.Extension;
+import ro.fortsoft.pf4j.demo.api.Greeting;
+
+/**
+ * @author Decebal Suiu
+ */
+@Extension
+public class WhazzupGreeting implements Greeting {
+
+    @Override
+    public String getGreeting() {
+        return "Whazzup";
+    }
+
+}
index 066d5e5d005773f621302b69e962f32bc8aedb63..16ce034919727b8f77a5e044040a713729ac263f 100644 (file)
@@ -4,6 +4,7 @@ log4j.rootLogger=DEBUG, Console
 # PF4J log
 #
 log4j.logger.ro.fortsoft.pf4j=DEBUG, Console
+log4j.logger.ro.fortsoft.pf4j.PluginClassLoader=WARN, Console
 log4j.additivity.ro.fortsoft.pf4j=false
 
 #
index 3234163d2e4170eca2d6de5cac4f217946103869..0f04ed7a72acc5840d11fb4c9e4dc2cb8b176100 100644 (file)
@@ -57,16 +57,24 @@ public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListe
         for (Map.Entry<String, Set<String>> entry : entries.entrySet()) {
             String pluginId = entry.getKey();
 
-            PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
-            if (PluginState.STARTED != pluginWrapper.getPluginState()) {
-               continue;
+            if (pluginId != null) {
+                PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
+                if (PluginState.STARTED != pluginWrapper.getPluginState()) {
+                    continue;
+                }
             }
 
             Set<String> extensionClassNames = entry.getValue();
 
             for (String className : extensionClassNames) {
                 try {
-                    Class<?> extensionType = pluginManager.getPluginClassLoader(pluginId).loadClass(className);
+                    Class<?> extensionType;
+                    if (pluginId != null) {
+                        extensionType = pluginManager.getPluginClassLoader(pluginId).loadClass(className);
+                    } else {
+                        extensionType = getClass().getClassLoader().loadClass(className);
+                    }
+
                     log.debug("Checking extension type '{}'", extensionType.getName());
                     if (type.isAssignableFrom(extensionType)) {
                         Object instance = extensionFactory.create(extensionType);
@@ -140,36 +148,71 @@ public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListe
             return entries;
         }
 
-        entries = new HashMap<String, Set<String>>();
+        entries = new LinkedHashMap<String, Set<String>>();
+
+        readClasspathIndexFiles();
+        readPluginsIndexFiles();
+
+        return entries;
+    }
+
+    private void readClasspathIndexFiles() {
+        log.debug("Reading extensions index files from classpath");
+
+        Set<String> bucket = new HashSet<String>();
+        try {
+            Enumeration<URL> urls = getClass().getClassLoader().getResources(ExtensionsIndexer.EXTENSIONS_RESOURCE);
+            while (urls.hasMoreElements()) {
+                URL url = urls.nextElement();
+                log.debug("Read '{}'", url.getFile());
+                Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
+                ExtensionsIndexer.readIndex(reader, bucket);
+            }
+
+            if (bucket.isEmpty()) {
+                log.debug("No extensions found");
+            } else {
+                log.debug("Found possible {} extensions:", bucket.size());
+                for (String entry : bucket) {
+                    log.debug("   " + entry);
+                }
+            }
+
+            entries.put(null, bucket);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    private void readPluginsIndexFiles() {
+        log.debug("Reading extensions index files from plugins");
 
         List<PluginWrapper> plugins = pluginManager.getPlugins();
         for (PluginWrapper plugin : plugins) {
             String pluginId = plugin.getDescriptor().getPluginId();
             log.debug("Reading extensions index file for plugin '{}'", pluginId);
-            Set<String> entriesPerPlugin = new HashSet<String>();
+            Set<String> bucket = new HashSet<String>();
 
             try {
                 URL url = plugin.getPluginClassLoader().getResource(ExtensionsIndexer.EXTENSIONS_RESOURCE);
                 log.debug("Read '{}'", url.getFile());
                 Reader reader = new InputStreamReader(url.openStream(), "UTF-8");
-                ExtensionsIndexer.readIndex(reader, entriesPerPlugin);
+                ExtensionsIndexer.readIndex(reader, bucket);
 
-                if (entriesPerPlugin.isEmpty()) {
+                if (bucket.isEmpty()) {
                     log.debug("No extensions found");
                 } else {
-                    log.debug("Found possible {} extensions:", entriesPerPlugin.size());
-                    for (String entry : entriesPerPlugin) {
+                    log.debug("Found possible {} extensions:", bucket.size());
+                    for (String entry : bucket) {
                         log.debug("   " + entry);
                     }
                 }
 
-                entries.put(pluginId, entriesPerPlugin);
+                entries.put(pluginId, bucket);
             } catch (IOException e) {
                 log.error(e.getMessage(), e);
             }
         }
-
-        return entries;
     }
 
     private boolean isExtensionPoint(Class<?> type) {
index 055c16357b5b9a7af2ad8a31b2e422ad74472575..81675651bde8e6e4a5cdfdb526090357fb6ac893 100644 (file)
@@ -29,8 +29,6 @@ public class PluginClassLoader extends URLClassLoader {
 
     private static final Logger log = LoggerFactory.getLogger(PluginClassLoader.class);
 
-//     private static final String JAVA_PACKAGE_PREFIX = "java.";
-//     private static final String JAVAX_PACKAGE_PREFIX = "javax.";
        private static final String PLUGIN_PACKAGE_PREFIX = "ro.fortsoft.pf4j.";
 
        private PluginManager pluginManager;
@@ -48,41 +46,45 @@ public class PluginClassLoader extends URLClassLoader {
                super.addURL(url);
        }
 
+    /**
+     * This implementation of loadClass uses a child first delegation model rather than the standard parent first.
+     * If the requested class cannot be found in this class loader, the parent class loader will be consulted
+     * via the standard ClassLoader.loadClass(String) mechanism.
+     */
        @Override
     public Class<?> loadClass(String className) throws ClassNotFoundException {
-//             System.out.println(">>>" + className);
-
-               /*
-                // javax.mail is not in JDK ?!
-               // first check whether it's a system class, delegate to the system loader
-               if (className.startsWith(JAVA_PACKAGE_PREFIX) || className.startsWith(JAVAX_PACKAGE_PREFIX)) {
-                       return findSystemClass(className);
-               }
-               */
-
+        log.debug("Received request to load class '{}'", className);
         // if the class it's a part of the plugin engine use parent class loader
         if (className.startsWith(PLUGIN_PACKAGE_PREFIX)) {
+            log.debug("Delegate the loading of class '{}' to parent", className);
             try {
-                return PluginClassLoader.class.getClassLoader().loadClass(className);
+                return getClass().getClassLoader().loadClass(className);
             } catch (ClassNotFoundException e) {
                 // try next step
+                // TODO if I uncomment below lines (the correct approach) I received ClassNotFoundException for demo (ro.fortsoft.pf4j.demo)
+//                log.error(e.getMessage(), e);
+//                throw e;
             }
         }
 
         // second check whether it's already been loaded
-        Class<?> loadedClass = findLoadedClass(className);
-        if (loadedClass != null) {
-               return loadedClass;
+        Class<?> clazz = findLoadedClass(className);
+        if (clazz != null) {
+            log.debug("Found loaded class '{}'", className);
+               return clazz;
         }
 
         // nope, try to load locally
         try {
-               return findClass(className);
+            clazz = findClass(className);
+            log.debug("Found class '{}' in plugin classpath", className);
+            return clazz;
         } catch (ClassNotFoundException e) {
                // try next step
         }
 
         // look in dependencies
+        log.debug("Look in dependencies for class '{}'", className);
         List<PluginDependency> dependencies = pluginDescriptor.getDependencies();
         for (PluginDependency dependency : dependencies) {
                PluginClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId());
@@ -93,10 +95,33 @@ public class PluginClassLoader extends URLClassLoader {
                }
         }
 
+        log.debug("Couldn't find class '{}' in plugin classpath. Delegating to parent");
+
         // use the standard URLClassLoader (which follows normal parent delegation)
         return super.loadClass(className);
     }
 
+    /**
+     * Load the named resource from this plugin. This implementation checks the plugin's classpath first
+     * then delegates to the parent.
+     *
+     * @param name the name of the resource.
+     * @return the URL to the resource, <code>null</code> if the resource was not found.
+     */
+    @Override
+    public URL getResource(String name) {
+        log.debug("Trying to find resource '{}' in plugin classpath", name);
+        URL url = findResource(name);
+        if (url != null) {
+            log.debug("Found resource '{}' in plugin classpath", name);
+            return url;
+        }
+
+        log.debug("Couldn't find resource '{}' in plugin classpath. Delegating to parent");
+
+        return super.getResource(name);
+    }
+
     /**
      * Release all resources acquired by this class loader.
      * The current implementation is incomplete.