Browse Source

load extensions from classpath; before this commit only plugins can declare extensions

pull/22/head
Decebal Suiu 11 years ago
parent
commit
e58e71a098
  1. 2
      README.md
  2. 9
      demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java
  3. 29
      demo/app/src/main/java/ro/fortsoft/pf4j/demo/WhazzupGreeting.java
  4. 1
      demo/app/src/main/resources/log4j.properties
  5. 69
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
  6. 59
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java

2
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:

9
demo/app/src/main/java/ro/fortsoft/pf4j/demo/Boot.java

@ -52,12 +52,19 @@ public class Boot {
System.out.println(">>> " + greeting.getGreeting());
}
// print extensions from classpath (non plugin)
System.out.println(String.format("Extensions added by classpath:"));
Set<String> extensionClassNames = pluginManager.getExtensionClassNames(null);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
}
// print extensions for each started plugin
List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();
for (PluginWrapper plugin : startedPlugins) {
String pluginId = plugin.getDescriptor().getPluginId();
System.out.println(String.format("Extensions added by plugin '%s':", pluginId));
Set<String> extensionClassNames = pluginManager.getExtensionClassNames(pluginId);
extensionClassNames = pluginManager.getExtensionClassNames(pluginId);
for (String extension : extensionClassNames) {
System.out.println(" " + extension);
}

29
demo/app/src/main/java/ro/fortsoft/pf4j/demo/WhazzupGreeting.java

@ -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";
}
}

1
demo/app/src/main/resources/log4j.properties

@ -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
#

69
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java

@ -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) {

59
pf4j/src/main/java/ro/fortsoft/pf4j/PluginClassLoader.java

@ -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.

Loading…
Cancel
Save