From ServiceLoader to Lookup for Griffon

In An Approach to Pluggable Griffon Applications, I discussed how the JDK 6 ServiceLoader class is useful in the context of Griffon. In effect, it provides Griffon with plugins. However, the ServiceLoader doesn't have the concept of 'hot deploy'. Changes made on the classpath are only presented to a Griffon application upon restart. Let's take an alternative approach—instead of the ServiceLoader class, we'll use the Lookup class. Just add org-openide-util.jar from the NetBeans IDE distribution (and then, if that's how you feel, simply delete NetBeans IDE, because that one JAR is all you need) to the 'lib' folder of your Griffon application.

Now, as before, to get started, we need to create a JAR that provides a service. It exposes the service via this very simple service provider interface:

package com.example;

public interface ResultSet {

    public int getResult();
    
}

In this case, for this example, the JAR contains nothing other than the above. In reality, there could be a lot of supporting classes. But all that we need, in essence, is an interface to implement in our service provider.

Now, in our Griffon controller, we'll want to invoke the above method on all the implementations of that interface. However, in addition, wouldn't it be cool if we could set a listener on the result? Then, if the result changes, we'd know about it within our Griffon application. This is something that the ServiceLoader cannot do. As before, from our Griffon Startup.groovy, we call a method in the controller, for loading our service:

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

However, our service loader method (i.e., in the controller), is now as follows:

def Result res;

def loadService(){
    Lookup lkp = Selection.getSelection()
    res = lkp.lookupResult(ResultSet.class)
    res.addLookupListener(this)
    resultChanged(null)
}

The first line in our service loader method refers to a provider class, called 'Selection', which will define our lookup. We haven't defined this provider class yet. And what is a lookup, anyway? A lookup is "a bag of stuff". It is a map, with its keys being objects, with the values being instances of that object. Look at the code above. The 'key' of our map is called 'ResultSet'. That is the object that the service provider will have implemented, since that is the interface with which this blog entry started. And what is a 'Result' object? Well, for that you'll need to know our import statements for the above snippet:

import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Lookup.Result;

The result is the key 'ResultSet', to which we add a LookupListener. Finally, a method resultChanged will be called. This is for the first time we start up our Griffon application, at which point the result will not have changed. After that, whenever a change occurs to the Result, the resultChanged will be called automatically. Why automatically? Because our controller implements the LookupListener class. The only abstract method (i.e., the only required method) for a class implementing LookupListener is resultChanged. And here is the definition of that method:

StringBuilder sb = new StringBuilder()

@Override
public void resultChanged(LookupEvent arg0) {
    long mills = System.currentTimeMillis()
    Collection instances = res.allInstances()
    instances.each() {
        def nextResult = it.getResult()
        sb.append("At " + mills + ": " + nextResult + "\\n")
        view.encoderList.text = sb.toString()
    }
}

For each iteration through the collection of instances of the interface, we add a timestamp to the StringBuilder, together with the next result. This happens whenever a change is made to the instance. Again, "why?" And again, because the Groovy class implements LookupListener, which calls resultChanged whenever a change is made to any of the implementations of the given interface.

Then, in the final line above, the stringified version of the StringBuilder is put into 'view.encoderList.text'. What's that? Firstly, 'view' is defined as the view class (thanks to one of the Griffon configuration files), 'encoderList' is the id property of the JTextArea in the view, and 'text' is the JTextArea's 'text' property. See it in the penultimate line below, which is the complete definition of the view:

import javax.swing.border.TitledBorder
import javax.swing.border.EtchedBorder
application(title:'Encoder Sales', pack:true) {
    border = new TitledBorder( new EtchedBorder(), 'Incoming Results')
    panel(border:border) {
        textArea( id:'encoderList', rows:10, columns:30, editable:false )
   }    
}

Now we'll create one implementation of the ResultSet interface. We'll do so in a completely new JAR. Create a new Java application, put the JAR that provides the interface on the JAR's classpath and then implement it (and have it created) as follows:

public class Selection {

    private Selection() {
    }
    
    private static MyLookup LKP = new MyLookup();

    //Accessor for the lookup:
    public static Lookup getSelection() {
        return LKP;
    }

    //The lookup, which adds new ResultSetImpls to itself:
    private static final class MyLookup extends ProxyLookup implements Runnable {

        private static ScheduledExecutorService EX = Executors.newSingleThreadScheduledExecutor();

        public MyLookup() {
            EX.schedule(this, 2000, TimeUnit.MILLISECONDS);
        }
        private int i;

        @Override
        public void run() {
            //Add to the Lookup a new ResultSetImpl:
            setLookups(Lookups.singleton(new ResultSetImpl(i++)));
            EX.schedule(this, 2000, TimeUnit.MILLISECONDS);
        }
    }

    //The implementation of the interface:
    private static final class ResultSetImpl implements ResultSet {

        public int result;

        public ResultSetImpl(int i) {
            result = i;
        }

        public int getResult() {
            return result;
        }

    }
    
}

Now, based on the above ScheduledExecutorService, a new instance of ResultSetImpl is added to the lookup, which is exposed via getSelection(). And that's the method that defined the lookup in the controller:

Lookup lkp = Selection.getSelection()

And when you run the above Griffon application, with the two JARs above on the classpath? The view shows the following:

The view is automatically updated, based on the above ScheduledExecutorService, which specifies that every few milliseconds a new instance of the implementation is created and added to the lookup, on which a LookupListener is set in the controller. Clearly, the key to all of this is that LookupListener. Without it, there would have been no way of knowing that the result is changing every few milliseconds and therefore no way of updating the view. In this way, long running processes can be handled by a separate JAR, with output asynchronously loaded into a Griffon application.

Comments:

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
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today