Features/Benefits
-------------------
With PF4J you can easily transform a monolithic java application in a modular application.
-PF4J is an open source (Apache license) lightweight (around 35KB) plugin framework for java, with minimal dependencies and very extensible (see PluginDescriptorFinder and ExtensionFinder).
+PF4J is an open source (Apache license) lightweight (around 50KB) plugin framework for java, with minimal dependencies (only slf4j-api) and very extensible (see PluginDescriptorFinder and ExtensionFinder).
No XML, only Java.
}
Another important internal component is **ExtensionFinder** that describes how the plugin manager discovers extensions for the extensions points.
-**DefaultExtensionFinder** is a "link" to **SezpozExtensionFinder** that looks up extensions using **Extension** annotation.
+**DefaultExtensionFinder** looks up extensions using **Extension** annotation.
public class WelcomePlugin extends Plugin {
The DefaultPluginManager determines automatically the correct runtime mode and for DEVELOPMENT mode overrides some components(pluginsDirectory is __"../plugins"__, __PropertiesPluginDescriptorFinder__ as PluginDescriptorFinder, __DevelopmentPluginClasspath__ as PluginClassPath).
Another advantage of DEVELOPMENT runtime mode is that you can execute some code lines only in this mode (for example more debug messages).
-If you use maven as build manger, after each dependency modification in you plugin (maven module) you must run Maven>Update Project...
+If you use maven as build manger, after each dependency modification in your plugin (maven module) you must run Maven>Update Project...
For more details see the demo application.
System.out.println("HelloPlugin.stop()");
}
- @Extension
+ @Extension(ordinal=1)
public static class HelloGreeting implements Greeting {
@Override
<packaging>jar</packaging>
<name>PF4J</name>
<description>Plugin Framework for Java</description>
-
- <dependencies>
- <dependency>
- <groupId>net.java.sezpoz</groupId>
- <artifactId>sezpoz</artifactId>
- <version>1.9</version>
- </dependency>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgument>-proc:none</compilerArgument>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
*/
package ro.fortsoft.pf4j;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
- * The default implementation for ExtensionFinder.
- * Now, this class it's a "link" to {@link ro.fortsoft.pf4j.SezpozExtensionFinder}.
+ * The default implementation for ExtensionFinder.
+ * All extensions declared in a plugin are indexed in a file "META-INF/extensions.idx".
+ * This class lookup extensions in all extensions index files "META-INF/extensions.idx".
*
* @author Decebal Suiu
*/
-public class DefaultExtensionFinder extends SezpozExtensionFinder {
+public class DefaultExtensionFinder implements ExtensionFinder {
+ private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFinder.class);
+
+ private ClassLoader classLoader;
+ private volatile Set<String> entries;
+
public DefaultExtensionFinder(ClassLoader classLoader) {
- super(classLoader);
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public <T> List<ExtensionWrapper<T>> find(Class<T> type) {
+ log.debug("Find extensions for extension point {}", type.getName());
+ List<ExtensionWrapper<T>> result = new ArrayList<ExtensionWrapper<T>>();
+ if (entries == null) {
+ entries = readIndexFiles();
+ }
+
+ for (String entry : entries) {
+ try {
+ Class<?> extensionType = classLoader.loadClass(entry);
+ log.debug("Checking extension type {}", extensionType.getName());
+ if (type.isAssignableFrom(extensionType)) {
+ Object instance = extensionType.newInstance();
+ if (instance != null) {
+ Extension extension = extensionType.getAnnotation(Extension.class);
+ log.debug("Added extension {} with ordinal {}", extensionType, extension.ordinal());
+ result.add(new ExtensionWrapper<T>(type.cast(instance), extension.ordinal()));
+ }
+ } else {
+ log.warn("{} is not an extension for extension point {}", extensionType, type.getName());
+ }
+ } catch (ClassNotFoundException e) {
+ log.error(e.getMessage(), e);
+ } catch (InstantiationException e) {
+ log.error(e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ if (entries.isEmpty()) {
+ log.debug("No extensions found for extension point {}", type.getName());
+ } else {
+ log.debug("Found {} extensions for extension point {}", entries.size(), type.getName());
+ }
+
+ // sort by "ordinal" property
+ Collections.sort(result);
+
+ return result;
}
+ private Set<String> readIndexFiles() {
+ log.debug("Reading extensions index files");
+ Set<String> entries = new HashSet<String>();
+
+ try {
+ Enumeration<URL> indexFiles = classLoader.getResources(ExtensionsIndexer.EXTENSIONS_RESOURCE);
+ while (indexFiles.hasMoreElements()) {
+ Reader reader = new InputStreamReader(indexFiles.nextElement().openStream(), "UTF-8");
+ ExtensionsIndexer.readIndex(reader, entries);
+ }
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+
+ if (entries.isEmpty()) {
+ log.debug("No extensions found");
+ } else {
+ log.debug("Found possible {} extensions", entries.size());
+ }
+
+ return entries;
+ }
+
}
filterList.add(new NotFileFilter(createHiddenPluginFilter()));\r
FileFilter pluginsFilter = new AndFileFilter(filterList);\r
File[] directories = pluginsDirectory.listFiles(pluginsFilter);\r
- log.debug("Possible plugins: {}", Arrays.asList(directories));\r
+ log.debug("Found possible {} plugins: {}", directories.length, Arrays.asList(directories));\r
if (directories.length == 0) {\r
log.info("No plugins");\r
return;\r
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-import net.java.sezpoz.Indexable;
-
/**
* @author Decebal Suiu
*/
-@Indexable
@Retention(RUNTIME)
@Target(TYPE)
@Documented
--- /dev/null
+/*
+ * 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.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+/**
+ * @author Decebal Suiu
+ */
+public class ExtensionsIndexer extends AbstractProcessor {
+
+ public static final String EXTENSIONS_RESOURCE = "META-INF/extensions.idx";
+
+ private List<TypeElement> extensions = new ArrayList<TypeElement>();
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ Set<String> annotationTypes = new HashSet<String>();
+ annotationTypes.add(Extension.class.getName());
+
+ return annotationTypes;
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (roundEnv.processingOver()) {
+ return false;
+ }
+
+ for (Element element : roundEnv.getElementsAnnotatedWith(Extension.class)) {
+ if (!(element instanceof TypeElement)) {
+ continue;
+ }
+
+ TypeElement typeElement = (TypeElement) element;
+ String message = "Extension found in " + processingEnv.getElementUtils().getBinaryName(typeElement).toString();
+ processingEnv.getMessager().printMessage(Kind.NOTE, message);
+ extensions.add(typeElement);
+ }
+
+ /*
+ if (!roundEnv.processingOver()) {
+ return false;
+ }
+ */
+
+ write();
+
+ return false;
+// return true; // no further processing of this annotation type
+ }
+
+ private void write() {
+ Set<String> entries = new HashSet<String>();
+ for (TypeElement typeElement : extensions) {
+ entries.add(processingEnv.getElementUtils().getBinaryName(typeElement).toString());
+ }
+
+ read(entries); // read old entries
+ write(entries); // write entries
+ }
+
+ private void write(Set<String> entries) {
+ try {
+ FileObject file = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE);
+ Writer writer = file.openWriter();
+ for (String entry : entries) {
+ writer.write(entry);
+ writer.write("\n");
+ }
+ writer.close();
+ } catch (FileNotFoundException e) {
+ // it's the first time, create the file
+ } catch (IOException e) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, e.toString());
+ }
+ }
+
+ private void read(Set<String> entries) {
+ try {
+ FileObject file = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", EXTENSIONS_RESOURCE);
+ readIndex(file.openReader(true), entries);
+ } catch (FileNotFoundException e) {
+ } catch (IOException e) {
+ // thrown by Eclipse JDT when not found
+ } catch (UnsupportedOperationException e) {
+ // java6 does not support reading old index files
+ }
+ }
+
+ public static void readIndex(Reader reader, Set<String> entries) throws IOException {
+ BufferedReader bufferedReader = new BufferedReader(reader);
+
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ entries.add(line);
+ }
+
+ reader.close();
+ }
+
+}
+
\ No newline at end of file
+++ /dev/null
-/*
- * 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;
- }
-
-}
--- /dev/null
+ro.fortsoft.pf4j.ExtensionsIndexer