Our goal is to create a simple plugin mechanism in Java. On top of that we want our execution environment to be divided into three distinct phases. These phases are named:

  1. PRE_PROCESS
  2. PROCESS
  3. POST_PROCESS

This will allow us to run certain plugins at specific phases during program execution. So if a plugin is for example registered on the PRE_PROCESS hook, we can - and must - guarantee that this plugin will be executed before the PROCESS phase.

After we’ve laid out the basic requirements now, the question is: “How do we implement that?”.

The first thing that probably comes to mind when talking about a plugin mechanism is the standard Java ServiceLoader that was officially opened with the JDK6 and has been in there since the JDK 1.3 days. Another mechanism that can be used to implement a system like this is the Netbeans Lookup API.

After defining a basic interface for the plugins we want to create, we’ll have a look at both mechanisms to see how they compare.

Create Domain Objects

Ok, so let’s start defining the actual Plugin interface so we have a common hook to execute our plugins on.

We make the interface itself very easy and just define a process method in which we’ll later have implementations append the actual phase and class name to the String instance that was passed as a parameter. We’ll return that String to the caller, so we can subsequently use it and demonstrate very easily when and where the plugin was executed.

The interface we want to implement looks like this:

package org.coffeecrew.tutorials.simplepluginmechanism;

/**
 Simple plugin interface.
 <p/>
 @author Jens Frey
 */
public interface Plugin {

    /**
     Simple worker method that will append data on the processingToken.
     <p/>
     And another.
     <p/>
     @param processingToken Token where data should be appended during
                            processing.
     <p/>
     @return processingToken that has been worked on.
     */
    public String process(String processingToken);
}

As a next step we define three interfaces that act as marker interfaces so we can differentiate the lifecycle phase the plugin should be run in by the name of the implemented interface. These interfaces are defined as shown below.

For the PRE_PROCESS phase:

package org.coffeecrew.tutorials.simplepluginmechanism;

/**
 Marker Interface to tell this is a plugin running in the PRE_PROCESS phase.
 */
public interface PreProcessable extends Plugin {
}

For the PROCESS phase:

package org.coffeecrew.tutorials.simplepluginmechanism;

/**
 Marker Interface to tell this is a plugin running in the PROCESS phase.
 */
public interface Processable extends Plugin {
}

For the POST_PROCESS phase:

package org.coffeecrew.tutorials.simplepluginmechanism;

/**
 Marker Interface to tell this is a plugin running in the POST_PROCESS phase.
 */
public interface PostProcessable extends Plugin {
}

Now that we’ve defined the interfaces we need some implementations that actually do something. At first we implement a plugin that should be run in the PRE_PROCESS phase. It looks like:

package org.coffeecrew.tutorials.simplepluginmechanism;

public class PreProcessPlugin implements PreProcessable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[PRE_PROCESS] " + this.getClass().getName() + "\n";
    }
}

The following implementations are running on the main processing hook PROCESS and look as follows:

package org.coffeecrew.tutorials.simplepluginmechanism;

public class ProcessPlugin implements Processable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[PROCESS] " + this.getClass().getName() + "\n";
    }
}
package org.coffeecrew.tutorials.simplepluginmechanism;

public class MoreProcessPlugin implements Processable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[PROCESS] " + this.getClass().getName() + "\n";
    }
}

And finally some implementation for the POST_PROCESS hook:

package org.coffeecrew.tutorials.simplepluginmechanism;

public class PostProcessPlugin implements PostProcessable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[POST_PROCESS] " + this.getClass().getName() + "\n";
    }
}

Now that was quite a bit of work, wasn’t it? But you’ll be shortly rewarded with a nice and pluggable system. Now all we need to be happy is a piece of code that actually runs all of these plugins. Since this code looks slightly different whether we use Java’s ServiceLoader mechanism directly or the Netbeans Lookup all the bits ’n pieces required to make this work are outlined in separate sections shown below.

Service Loader

