Oliver Drotbohm Archive About Tags

The smallest plugin system ever

October 9th, 2008

I blogged about Hades, an OpenSource project I lead a while ago. Today I want to launch another neat library. Much smaller than Hades but IMHO useful nevertheless.

Developing Spring applications you often come to the point of having a component that should get a list of collaborateurs injected. The component in turn does select one or many of these collaborateurs based on a defined criteria and works with them. This raises a few requirements:

Here’s the solution I came up with…

I have an interface Plugin<T>, where T defines the criteria or delimiter to select plugins that shall actually be invoked. The only method this interface contains is boolean supports(T delimiter). To define an actual plugin interface you create an interface that extends Plugin, declare the delimiter and add the business methods you want the plugin to provide. E.g.:

public interface NotificationProvider implements Plugin<String> {
  void notify(Notification notification);
}

Your NotificationProvider instances have to implement supports thus decide if they are able to handle the given delimiter or not. In most cases the delimiter might be a parameter of some business method like Notification in this case, but it does not have to be.

The next core abstraction is the PluginRegistry<S extends Plugin<T>, T>. Wow, whatta signature. Instantiating it, it allows you to register Plugins of type S and in turn access the by providing delimiter in various ways. You can ask the registry for the first suitable Plugin, all suitable plugins or ask for mandatory plugins and let the registry throw an exception if none found. See the JavaDoc for details.

Thats a lot of lowlevel code but enables you to define clean plugin interfaces and manage access to plugin instances in an elegant way. So you can easily inject a PluginRegistry into a Spring bean and access plugins as you like.

<bean id="notificationProviders" class="org.synyx.plugin.core.PluginRegistry">
  <property name="plugins">
    <list>
      <bean class="com.acme.notification.EmailNotificationProvider" />
      <bean class="com.acme.notification.JabberNotificationProvider" />
    </list>
  </property>
</bean>

How to create a registry?

Well, thats nice but quite static. What if you would like to allow your colleague to create a JAR file to drop in your classpath and automatically find his NotificationProvider implementation after next application restart.

Note that I do not want to cover exchanging JARs at runtime, rather want to feature some kind of “configuration on packaging” with the least overhead possible.

This is where Spring comes into play again. I implemented a PluginRegistryBeanFactoryPostProcessor (only less than half of the class name length is my fault ;). It will rummage the parsed bean definitions and combine those implementing a certain interface in a PluginRegistry and expose it as a new bean in the application context itself.

<bean class="org.synyx.plugin.support.PluginRegistryBeanFactoryPostProcessor">
  <property name="lists">
    <map>
      <entry key="notificationProviders"
        value="com.acme.notification.NotificationProvider" />
    </map>
  </property>
</bean>

The property name lists is somewhat ugly as it expects a map as parameter. This is because you ca define multiple lists, each entry results in one list or better named PluginRegistry. This is mainly due to the requirement to create multiple PluginRegistrys with one BeanPostProcessor. Maybe we’d drop this providing a more speaking interface but take into account to register one BeanFactoryPostProcessor for one PluginRegistry.

So how do you set up your plugin JARs?

What we can’t free you from is declaring your plugin implementation as a Spring bean. You should define a special configuration file location and name to allow the plugin host to automatically lookup the plugin configuration file via a wildcarded import, e.g.

<import resource="classpath*:com/acme/**/plugin-context.xml" />

Defining this along with the BeanPostProcessor above in your plugin host JAR will allow you to simply drop plugin JARs into your classpath and let them provide plugin implementation. Using maven you simply need to declare the plugin implementation project as dependency, turning Maven pom.xml files into some kind of program instance configuration.

Syntactic sugar

As you might have noticed, registration of the BeanFactoryPostProcessor is somewhat ugly. Besides the enormous name it is a LOT of XML to just define registering all beans implementing Foo under bar. Furthermore the entry’s “key” is actually the bean id the PluginRegistry is registered under, the “value” is actually a type name.

To ease configuration a little I’ve added a Spring namespace implementation , that boils declaring a PluginRegistry down to something like this:

<plugin:registry id="notificationProviders"
  class="com.acme.notification.NotificationProvider" />

You can gain Eclipse classname code completion by adding org/synyx/plugin/core/config/plugin-config.xsd to your XML catalog. If you installed Spring IDE, it should come up with the code completion if you edit the class attribute of the registry element.

Simple lists

The project also includes Spring support for a little stripped down version of the requirements by providing a BeanListBeanFactoryPostProcessor, that simply combines bean implementing a given interface to a list. This is useful if you want to leverage the automatic lookup functionality but do not need to select beans based on a given criteria and thus in turn not need to implement Plugin and use PluginRegistry. Of course there is also a namespace element list, that provides a configuration shortcut to define this BeanFactoryPostProcessor, too.

Summary

The library offers sophisticated support for the little man’s need to dynamically add and remove plugins at packaging time and thus helps to modularize applications where a fully fledged plugin environment like OSGi can not be used or would be overkill to use.

The library is available in version 0.1 under Apache License 2.0 and can be downloaded from the project website or directly accessed via our Maven repository. I haven’t really polished the website nor declared license text in the source files. This is due to change in a few days.

We don’t have an offical name for the library yet, so any suggestion is welcome. It would be nice, if we could find a name based in greek mythology as we at Synyx typically use names from that scope for software projects.

If you have any comments, ideas, critique dont’t hesitate to leave a comment or contact me directly.

Update: We decided to use Hera as the name for the project as all our projects are named around gods of the greek mythology. This affected the project URL.

blog comments powered by Disqus