Now that we’ve answered any questions anyone would have — ever — about plugin loading, let’s spend some time on the more interesting part: how do I design the plugins. I would like to note that it’s probably not possible to write a complete, generally useful plugin system that works across applications, problem domains. If someone were determined enough to prove me wrong and actually did just that, I’d be willing to be that it would be difficult to use, heavy, etc. If this person were truly determined, gifted, etc. to prove me wrong yet again, well… good for him. :) For the rest of us (or, in this case, just me), Java EE makes this so easy that you really don’t need any extra frameworks. Specifically, we’ll take a look at JSF, CDI, and JAX-RS.
View Extensibility
Let’s start by taking a look at view extensibility, as, after all, even if you have the greatest plugin system in the world, if it can’t affect the view, then it’s pretty much worthless. To demonstrate this technique, we’re going to use JavaServer Faces, as it is the Java EE standard for web applications. You may prefer another framework, such as Spring MVC, Wicket, or GWT, or you may even be using desktop technologies such as Swing, SWT, or JavaFX to build views for your Java EE application. The technique here should work the same regardless of framework, more or less. You’ll just have to determine how to integrate into your technology of choice.
For a plugin to add content to the view, it will have to provide what we on the GlassFish Console team ended up calling view fragments. These fragments are exactly what they sound like, small pieces of UI…widgets that are added at specific points in the view. These fragments are categorized, by the plugin, into types, as defined by the consuming application. This means that the app might declare the types tab, treeNode, and widget. Aplugin, then, might add a tab to a configuration page, a treeNode to the navigation system, and a recent tweets widget to the sidebar. As we’ll see, how complex or simple the categorization/differentiaton exposed by the application is is completly up to you as the application author/architect.
Having defined the terms, then, how might one implement this? First off, let’s take a look at ViewFragment.java in the plummer-api module:
public @interface ViewFragment {
String type();
String parent() default "";
}
This simple interface defines type and parent properties. A plugin author would use it like this:
public class SamplePlugin implements Plugin {
@ViewFragment(type: "foo")
public static String sample1 = "sample1.xhtml";
@Override
public int getPriority() {
return 500;
}
}
There are several things to note here. First, let’s look at the annotation. Here, we are defining a ViewFragment of type foo. It is attached to a public static final String, whose value is sample.xhtml. When the system processes this annotation, it will store the value sample.xhtml in a Map, keyed by the value foo. When the view asks for view fragments of type foo, this piece of markup will be included. That file, by the way, is a simple JSF 2 Facelets file:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:fragment xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h1>Plugin Fragment</h1>
This text comes from a fragment. Shiny!
</ui:fragment>
Very simple. The question that should come to mind now is, "How does the system find this annotation, and then how do I tell the system to insert this into my view?" The answer to the first half of that question is the Plugin interface. Those interested in the nitty gritty can read the code (PluginService.java in plummer-kernel), but for those not that curious, CDI again saves the day. In a nutshell, we ask CDI for all the beans that implement Plugin, scan them for fields annotated with ViewFragment, and store the metadata. On the view side, we use the pl:viewFragment custom component that Plummer offers:
<pl:viewFragment type: "foo"/>
The system does the rest. What you put in your view fragments is completely up to you. We’ve put everything from simple markup to `h:form`s with no known issues. One note with regard to resources: since the resources are stored in JARs and not in the application’s document root, you will need to use JSF 2’s resource mechanism to reference images, javascript, CSS, etc:
<h:graphicImage value="#\{resource['myImage.JPG']}" height="200"
title: "Here's a picture of something really cool!"/>"
You can see a complete example of this in plummer-sample2.
One final note before we move on: The code in Plummer is mostly standards-compliant, but some Mojarra-specific classes needed to be used get access to the FaceletFactory needed to insert the view fragments into the component tree. MyFaces users can still use Plummer, but someone will need to implement the MyFaces-specific code to reproduce this functionality. It would be great if the spec could expose this kind of functionality, but that would require someone to file a request and then, preferably, submit the spec prose and implementation code to make that happen, and I just haven’t had the time. ;)
Application Extensibility
The real work, of course, is done at lower levels. Here we’ll see just how simple Java EE makes things. Specifically, we’ll look at two parts of CDI, events, and what we’ll simply refer to as programmatic bean lookup.
CDI events is, conceptually, just a simple pub/sub system. One part of the system fires, or publishes, events, and another observes (subscribes). This makes it very easy to loosely couple parts of the system: the core of your application need not worry about what, if anything handles, the event. It also easily allows multiple recipients to respond to the event fired. Again, the system doesn’t care. In Ron Popeil style, you just "set it and forget it".
So what does this look like in practice? To demonstrate that in a meaningful way, we need a sample application, so we’ll write a very simple blogging system. If you’ve ever interacted with a blog, either as an author or a reader, you’ve likely seen the option by which a user can subscribe and get notifications of new posts. Let’s implement that. First up, we’ll need a way to create blog entries. You can find this BlogBean.java in the webapp, but here are the interesting parts:
// ...
@Inject
private Event<BlogPostedEvent> blogPostedEvents;
public String addEntry() {
entries.add(entry);
blogPostedEvents.fire(new BlogPostedEvent(entry));
entry = null;
return null;
}
// ...
For the sake of brevity here (too late, right?), you can find the view in examples/webapp/src/main/webapp/blog.xhtml. First, notice the @Inject. Here, we’re asking CDI to inject an Event that takes a BlogPostEvent payload. We use this in addEntry(), when we call blogPostedEvents.fire(new BlogPostedEvent(entry)). The code, simple as it is, should be pretty self-explanatory: we’re firing an event of type BlogPostedEvent, which looks like this:
public class BlogPostedEvent {
private String blogEntry;
public BlogPostedEvent(String blogEntry) {
this.blogEntry = blogEntry;
}
public String getBlogEntry() {
return blogEntry;
}
}
In this example, our payload is very simple. In a real world, this could be much more complex if your application’s needs warrant. Responding to this event is just as simple as firing it:
public void sendEmail(@Observes BlogPostedEvent event) {
emailService.sendEmail(event.getBlogEntry());
}
That’s really all there is to it. By using CDI events, we are able to push data into our plugins in a loosely coupled manner. Again, in a real world application, the data push and the processing required to handle will likely be more complex, but the means of pushing it will not be. CDI for the win!
Perhaps you need to allow a plugin to process data in the system. For example, in our system we want allow plugins to translate the blog entry into another language. To do so, we first need to define the interface by which the plugin will be called:
public interface BlogEntryProcessor extends Serializable {
String getName();
String process(String text);
}
From our blogging system, we can get a list of all of the BlogEntryProcessor instances, if any, with this CDI injection:
@Inject @Translator
Instance<BlogEntryProcessor> translators;
This gives us an Instance instance that contains any BlogEntryProcessor`s defined in the system. We’ll come back to `@Translator in a bit. Next, we can provide a way for the user to pick a language with this code:
<h:form>
<h:selectOneMenu value="#\{blogBean.translator}" converter="#\{translatorConvertor}">
<f:ajax render=":entries" event="change" execute="@form"/>
<f:selectItems value="#\{blogBean.translators}" var="t" itemLabel="#\{t.name}" />
</h:selectOneMenu>
</h:form>
public List<BlogEntryProcessor> getTranslators() {
List<BlogEntryProcessor> list = new ArrayList<BlogEntryProcessor>();
for (BlogEntryProcessor t : translators) {
list.add(t);
}
return list;
}
This lets us change the language, but how do we get a default? Let’s define a Qualifier:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target(\{ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface English { }
This simple class lets us differentiate at injection time:
@Inject
@English
private BlogEntryProcessor translator;
Instead of injecting Instance<BlogEntryProcessor>, we’re injecting a single..um…instance. Since there might be more than BlogEntryProcessor on the system, we have to qualify which one we mean:
@English
@Singleton
@Translator
public class EnglishTranslator implements BlogEntryProcessor {
// ...
}
EnglishTranslator is-a BlogEntryProcessor, and it has been marked as @English, which means this instance, which is also a singleton, will satisfy the injection above. We could have annotated this with @Default, both here and at the injection point, but the creation of a custom @Qualifier is a good exercise. :)
But what’s up with that @Translator? That’s another @Qualifier, which must be applied to any BlogEntryProcessor that is intended to act as a translator (and which we document clearly in our system documentation, right? ;). Why is that important? In a simple system, we wouldn’t need that, but we’re going to intentionally muddy things a bit and introduce a different type of BlogEntryProcessor, one which allows for tags.
One common type of plugin in systems like Wordpress allows a user to wrap certain text in a tag. This entry, for example, uses the code tag to get syntax highlighting. In our system, we’ll implement a tag that creates links to Google Maps. For example:
Disneyland can be found at [map]1313 North Harbor Boulevard, Anaheim, CA[/map].
How is this implemented? Just like the translators:
@Tag
public class GoogleMapsProcessor implements BlogEntryProcessor {
@Override
public String getName() {
return "Google Maps Processor";
}
@Override
public String process(String text) {
Pattern pattern = Pattern.compile("\\[map\\](.*?)\\[\\/map\\]");
String replaceStr = "<a href=\\\"https://maps.google.com/maps?q=$1\\\">$1</a>";
Matcher matcher = pattern.matcher(text);
String result = matcher.replaceAll(replaceStr);
return result;
}
}
This looks just like the translators, right? The only difference is the @Tag qualifier, whose source you can see in the bundle. In BlogBean, we access it and the translators in getEntries():
@Inject
@Tag
Instance<BlogEntryProcessor> tags;
public List<String> getEntries() {
List<String> list = new ArrayList<String>();
for (String text : entries) {
for (BlogEntryProcessor tag : tags) {
text = tag.process(text);
}
text = translator.process(text);
list.add(text);
}
return list;
}
You can build and deploy the system to see this in action. Very simple, but very effective.
REST Resources
We’ve seen how to expose functionality to plugins loaded in the system, but what if we want to allow these plugins to expose this functionality to external clients, say, via REST? Again, Java EE makes this incredibly simple, using two specs in concert, CDI and JAX-RS.
One of the ways one might configure a JAX-RS application is to provide a custom Application class, one which extends javax.ws.rs.core.Application. Plummer provides such an Application, so all Plummer users need do is configure it in the web application:
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.steeplesoft.plummer.kernel.rest.RestApplication</param-value>
</init-param>
</servlet>
Oddly, note that the Application class is standardized, but the REST servlet is not (unlike, for example, JSF’s FacesServlet), so if you’re not using Jersey like we are here, then you’ll need to use the Servlet appropriate for your JAX-RS implementation.
So how does RestApplication work? It uses CDI, but since it’s not handled by the CDI runtime, we can’t rely on injection. Instead, we’ll perform a manual look up of the BeanManager, a class provided by CDI’s excellent portable extension mechanism. We then query the BeanManager for our desired classes. But how do we identify our REST resources? Remember the Plugin marker interface? Plummer defines another marker, RestResource, to mark the JAX-RS resources we want to load, which are typical JAX-RS resources with the exception of this extra interface:
@Path("myurl")
public class PluginRestResource implements RestResource {
@GET
public String test(@QueryParam("text") String text) {
return "You sent " + text;
}
}
When the REST application is initialized, this class is loaded and exposed at /myurl as you would expect.