JPF Part 2 – Integrating PluginManager with Spring
24. March 2010
In the second part of this tutorial it is all about integrating JPF with the Spring Framework. Basically, getting JPF up and running is very straightforward and extensively covered on the JPF website, I’ll spare you the details.
When working with the Spring Framework, especially in a J2EE context, it is usually not very thoughtful to deal with file system locations as you never know how and where your application gets deployed. So the first thing we need to change is how plugins are discovered by the PluginManager. In the example below our custom PluginManager just wraps the original JPF PluginManager and takes an array of plugin resources as constructor argument, hence offloading the actual discovery to the Spring container.
@Service public class PluginManager implements InitializingBean { private static final String DEFAULT_PLUGIN = "plugin-core"; private static final String DEFAULT_EXTENSION_POINT = "plugin"; private final Resource[] repositories; private final org.java.plugin.PluginManager manager; public PluginManager(Resource[] repositories) throws Exception { this.repositories = repositories; ObjectFactory objectFactory = ObjectFactory.newInstance(); manager = objectFactory.createManager(); } @Override public void afterPropertiesSet() throws Exception { PluginLocation[] pluginLocations = discoverPluginLocations(); manager.publishPlugins(pluginLocations); Plugin plugin = manager.getPlugin(DEFAULT_PLUGIN); ExtensionPoint extensionPoint = plugin.getDescriptor().getExtensionPoint(DEFAULT_EXTENSION_POINT); for (Extension extension : extensionPoint.getAvailableExtensions()) { try { PluginDescriptor descriptor = extension.getDeclaringPluginDescriptor(); manager.activatePlugin(descriptor.getId()); } catch (Exception e) { logger.error(e.getMessage(), e); } } } private PluginLocation[] discoverPluginLocations() throws IOException, MalformedURLException { List<pluginLocation> result = new LinkedList<pluginLocation>(); for (Resource repository : repositories) { if (!repository.exists()) continue; for (File plugin : repository.getFile().listFiles()) { result.add(StandardPluginLocation.create(plugin)); } } return result.toArray(new PluginLocation[result.size()]); } }
We assume that on of the plugins discovered by our PluginManager is the DEFAULT_PLUGIN which defines a generic extension point that needs to be extended by every plugin that should be loaded into our application. Now after the discovery, as you can see in the afterPropertiesSet() method, we automatically activate all plugins that extend this extension point. This is usually what you want, but as you can imagine it would be a snap to introduce a more sophisticated loading mechanism here.
By adding the following bean definition to you application context you just need to drop your plugins into the web application’s WEB-INF/plugins folder and the very basic integration should already be up and running.
<bean id="pluginManager" class="cc.catalysts.cp.plugin.core.impl.PluginManager"> <constructor-arg> <list> <value>WEB-INF/plugins</value> </list> </constructor-arg> </bean>
Beeing used to Spring this is only half of the game, we of course want our plugins to be able to define their own application context, ideally as a sub-context of the loading application’s context so that application contexts of different plugins don’t interfere with each other. And because as Spring developers we are used to Java annotations and auto-discovery all that cool stuff, we introduced an annotation based approach and don’t want to hassle with the JPF plugin descriptors for that.
I basically just want to put a class like
@Plugin public class MyPlugin { }
into my JPF plugin bundle and have it completely decoupled from the JPF infrastructure and initialized correctly without any further action. For that, we first declare the @Plugin annotation as
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Plugin { String value() default "classpath:META-INF/plugin/plugin-context.xml"; }
with a parameter to specify the context location and a reasonable default value for that. Furthermore we have to write some glue code that extends the mandatory JPF classes and does the bootstrapping of the plugin context. We start with the PluginWrapper here, that takes an instance of the annotated class, does the processing of the annotation, and implements the necessary JPF hooks.
public class PluginWrapper extends org.java.plugin.Plugin { private final Collection<xmlWebApplicationContext> plugins = new ArrayList<xmlWebApplicationContext>(); private final PluginDescriptor pluginDescriptor; private final PluginManager pluginManager; public PluginWrapper(PluginManager pluginManager, PluginDescriptor pluginDescriptor, Object plugin) { Class<?> clazz = plugin.getClass(); this.pluginManager = pluginManager; this.pluginDescriptor = pluginDescriptor; processAnnotations(clazz); } private void processAnnotations(Class<?> clazz) { Plugin plugin = clazz.getAnnotation(Plugin.class); XmlWebApplicationContext context = new XmlWebApplicationContext(); context.setClassLoader(getPluginClassLoader()); context.setNamespace(pluginDescriptor.getId()); context.setConfigLocation(plugin.value()); context.addBeanFactoryPostProcessor(new EventServiceBeanFactoryPostProcessor()); plugins.add(context); } private ClassLoader getPluginClassLoader() { return pluginManager.getPluginClassLoader(pluginDescriptor); } @Override protected void doStart() throws Exception { for (XmlWebApplicationContext context : plugins) { pluginManager.registerApiPlugin(context); } } @Override protected void doStop() throws Exception { } }
To have that PluginWrapper picked up by the JPF ObjectFactory we also need to provide a custom lifecycle handler that properly wraps all plugin instances created by the JPF infrastructure.
public class PluginLifecycleHandler extends StandardPluginLifecycleHandler { private PluginManager pluginManager; public void setPluginManager(PluginManager pluginManager) { this.pluginManager = pluginManager; } @Override protected Plugin createPluginInstance(final PluginDescriptor descr) throws PluginLifecycleException { PluginClassLoader loader = getPluginManager().getPluginClassLoader(descr); Class<?> pluginClass = loader.loadClass(descr.getPluginClassName()); return new PluginWrapper(pluginManager, descr, pluginClass.newInstance()); } }
Furthermore we need to override the default object factory to inject our custom PluginManager into the lifecycle handler.
public class PluginManagerObjectFactory extends StandardObjectFactory { private PluginManager pluginManager; public void setPluginManager(PluginManager pluginManager) { this.pluginManager = pluginManager; } @Override protected PluginLifecycleHandler createLifecycleHandler() { PluginLifecycleHandler result = new PluginLifecycleHandler(); result.configure(config.getSubset(PluginLifecycleHandler.class.getName() + ".")); result.setPluginManager(pluginManager); return result; } }
Almost there now, we finally again need to touch our PluginManager and do some modifications:
1. make it ApplicationContextAware
2. add
public void registerApiPlugin(XmlWebApplicationContext context) { context.setParent(applicationContext); context.setServletContext(applicationContext.getServletContext()); context.refresh(); }
3. change ObjectFactory.newInstance() to
ExtendedProperties extendedProperties = new ExtendedProperties(); extendedProperties.setProperty("org.java.plugin.ObjectFactory", "cc.catalysts.cp.plugin.core.PluginManagerObjectFactory"); PluginManagerObjectFactory objectFactory = (PluginManagerObjectFactory) ObjectFactory.newInstance(extendedProperties); objectFactory.setPluginManager(this);
VoilĂ , isn’t it nice now?


Thanks for a great post! Is it possible that you could post the example source?
There is still one open issue with aspectj (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=306915), once this is resolved I will think about how we can make the sources publically available.