Browse Source

add PluginClasspath - now you can add any classes and lib directories to plugin classpath

pull/3/head
Decebal Suiu 11 years ago
parent
commit
5b30e5fed1
  1. 2
      demo/app/src/main/resources/log4j.properties
  2. 63
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultExtensionFinder.java
  3. 76
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java
  4. 18
      pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java
  5. 102
      pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java
  6. 59
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginClasspath.java
  7. 114
      pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java
  8. 80
      pf4j/src/main/java/ro/fortsoft/pf4j/SezpozExtensionFinder.java

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

@ -2,5 +2,5 @@ log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n log4j.appender.Console.layout.conversionPattern=%-5p - %-30.30c{1} - %m\n

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2013 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
@ -12,69 +12,16 @@
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
/** /**
* Using Sezpoz(http://sezpoz.java.net/) for extensions discovery. * The default implementation for ExtensionFinder.
* Now, this class it's a "link" to {@link ro.fortsoft.pf4j.SezpozExtensionFinder}.
* *
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultExtensionFinder implements ExtensionFinder { public class DefaultExtensionFinder extends SezpozExtensionFinder {
private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class);
private volatile List<IndexItem<Extension, Object>> indices;
private ClassLoader classLoader;
public DefaultExtensionFinder(ClassLoader classLoader) { public DefaultExtensionFinder(ClassLoader classLoader) {
this.classLoader = classLoader; super(classLoader);
} }
@Override
public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
log.debug("Find extensions for {}", type);
List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
getIndices();
// System.out.println("indices = "+ indices);
for (IndexItem<Extension, Object> item : indices) {
try {
AnnotatedElement element = item.element();
Class<?> extensionType = (Class<?>) element;
log.debug("Checking extension type {}", extensionType);
if (type.isAssignableFrom(extensionType)) {
Object instance = item.instance();
if (instance != null) {
log.debug("Added extension {}", extensionType);
result.add(new ExtensionWrapper<T>(type.cast(instance), item.annotation().ordinal()));
}
}
} catch (InstantiationException e) {
log.error(e.getMessage(), e);
}
}
return result;
}
private List<IndexItem<Extension, Object>> getIndices() {
if (indices == null) {
indices = new ArrayList<IndexItem<Extension, Object>>();
Iterator<IndexItem<Extension, Object>> it = Index.load(Extension.class, Object.class, classLoader).iterator();
while (it.hasNext()) {
indices.add(it.next());
}
}
return indices;
}
} }

76
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginDescriptorFinder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2012 Decebal Suiu * Copyright 2013 Decebal Suiu
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with * 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: * the License. You may obtain a copy of the License in the LICENSE file, or at:
@ -12,78 +12,16 @@
*/ */
package ro.fortsoft.pf4j; package ro.fortsoft.pf4j;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import ro.fortsoft.pf4j.util.StringUtils;
/** /**
* Read the plugin descriptor from the manifest file. * The default implementation for PluginDescriptorFinder.
* * Now, this class it's a "link" to {@link ro.fortsoft.pf4j.ManifestPluginDescriptorFinder}.
*
* @author Decebal Suiu * @author Decebal Suiu
*/ */
public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
@Override
public PluginDescriptor find(File pluginRepository) throws PluginException {
// TODO it's ok with classes/ ?
File manifestFile = new File(pluginRepository, "classes/META-INF/MANIFEST.MF");
if (!manifestFile.exists()) {
throw new PluginException("Cannot find '" + manifestFile + "' file");
}
FileInputStream input = null;
try {
input = new FileInputStream(manifestFile);
} catch (FileNotFoundException e) {
// not happening
}
Manifest manifest = null;
try {
manifest = new Manifest(input);
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
} finally {
try {
input.close();
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
}
}
PluginDescriptor pluginDescriptor = new PluginDescriptor();
// TODO validate !!!
Attributes attrs = manifest.getMainAttributes();
String id = attrs.getValue("Plugin-Id");
if (StringUtils.isEmpty(id)) {
throw new PluginException("Plugin-Id cannot be empty");
}
pluginDescriptor.setPluginId(id);
String clazz = attrs.getValue("Plugin-Class");
if (StringUtils.isEmpty(clazz)) {
throw new PluginException("Plugin-Class cannot be empty");
}
pluginDescriptor.setPluginClass(clazz);
String version = attrs.getValue("Plugin-Version");
if (StringUtils.isEmpty(version)) {
throw new PluginException("Plugin-Version cannot be empty");
}
pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version));
String provider = attrs.getValue("Plugin-Provider");
pluginDescriptor.setProvider(provider);
String dependencies = attrs.getValue("Plugin-Dependencies");
pluginDescriptor.setDependencies(dependencies);
return pluginDescriptor; public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) {
super(pluginClasspath);
} }
} }