So let’s first have a look at the ServiceLoader mechanism. To prepare implementations of a specific interface for automatic lookup by the ServiceLoader you need to create files in the META-INF/services of your resulting JAR file. I will describe inclusion of that folder on a Maven based project.

In order to get the META-INF/services folder into your maven output structure you have to create a folder named resources under src/main. Inside the created resources folder you must place the folder META-INF and underneath that the folder services which Maven will copy when you launch the build.

As initially mentioned you need to put files inside the services folder. These files must be named after the interface you want the automatic lookup mechanism to find implementations for.

The content of these files is the actual class name of every implementation you want the ServiceLoader to find. If you have multiple implementations of a interface you need to include every implementation class name inside that file delimited by a new line.

In our case these three files we need to create must be named (please ensure you choose the correct package name that prepends the interface name):

  • org.coffeecrew.tutorials.simplepluginmechanism.PreProcessable
  • org.coffeecrew.tutorials.simplepluginmechanism.Processable
  • org.coffeecrew.tutorials.simplepluginmechanism.PostProcessable

These files must be filled with the following content (please ensure you choose the correct package name that prepends the implementation class name).

org.coffeecrew.tutorials.simplepluginmechanism.PreProcessable
org.coffeecrew.tutorials.simplepluginmechanism.PreProcessPlugin
org.coffeecrew.tutorials.simplepluginmechanism.Processable
org.coffeecrew.tutorials.simplepluginmechanism.ProcessPlugin
org.coffeecrew.tutorials.simplepluginmechanism.MoreProcessPlugin
org.coffeecrew.tutorials.simplepluginmechanism.PostProcessable
org.coffeecrew.tutorials.simplepluginmechanism.PostProcessPlugin

Now we’ve hopefully prepared everything the ServiceLoader needs to do it’s job. All that’s missing now is a simple program that executes our plugin mechanism.

Plugin Executor

A very simple implementation that will execute all plugins we have written with the ServiceLoader mechanism is quickly written. To guarantee the execution order of the plugins as we initially defined we must implement the code as shown below.

package org.coffeecrew.tutorials.simplepluginmechanism;

import java.util.ServiceLoader;

public class SimplePluginExecutorServiceLoader {

    public static void main(String[] args) {
        String processingToken = "";

        final ServiceLoader<PreProcessable> preProcessables = ServiceLoader.load(PreProcessable.class);
        for (final PreProcessable preProcessable : preProcessables) {
            processingToken = preProcessable.process(processingToken);
        }

        final ServiceLoader<Processable> processables = ServiceLoader.load(Processable.class);
        for (final Processable processable : processables) {
            processingToken = processable.process(processingToken);
        }

        final ServiceLoader<PostProcessable> postProcessables = ServiceLoader.load(PostProcessable.class);
        for (final PostProcessable postProcessable : postProcessables) {
            processingToken = postProcessable.process(processingToken);
        }

        System.out.println(processingToken);

    }
}

As you can see the code is a pretty straight forward implementation. It runs through every implementation that is available for a given interface type and runs the process() method on it.

If you did everything right you should now see some output similar to:

