An Approach to Pluggable Griffon Applications

If I were someone evaluating the existing Swing desktop frameworks, I wouldn't hesitate to choose the NetBeans Platform over Griffon or Spring RCP—for one very specific reason: any application built atop the NetBeans Platform is inherently extensible. John O'Conner's definition of extensibility applies here: "An extensible application is one that you can extend easily without modifying its original code base." In this sense, a plugin is not what Grails understands a plugin to be. A Grails plugin is applicable to a developer making use of the Grails framework. For example, the Grails Wicket plugin lets users of Grails incorporate Wicket view technology into the development of a Grails application. I imagine that the Griffon creators have the same definition of "plugin" in mind. Plugins for users of a framework are incredibly useful—users of Grails and, at some point, Griffon, are not limited to the original code that the Grails (and Griffon) creators originally provided, nor do they need to wait for the next release of the framework before making use of their favorite technology in combination with Grails (or Griffon). They're able to create a plugin that extends the Grails (or Griffon) framework and can then merrily continue creating the application of their dreams using the technologies that they're personally most comfortable with.

Those plugins, though, are not the ones I have in mind here. I mean end-user plugins, such as those that users of Firefox can plug into Firefox to add new functionality to it. (Here's a nice opportunity for me to plug the DZone Voting plugin for Firefox. Try it, try it now! It's great.) Similarly, unlike the Griffon framework (and unlike Spring RCP), the NetBeans Platform lets you extend existing applications without changing the original code base, by creating plugins. Does this current gap in Griffon (and Spring RCP) functionality mean it should not be used? Not at all. Since both Griffon and Spring RCP let you create good old Java applications, you can use the JDK 6 java.util.ServiceLoader class and, without the Griffon (and Spring RCP) creators needing to do anything else, you're able to let users of applications built atop these frameworks extend it too! In addition, in each case there are certain benefits in using the ServiceLoader class in these two frameworks (the benefits being distinct and particular to each) that the NetBeans Platform cannot, at least on the face of it, benefit from. However, the NetBeans Platform doesn't make use of the ServiceLoader class, it has an objectively superior approach (which could be reused in Griffon or Spring RCP, by the way), but that's a different story. The short point of this whole story is that you can already create extensible applications in Griffon (and in Spring RCP, but I'll provide a full scenario around that another day).

Here's a condensed step-by-step generic approach to working with ServiceLoader:

  1. Create a service. A service is a set of classes that exposes features via a public interface.

  2. Create a service provider. A service provider provides one or more implementations of the service. In order to provide implementations of the service, the service provider needs only to have the JAR that defines the service on its classpath. In other words, the service and the service provider can be (but do not have to be) in different JARs. The service provider is sometimes referred to as an 'extension point'. The service provider can also be seen as a 'plugin'.

  3. Publish the service provider. A provider configuration file needs to be placed in the service provider JAR's META-INF/services folder. The name of the file needs to match the FQN of the service. Each service provider made available by the JAR needs to be named in the file, by its FQN.

  4. Distribute the service provider. The service provider JAR needs to be put on the classpath of the application that needs to be extended. The JAR that contains the service needs to be on that classpath too.

  5. Load the service. Within the application that needs to be extended, a ServiceLoader needs to have been defined. The ServiceLoader will load the service. Then methods defined on the service (i.e., the interface) are invoked, which are called on each of the available service providers, if they have been published as described in step 3 above.

Via the above approach, the application has no direct relationship to any of the service providers. If a service provider isn't there, it simply isn't loaded. A default service provider can be created to handle the situation where no service providers are available.

But... can this work with Groovy? If the answer is "Yes", then Griffon applications are extensible, aren't they, since Griffon is nothing more than strictly structured Groovy code? And what could be the answer other than "Yes", given that Groovy is Java? And that right there is the benefit that Griffon has over the NetBeans Platform when it comes to creating extensible applications—you have the additional option of using Groovy to do so. (However, I guess that one could probably also create NetBeans Platform applications in Groovy, but lets leave those ruminations for another day too.)

