@@ -2,5 +2,5 @@ log4j.rootLogger=DEBUG,Console | |||
log4j.appender.Console=org.apache.log4j.ConsoleAppender | |||
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 | |||
@@ -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 | |||
* the License. You may obtain a copy of the License in the LICENSE file, or at: | |||
@@ -12,69 +12,16 @@ | |||
*/ | |||
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 | |||
*/ | |||
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) { | |||
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; | |||
} | |||
} |
@@ -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 | |||
* the License. You may obtain a copy of the License in the LICENSE file, or at: | |||
@@ -12,78 +12,16 @@ | |||
*/ | |||
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 | |||
*/ | |||
public class DefaultPluginDescriptorFinder implements PluginDescriptorFinder { | |||
@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); | |||
public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder { | |||
return pluginDescriptor; | |||
public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) { | |||
super(pluginClasspath); | |||
} | |||
} |
@@ -47,9 +47,11 @@ public class DefaultPluginManager implements PluginManager { | |||
*/ | |||
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'). | |||
@@ -114,6 +116,7 @@ public class DefaultPluginManager implements PluginManager { | |||
disabledPlugins = new ArrayList<String>(); | |||
compoundClassLoader = new CompoundClassLoader(); | |||
pluginClasspath = createPluginClasspath(); | |||
pluginDescriptorFinder = createPluginDescriptorFinder(); | |||
extensionFinder = createExtensionFinder(); | |||
@@ -278,7 +281,7 @@ public class DefaultPluginManager implements PluginManager { | |||
* Add the possibility to override the PluginDescriptorFinder. | |||
*/ | |||
protected PluginDescriptorFinder createPluginDescriptorFinder() { | |||
return new DefaultPluginDescriptorFinder(); | |||
return new DefaultPluginDescriptorFinder(pluginClasspath); | |||
} | |||
/** | |||
@@ -288,6 +291,13 @@ public class DefaultPluginManager implements PluginManager { | |||
return new DefaultExtensionFinder(compoundClassLoader); | |||
} | |||
/** | |||
* Add the possibility to override the PluginClassPath. | |||
*/ | |||
protected PluginClasspath createPluginClasspath() { | |||
return new PluginClasspath(); | |||
} | |||
protected boolean isPluginDisabled(String pluginId) { | |||
if (enabledPlugins.isEmpty()) { | |||
return disabledPlugins.contains(pluginId); | |||
@@ -325,7 +335,7 @@ public class DefaultPluginManager implements PluginManager { | |||
// load plugin | |||
log.debug("Loading plugin '{}'", pluginPath); | |||
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory); | |||
PluginLoader pluginLoader = new PluginLoader(this, pluginDescriptor, pluginDirectory, pluginClasspath); | |||
pluginLoader.load(); | |||
log.debug("Loaded plugin '{}'", pluginPath); | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -15,6 +15,7 @@ package ro.fortsoft.pf4j; | |||
import java.io.File; | |||
import java.io.FileFilter; | |||
import java.net.MalformedURLException; | |||
import java.util.List; | |||
import java.util.Vector; | |||
import org.slf4j.Logger; | |||
@@ -40,22 +41,12 @@ class PluginLoader { | |||
*/ | |||
private File pluginRepository; | |||
/* | |||
* The directory with '.class' files. | |||
*/ | |||
private File classesDirectory; | |||
/* | |||
* The directory with '.jar' files. | |||
*/ | |||
private File libDirectory; | |||
private PluginClasspath pluginClasspath; | |||
private PluginClassLoader pluginClassLoader; | |||
public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository) { | |||
public PluginLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, File pluginRepository, PluginClasspath pluginClasspath) { | |||
this.pluginRepository = pluginRepository; | |||
classesDirectory = new File(pluginRepository, "classes"); | |||
libDirectory = new File(pluginRepository, "lib"); | |||
this.pluginClasspath = pluginClasspath; | |||
ClassLoader parent = getClass().getClassLoader(); | |||
pluginClassLoader = new PluginClassLoader(pluginManager, pluginDescriptor, parent); | |||
log.debug("Created class loader {}", pluginClassLoader); | |||
@@ -77,6 +68,60 @@ class PluginLoader { | |||
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) { | |||
FileFilter jarFilter = new JarFileFilter(); | |||
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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |