Errai: The browser as a platform

Tuesday, December 8, 2009

Integrating Errai with Weld (CDI), Part 1

While GWT is strong on the client side it doesn't provide any server side programming models. There is GWT RPC, but that's merely an integration layer. We've been discussing various options within the Errai team, looking for some best practice recommendations. In this article we'll look into CDI, which is part of the EE6 specification and offers a concise, easy to use programming model.

In the first part of this series, we'll focus on the CDI Event API and see how it integrates with Errai Bus.


Some knowledge about Errai Bus and GWT is recommended before reading any further. If you don't know about the Bus and it's purpose, then checkout the project page first.


Creating a CDI event consumer


To begin with, we'll create a simple CDI component that listens on paticular event types:

@ApplicationScoped
public class EchoService
{
@Inject
BeanManager beanManager;

public void echo(@Observes EchoRequest message)
{
String replyText = message.getMessageText() + " Response";

beanManager.fireEvent(
new EchoResponse(replyText),
new ErraiBusQualifier()
);
}

}


In this example the echo(...) operation will be invoked on any occurance of an event type EchoRequest. It will create an EchoResponse event type and dispatch it through the BeanManager.

A simple GWT client


Now that we've got our server side component implementation, it's time to look at the GWT client. The client component basically needs to do two things:


  1. Fire events that will ultimately invoke our server side component
  2. Listen to events that are send in response


Create an event producer
We keep it simple and fire event when a button is clicked:

Button b = new Button("Send", new ClickHandler()
{
public void onClick(ClickEvent clickEvent)
{
CommandMessage.create(WeldCommands.WELD_EVENT)
.toSubject("weldDispatcher")
.set(WeldProtocol.TYPE, EchoRequest.class.getName())
.set(WeldProtocol.OBJECT_REF,
new EchoRequest("Message from client"))
.sendNowWith(bus);
}
});


Register a listener for response messages:

MessageBus.subscribe("echoClient", // local endpoint
new MessageCallback()
{
public void callback(CommandMessage message)
{
[...]
}
}
);


In this case, we've create a listener that will receive messages that are send to the subject "echoClient".

Great, now we have a GWT event producer that sends messages through Errai Bus and we've created a CDI event consumer that listens on the internal CDI event subsystem. But how do we introduce them to each other?

Bridge between Errai Bus and the CDI Event Subsystem
If you carefully followed the example, you might have realized the event producer sends messages to a subject called "weldDispatcher". Actually this is an Errai server side service consuming messages send from a GWT client. That means any message put on the Bus that addresses this particular subject will be directed to a service that is registered to this subject:

@Service("weldDispatcher")
@ApplicationScoped
public class WeldDispatcher implements MessageCallback
{
[...]

// Invoked by Errai
public void callback(CommandMessage message)
{
try
{
switch (WeldCommands.valueOf(message.getCommandType()))
{
case WELD_EVENT:
String type =
message.get(String.class, WeldProtocol.TYPE);
Class clazz =
getClass().getClassLoader().loadClass(type);
Object o = message.get(clazz, WeldProtocol.OBJECT_REF);
beanManager.fireEvent(o);
break;
default:
throw new IllegalArgumentException(
"Unknown command type "+message.getCommandType()
);
}
}
catch (Exception e)
{
throw new RuntimeException(
"Failed to dispatch Weld Event", e
);
}
}

// Invoked by Weld Event producer
public void sendMessage(@Observes @ErraiBus Object message)
{
String subject = null;
ErraiBus busAnnotation =
message.getClass().getAnnotation(ErraiBus.class);
if(busAnnotation!=null)
subject = busAnnotation.subject();

assert subject!=null : "Missing subject declaration";

// put on the bus
CommandMessage.create(WeldCommands.WELD_EVENT)
.toSubject(subject)
.set(WeldProtocol.TYPE, message.getClass().getName())
.set(WeldProtocol.OBJECT_REF, message)
.sendNowWith(erraiBus);
}

[...]
}


What's happening here?

The WeldDispatcher implementation acts a bridge between the Errai Bus and the CDI event subsystem. It does listen on messages in both directions and fowards the event message to the corresponding subsystem: either Errai Bus or CDI.

Receiving messages send from the client
When a messages is send to the "weldDispatcher" subject the callback(CommandMessage message) operation will be invoked. It umarshalls the the event type and forwards it to CDI using the BeanManager API:


String type = message.get(String.class, WeldProtocol.TYPE);
Class clazz = getClass().getClassLoader().loadClass(type);
Object o = message.get(clazz, WeldProtocol.OBJECT_REF);
beanManager.fireEvent(o);


Forwarding events produced on the server side
The CDI component that we've shown in the beginning of this article, fires EchoResponse events in response to it's invocation. These events are caught by a CDI ObserverMethod (sendMessage(@Observes @ErraiBus Object message)) that extracts the recipient subject, creates an Errai message and put's it on the bus:

CommandMessage.create(WeldCommands.WELD_EVENT)
.toSubject(subject)
.set(WeldProtocol.TYPE, message.getClass().getName())
.set(WeldProtocol.OBJECT_REF, message)
.sendNowWith(erraiBus);


Wrap up


That's it for today. To sum up, here's what we've shown in this example:

  1. We've created a simple example that uses GWT on the client side.
  2. It does leverage Errai Bus for integrating with the server side
  3. We've used a CDI component on the server side to back our application
  4. All communication is done asynchronously, using a bridge between Errai Bus and the CDI event subsystem


Stay tuned. More articles on Errai and CDI integration are following.


Update
More recent examples can be found here:
http://anonsvn.jboss.org/repos/errai/projects/weld-integration/trunk/