Sample Output
[PRE_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PreProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.ProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.MoreProcessPlugin
[POST_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PostProcessPlugin

As we can see from the output every phase was executed. Where we had two plugins available, both were run as well. This fulfills our basic requirements.

Netbeans Lookup

By utilizing the Lookup library you can very easily achieve loose coupling, as you can with the ServiceLoader described above. On top of that you get some nice annotations and the ability to give your service implementation a certain priority when it should be run.

Now let’s have a look at the Netbeans Lookup API and see what’s the difference to the ServiceLoader.

Well, the obvious first difference is that you need to include a additional library in your project. That library, which was initially strongly coupled to the Netbeans platform itself, was over time separated from the platform so it can be used in applications that are not based on the Netbeans platform and is available as a separate Maven dependency these days.

For the dependency resolution to work you need to include the Netbeans repository in your pom.xml so you’re able to get a hold of the library as this library is not available in one of the well know central repositories as for example The Central Repository. There is a discussion going on about this which may or may not lead to a centrally available dependency in the future.

You can however reference the library by appending the following information into your projects pom.xml file.

Netbeans Lookup Maven Dependencies
<repositories>
   <repository>
      <id>Netbeans Repository</id>
      <url>http://bits.netbeans.org/maven2/</url>
   </repository>
</repositories>

<dependencies>
   <dependency>
      <groupId>org.netbeans.api</groupId>
      <artifactId>org-openide-util-lookup</artifactId>
      <version>RELEASE73</version>
   </dependency>
</dependencies>

Don’t forget to execute a build of your project, so maven can download the newly added dependency for you.

If you’re still working on the initial project where we’ve used the ServiceLoader mechanism please make sure you delete the resources folder now, you don’t need it in conjunction with the Lookup API.

For the Lookup API to work you need to add annotations to your domain objects. These annotations will create the files with the interface name and fill their content with the class names that provide an implementation for that interface.

This probably sounds more difficult than it actually is now, but it’s actually a trivial thing to do. You simply need to add a little @ServiceProvider annotation in front of your class name.

To make this as simple as possible I print the four classes again, their content is the same as outlined in the Create Domain Objects section, just with the added annotation.

PreProcessPlugin.java
package org.coffeecrew.tutorials.simplepluginmechanism;

import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = PreProcessable.class)
public class PreProcessPlugin implements PreProcessable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[PRE_PROCESS] " + this.getClass().getName() + "\n";
    }
}
ProcessPlugin.java
package org.coffeecrew.tutorials.simplepluginmechanism;

import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = Processable.class, position = 10)
public class ProcessPlugin implements Processable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[PROCESS] " + this.getClass().getName() + "\n";
    }
}
MoreProcessPlugin.java
package org.coffeecrew.tutorials.simplepluginmechanism;

import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = Processable.class)
public class MoreProcessPlugin implements Processable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[PROCESS] " + this.getClass().getName() + "\n";
    }
}
PostProcessPlugin.java
package org.coffeecrew.tutorials.simplepluginmechanism;

import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = PostProcessable.class)
public class PostProcessPlugin implements PostProcessable {

    @Override
    public String process(String processingToken) {
        return processingToken + "[POST_PROCESS] " + this.getClass().getName() + "\n";
    }
}

Now that we’ve changed our domain object to utilize the annotations provided by the Lookup API we need an executor method to run our plugins in. All that changes compared to the ServiceLoader approach is the way you look up the implementations. It’s just a different API call.

Plugin Executor

Again we’ll show a very simple implementation that will execute all plugins we have written.

SimplePluginExecutorLookupAPI.java
package org.coffeecrew.tutorials.simplepluginmechanism;

import java.util.Collection;
import org.openide.util.Lookup;

public class SimplePluginExecutorLookupAPI {

    public static void main(String[] args) {
        String processingToken = "";

        final Collection<? extends PreProcessable> preProcessables = Lookup.getDefault().lookupAll(PreProcessable.class);
        for (final PreProcessable preProcessable : preProcessables) {
            processingToken = preProcessable.process(processingToken);
        }

        final Collection<? extends Processable> processables = Lookup.getDefault().lookupAll(Processable.class);
        for (final Processable processable : processables) {
            processingToken = processable.process(processingToken);
        }

        final Collection<? extends PostProcessable> postProcessables = Lookup.getDefault().lookupAll(PostProcessable.class);
        for (final PostProcessable postProcessable : postProcessables) {
            processingToken = postProcessable.process(processingToken);
        }

        System.out.println(processingToken);

    }
}

As you can see this code is basically the same as with the ServiceLoader approach. It runs through every implementation that is available for a given interface type and runs the process() method on it.

However, you might notice that the output slightly changed. With the Lookup API the MoreProcessPlugin is executed before the ProcessPlugin. This is something we may not want in case we care about the order in which these plugins need to run when they are executed within the same phase.