I reckon the ServiceLoader Javadoc is very good and so I'll use the example described there in my scenario below. So, based on that (maybe read it all, if you haven't yet, before going further) here's a simple scenario of how everything described above fits together concretely in the context of Griffon:

  1. Create the application. Run "griffon create-app" and create an application called "EncoderSales". Here's the application (at least, here's how it looks for me in NetBeans IDE, via my tweaked Grails plugins for NetBeans IDE):

    So, this is the application that we will deliver to our users. Let's say that it will let the user choose an encoder (for something or other) from a list and then (at some further stage in the application, not covered here) somehow purchase it. However, we want to make it possible for the application to be extensible, so that providers of other encoders can add their encoders to the list. The encoder market is large and growing, one assumes, so we need to let the application be supplemented externally with additional encoder offerings. That's a pretty realistic scenario.

  2. Create the service. So, and this is unavoidably step 1 of the whole process, we'll create a service. To that end, create a brand new Java application called 'CodecSetService', with an interface named 'com.example.CodecSet' (which is the name of the example service in the Javadoc). The service will define what the set of encoders will consist of, in order for a new encoder to be allowed to be added to the application.

    The service could also be created in Groovy, that's neither here nor there, whatever you're comfortable with:

    package com.example;
    
    public interface CodecSet {
    
        public String getEncoder();
        
    }

    To really simplify things, we'll have one method instead of two and we'll use strings instead of the Encoder/Decoder return types referred to in the Javadoc example.

  3. Create the service provider. Next, we'll create our first service provider. Remember that a service provider is an implementation of a service. We will create it in a new Java application. Again, we'll follow the example from the Javadoc and call our service provider 'StandardCodecsProvider', with the implementing class being called 'com.example.impl.StandardCodecs'. Again, for now we'll use a Java class for the service provider too.

    To fulfil all the requirements for creating a service provider, the 3 bullets that follow will result in a Java application that looks as follows:

    • First, build the service and put its JAR on the classpath of the service provider's application. Now that the service is available to the service provider, the latter can implement the former.

    • We'll create a very simple implementation (how could it be otherwise, since we're simply returning the name of an encoder):
      package com.example.impl;
      
      import com.example.CodecSet;
      
      public class StandardCodecs implements CodecSet {
      
          @Override
          public String getEncoderName() {
              return "Standard Encoder";
          }
      
      }

    • Finally, in the service provider's application, create a folder structure within 'src', named 'META-INF/services'. Within it, create a file, without any extension, named 'com.example.CodecSet'. Inside that file, write one line and one line only, the content being 'com.example.impl.StandardCodecs' (without the quotes around it).

  4. Put the service provider on the application's classpath. Now, put both JARs that you've created (i.e., the service JAR, as well as the service provider JAR) in the Griffon application's "lib" folder. Your EncoderSales application should now look as follows:

  5. Load the service interface. Now we simply need to load the service interface into our Griffon application! Here we go—we use generics to specify the type and are then able to call the "getEncoderName()" on each service provider that is on our classpath and that has been registered according to the META-INF/services approach, as described above:
    import com.example.CodecSet
    
    class EncoderSalesController {
    
        def view
        def i = 1
    
        def loadService(){
            ServiceLoader<CodecSet> sl = ServiceLoader.load(CodecSet.class)
            sl.each() {
                view.encoderList.text =
                       view.encoderList.text +
                       "\\n" +
                       i++ +
                       ". "+
                       it.getEncoderName()
            }
        }
    
    }

    We call the above from Startup.groovy:

    def rootController = app.controllers.root
    rootController.loadService()

    And 'view.encoderList.text' in the controller? What's that all about? That refers to a JTextArea in the view, which is defined as follows:

    application(title:'Encoder Sales',  pack:true, locationByPlatform:true) {
        textArea( id:'encoderList', rows:10, columns:30 )
    }

    Run the application. Isn't it beautiful? Here it is:

    It's clearly time to distribute your application to all your customers! Do so now.

  6. Extend the distributed application. Good, your wonderful application is now distributed to your users and they're making use of it and telling you how wonderful it is. Then comes the moment when they'd like to extend it and, for whatever reason (you don't want to create the requested features in your original code base, or you don't have the time to do so, or the customer has some private features that need to be added, i.e., features that are germane to the customer and irrelevant to all the other users). In other words, there's a new encoder to be added to the list. Time to create a new service provider:

    • Put the service on your new service provider's classpath.

    • Implement the service:
      package com.example.impl;
      
      import com.example.CodecSet;
      
      public class SpecialCodecs implements CodecSet {
      
          public String getEncoderName() {
              return "Special Encoder";
          }
      
      }

    • Publish the service provider via MET-INF/services. Your service provider should look very similar to the one discussed earlier, only the implementation is different (and that's exactly the point):

    • Distribute the new JAR to to the end users, who need to put it on their EncoderSales application's classpath. Below you can see that I have three service provider JARs, together with the service JAR:

    • When the application restarts (which is just one area where the NetBeans Lookup class is superior, in that it has a handy Listener, unlike the ServiceLoader, which means that if the classpath changes dynamically, you will be able to unload and reload objects, which lets you hot-reload JARs without restarting the application, as described by Tim here on Javalobby), i.e., via the reloaded service the new service providers, i.e., those that are on the classpath and registered correctly, are invoked, and they'll provide new entries in the list, each potentially provided by different service providers, all simply as a result of the ServiceLoader loading the service and then having the methods invoked on the service providers:

    Only the fourth bullet above, i.e., distribution, is slightly inconvenient. (On the other hand, that's how it works for the Lobo Browser too, last time I checked.) Your granny Smith end user isn't very happy receiving JARs and being told to put them in special places. So, why not create a Plugin Manager in your 'EncoderSales' application? Add a menu item that says 'Plugins' and, when selected, a dialog appears that lets the user browse for the JAR. When they click 'Install', the JAR will simply be put into one of the application's folders (a user directory or even in the installation directory itself), so long as it is on the classpath, which is all that is needed for the ServiceLoader in the application to call its 'getEncoderName' method. (Perhaps the Griffon framework could provide this kind of functionality itself, so that the Griffon user could via a few lines of code simply enable the presence of a Plugin Manager, which is something the NetBeans Platform allows you to do too.)

(By the way, in case you're wondering about this, you can also specify the order of instantiation as well.) And that's how Griffon application are, in fact, extensible. (And, as one should be able to see, Spring RCP too.) So, returning to my original (slightly provocative) statement, extensibility is not a reason for choosing the NetBeans Platform over Griffon or Spring RCP. Yes, there's a little bit of extra work involved, at the moment anyway, but isn't that always the case with plugins?

Postscript: The John O'Conner quote at the start of this story comes from his excellent article Creating Extensible Applications With the Java Platform, which you should definitely read if you haven't already!

Comments:

Time for a brief plug for a little tool I wrote which is similar to ServiceLoader but more powerful:

https://sezpoz.dev.java.net/

The main advantages are that (1) you can use annotations to register services, rather than create separate metadata files; (2) you can inspect annotation metadata from e.g. EncoderSalesController without being committed to loading every service class right away, which could matter if there are dozens of them and startup time is a concern; (3) you can register services from fields or factory methods, not just whole classes. A future version of the NetBeans Platform may include a similar facility (details still TBD). I don't know yet whether the annotation processor works correctly on .groovy files, but it should at least work to define plug-ins in Java yet load them from Groovy.

BTW with either ServiceLoader or SezPoz, if you want to support dynamic plug-in reloading, just pass a custom ClassLoader. The main difference from NetBeans' Lookup is that there is no listener facility; but if you know that a plug-in has been reloaded, you can perhaps just reload all the services. (Lookup will attempt to preserve existing service instances if they can still be loaded from the same defining ClassLoader, which gives better performance when there are a lot of services and only a few modules are being reloaded.)

Posted by Jesse Glick on September 22, 2008 at 12:07 AM PDT #

Very Good!

Posted by 一卡多号 on September 23, 2008 at 04:44 AM PDT #

Dea sir,
my Queation is how set String at particular postion at JTextArea

Posted by V VINOTH on August 01, 2009 at 08:24 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today