zaterdag 6 december 2008

Where do GWT widget events come from??

(This post applies to GWT 1.5. In GWT 1.6 the event system has been changed.)

The Google Web Toolkit works with an event mechanism that implements the Observer pattern: If an event occurs within a certain object called the `observed' object, this triggers a method invocation on each object within a set of `observing' objects called the listeners.

Various event types exist. Most of them (or all of them -- i'm not sure) represent events happening on the user-interface, e.g. a mouse entering or leaving a widget, a mouse click on a widget, etcetera. It was unclear to me how this widget event model worked, and i did not find a good explanation in the docs, so that's why i wrote this post.

The `observed' widget acts as an event source, e.g., a Button acts as a source for click events. In GWT, if a widget acts as an event source it must implement a sourcesXXXEvents interface. There are 15 somewhat sourcesXXXEvents interfaces defined in the GWT API. SourcesClickEvents, SourcesChangeEvents, SourcesKeyboardEvents, etcetera. These interfaces require that event listeners can be added to and removed from the listener collection of the event source.

The `observing' objects -- the ones that are registered as listeners -- must implement methods to deal with events if they are notified. E.g., an object listening to a SourcesKeyboardEvents object must implement the KeyBoardListener interface, requiring it to implement the onKeyDown, onKeyPress, onKeyUp methods. As such, each listener interface requires its own methods to be implemented. This is all pretty standard and straightforward if you ever did some event programming in something like Swing or Windows.

So-far-so-good, but one question remains: what is the rock-bottom source of events in GWT? Is declaring the implementation of an SourcesXXXEvents interface enough to have the events actually generated?

For instance, the Button class does not implement the SourcesMouseEvents interface. (At least not in GWT 1.5.3.) The SourcesMouseEvents interface contains methods onMouseEnter() and onMouseLeave(). These could be used to e.g. change the widget's text when the mouse hovers over it. Suppose i subclass Button as follows:

public class MouseEventsButton extends Button implements SourcesMouseEvents {

MouseListenerCollection mouseListenerCollection;

public MouseEventsButton(String string) {
super(string);
}

public void addMouseListener(MouseListener listener) {
if (mouseListenerCollection == null) {
mouseListenerCollection = new MouseListenerCollection();
}
mouseListenerCollection.add(listener);
}

public void removeMouseListener(MouseListener listener) {
if (mouseListenerCollection != null) {
mouseListenerCollection.remove(listener);
}
}


}

Will my new button respond to a mouse entering and leaving it now?? Let's try.

public class DemoGwtEvents implements EntryPoint {

public void onModuleLoad() {

VerticalPanel vPanel = new VerticalPanel();
vPanel.setWidth("100%");
vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
RootPanel.get().add(vPanel);

// create the new button
MouseEventsButton mouseEventsButton = new MouseEventsButton("Rub me!");

// add a listener to it that changes text on events
mouseEventsButton.addMouseListener(new MouseListenerAdapter() {

public void onMouseEnter(Widget sender) {
Button b = (Button) sender;
b.setHTML("thank you");
}

public void onMouseLeave(Widget sender) {
Button b = (Button) sender;
b.setHTML("rub me again!!");
}
});

vPanel.add(mouseEventsButton);

}
}
NOOOOO, that doesn't work... Nothing happens if i move my mouse over the MouseEventsButton.

The key is that i must make a connection between low-level DOM-events and high-level GWT events somehow. DOM-events are events that happen in the browser window. GWT events happen in the Java layer of a GWT app. Somehow, the DOM-events must be captured and re-launched into the Java layer. This is done in two steps:
  1. Tell the new widget (the button) to CAPTURE certain DOM-events by calling sinkEvents. In our case this is this.sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
  2. Write an event handler to capture the sunk DOM events and re-launch them in the java world. This is done by overriding the onBrowserEvent method of the widget. In our case this is
     public void onBrowserEvent(Event event) {
    super.onBrowserEvent(event);
    // Handle events as a normal Button would
    int type = DOM.eventGetType(event);
    // Look at the type of event again
    switch (type) {
    case Event.ONMOUSEOVER:
    mouseListenerCollection.fireMouseEnter(this);
    break;
    case Event.ONMOUSEOUT:
    mouseListenerCollection.fireMouseLeave(this);
    }
    }
    Note that to throw the events into the java world, the MouseListenerCollection class provides utility methods as `fireMouseLeave' etc.
Putting it all together, we get the following code for MouseEventsButton:

public class MouseEventsButton extends Button implements SourcesMouseEvents {

MouseListenerCollection mouseListenerCollection;

public MouseEventsButton(String string) {
super(string);
this.sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
}

public void addMouseListener(MouseListener listener) {
if (mouseListenerCollection == null) {
mouseListenerCollection = new MouseListenerCollection();
}
mouseListenerCollection.add(listener);
}

public void removeMouseListener(MouseListener listener) {
if (mouseListenerCollection != null) {
mouseListenerCollection.remove(listener);
}
}

public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
// Handle events as a normal Button would
int type = DOM.eventGetType(event);
// Look at the type of event again
switch (type) {
case Event.ONMOUSEOVER:
mouseListenerCollection.fireMouseEnter(this);
break;
case Event.ONMOUSEOUT:
mouseListenerCollection.fireMouseLeave(this);
}
}
}
So, in summary we may say that GWT widget events are created explicitly as 2-nd layer events after explicitly catching and handling low-level DOM events.

Looking at the current version of the source code (dec. 8, 2008) checked out from svn, many of the classes and methods involved in event handling have been deprecated, so it looks asif a major refactoring of the event mechanism is pending. Hopefully, this will be better documented than it was in the current version.

Events in GWT Composite Widgets

This blog post is on event handling in composite widgets in the Google Web Toolkit. For a general description on event handling in GWT, please see my earlier post: Where do GWT events come from?

I have been working with Google Web Toolkit (GWT) for a while, but i never created any custom widgets yet. Now, that i'm trying to build one, i'm kind of confused about the way composite widgets deal with events.

Now, if i create a composite widget in GWT, what happens with the events? A number of questions come up:
  • Can the composite widget be an event source?
  • Can the composite widget listen to external event sources?
  • Can the composite widget listen to internal event sources?
  • Can a wrapped widget within the composite listen to external events?
  • Can a wrapped widget within the composite source events to external listerers, bypassing the composite widget?
Below, i'll answer these questions one by one...

Q: Can the composite widget be an event source?

A: Yes. The Composite widget can pass on the events generated by a contained widget and as such, be an event source by itself. GWT actually provides special classes and methods to support this: This is done through the DelegatingXXXListener approach. As an example, consider the following Composite widget, that contains only a Button.

public class DelegatingClickWidgetDemo extends Composite implements
SourcesClickEvents {
Button button1;

DelegatingClickListenerCollection clickListeners;

public DelegatingClickWidgetDemo(String caption) {

VerticalPanel verticalPanel = new VerticalPanel();
button1 = new Button(caption);
verticalPanel.add(new HTML("Composite widget delegatingclicklisterer"));
verticalPanel.add(button1);

initWidget(verticalPanel);
}

public void addClickListener(ClickListener listener) {
if (clickListeners == null) {
clickListeners = new DelegatingClickListenerCollection(this,
button1);
}
clickListeners.add(listener);
}

public void removeClickListener(ClickListener listener) {
if (clickListeners != null)
clickListeners.remove(listener);
}

}
There are three things to notice about this code (marked in red in the listing):
  1. The class implements the SourcesClickEvents interface, thus is must implement addClickListener and removeClickListener.
  2. The class maintains a member of class DelegatingClickListenerCollection. This object delegates click listeners to another object, in this case the warpped Button.
  3. When DelegatingClickListenerCollection is instantiated, it is given the Button object it should delegate to as an argument.
So, the Composite widget maintains a Listener collection that actually listens to a contained widget!

(Sideways remark: It seems difficult to maintain separate Listener collections for multiple contained widgets at the Composite level, since there is only one addXXXListener method. But there may be a workaround.)

(Sideways remark 2: i don't know whether the composite can also be an event source detached from its contained widgets. Since the rock-bottom source of GWT events are DOM browser events, it seems that all GWT events must come from widgets that are associated with a browser object. So i guess the answer is no.)

If you want to use this class you must instantiate the composite widget and register an event listener:

public class DemoGwtComposite implements EntryPoint {

public void onModuleLoad() {
VerticalPanel vPanel = new VerticalPanel();
vPanel.setWidth("100%");
vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
RootPanel.get().add(vPanel);

DelegatingClickWidgetDemo dcwd = new delegatingClickWidgetDemo("DCWD");
vPanel.add(dcwd);

// add a click listener to the COMPOSITE object, not to the button
// itself!
dcwd.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
sender
.setTitle("This title was set by a delegating click listener");
}
});
}
}
If you run this example you will see that the click-listener registered with the composite widget handles the button's clicks!

Q: Can the composite widget listen to external event sources?


A: Sure, why not? It must just implement the methods required to be an event listerer... E.g., for click events it must implement the onClick method.

Q: Can the composite widget listen to internal event sources?

Yes. There is no difference between external and contained widgets in this respect. A composite can register itself as a listener to events generated by wrapped widgets.

Q: Can a widget within the composite listen to external events?

Yes, as long as it implements the required interfaces and registers itself as a listener. I did not test this.

Q: Can a widget within the composite source events to external listerers, bypassing the composite widget?

I think that in principe, this should be possible. However, this against the intention of Composite widgets, since a composite widget shields the methods of its contained widgets from the outside world. Thus, it is impossible to register listeners with the contained widget directly. (Instead, one can use the DelegatingXXXListener approach outlines above.)

If, against the spirit of composite widgets, you want a contained widget to source events to the outside world, you must make its addXXXListener methods available to the outside world somehow. You could do that by creating helper methods on the Composite object, and/or by passing a reference to the contained object to the outside world.

woensdag 5 november 2008

Making an RMI service pluggable

This post is about making web services (such as RMI) flexible such that their functionality can be extended through plugins.

Remote Method Invocation (RMI) is a mechanism that allows you to access remote functionality over a network. In that sense, it is just a remote procedure call, for which multiple frameworks exist, both java-specific and non-java-specific. RMI, however, appears to be among the top performers if it comes to speed.

The ServiceLoader API allows you to develop extensible systems, that is, systems whose functionality can be extended without access to the original source code. This is usually done by defining an interface for the extensions. This interface, called the extension point, defines on a high level what the extension should do, e.g., it should process an XML file and yield another XML-file, it should add an entry to a menu, etc. The interface does not specify what goes on within an implementation of an extension. When the host application starts, it looks for registered plugins and does something with these. Besides the ServiceLoader API, other frameworks for developing extensible applications exist. (I am only slightly familiar with those, and i would like to see a good review of those.)

Extensibility is often confused with modularity, but this is not correct: A modular application need not be extensible, but an extensible application *is* necessarily modular.

Another source of confusion (at least to me) is formed by the multiple terms used to designate plugins: These are also called extensions, modules, add-ons and sometimes services. The ServiceLoader API calls them services, hence the name ServiceLoader.

If you are designing a client-server system for remote procedure calls in Java, and you want the server to have pluggable functionality, then you can use service mechanism, such as RMI, together with a plugin mechanism, such as the ServiceLoader API. In this post i will show a toy example of such an architecture. There are a number of steps you should take to get this going.

1. Define the plugin interface.

The first thing we must do is define the interface that the plugins must implement. In this case, the plugins are RMI services. Each service must have methods for starting and stopping it, and it must have a method yielding its name. Thus we have
package pluginRMI.spi;

import java.rmi.Remote;

public interface IPluginRMIService {
public void start();
public void stop();
public String getName();
}

As you see, the inteface is placed in package pluginRMI.spi. Spi stands for `service provider interface'. This naming convention was adopted from SUN's tutorial and it comes from the fact that ServiceLoader calls plugins services. Since RMI also works by implementing an interface, it is important not to confuse this interface with the interfaces that the individual RMI services will implement. This will become clear below.


2. Create some plugins.


The individual plugins are RMI services in our case. Hence, to create the plugins we must first create an interface for each RMI service that we want to start as a plugin. These interfaces are places in package `common' as they are shared between the RMI server and the RMI client.

The first interface defines a simple calculator service that can add and multiply integers:
package pluginRMI.common;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ICalculator extends Remote {
public Integer add(Integer a, Integer b) throws RemoteException;
public Integer multiply(Integer a, Integer b) throws RemoteException;
}

The second interface generates a String containing a fortune:
package pluginRMI.common;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IFortuneTeller extends Remote {
public String getFortune() throws RemoteException;
}


Next, we have to implement the actual plugins. I only show the implementation of the FortuneTeller plugin. Note that this class implements two interfaces: one for the ServiceLoader and one for the RMI service.

package pluginRMI.implementations;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Random;

import pluginRMI.common.IFortuneTeller;
import pluginRMI.spi.IPluginRMIService;

public class FortuneTellerRMIService implements IPluginRMIService,
IFortuneTeller {

public FortuneTellerRMIService() {
}

public String getName() {
return "FortuneTeller";
}

public void start() {

// Most of this code can probably be moved outside of this class...
try {
IFortuneTeller stub = (IFortuneTeller) UnicastRemoteObject
.exportObject(this, 0);

// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry();
registry.rebind(getName(), stub);

System.err.println("RMIServer " + getName() + " ready");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();

}
}

public void stop() {
try {
Registry registry = LocateRegistry.getRegistry();
registry.unbind(getName());

System.err.println("Stopped RMIServer " + getName());
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}

public String getFortune() throws RemoteException {
Random random = new Random();
if (random.nextBoolean()) {
return ("Live in a world of your own, but always welcome visitors.");
} else {
return ("The hardest part of climbing the ladder of success is getting throug "
+ " the crowd at the bottom.");
}
}
}


(Aside: When the IPluginRMIService is changed into an abstract class it may be possible to move the code in start() and stop() into the abstract class, preventing duplication among plugins.)


3. Create the host application.


The host application is responsible for locating and starting the RMI services that have been registered with the system. This is the place where the ServiceLoader API comes into play. The code:

package pluginRMI;

import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

import pluginRMI.spi.IPluginRMIService;

public class PluginRMIServiceLoader {

public static void main(String[] args) {

ServiceLoader<IPluginRMIService> loader;
loader = ServiceLoader.load(IPluginRMIService.class);

try {
System.out.println("Starting all RMI plugins available...");
Iterator<IPluginRMIService> pluginRmiServices = loader.iterator();
while (pluginRmiServices.hasNext()) {
IPluginRMIService plugin = pluginRmiServices.next();
System.out.println("Starting " + plugin.getName());
plugin.start();
}

} catch (ServiceConfigurationError serviceError) {
serviceError.printStackTrace();
}
}
}




4. Register the implemented plugins with the ServiceLoader


In order for the ServiceLoader to be able to locate the implemented plugins, they must be registered. This is done by entering some meta data in META-INF/services. Here you must create a text file of which the name equals the full class name (incl. package) of the plugin interface. In our case this filename is pluginRMI.spi.IPluginRMIService. The contents of that file is simly the list of class names implementing the plugin interface.

pluginRMI.implementations.CalculatorRMIService
pluginRMI.implementations.FortuneTellerRMIService


Obviously, the ServiceLoader can use this information to locate the plugins.


5. Create an RMI client.


The final step is to create the client for the RMI service. This is straightforward.
package pluginRMI.client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import pluginRMI.common.ICalculator;
import pluginRMI.common.IFortuneTeller;

public class RMIClient {

private RMIClient() {
}

public static void main(String[] args) {

String host = (args.length < 1) ? null : args[0];
try {
Registry registry = LocateRegistry.getRegistry(host);

// First do some stuff with the calculator service...
ICalculator stubCalc = (ICalculator) registry.lookup("Calculator");
Integer sum = stubCalc.add(1, 1);
Integer prod = stubCalc.multiply(2, 2);
System.out.println("Calculator service generated values " + sum
+ " and " + prod);

// Then do some stuff with the fortuneteller
IFortuneTeller stubFort = (IFortuneTeller) registry
.lookup("FortuneTeller");
System.out.println("FortuneTeller service generated fortune: "
+ stubFort.getFortune());
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}



6. Start the application.

  1. Start the RMI registry.
  2. Start the host application. Don't forget to set the correct value for the codebase VM argument or the RMI services will not start. (In eclipse this is -Djava.rmi.server.codebase=file:${workspace_loc}/PluginRMI/ under run configurations | arguments tab | VM arguments .)
  3. Start the RMI client.
Et voila, we have a system into which arbitrary functionality can be added at the server side. Of course this only makes sense when the plugins can share data among them and with the host application, but this is easy to achieve by e.g. giving them a pointer to a common Map.

I hope this post was helpful to you in designing a flexible architecture. Sorry for the crappy layout here and there, but i just can't work with wysiwyg editors.