Service Loader Output
[PRE_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PreProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.ProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.MoreProcessPlugin
[POST_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PostProcessPlugin
Lookup API Output
[PRE_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PreProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.MoreProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.ProcessPlugin
[POST_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PostProcessPlugin

Luckily there s a mechanism which allows you to control exactly that behavior. And it’s built in right into the Lookup API. The @ServiceProvider annotation let’s you set a position attribute. This position attribute defines when this implementation should be run, if another implementation is also available. The higher the position number is, the earlier the implementation is run.

Since the default position value is 0 it is sufficient to assign a value only to the ProcessPlugin implementation. Changing the @ServiceProvider line in the ProcessPlugin to

@ServiceProvider(service = Processable.class, position = 10)

Will result in the following output which reflects the actual processing order of the ServiceLoader example.

Comparison

As you could see the Lookup API simplifies work by generating the files you’d normally have to create in META-INF/services manually. By the generative approach and the position attribute you can also guarantee a specific execution order without the hassle to manually reorder the content of the files in META-INF/services.

The Lookup API offers much more advanced features aside from the ones that were mentioned here, like for example Lookup templates that allow you to query information on an object without instantiating it or listening to changes in a Lookup. You could also lookup different implementations for a service based on it’s MIME type.

One More Thing

Now I have actually one more thing I want to share with you. This approach works for both previously introduced plugin mechanisms, but will be included in the Lookup API example only.

It allows you to add or remove processing phases without having to rewrite all your processing code. This can simply be achieved by assigning the interface classes to an enumeration type. So let’s first implement the enumeration type that ties together the processing phase and the interface assigned to it.

Phase.java
package org.coffeecrew.tutorials.simplepluginmechanism;

public enum Phase {

//    PRE_PROCESS(PreProcessable.class),
    PROCESS(Processable.class),
    POST_PROCESS(PostProcessable.class);
    private final Class<? extends Plugin> plugin;

    private Phase(Class<? extends Plugin> plugin) {
        this.plugin = plugin;
    }

    public Class<? extends Plugin> getPhaseInterface() {
        return plugin;
    }
}

As you can see the implementation is fairly trivial, yet effective. Now all we need is an adapted executor for this to work. An implementation that utilizes the new idea looks as follows:

SimplePhaseExecutor.java
package org.coffeecrew.tutorials.simplepluginmechanism;

import java.util.Collection;
import org.openide.util.Lookup;

public class SimplePhaseExecutor {

    public static void main(String[] args) {
        String processingToken = "";

        for (final Phase p : Phase.values()) {
            final Collection<? extends Plugin> processables = Lookup.getDefault().lookupAll(p.getPhaseInterface());
            for (final Plugin plugin : processables) {
                processingToken = plugin.process(processingToken);
            }
        }
        System.out.println(processingToken);

    }
}

Execution of the above code will result in the following output:

Lookup API Output
[PRE_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PreProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.ProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.MoreProcessPlugin
[POST_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PostProcessPlugin

Which is what we have expected it to do. The cool part however is - if you need to change the execution order of the phases, add a new phase, or delete a phase - you do not need to change the executor code but only the order or number of elements in the Phase enumeration.

So should you decide to no longer include the PRE_PROCESS phase anymore you just need to remove the phase from the enumeration type, re-run the example aaaand:

Output - PRE_PROCESS Phase Removed
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.ProcessPlugin
[PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.MoreProcessPlugin
[POST_PROCESS] org.coffeecrew.tutorials.simplepluginmechanism.PostProcessPlugin

There you go, no more annoying PRE_PROCESSING, just as expected :)

If you like you can clone the two projects from Github:

Hope you enjoyed the post! Leave comments, like ’n share!

Like this post? Share on: TwitterFacebookEmail


Jens Frey Avatar Jens Frey is the creator of the datapile blog.

Keep Reading


Published

Category

Programming

Tags

Stay in Touch