HowTo JPF: Unterschied zwischen den Versionen

Aus Java Student User Group Austria - Java + JVM in Wien Österreich / Vienna Austria
Wechseln zu: Navigation, Suche
(create an extension)
(example)
 
(6 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 7: Zeile 7:
 
''One major goal of JPF is that the application (and its end-user) should not pay any memory or performance penalty for plug-ins that are installed, but not used. Plug-ins are added to the registry at application start-up or while the application is running but they are not loaded until they are called.''" [http://jpf.sourceforge.net/ http://jpf.sourceforge.net/]
 
''One major goal of JPF is that the application (and its end-user) should not pay any memory or performance penalty for plug-ins that are installed, but not used. Plug-ins are added to the registry at application start-up or while the application is running but they are not loaded until they are called.''" [http://jpf.sourceforge.net/ http://jpf.sourceforge.net/]
  
some nice characteristics/features it provides:
+
some characteristics and features:
 
* open source (LGPL)
 
* open source (LGPL)
* low barrier of entrance
+
* low barrier of entrance (compared to OSGi)
 +
* easy configuration (single xml file for each plugin)
 
* ant tasks
 
* ant tasks
 
* hot deployment
 
* hot deployment
Zeile 15: Zeile 16:
 
* integrity check
 
* integrity check
 
* embedded documentation
 
* embedded documentation
 +
  
 
==architecture overview==
 
==architecture overview==
  
in the following example i will show you a simple demo application, divided in three main parts: the '''boot module''' (got the <code>main</code> method and fires up jpf), '''core module''' (alias application plugin, will display the main window and provide extension points for menubar entries) and the '''export module''' (imports core and uses the extension point to add a single entry to the menubar).
+
in the following example i will show you a simple demo application, divided into three parts: the '''boot module''' (got the <code>main</code> method and fires up jpf), the '''core module''' (alias application plugin, will display the main window and provide extension points for menubar entries) and the '''export module''' (imports core and uses the extension point to add a single entry to the menubar).
  
 
<code>
 
<code>
Zeile 31: Zeile 33:
 
  |              |              |              |
 
  |              |              |              |
 
  ++++++++++++++++++++ JPF +++++++++++++++++++++
 
  ++++++++++++++++++++ JPF +++++++++++++++++++++
  ************ Java, VM, Classloader ***********
+
  ************** JVM, Classloader **************
 
</code>
 
</code>
  
as you can see the core plugin (extends from [http://jpf.sourceforge.net/api/org/java/plugin/boot/ApplicationPlugin.html <code>ApplicationPlugin</code>]) itself is a plugin.
+
as you can see the core plugin (extends [http://jpf.sourceforge.net/api/org/java/plugin/boot/ApplicationPlugin.html <code>ApplicationPlugin</code>]) itself is a plugin.
 +
 
  
==the plugin concept==
+
==manifest file==
  
the three main parts are decalred in the so-called manifest file (<code>plugin.xml</code>):
+
the three main parts are declared in the so-called manifest file (<code>plugin.xml</code>):
  
* '''plugin''': with jpf the whole application is separated in plugins (kind of module). it holds the code, resources, libraries and defines requirements/imports, exports, extension-points and extensions.
+
* '''plugin''': with jpf the whole application is separated into independent plugins (kind of module). it holds the code, resources, libraries and defines imports (requirements), exports, extension-points and extensions. the class attribute is necessary if this plugin is an application plugin.
 
<code>
 
<code>
  <!--
+
  <!-
 
  PLUG-IN
 
  PLUG-IN
 
  This is container for all other plug-in manifest elements.
 
  This is container for all other plug-in manifest elements.
 
 
 
 
  "docs-path": path to documentation folder, relative to plug-in context
+
  "docs-path": path to documentation folder, relative to plug-in context (home) folder
            (home) folder
 
 
  -->
 
  -->
 
  <!ELEMENT plugin (doc?, attributes?, requires?, runtime?,
 
  <!ELEMENT plugin (doc?, attributes?, requires?, runtime?,
Zeile 57: Zeile 59:
 
  <!ATTLIST plugin docs-path  CDATA #IMPLIED>
 
  <!ATTLIST plugin docs-path  CDATA #IMPLIED>
 
</code>
 
</code>
* '''extension-point''': an interface which is provided by the plugin. an extension-point is identified by its plugin-id and extension-point-id, and defines parameters for that interface.
+
* '''extension-point''': an interface which is provided by the plugin so other can provide implementations. an extension-point is identified by its plugin-id and extension-point-id, anddefines parameters for that interface. usually i only use an parameter called "class" for the fully qualified name of a class which implements a certain java interface and then define other attributes as getter on that java interface).
 
<code>
 
<code>
  <!--
+
  <!-
 
  EXTENSION POINT
 
  EXTENSION POINT
 
  Tags extension-point describe the places where the functionality of
 
  Tags extension-point describe the places where the functionality of
Zeile 69: Zeile 71:
 
  one-per-plugin  only one extension can be defined in one plug-in
 
  one-per-plugin  only one extension can be defined in one plug-in
 
  none            no extension can be defined for this point
 
  none            no extension can be defined for this point
                (used to declare "abstract" extension points, that can
+
                        (used to declare "abstract" extension points,
be only "inherited" by other points using "parent"
+
                        that can be only "inherited" by other points using "parent" attributes)
attributes)
 
 
  -->
 
  -->
 
  <!ELEMENT extension-point (doc?, parameter-def*)>
 
  <!ELEMENT extension-point (doc?, parameter-def*)>
Zeile 77: Zeile 78:
 
  <!ATTLIST extension-point parent-plugin-id  CDATA #IMPLIED>
 
  <!ATTLIST extension-point parent-plugin-id  CDATA #IMPLIED>
 
  <!ATTLIST extension-point parent-point-id  CDATA #IMPLIED>
 
  <!ATTLIST extension-point parent-point-id  CDATA #IMPLIED>
  <!ATTLIST extension-point extension-multiplicity  (any | one | one-per-plugin
+
  <!ATTLIST extension-point extension-multiplicity  (any | one | one-per-plugin | none) "any">
                                                  | none) "any">
 
 
   
 
   
  <!--
+
  <!-
 
  EXTENSION POINT PARAMETER DEFINITION
 
  EXTENSION POINT PARAMETER DEFINITION
 
  ... documentation shortened ...
 
  ... documentation shortened ...
-->
+
-->
 
  <!ELEMENT parameter-def (doc?, parameter-def*)>
 
  <!ELEMENT parameter-def (doc?, parameter-def*)>
 
  <!ATTLIST parameter-def id            CDATA #REQUIRED>
 
  <!ATTLIST parameter-def id            CDATA #REQUIRED>
 
  <!ATTLIST parameter-def multiplicity  (one | any | none-or-one | one-or-more)
 
  <!ATTLIST parameter-def multiplicity  (one | any | none-or-one | one-or-more)
 
                                         "one">
 
                                         "one">
  <!ATTLIST parameter-def type          (string | boolean | number | date | time
+
  <!ATTLIST parameter-def type          (string | boolean | number | date | time | date-time | null | any | plugin-id
                                        | date-time | null | any | plugin-id
+
                                        | extension-point-id | extension-id | fixed | resource) "string">
  | extension-point-id | extension-id
 
  | fixed | resource) "string">
 
 
  <!ATTLIST parameter-def custom-data    CDATA #IMPLIED>
 
  <!ATTLIST parameter-def custom-data    CDATA #IMPLIED>
 
  <!ATTLIST parameter-def default-value  CDATA #IMPLIED>
 
  <!ATTLIST parameter-def default-value  CDATA #IMPLIED>
Zeile 97: Zeile 95:
 
* '''extension''': could be thought as an implementation for a given interface. a plugin can extend the extension-point of another plugin and pass concrete values as arguments (most of the time only a "class" attribute which holds the full qualified name of a class).
 
* '''extension''': could be thought as an implementation for a given interface. a plugin can extend the extension-point of another plugin and pass concrete values as arguments (most of the time only a "class" attribute which holds the full qualified name of a class).
 
<code>
 
<code>
  <!--
+
  <!-
 
  EXTENSION
 
  EXTENSION
  Tags extension describe the functionality the plug-in contribute to the
+
  Tags extension describe the functionality the plug-in contribute to the system.
system.
 
 
 
 
 
     "optional": if "true" than absense of required extension point will not
+
     "optional": if "true" than absense of required extension point will not cause runtime error
            cause runtime error
 
 
  -->
 
  -->
 
  <!ELEMENT extension (doc?, parameter*)>
 
  <!ELEMENT extension (doc?, parameter*)>
Zeile 111: Zeile 107:
 
  <!ATTLIST extension optional  (true | false) "false">
 
  <!ATTLIST extension optional  (true | false) "false">
 
   
 
   
  <!--
+
  <!-
 
  EXTENSION PARAMETER
 
  EXTENSION PARAMETER
 
 
 
 
  If both attribute and tag "value" present, only attribute will be taken
+
  If both attribute and tag "value" present, only attribute will be taken into account, the content of tag will be ignored.
into account, the content of tag will be ignored.
+
  Parameter value will be validated according to parameter definition with corresponding ID.
  Parameter value will be validated according to parameter definition
 
with corresponding ID.
 
 
  -->
 
  -->
 
  <!ELEMENT parameter (doc?, value?, parameter*)>
 
  <!ELEMENT parameter (doc?, value?, parameter*)>
Zeile 124: Zeile 118:
 
</code>
 
</code>
  
you can of course have a look at the [http://jpf.sourceforge.net/dtd.html full DTD] hosted on the jpf website.
+
you also can have a look at the [http://jpf.sourceforge.net/dtd.html full DTD] provided on the jpf website.
  
 
=example=
 
=example=
 +
 +
 +
==maven dependency==
 +
 +
if you decide to use maven -and i really hope you did so already- just copy&paste these two dependencies into your pom:
 +
<source lang="xml">
 +
<dependency>
 +
<groupId>net.sf.jpf</groupId>
 +
<artifactId>jpf</artifactId>
 +
<version>1.5</version>
 +
<scope>provided</scope>
 +
</dependency>
 +
 +
<dependency>
 +
<groupId>net.sf.jpf</groupId>
 +
<artifactId>jpf-boot</artifactId>
 +
<version>1.5</version>
 +
<scope>provided</scope>
 +
</dependency>
 +
</source>
 +
 +
this gives you access to the most recent jpf libraries (available december 2008) in ''provided'' scope, because the boot module got the two jars already in its classpath.
 +
  
 
==startuping up the framework==
 
==startuping up the framework==
  
you can start the java plugin framework either by simply providing a <code>boot.properties</code> file and executing a utility jar, or by writing custom code which gives you full control of boot procedure (preferred way). the last option is nevertheless required for unit tests.
+
you can start the java plugin framework either by simply providing a <code>boot.properties</code> file in the project root and executing a utility jar (jpf-boot.jar), or by writing custom code which gives you full control of the boot procedure (preferred way). the last option is nevertheless required for unit tests.
 +
 
  
 
===let properties-file do the work (simple)===
 
===let properties-file do the work (simple)===
  
file <code>boot.properties</code>
+
file <code>boot.properties</code>:
 
<code>
 
<code>
 
  # application plugin id
 
  # application plugin id
Zeile 154: Zeile 172:
 
</code>
 
</code>
  
execute following line on the command line interface: <code>java -jar lib/jpf-boot.jar</code>
+
executing following command should bring up your application: <code>java -jar lib/jpf-boot.jar</code>
 +
 
 +
here, the most important property is <code>org.java.plugin.boot.applicationPlugin</code> which looks for a plugin with the given id, looks up the manifest file and instantiates the type (<code>ApplicationPlugin</code>) defined by the class attribute.
  
 
===take over full control (advanced)===
 
===take over full control (advanced)===
  
 +
file <code>JpfBooter.java</code>:
 
<source lang="java">
 
<source lang="java">
 
package at.ac.tuwien.jsug.jpf.boot;
 
package at.ac.tuwien.jsug.jpf.boot;
Zeile 174: Zeile 195:
 
  */
 
  */
 
public class JpfBooter {
 
public class JpfBooter {
 +
 +
/** path to folder where plugins reside (either zipped, or unpacked as a simple folder) */
 
private static final String PLUGINS_REPOSITORY = "./plugins";
 
private static final String PLUGINS_REPOSITORY = "./plugins";
 +
 +
/** plugin id of the core module, defined in it's plugin.xml class attribute */
 +
private static final String CORE_PLUGIN_ID = "at.ac.tuwien.jsug.jpf.core";
  
 
public static void main(String[] args) {
 
public static void main(String[] args) {
Zeile 183: Zeile 209:
  
 
private void start() {
 
private void start() {
 +
// instantiate necessary objects
 
final PluginManager manager = ObjectFactory.newInstance().createManager();
 
final PluginManager manager = ObjectFactory.newInstance().createManager();
 
final DefaultPluginsCollector collector = new DefaultPluginsCollector();
 
final DefaultPluginsCollector collector = new DefaultPluginsCollector();
 
final ExtendedProperties props = new ExtendedProperties();
 
final ExtendedProperties props = new ExtendedProperties();
 +
 +
// prepare configuration
 
props.setProperty("org.java.plugin.boot.pluginsRepositories", PLUGINS_REPOSITORY);
 
props.setProperty("org.java.plugin.boot.pluginsRepositories", PLUGINS_REPOSITORY);
 
 
 
try {
 
try {
 
collector.configure(props);
 
collector.configure(props);
 +
// examine plugins repository for plugins
 
manager.publishPlugins(collector.collectPluginLocations().toArray(new PluginLocation[] {}));
 
manager.publishPlugins(collector.collectPluginLocations().toArray(new PluginLocation[] {}));
  
final ICoreApplicationPlugin corePlugin = (ICoreApplicationPlugin) manager.getPlugin("at.ac.tuwien.jsug.jpf.core");
+
// finally retrieve the core plugin and start it up
 +
final ICoreApplicationPlugin corePlugin = (ICoreApplicationPlugin) manager.getPlugin(CORE_PLUGIN_ID);
 
corePlugin.startApplication();
 
corePlugin.startApplication();
 
} catch (Exception e) {
 
} catch (Exception e) {
Zeile 200: Zeile 231:
 
}
 
}
 
</source>
 
</source>
 +
  
 
==create the main boot part==
 
==create the main boot part==
  
 +
besides the <code>plugins/</code> folder and a log4j configuration file, there is only the <code>JpfBooter</code> class (a listing was shown in the preceding section).
 +
 +
the project structure should look like this:
 
<code>
 
<code>
 
  * src/main/java/
 
  * src/main/java/
Zeile 212: Zeile 247:
 
   - at.ac.tuwien.jsug.jpf.export/ ... target output for export plugin
 
   - at.ac.tuwien.jsug.jpf.export/ ... target output for export plugin
 
</code>
 
</code>
 +
  
 
==create the core application plugin==
 
==create the core application plugin==
  
 +
the application plugin has its manifest file (<code>plugin.xml</code>) and three types: the core application interface and its implementation and an interface for the menubar extension point.
 +
 +
after creating the files, the project structure should look like this:
 
<code>
 
<code>
 
  * src/main/java/
 
  * src/main/java/
Zeile 224: Zeile 263:
 
</code>
 
</code>
  
file <code>plugin.xml</code>
+
file <code>plugin.xml</code>:
 
<source lang="xml">
 
<source lang="xml">
 
<?xml version="1.0" ?>
 
<?xml version="1.0" ?>
Zeile 236: Zeile 275:
 
</runtime>
 
</runtime>
 
 
<!-- define a single extension point
+
<!-- define a single extension point -->
 
<extension-point id="MenuBar">
 
<extension-point id="MenuBar">
 
<!-- "one" and "string" is default -->
 
<!-- "one" and "string" is default -->
Zeile 244: Zeile 283:
 
</source>
 
</source>
  
you might want to use the following handy method for retrieving plugins:
+
file <code>ICoreApplicationPlugin.java</code>:
 
<source lang="java">
 
<source lang="java">
@SuppressWarnings("unchecked")
+
package at.ac.tuwien.jsug.jpf.core;
public static <T> List<T> fetchPlugins(
+
 
final Plugin plugin,
+
public interface ICoreApplicationPlugin {
final String extPointPluginId,
+
void startApplication();
final String extPointId,
+
}
final String attributeName) throws Exception {
+
</source>
final List<T> result = new LinkedList<T>();
+
 
+
file <code>CoreApplicationPlugin.java</code>:
final PluginManager manager = plugin.getManager();
+
<source lang="java">
+
package at.ac.tuwien.jsug.jpf.core;
final ExtensionPoint extPoint = manager.getRegistry().getExtensionPoint(extPointPluginId, extPointId);
+
 
for (final Extension extension : extPoint.getConnectedExtensions()) {
+
public class CoreApplicationPlugin implements ICoreApplicationPlugin {
// LOG.info("Processing extension point: " + extension);
+
public void startApplication() {
+
final JFrame frame = new JFrame();
final PluginDescriptor extensionDescriptor = extension.getDeclaringPluginDescriptor();
+
frame.getContentPane().add(new JLabel("JPF Core Module"));
manager.activatePlugin(extensionDescriptor.getId());
+
 
final ClassLoader classLoader = manager.getPluginClassLoader(extensionDescriptor);
+
// ... lookup all extensions and load them into the menubar ... see code down below in "use the extension" section
final String pluginClassName = extension.getParameter(attributeName).valueAsString();
+
 
final Class<T> pluginClass = (Class<T>) classLoader.loadClass(pluginClassName);
+
frame.pack();
final T pluginInstance = pluginClass.newInstance();
+
frame.setVisible(true);
result.add(pluginInstance);
 
 
}
 
}
 
return result;
 
 
}
 
}
 
</source>
 
</source>
 +
 +
file <code>IMenuBarPlugin.java</code>:
 +
<source lang="java">
 +
package at.ac.tuwien.jsug.jpf.core;
 +
 +
public interface IMenuBarPlugin {
 +
String getTitle();
 +
void execute();
 +
}
 +
</source>
 +
  
 
==create an extension==
 
==create an extension==
 +
 +
a plugin now simply declares the core plugin as a requirement in its manifest file (and therefore has access to the <code>IMenuBarPlugin</code> interface). then a proper implementation  can be written for the menubar extension.
 +
 +
after creating the files, the project structure should look like this:
  
 
<code>
 
<code>
Zeile 296: Zeile 347:
 
</runtime>
 
</runtime>
 
 
 +
<!-- declare the actual extension: specify which extension point and pass the full qualified name in the class attribute -->
 
<extension plugin-id="at.ac.tuwien.jsug.jpf.core" point-id="MenuBar" id="ExportMenuBar">
 
<extension plugin-id="at.ac.tuwien.jsug.jpf.core" point-id="MenuBar" id="ExportMenuBar">
<parameter id="class" value="at.ac.tuwien.jsug.jpf.export. ExportMenuBarPlugin" />
+
<parameter id="class" value="at.ac.tuwien.jsug.jpf.export.ExportMenuBarPlugin" />
 
</extension>
 
</extension>
 
</plugin>
 
</plugin>
Zeile 315: Zeile 367:
 
public void execute() {
 
public void execute() {
 
System.out.println("exec export");
 
System.out.println("exec export");
 +
}
 +
}
 +
</source>
 +
 +
 +
==use the extension==
 +
 +
you might want to use the following handy methods for retrieving plugins:
 +
<source lang="java">
 +
/**
 +
* overloaded method setting default "class" attribute name
 +
* @see #fetchPlugins(Plugin,String,String,String)
 +
*/
 +
public static <T> List<T> fetchPlugins(
 +
final Plugin plugin,
 +
final String extPointPluginId,
 +
final String extPointId) throws Exception {
 +
return fetchPlugins(plugin, extPointPluginId,  extPointId, "class");
 +
}
 +
 +
/**
 +
* fetches all extensions for the given extension point qualifiers
 +
*/
 +
@SuppressWarnings("unchecked")
 +
public static <T> List<T> fetchPlugins(
 +
final Plugin plugin,
 +
final String extPointPluginId,
 +
final String extPointId,
 +
final String attributeName) throws Exception {
 +
final List<T> plugins = new LinkedList<T>();
 +
 +
final PluginManager manager = plugin.getManager();
 +
 +
final ExtensionPoint extPoint = manager.getRegistry().getExtensionPoint(extPointPluginId, extPointId);
 +
for (final Extension extension : extPoint.getConnectedExtensions()) {
 +
// LOG.info("Processing extension point: " + extension);
 +
 +
final PluginDescriptor extensionDescriptor = extension.getDeclaringPluginDescriptor();
 +
manager.activatePlugin(extensionDescriptor.getId());
 +
final ClassLoader classLoader = manager.getPluginClassLoader(extensionDescriptor);
 +
final String pluginClassName = extension.getParameter(attributeName).valueAsString();
 +
final Class<T> pluginClass = (Class<T>) classLoader.loadClass(pluginClassName);
 +
final T pluginInstance = pluginClass.newInstance();
 +
plugins.add(pluginInstance);
 +
}
 +
 +
return Collections.unmodifiableList(plugins);
 +
}
 +
</source>
 +
 +
file <code>CoreApplicationPlugin</code>:
 +
<source lang="java">
 +
package at.ac.tuwien.jsug.jpf.core;
 +
 +
public class CoreApplicationPlugin implements ICoreApplicationPlugin {
 +
 +
private static final String EXTPOINT_ID_MENUBAR = "MenuBar";
 +
 +
public void startApplication() {
 +
final JFrame frame = new JFrame();
 +
frame.getContentPane().add(new JLabel("JPF Core Module"));
 +
 +
// setup menubar
 +
final List<IMenuBarPlugin> menuBarPlugins = JpfUtil.fetchPlugins(this, this.getDescriptor().getId(), EXTPOINT_ID_MENUBAR);
 +
setJMenuBar(newMenuBar(menuBarPlugins));
 +
 +
frame.pack();
 +
frame.setVisible(true);
 +
}
 +
 +
private JMenuBar newMenuBar(final List<IMenuBarPlugin> menuBarPlugins) {
 +
final JMenuBar bar = new JMenuBar();
 +
final JMenu menu = new JMenu("Application");
 +
 +
for (int i = 0; i < menuBarPlugins.size(); i++) {
 +
final IMenuBarPlugin plugin = menuBarPlugins.get(i);
 +
final JMenuItem item = new JMenuItem(plugin.getTitle());
 +
 +
item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) {
 +
plugin.execute();
 +
}});
 +
 +
menu.add(item);
 +
}
 +
 +
bar.add(menu);
 +
return bar;
 
}
 
}
 
}
 
}
Zeile 320: Zeile 459:
  
 
=appendix=
 
=appendix=
 +
<!--
 +
// TODO TODO TODO TODO TODO TODO TODO TODO TODO
 +
// TODO TODO TODO TODO TODO TODO TODO TODO TODO
 +
// TODO TODO TODO TODO TODO TODO TODO TODO TODO
 +
// TODO TODO TODO TODO TODO TODO TODO TODO TODO
 +
==sourcecode==
 +
 +
'''download''' the full sources:
 +
* source code
 +
* eclipse project (source code + eclipse project files)
 +
-->
 +
  
 
==links==
 
==links==
  
 
* [http://jpf.sourceforge.net http://jpf.sourceforge.net] ... official website of the JPF project
 
* [http://jpf.sourceforge.net http://jpf.sourceforge.net] ... official website of the JPF project
 +
* [http://page.mi.fu-berlin.de/oezbek/jpf/ http://page.mi.fu-berlin.de/oezbek/jpf/] ... JPF code generator
 +
* [http://maven.koecke.net/maven-jpf-plugin/ http://maven.koecke.net/maven-jpf-plugin/] ... mojo to do the cumbersomely work of packaging (unfortunately, somehow unfinished).
 +
  
 
==notes==
 
==notes==
* although it is more common to write "plug-in" instead of "plugin", i am going not to write the additional "-" character :)
+
* although it is more common to write "plug-in" instead of "plugin" i have choosen not to write the additional "-" character :)
  
 
[[Category:HowTo]]
 
[[Category:HowTo]]

Aktuelle Version vom 4. Januar 2009, 21:47 Uhr

written by Christoph P; created Tuesday, December 30, 2008

introduction

"JPF provides a runtime engine that dynamically discovers and loads "plug-ins". A plug-in is a structured component that describes itself to JPF using a "manifest". JPF maintains a registry of available plug-ins and the functions they provide (via extension points and extensions).

One major goal of JPF is that the application (and its end-user) should not pay any memory or performance penalty for plug-ins that are installed, but not used. Plug-ins are added to the registry at application start-up or while the application is running but they are not loaded until they are called." http://jpf.sourceforge.net/

some characteristics and features:

  • open source (LGPL)
  • low barrier of entrance (compared to OSGi)
  • easy configuration (single xml file for each plugin)
  • ant tasks
  • hot deployment
  • lazy loading
  • integrity check
  • embedded documentation


architecture overview

in the following example i will show you a simple demo application, divided into three parts: the boot module (got the main method and fires up jpf), the core module (alias application plugin, will display the main window and provide extension points for menubar entries) and the export module (imports core and uses the extension point to add a single entry to the menubar).

+--------------------------------------------+
|                                            |
|                  boot                      |
|                                            |
+--------------------------------------------+
|              |              |              |
|     core     |    export    |      ...     |   
|  (plugin_0)  |  (plugin_1)  |  (plugin_n)  |
|              |              |              |
++++++++++++++++++++ JPF +++++++++++++++++++++
************** JVM, Classloader **************

as you can see the core plugin (extends ApplicationPlugin) itself is a plugin.


manifest file

the three main parts are declared in the so-called manifest file (plugin.xml):

  • plugin: with jpf the whole application is separated into independent plugins (kind of module). it holds the code, resources, libraries and defines imports (requirements), exports, extension-points and extensions. the class attribute is necessary if this plugin is an application plugin.

<!-
	PLUG-IN
	This is container for all other plug-in manifest elements.
	
	"docs-path": path to documentation folder, relative to plug-in context (home) folder
-->
<!ELEMENT plugin (doc?, attributes?, requires?, runtime?,
                  (extension-point|extension)*)>
<!ATTLIST plugin id         CDATA #REQUIRED>
<!ATTLIST plugin version    CDATA #REQUIRED>
<!ATTLIST plugin vendor     CDATA #IMPLIED>
<!ATTLIST plugin class      CDATA #IMPLIED>
<!ATTLIST plugin docs-path  CDATA #IMPLIED>

  • extension-point: an interface which is provided by the plugin so other can provide implementations. an extension-point is identified by its plugin-id and extension-point-id, anddefines parameters for that interface. usually i only use an parameter called "class" for the fully qualified name of a class which implements a certain java interface and then define other attributes as getter on that java interface).

<!-
	EXTENSION POINT
	Tags extension-point describe the places where the functionality of
	this plug-in can be extended.
	
	Extension point multiplicity attribute description:
	any             any number of extensions can be available
	one             only one extension can be available
	one-per-plugin  only one extension can be defined in one plug-in
	none            no extension can be defined for this point
                        (used to declare "abstract" extension points,
                        that can be only "inherited" by other points using "parent" attributes)
-->
<!ELEMENT extension-point (doc?, parameter-def*)>
<!ATTLIST extension-point id                CDATA #REQUIRED>
<!ATTLIST extension-point parent-plugin-id  CDATA #IMPLIED>
<!ATTLIST extension-point parent-point-id   CDATA #IMPLIED>
<!ATTLIST extension-point extension-multiplicity  (any | one | one-per-plugin | none) "any">

<!-
	EXTENSION POINT PARAMETER DEFINITION
	... documentation shortened ...
-->
<!ELEMENT parameter-def (doc?, parameter-def*)>
<!ATTLIST parameter-def id             CDATA #REQUIRED>
<!ATTLIST parameter-def multiplicity   (one | any | none-or-one | one-or-more)
                                       "one">
<!ATTLIST parameter-def type           (string | boolean | number | date | time | date-time | null | any | plugin-id
                                       | extension-point-id | extension-id | fixed | resource) "string">
<!ATTLIST parameter-def custom-data    CDATA #IMPLIED>
<!ATTLIST parameter-def default-value  CDATA #IMPLIED>

  • extension: could be thought as an implementation for a given interface. a plugin can extend the extension-point of another plugin and pass concrete values as arguments (most of the time only a "class" attribute which holds the full qualified name of a class).

<!-
	EXTENSION
	Tags extension describe the functionality the plug-in contribute to the system.
	
    "optional": if "true" than absense of required extension point will not cause runtime error
-->
<!ELEMENT extension (doc?, parameter*)>
<!ATTLIST extension plugin-id  CDATA #REQUIRED>
<!ATTLIST extension point-id   CDATA #REQUIRED>
<!ATTLIST extension id         CDATA #REQUIRED>
<!ATTLIST extension optional   (true | false) "false">

<!-
	EXTENSION PARAMETER
	
	If both attribute and tag "value" present, only attribute will be taken into account, the content of tag will be ignored.
	Parameter value will be validated according to parameter definition with corresponding ID.
-->
<!ELEMENT parameter (doc?, value?, parameter*)>
<!ATTLIST parameter id     CDATA #REQUIRED>
<!ATTLIST parameter value  CDATA #IMPLIED>

you also can have a look at the full DTD provided on the jpf website.

example

maven dependency

if you decide to use maven -and i really hope you did so already- just copy&paste these two dependencies into your pom: <source lang="xml"> <dependency> <groupId>net.sf.jpf</groupId> <artifactId>jpf</artifactId> <version>1.5</version> <scope>provided</scope> </dependency>

<dependency> <groupId>net.sf.jpf</groupId> <artifactId>jpf-boot</artifactId> <version>1.5</version> <scope>provided</scope> </dependency> </source>

this gives you access to the most recent jpf libraries (available december 2008) in provided scope, because the boot module got the two jars already in its classpath.


startuping up the framework

you can start the java plugin framework either by simply providing a boot.properties file in the project root and executing a utility jar (jpf-boot.jar), or by writing custom code which gives you full control of the boot procedure (preferred way). the last option is nevertheless required for unit tests.


let properties-file do the work (simple)

file boot.properties:

# application plugin id
org.java.plugin.boot.applicationPlugin = at.ac.tuwien.jsug.jpf.startup
# [ full | light | off ]
org.java.plugin.boot.integrityCheckMode = light
#org.java.plugin.boot.splashImage = ${applicationRoot}/splash.png

#-------------------------------------------------------------------------------
# JPF runtime configuration

org.java.plugin.PathResolver = org.java.plugin.standard.ShadingPathResolver
org.java.plugin.standard.ShadingPathResolver.shadowFolder = ${applicationRoot}/temp/.jpf-shadow
org.java.plugin.standard.ShadingPathResolver.unpackMode = smart
org.java.plugin.standard.ShadingPathResolver.excludes = CVS

#-------------------------------------------------------------------------------
# could be some more properties defined for own usage

executing following command should bring up your application: java -jar lib/jpf-boot.jar

here, the most important property is org.java.plugin.boot.applicationPlugin which looks for a plugin with the given id, looks up the manifest file and instantiates the type (ApplicationPlugin) defined by the class attribute.

take over full control (advanced)

file JpfBooter.java: <source lang="java"> package at.ac.tuwien.jsug.jpf.boot;

import javax.swing.SwingUtilities; import org.java.plugin.ObjectFactory; import org.java.plugin.PluginManager; import org.java.plugin.PluginManager.PluginLocation; import org.java.plugin.boot.DefaultPluginsCollector; import org.java.plugin.util.ExtendedProperties; import at.ac.tuwien.jsug.jpf.core.ICoreApplicationPlugin;

/**

* entry point for the application to bootstrap jpf and
* invoke CoreApplicationPlugin.startApplication().
*/

public class JpfBooter {

/** path to folder where plugins reside (either zipped, or unpacked as a simple folder) */ private static final String PLUGINS_REPOSITORY = "./plugins";

/** plugin id of the core module, defined in it's plugin.xml class attribute */ private static final String CORE_PLUGIN_ID = "at.ac.tuwien.jsug.jpf.core";

public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new App().start(); }}); }

private void start() { // instantiate necessary objects final PluginManager manager = ObjectFactory.newInstance().createManager(); final DefaultPluginsCollector collector = new DefaultPluginsCollector(); final ExtendedProperties props = new ExtendedProperties();

// prepare configuration props.setProperty("org.java.plugin.boot.pluginsRepositories", PLUGINS_REPOSITORY);

try { collector.configure(props); // examine plugins repository for plugins manager.publishPlugins(collector.collectPluginLocations().toArray(new PluginLocation[] {}));

// finally retrieve the core plugin and start it up final ICoreApplicationPlugin corePlugin = (ICoreApplicationPlugin) manager.getPlugin(CORE_PLUGIN_ID); corePlugin.startApplication(); } catch (Exception e) { e.printStackTrace(); } } } </source>


create the main boot part

besides the plugins/ folder and a log4j configuration file, there is only the JpfBooter class (a listing was shown in the preceding section).

the project structure should look like this:

* src/main/java/
  - at.ac.tuwien.jsug.jpf.boot.JpfBooter.java
* src/main/resources/
  - log4j.properties
* plugins/
  - at.ac.tuwien.jsug.jpf.core/ ... target output for core plugin
  - at.ac.tuwien.jsug.jpf.export/ ... target output for export plugin


create the core application plugin

the application plugin has its manifest file (plugin.xml) and three types: the core application interface and its implementation and an interface for the menubar extension point.

after creating the files, the project structure should look like this:

* src/main/java/
  - at.ac.tuwien.jsug.jpf.core.CoreApplicationPlugin.java
  - at.ac.tuwien.jsug.jpf.core.ICoreApplicationPlugin.java
  - at.ac.tuwien.jsug.jpf.core.IMenuBarPlugin.java
* src/main/resources/
  - plugin.xml

file plugin.xml: <source lang="xml"> <?xml version="1.0" ?> <!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0" "http://jpf.sourceforge.net/plugin_1_0.dtd"> <plugin id="at.ac.tuwien.jsug.jpf.core" version="0.0.1" class="at.ac.tuwien.jsug.jpf.core.CoreApplicationPlugin">

<runtime> <library id="core" path="/" type="code"> <export prefix="*" /> </library> </runtime>

<extension-point id="MenuBar"> <parameter-def id="class" /> </extension-point> </plugin> </source>

file ICoreApplicationPlugin.java: <source lang="java"> package at.ac.tuwien.jsug.jpf.core;

public interface ICoreApplicationPlugin { void startApplication(); } </source>

file CoreApplicationPlugin.java: <source lang="java"> package at.ac.tuwien.jsug.jpf.core;

public class CoreApplicationPlugin implements ICoreApplicationPlugin { public void startApplication() { final JFrame frame = new JFrame(); frame.getContentPane().add(new JLabel("JPF Core Module"));

// ... lookup all extensions and load them into the menubar ... see code down below in "use the extension" section

frame.pack(); frame.setVisible(true); } } </source>

file IMenuBarPlugin.java: <source lang="java"> package at.ac.tuwien.jsug.jpf.core;

public interface IMenuBarPlugin { String getTitle(); void execute(); } </source>


create an extension

a plugin now simply declares the core plugin as a requirement in its manifest file (and therefore has access to the IMenuBarPlugin interface). then a proper implementation can be written for the menubar extension.

after creating the files, the project structure should look like this:

* src/main/java/
  - at.ac.tuwien.jsug.jpf.export.ExportMenuBarPlugin.java
* src/main/resources/
  - plugin.xml

file plugin.xml: <source lang="xml"> <?xml version="1.0" ?> <!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0" "http://jpf.sourceforge.net/plugin_1_0.dtd"> <plugin id="at.ac.tuwien.jsug.jpf.export" version="0.0.1">

<requires> <import plugin-id="at.ac.tuwien.jsug.jpf.core" /> </requires>

<runtime> <library id="src" path="/" type="code" /> </runtime>

<extension plugin-id="at.ac.tuwien.jsug.jpf.core" point-id="MenuBar" id="ExportMenuBar"> <parameter id="class" value="at.ac.tuwien.jsug.jpf.export.ExportMenuBarPlugin" /> </extension> </plugin> </source>

file ExportMenuBarPlugin.java: <source lang="java"> package phudy.jpf.pluginexport;

import at.ac.tuwien.jsug.jpf.core.IMenuBarPlugin;

public class ExportMenuBarPlugin implements IMenuBarPlugin { public String getTitle() { return "Export"; }

public void execute() { System.out.println("exec export"); } } </source>


use the extension

you might want to use the following handy methods for retrieving plugins: <source lang="java"> /**

* overloaded method setting default "class" attribute name
* @see #fetchPlugins(Plugin,String,String,String)
*/

public static <T> List<T> fetchPlugins( final Plugin plugin, final String extPointPluginId, final String extPointId) throws Exception { return fetchPlugins(plugin, extPointPluginId, extPointId, "class"); }

/**

* fetches all extensions for the given extension point qualifiers
*/

@SuppressWarnings("unchecked") public static <T> List<T> fetchPlugins( final Plugin plugin, final String extPointPluginId, final String extPointId, final String attributeName) throws Exception { final List<T> plugins = new LinkedList<T>();

final PluginManager manager = plugin.getManager();

final ExtensionPoint extPoint = manager.getRegistry().getExtensionPoint(extPointPluginId, extPointId); for (final Extension extension : extPoint.getConnectedExtensions()) { // LOG.info("Processing extension point: " + extension);

final PluginDescriptor extensionDescriptor = extension.getDeclaringPluginDescriptor(); manager.activatePlugin(extensionDescriptor.getId()); final ClassLoader classLoader = manager.getPluginClassLoader(extensionDescriptor); final String pluginClassName = extension.getParameter(attributeName).valueAsString(); final Class<T> pluginClass = (Class<T>) classLoader.loadClass(pluginClassName); final T pluginInstance = pluginClass.newInstance(); plugins.add(pluginInstance); }

return Collections.unmodifiableList(plugins); } </source>

file CoreApplicationPlugin: <source lang="java"> package at.ac.tuwien.jsug.jpf.core;

public class CoreApplicationPlugin implements ICoreApplicationPlugin {

private static final String EXTPOINT_ID_MENUBAR = "MenuBar";

public void startApplication() { final JFrame frame = new JFrame(); frame.getContentPane().add(new JLabel("JPF Core Module"));

// setup menubar final List<IMenuBarPlugin> menuBarPlugins = JpfUtil.fetchPlugins(this, this.getDescriptor().getId(), EXTPOINT_ID_MENUBAR); setJMenuBar(newMenuBar(menuBarPlugins));

frame.pack(); frame.setVisible(true); }

private JMenuBar newMenuBar(final List<IMenuBarPlugin> menuBarPlugins) { final JMenuBar bar = new JMenuBar(); final JMenu menu = new JMenu("Application");

for (int i = 0; i < menuBarPlugins.size(); i++) { final IMenuBarPlugin plugin = menuBarPlugins.get(i); final JMenuItem item = new JMenuItem(plugin.getTitle());

item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { plugin.execute(); }});

menu.add(item); }

bar.add(menu); return bar; } } </source>

appendix

links


notes

  • although it is more common to write "plug-in" instead of "plugin" i have choosen not to write the additional "-" character :)