18
pf4j/src/main/java/ro/fortsoft/pf4j/DefaultPluginManager.java

@ -47,9 +47,11 @@ public class DefaultPluginManager implements PluginManager {
*/ */
private File pluginsDirectory; private File pluginsDirectory;
private ExtensionFinder extensionFinder; private final ExtensionFinder extensionFinder;
private PluginDescriptorFinder pluginDescriptorFinder; private final PluginDescriptorFinder pluginDescriptorFinder;
private final PluginClasspath pluginClasspath;
/** /**
* A map of plugins this manager is responsible for (the key is the 'pluginId'). * A map of plugins this manager is responsible for (the key is the 'pluginId').
@ -114,6 +116,7 @@ public class DefaultPluginManager implements PluginManager {
disabledPlugins = new ArrayList<String>(); disabledPlugins = new ArrayList<String>();
compoundClassLoader = new CompoundClassLoader(); compoundClassLoader = new CompoundClassLoader();
pluginClasspath = createPluginClasspath();
pluginDescriptorFinder = createPluginDescriptorFinder(); pluginDescriptorFinder = createPluginDescriptorFinder();
extensionFinder = createExtensionFinder(); extensionFinder = createExtensionFinder();
@ -278,7 +281,7 @@ public class DefaultPluginManager implements PluginManager {
* Add the possibility to override the PluginDescriptorFinder. * Add the possibility to override the PluginDescriptorFinder.
*/ */
protected PluginDescriptorFinder createPluginDescriptorFinder() { protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new DefaultPluginDescriptorFinder(); return new DefaultPluginDescriptorFinder(pluginClasspath);
} }
/** /**
@ -288,6 +291,13 @@ public class DefaultPluginManager implements PluginManager {
return new DefaultExtensionFinder(compoundClassLoader); return new DefaultExtensionFinder(compoundClassLoader);
} }
/**
* Add the possibility to override the PluginClassPath.
*/
protected PluginClasspath createPluginClasspath() {
return new PluginClasspath();
}
protected boolean isPluginDisabled(String pluginId) { protected boolean isPluginDisabled(String pluginId) {
if (enabledPlugins.isEmpty()) { if (enabledPlugins.isEmpty()) {
return disabledPlugins.contains(pluginId); return disabledPlugins.contains(pluginId);
@ -325,7 +335,7 @@ public class DefaultPluginManager implements PluginManager {
// load plugin // load plugin
log.debug("Loading plugin '{}'", pluginPath); log.debug("Loading plugin '{}'", pluginPath);
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory); PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath);
pluginLoader.load(); pluginLoader.load();
log.debug("Loaded plugin '{}'", pluginPath); log.debug("Loaded plugin '{}'", pluginPath);

102
pf4j/src/main/java/ro/fortsoft/pf4j/ManifestPluginDescriptorFinder.java

@ -0,0 +1,102 @@
/*
* Copyright 2012 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;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.util.StringUtils;
/**
* Read the plugin descriptor from the manifest file.
*
* @author Decebal Suiu
*/
public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder {
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class);
private PluginClasspath pluginClasspath;
public ManifestPluginDescriptorFinder(PluginClasspath pluginClasspath) {
this.pluginClasspath = pluginClasspath;
}
@Override
public PluginDescriptor find(File pluginRepository) throws PluginException {
// TODO it's ok with first classes directory? Another idea is to specify in PluginClasspath the folder.
String classes = pluginClasspath.getClassesDirectories().get(0);
File manifestFile = new File(pluginRepository, classes + "/META-INF/MANIFEST.MF");
log.debug("Lookup plugin descriptor in '{}'", manifestFile);
if (!manifestFile.exists()) {
throw new PluginException("Cannot find '" + manifestFile + "' file");
}
FileInputStream input = null;
try {
input = new FileInputStream(manifestFile);
} catch (FileNotFoundException e) {
// not happening
}
Manifest manifest = null;
try {
manifest = new Manifest(input);
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
} finally {
try {
input.close();
} catch (IOException e) {
throw new PluginException(e.getMessage(), e);
}
}
PluginDescriptor pluginDescriptor = new PluginDescriptor();
// TODO validate !!!
Attributes attrs = manifest.getMainAttributes();
String id = attrs.getValue("Plugin-Id");
if (StringUtils.isEmpty(id)) {
throw new PluginException("Plugin-Id cannot be empty");
}
pluginDescriptor.setPluginId(id);
String clazz = attrs.getValue("Plugin-Class");
if (StringUtils.isEmpty(clazz)) {
throw new PluginException("Plugin-Class cannot be empty");
}
pluginDescriptor.setPluginClass(clazz);
String version = attrs.getValue("Plugin-Version");
if (StringUtils.isEmpty(version)) {
throw new PluginException("Plugin-Version cannot be empty");
}
pluginDescriptor.setPluginVersion(PluginVersion.createVersion(version));
String provider = attrs.getValue("Plugin-Provider");
pluginDescriptor.setProvider(provider);
String dependencies = attrs.getValue("Plugin-Dependencies");
pluginDescriptor.setDependencies(dependencies);
return pluginDescriptor;
}
}

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

@ -0,0 +1,59 @@
/*
* Copyright 2013 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;
import java.util.ArrayList;
import java.util.List;
/**
* The classpath of the plugin after it was unpacked.
* It contains classes directories and lib directories (directories that contains jars).
* All directories are relativ to plugin repository.
* The default values are "classes" and "lib".
*
* @author Decebal Suiu
*/
public class PluginClasspath {
private static final String DEFAULT_CLASSES_DIRECTORY = "classes";
private static final String DEFAULT_LIB_DIRECTORY = "lib";
private List<String> classesDirectories;
private List<String> libDirectories;
public PluginClasspath() {
classesDirectories = new ArrayList<String>();
libDirectories = new ArrayList<String>();
// add defaults
classesDirectories.add(DEFAULT_CLASSES_DIRECTORY);
libDirectories.add(DEFAULT_LIB_DIRECTORY);
}
public List<String> getClassesDirectories() {
return classesDirectories;
}
public void setClassesDirectories(List<String> classesDirectories) {
this.classesDirectories = classesDirectories;
}
public List<String> getLibDirectories() {
return libDirectories;
}
public void setLibDirectories(List<String> libDirectories) {
this.libDirectories = libDirectories;
}
}

114
pf4j/src/main/java/ro/fortsoft/pf4j/PluginLoader.java

@ -15,6 +15,7 @@ package ro.fortsoft.pf4j;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.util.List;
import java.util.Vector; import java.util.Vector;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -40,22 +41,12 @@ class PluginLoader {
*/ */
private File pluginRepository; private File pluginRepository;
/* private PluginClasspath pluginClasspath;
* The directory with '.class' files.
*/
private File classesDirectory;
/*
* The directory with '.jar' files.
*/
private File libDirectory;
private PluginClassLoader pluginClassLoader; private PluginClassLoader pluginClassLoader;
public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository) { public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository, PluginClasspath pluginClasspath) {
this.pluginRepository = pluginRepository; this.pluginRepository = pluginRepository;
classesDirectory = new File(pluginRepository, "classes"); this.pluginClasspath = pluginClasspath;
libDirectory = new File(pluginRepository, "lib");
ClassLoader parent = getClass().getClassLoader(); ClassLoader parent = getClass().getClassLoader();
pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent); pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent);
log.debug("Created class loader {}", pluginClassLoader); log.debug("Created class loader {}", pluginClassLoader);
@ -77,6 +68,60 @@ class PluginLoader {
return loadClasses() && loadJars(); return loadClasses() && loadJars();
} }
private boolean loadClasses() {
List<String> classesDirectories = pluginClasspath.getClassesDirectories();
// add each classes directory to plugin class loader
for (String classesDirectory : classesDirectories) {
// make 'classesDirectory' absolute
File file = new File(pluginRepository, classesDirectory).getAbsoluteFile();
if (file.exists() && file.isDirectory()) {
log.debug("Found '{}' directory", file.getPath());
try {
pluginClassLoader.addURL(file.toURI().toURL());
log.debug("Added '{}' to the class loader path", file);
} catch (MalformedURLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
return false;
}
}
}
return true;
}
/**
* Add all *.jar files from lib directories to class loader.
*/
private boolean loadJars() {
List<String> libDirectories = pluginClasspath.getLibDirectories();
// add each jars directory to plugin class loader
for (String libDirectory : libDirectories) {
// make 'libDirectory' absolute
File file = new File(pluginRepository, libDirectory).getAbsoluteFile();
// collect all jars from current lib directory in jars variable
Vector<File> jars = new Vector<File>();
getJars(jars, file);
for (File jar : jars) {
try {
pluginClassLoader.addURL(jar.toURI().toURL());
log.debug("Added '{}' to the class loader path", jar);
} catch (MalformedURLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
return false;
}
}
}
return true;
}
private void getJars(Vector<File> bucket, File file) { private void getJars(Vector<File> bucket, File file) {
FileFilter jarFilter = new JarFileFilter(); FileFilter jarFilter = new JarFileFilter();
FileFilter directoryFilter = new DirectoryFileFilter(); FileFilter directoryFilter = new DirectoryFileFilter();
@ -95,47 +140,4 @@ class PluginLoader {
} }
} }
private boolean loadClasses() {
// make 'classesDirectory' absolute
classesDirectory = classesDirectory.getAbsoluteFile();
if (classesDirectory.exists() && classesDirectory.isDirectory()) {
log.debug("Found '{}' directory", classesDirectory.getPath());
try {
pluginClassLoader.addURL(classesDirectory.toURI().toURL());
log.debug("Added '{}' to the class loader path", classesDirectory);
} catch (MalformedURLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
return false;
}
}
return true;
}
/**
* Add all *.jar files from '/lib' directory.
*/
private boolean loadJars() {
// make 'jarDirectory' absolute
libDirectory = libDirectory.getAbsoluteFile();
Vector<File> jars = new Vector<File>();
getJars(jars, libDirectory);
for (File jar : jars) {
try {
pluginClassLoader.addURL(jar.toURI().toURL());
log.debug("Added '{}' to the class loader path", jar);
} catch (MalformedURLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
return false;
}
}
return true;
}
} }

80
pf4j/src/main/java/ro/fortsoft/pf4j/SezpozExtensionFinder.java

@ -0,0 +1,80 @@
/*
* Copyright 2012 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;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
/**
* Using Sezpoz(http://sezpoz.java.net/) for extensions discovery.
*
* @author Decebal Suiu
*/
public class SezpozExtensionFinder implements ExtensionFinder {
private static final Logger log = LoggerFactory.getLogger(SezpozExtensionFinder.class);
private volatile List<IndexItem<Extension, Object>> indices;
private ClassLoader classLoader;
public SezpozExtensionFinder(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
log.debug("Find extensions for {}", type);
List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
getIndices();
// System.out.println("indices = "+ indices);
for (IndexItem<Extension, Object> item : indices) {
try {
AnnotatedElement element = item.element();
Class<?> extensionType = (Class<?>) element;
log.debug("Checking extension type {}", extensionType);
if (type.isAssignableFrom(extensionType)) {
Object instance = item.instance();
if (instance != null) {
log.debug("Added extension {}", extensionType);
result.add(new ExtensionWrapper<T>(type.cast(instance), item.annotation().ordinal()));
}
}
} catch (InstantiationException e) {
log.error(e.getMessage(), e);
}
}
return result;
}
private List<IndexItem<Extension, Object>> getIndices() {
if (indices == null) {
indices = new ArrayList<IndexItem<Extension, Object>>();
Iterator<IndexItem<Extension, Object>> it = Index.load(Extension.class, Object.class, classLoader).iterator();
while (it.hasNext()) {
indices.add(it.next());
}
}
return indices;
}
}
Loading…
Cancel
Save