27 August 2009

Integrating gwt-dispatch with JBoss Seam (and probably Spring!)

In my last post I gave an example based on the Google Webtool Kit (GWT) Starter Application using MVP, dependency injection, event bus and command patterns. The command pattern made use of the excellent gwt-dispatch library and used Google Guice at the server side for wiring the service handlers together. My current project uses JBoss Seam for implementing services and this requires a slightly different configuration so that the server side command handlers are properly injected with Seam managed components.

Therefore the aim of this post is to demonstrate those changes involved so you can integrate your own gwt-dispatch based applications with Seam. I expect that most of the changes here will also apply to services implemented using other server frameworks such as Spring. Naturally, you'll need to appropriately tailor the steps for the Seam specific parts but it should serve as a good starting point nonetheless.

Note 1: I should also point out that the code here demonstrates the bare minimum to get the client talking to GWT and does not take into account how security or validation exceptions from the Seam framework might be handled etc.

Note 2: JBoss Seam does in fact support integration with Guice 1.0. However, since I am writing code from fresh rather than integrating with existing code I feel it is better to produce the command handlers as first class Seam citizens.

I'm assuming that you already have a Seam project configured to talk to GWT via regular RPC (if not, then see the Seam docs on how to do this), you've got the code from the MVP example copied into the project and wish to talk to Seam services instead of the Guice versions.

The approach we'll take is to create our own implementation of the command (a.k.a. Action) dispatch mechanism using standard RPC calls to Seam and then extend the gwt-dispatch default dispatcher implementation to create an ActionHandler instance that has been suitably wrapped by Seam's interceptors. Once this boiler plate code is in place, the original Action and Result code and (more importantly) the calls to them remain unchanged while the action handlers can be modified to use Seam components.

Changes to the Client

  • 1. Define a RPC service to process our commands;
  • 2. Point the client to Seam friendly URLs;
  • 3. Wire it all up.

First up, we need to define an RPC call to carry the command to the server using standard RPC:

DispatchService.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import com.google.gwt.user.client.rpc.RemoteService;

public interface DispatchService extends RemoteService {

Result execute(Action<?> action) throws Exception;
}
DispatchServiceAsync.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface DispatchServiceAsync {

void execute(Action<?> action, AsyncCallback<Result> callback);
}

Now we need to change the RPC service URL to a Seam friendly one. By default, gwt-dispatch will look to go to a servlet located at the URL http://<host>/<context>/dispatcher. Seam requires the URL point to http://<host>/<context>/seam/resource/gwt.

SeamDispatchAsync.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;

public class SeamDispatchAsync implements DispatchAsync {

private static final DispatchServiceAsync realService;

static {
realService = GWT.create( DispatchService.class );
final String endpointURL = getModuleBaseURL() + "seam/resource/gwt";

((ServiceDefTarget) realService).setServiceEntryPoint(endpointURL);
}

public static String getModuleBaseURL() {
// Make sure that communication is with the server that served the containing
// web page and not where the GWT resources came from (which is the case with
// GWT.getHostPageBaseURL)
final String url = GWT.getHostPageBaseURL();

return url;
}

public SeamDispatchAsync() {
}

public <A extends Action<R>, R extends Result> void execute( final A action, final AsyncCallback<R> callback ) {
realService.execute(action, new AsyncCallback<Result>() {

public void onFailure(final Throwable caught) {
callback.onFailure( caught );
}

@SuppressWarnings("unchecked")
public void onSuccess(final Result result) {
callback.onSuccess((R) result);
}
} );
}
}

All standard RPC stuff so far. Finally, to wire everything together we define a new Gin module called SeamDispatchModule and modify the annotation @GinModules on the GreetingGinjector class to use the new SeamDispatchModule rather than the default ClientDispatchModule:

SeamDispatchModule.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.DispatchAsync;
import com.google.gwt.inject.client.AbstractGinModule;
import com.google.inject.Singleton;

public class SeamDispatchModule extends AbstractGinModule {

@Override
protected void configure() {
bind( DispatchAsync.class ).to( SeamDispatchAsync.class ).in( Singleton.class );
}
}
GreetingGinjector.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.presenter.client.place.PlaceManager;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;

@GinModules({ SeamDispatchModule.class, GreetingClientModule.class })
public interface GreetingGinjector extends Ginjector {

AppPresenter getAppPresenter();

PlaceManager getPlaceManager();
}

That's it for the client. The existing GreetMvp code was written to use dispatcher interfaces so once we gave Gin an alternative implementation anywhere that uses the dispatcher is injected with new version. Ahhh, good old dependency injection.

Configuration for the Server

Under the GreetMvp example application, all code under the co.uk.hivedevelopment.greet.server.guice package is no longer required. Also if you were already making GWT RPC calls to Seam then there are no changes necessary to your existing Seam web.xml or components.xml. Just make sure that gwt-dispatch jars (aopalliance.jar and gwt-dispatch-1.0.0-SNAPSHOT.jar) are deployed with the application WAR file (i.e. add them to your Eclipse project and edit deployed-jars.list).

At the server we'll:

  • Define the server RPC dispatch implementation;
  • Create a Seam registry for handlers;
  • Modify the SendGreetingHandler for Seam injection.

The following is a standard Seam RPC end point. Notice how the component name matches the fully qualified client side DispatchService remote service interface - this is a key part of any Seam GWT integration:

DispatchServiceImpl.java

package co.uk.hivedevelopment.greet.server.seam;

import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.remoting.WebRemote;
import co.uk.hivedevelopment.greet.client.gin.DispatchService;

@Name("co.uk.hivedevelopment.greet.client.gin.DispatchService")
public class DispatchServiceImpl implements DispatchService {

@In GwtActionDispatcher gwtActionDispatcher;

@WebRemote
@Override
public Result execute(final Action<? extends Result> action) throws Exception {
return gwtActionDispatcher.execute(action);
}
}
GwtActionDispatcher.java

package co.uk.hivedevelopment.greet.server.seam;

import net.customware.gwt.dispatch.server.DefaultDispatch;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.ActionException;
import net.customware.gwt.dispatch.shared.Result;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import co.uk.hivedevelopment.greet.server.handler.SendGreetingHandler;

@Name("gwtActionDispatcher")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class GwtActionDispatcher {

private SeamActionHandlerRegistry actionHandlerRegistry;

@Create
public void init() {
actionHandlerRegistry = new SeamActionHandlerRegistry();

addHandlers();
}

private void addHandlers() {
// TODO: Add all your handlers here.
actionHandlerRegistry.addHandler(new SendGreetingHandler());
}

public Result execute(final Action<? extends Result> action) throws ActionException {
final DefaultDispatch dd = new DefaultDispatch(actionHandlerRegistry);

return dd.execute(action);
}
}
SeamActionHandlerRegistry.java

package co.uk.hivedevelopment.greet.server.seam;

import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.DefaultActionHandlerRegistry;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;
import org.jboss.seam.Component;

public class SeamActionHandlerRegistry extends DefaultActionHandlerRegistry {

@SuppressWarnings("unchecked")
@Override
public <A extends Action<R>, R extends Result> ActionHandler<A, R> findHandler(final A action) {
final ActionHandler<A, R> handler = super.findHandler(action);

if (handler == null) {
return null;
}

// The crucial part to the Seam dispatch implementation is to create handlers using getInstance
final ActionHandler<A, R> handler_ = (ActionHandler<A, R>) Component.getInstance(handler.getClass());

return handler_;
}
}

That completes the server side boiler plate code. The main bit to focus on is the SeamActionHandlerRegistry which extends the default gwt-dispatch implementation to create handlers via Seam's getInstance() method which ensures that appropriate Seam bijection takes place. Remember to add all your new handlers to GwtActionDispatcher.addHandlers().

Finally, here is the version of SendGreetingHandler amended for Seam:

SendGreetingHandler.java

package co.uk.hivedevelopment.greet.server.seam;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.ActionException;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;
import org.jboss.seam.web.ServletContexts;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;

@Name("sendGreetingHandler")
@Scope(ScopeType.STATELESS)
public class SendGreetingHandler implements ActionHandler<SendGreeting, SendGreetingResult> {

@Logger Log logger;
@In("org.jboss.seam.web.servletContexts") ServletContexts servletContexts;

@Override
public SendGreetingResult execute(final SendGreeting action,
final ExecutionContext context) throws ActionException {
final String name = action.getName();

try {
final HttpServletRequest request = servletContexts.getRequest();
final ServletContext sc = request.getSession().getServletContext();

final String serverInfo = sc.getServerInfo();
final String userAgent = request.getHeader("User-Agent");

final String message = "Hello, " + name + "! I am running " + serverInfo + ". It looks like you are using:" + userAgent;

return new SendGreetingResult(name, message);
}
catch (Exception cause) {
logger.error("Unable to send greeting", cause);

throw new ActionException(cause);
}
}

@Override
public void rollback(final SendGreeting action,
final SendGreetingResult result,
final ExecutionContext context) throws ActionException {
// Nothing to do here
}

@Override
public Class<SendGreeting> getActionType() {
return SendGreeting.class;
}
}

Good luck!

21 August 2009

Google Web Toolkit (GWT) MVP Example

Here at Hive Development, I'm currently working on the GWT based UI for a new website/RIA monitoring service called SiteAlright. I recommend you head over and check it out. As you might expect we try to follow best practises when developing our apps and there's been quite a lot of talk recently on GWT Google Groups regarding the use of elements from the recent talk by Ray Ryan at Google I/O 2009 entitled Google Web Toolkit Architecture: Best Practices For Architecting Your GWTApp.

This excellent talk laid out several best practise approaches for architecting your GWT application based on the team's experiences while developing the new AdWords interface. Some of the key recommendations were:

The aim of this tutorial is to demonstrate these GWT best practises by applying them the default starter application generated by the Google Plugin for Eclipse.

There are many benefits to these patterns and I'll leave it to the links above to go into these in detail. However, using these patterns the bottom line is that your GWT application will be:

  • Easier to extend - well defined structure makes the addition of new business logic much easier;
  • Easier to test - the decoupling makes quick unit testing of commands, events and presenters not only possible but easy;
  • GWT's History mechanism is baked into your application from the start - this is crucial for a GWT application and is a pain to retro-fit;

MVP Web Application Starter Project

For this tutorial I'm using Eclipse with the Google Plugin for Eclipse. With the Google plugin when you create a GWT project, it also creates a useful starter application with some widgets and server components. This tutorial will work with this starter application and will apply the above patterns.

Although this example has all the source code and references to required resources, you may download the complete GreetMvp project for Eclipse 3.5 from here.

As well as the Google plugin, you'll also need the following GWT libraries:

GWT-PresenterAn implementation of the MVP pattern;
GWT-DispatchAn implementation of the command pattern;
Google GinDependency Injection based on Google's Guice;
GWT-LogA log4j-style logger for GWT.

NOTE: currently GIN needs to be built from source using SVN/Ant.

You'll also need the following libraries at the server:

log4jA logging framework;
Google Guice 2.0A dependency injection framework for Java.

After introducing these best practise patterns, the structure starter application will be transformed. At this point, it's probably worth noting that:

  • You'll no longer make RPC service calls directly - these are wrapped up in the command pattern and are handled by the gwt-dispatch library;
  • The main page and the server response popup will be separated into respective view/presenters;
  • The Event Bus pattern is implemented using the GWT 1.6+ Handlers mechanism.

Unsurprisingly, the code size will jump from the initial starter app, but what you'll end up with offers much more and will hopefully serve as the starting point for a real application based on these best practises.

Let's begin.

Fire up Eclipse and generate your starter application, I've called it GreetMvp:

google plugin eclipse create project 1google plugin eclipse create project 2

This will generate the following structure:

gwt default application structure screenshot

If you're not already familiar with the default application I suggest you take a look at the entry point class which in my case is co.uk.hivedevelopment.greet.client.GreetMvp.java. You'll see all the view code, custom handler logic and server calls all lumped into one method. Fire up the application and you see the following:

gwt default application screenshot

The first thing we'll do is add the required libraries to the project - at this point you should have downloaded the listed dependencies and built Google Gin.

Create a folder called lib in your project at the top level of your project for client-only libraries and add the following:

gin.jar

For server and client dependencies, add the remaining jars into the web application lib folder located at war/WEB-INF/lib:

aopalliance.jar(from Google Gin)
guice-2.0.jar(from Google Gin. IMPORTANT - use the version supplied with Gin and not Guice)
guice-servlet-2.0.jar(from Google Guice)
gwt-dispatch-1.0.0-SNAPSHOT.jar(from gwt-dispatch)
gwt-log-2.6.2.jar(from gwt-log)
gwt-presenter-1.0.0-SNAPSHOT.jar(from gwt-presenter)
log4j.jar(from log4j)

Add all of the above jars to the project's build path. You should have something that looks similar to this:

gwt default application structure screenshot

Edit the GWT module definition file co.uk.hivedevelopment.greet/GreetMvp.gwt.xml:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd">
<module rename-to='greetmvp'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name="com.google.gwt.user.User" />
<inherits name="com.google.gwt.inject.Inject" />

<inherits name='net.customware.gwt.dispatch.Dispatch' />
<inherits name='net.customware.gwt.presenter.Presenter' />

<!-- Inherit the default GWT style sheet. You can change -->
<!-- the theme of your GWT application by uncommenting -->
<!-- any one of the following lines. -->
<inherits name='com.google.gwt.user.theme.standard.Standard' />
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->

<!-- Specify the app entry point class. -->
<entry-point class='co.uk.hivedevelopment.greet.client.GreetMvp' />

<!-- Add gwt-log support, default level `OFF` - check for
extended property 'log_level' to see if this is overridden -->
<inherits name="com.allen_sauer.gwt.log.gwt-log-OFF" />

<!-- Also compile Logger at `INFO` level -->
<extend-property name="log_level" values="INFO" />
<set-property name="log_level" value="INFO" />

<!-- Turn off the floating logger - output will be shown in the
hosted mode console -->
<set-property name="log_DivLogger" value="DISABLED" />

<source path="shared" />
<source path="client" />
</module>

Note the <source> tags. We have roughly 3 top level packages defined: client, shared and server. We do not want GWT accessing the server sub-packages so we have explicity told the GWT compiler what it can access.

Try to compile the project, it should compile cleanly.

Create View and Presenters



Now we'll split the app into the following components:
AppPresenter.javaRepresents the main application;
GreetingView.javaThe GUI components for the greeting example;
GreetingPresenterThe business logic for the greeting example;
GreetingResponseViewThe GUI for the reponse popup;
GreetingResponsePresenterThe business logic for the response popup.
AppPresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.dispatch.client.DispatchAsync;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.inject.Inject;

public class AppPresenter {
private HasWidgets container;
private GreetingPresenter greetingPresenter;

@Inject
public AppPresenter(final DispatchAsync dispatcher,
final GreetingPresenter greetingPresenter) {
this.greetingPresenter = greetingPresenter;
}

private void showMain() {
container.clear();
container.add(greetingPresenter.getDisplay().asWidget());
}

public void go(final HasWidgets container) {
this.container = container;

showMain();
}
}
GreetingView.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.widget.WidgetDisplay;

import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public class GreetingView extends Composite implements GreetingPresenter.Display {
private final TextBox name;
private final Button sendButton;

public GreetingView() {
final FlowPanel panel = new FlowPanel();

initWidget(panel);

name = new TextBox();
panel.add(name);

sendButton = new Button("Go");
panel.add(sendButton);

// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("nameFieldContainer").add(name);
RootPanel.get("sendButtonContainer").add(sendButton);

reset();
}

public HasValue getName() {
return name;
}

public HasClickHandlers getSend() {
return sendButton;
}

public void reset() {
// Focus the cursor on the name field when the app loads
name.setFocus(true);
name.selectAll();
}

/**
* Returns this widget as the {@link WidgetDisplay#asWidget()} value.
*/
public Widget asWidget() {
return this;
}

@Override
public void startProcessing() {
}

@Override
public void stopProcessing() {
}
}
GreetingPresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.presenter.client.DisplayCallback;
import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.place.Place;
import net.customware.gwt.presenter.client.place.PlaceRequest;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEvent;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HasValue;
import com.google.inject.Inject;

public class GreetingPresenter extends WidgetPresenter {
/**
* The message displayed to the user when the server cannot be reached or
* returns an error.
*/
private static final String SERVER_ERROR = "An error occurred while "
+ "attempting to contact the server. Please check your network "
+ "connection and try again.";

public interface Display extends WidgetDisplay {
public HasValue getName();

public HasClickHandlers getSend();
}

public static final Place PLACE = new Place("Greeting");

private final DispatchAsync dispatcher;

// FUDGE FACTOR! Although this is not used, having Gin pass the object
// to this class will force its instantiation and therefore will make the
// response presenter listen for events (via bind()). This is not a very good way to
// achieve this, but I wanted to put something together quickly - sorry!
private final GreetingResponsePresenter greetingResponsePresenter;

@Inject
public GreetingPresenter(final Display display,
final EventBus eventBus,
final DispatchAsync dispatcher,
final GreetingResponsePresenter greetingResponsePresenter) {
super(display, eventBus);

this.dispatcher = dispatcher;

this.greetingResponsePresenter = greetingResponsePresenter;

bind();
}

/**
* Try to send the greeting message
*/
private void doSend() {
Log.info("Calling doSend");

dispatcher.execute(new SendGreeting(display.getName().getValue()), new DisplayCallback(display) {

@Override
protected void handleFailure(final Throwable cause) {
Log.error("Handle Failure:", cause);

Window.alert(SERVER_ERROR);
}

@Override
protected void handleSuccess(final SendGreetingResult result) {
// take the result from the server and notify client interested components
eventBus.fireEvent(new GreetingSentEvent(result.getName(), result.getMessage()));
}

});
}

@Override
protected void onBind() {
// 'display' is a final global field containing the Display passed into the constructor.
display.getSend().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
doSend();
}
});
}

@Override
protected void onUnbind() {
// Add unbind functionality here for more complex presenters.
}

public void refreshDisplay() {
// This is called when the presenter should pull the latest data
// from the server, etc. In this case, there is nothing to do.
}

public void revealDisplay() {
// Nothing to do. This is more useful in UI which may be buried
// in a tab bar, tree, etc.
}

/**
* Returning a place will allow this presenter to automatically trigger when
* '#Greeting' is passed into the browser URL.
*/
@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onPlaceRequest(final PlaceRequest request) {
// Grab the 'name' from the request and put it into the 'name' field.
// This allows a tag of '#Greeting;name=Foo' to populate the name
// field.
final String name = request.getParameter("name", null);

if (name != null) {
display.getName().setValue(name);
}
}
}
GreetingResponseView.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class GreetingResponseView extends DialogBox implements GreetingResponsePresenter.Display {
private final Label textToServerLabel;
private final HTML serverResponseLabel;
private final Button closeButton;

public GreetingResponseView() {
setText("Remote Procedure Call");
setAnimationEnabled(true);

closeButton = new Button("Close");

// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");

textToServerLabel = new Label();
serverResponseLabel = new HTML();

final VerticalPanel dialogVPanel = new VerticalPanel();

dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("Sending name to the server:"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("Server replies:"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);

setWidget(dialogVPanel);
}

public HasText getTextToServer() {
return textToServerLabel;
}

public HasHTML getServerResponse() {
return serverResponseLabel;
}

public HasClickHandlers getClose() {
return closeButton;
}

public DialogBox getDialogBox() {
return this;
}

/**
* Returns this widget as the {@link WidgetDisplay#asWidget()} value.
*/
public Widget asWidget() {
return this;
}

@Override
public void startProcessing() {
}

@Override
public void stopProcessing() {
}
}
GreetingResponsePresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.place.Place;
import net.customware.gwt.presenter.client.place.PlaceRequest;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEvent;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEventHandler;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.HasText;
import com.google.inject.Inject;

public class GreetingResponsePresenter extends WidgetPresenter {
public interface Display extends WidgetDisplay {
HasText getTextToServer();

HasHTML getServerResponse();

HasClickHandlers getClose();

DialogBox getDialogBox();
}

public static final Place PLACE = new Place("GreetingResponse");

@Inject
public GreetingResponsePresenter(final Display display, final EventBus eventBus) {
super(display, eventBus);

bind();
}

@Override
protected void onBind() {
// Add a handler to close the DialogBox
display.getClose().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
display.getDialogBox().hide();

// Not sure of a nice place to put these!
// sendButton.setEnabled(true);
// sendButton.setFocus(true);
}
});

eventBus.addHandler(GreetingSentEvent.TYPE, new GreetingSentEventHandler() {

@Override
public void onGreetingSent(final GreetingSentEvent event) {
Log.info("Handling GreetingSent event");

display.getTextToServer().setText(event.getName());
display.getServerResponse().setHTML(event.getMessage());
display.getDialogBox().show();
}
});
}

@Override
protected void onUnbind() {
// Add unbind functionality here for more complex presenters.
}

public void refreshDisplay() {
// This is called when the presenter should pull the latest data
// from the server, etc. In this case, there is nothing to do.
}

public void revealDisplay() {
// Nothing to do. This is more useful in UI which may be buried
// in a tab bar, tree, etc.
}

/**
* Returning a place will allow this presenter to automatically trigger when
* '#GreetingResponse' is passed into the browser URL.
*/
@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onPlaceRequest(final PlaceRequest request) {
// this is a popup
}
}

At this point, we're still missing some code but hopefully you should start to see the structure coming together.

Events

Since this is a simple application, we only have one event - the GreetingSent event which has a corresponding handler:

GreetingSent.java

package co.uk.hivedevelopment.greet.shared.event;

import com.google.gwt.event.shared.GwtEvent;

public class GreetingSentEvent extends GwtEvent{
public static Type TYPE = new Type();

private final String name;
private final String message;

public GreetingSentEvent(final String name, final String message) {
this.name = name;
this.message = message;
}

public String getName() {
return name;
}

public String getMessage() {
return message;
}

@Override
public Type getAssociatedType() {
return TYPE;
}

@Override
protected void dispatch(final GreetingSentEventHandler handler) {
handler.onGreetingSent(this);
}
}
GreetingSentHandler.java

package co.uk.hivedevelopment.greet.shared.event;

import com.google.gwt.event.shared.EventHandler;

public interface GreetingSentEventHandler extends EventHandler {

void onGreetingSent(GreetingSentEvent event);

}

If you now look at the project references for the event and handler you can see where the events are fired and subsequently handled. The components are blissfully unaware of what produced the event and it just has the information that it needs to get the job done. Now imagine if you want to have another component that also reacts to this event, say to update another part of the GUI. Simple, just register another event handler - no spaghetti code.

RPC

Let's define client RPC. As mentioned earlier, we'll not be making the RPC calls directly. Instead we'll use the command pattern and let the gwt-dispatch library handle the underlying server calls. Although this is an implementation of the command pattern, it turns out that there is already a core GWT class called Command, so the authors of the gwt-dispatch library have opted to use Action instead - so "actions" are really "commands".

In our example, we'll define the Action SendGreeting which represent our server request and a SendGreetingResult class to encapsulate the server response:

SendGreeting.java

package co.uk.hivedevelopment.greet.shared.rpc;

import net.customware.gwt.dispatch.shared.Action;

public class SendGreeting implements Action {

private static final long serialVersionUID = 5804421607858017477L;

private String name;

@SuppressWarnings("unused")
private SendGreeting() {
}

public SendGreeting(final String name) {
this.name = name;
}

public String getName() {
return name;
}
}
SendGreetingResult.java

package co.uk.hivedevelopment.greet.shared.rpc;

import net.customware.gwt.dispatch.shared.Result;

public class SendGreetingResult implements Result {

private static final long serialVersionUID = 7917449246674223581L;

private String name;
private String message;

public SendGreetingResult(final String name, final String message) {
this.name = name;
this.message = message;
}

@SuppressWarnings("unused")
private SendGreetingResult() {
}

public String getName() {
return name;
}

public String getMessage() {
return message;
}
}

At this point, the client code should start to compile but there is still more to do before we can run it. We need some utility classes for the dispatcher and Gin:

CachingDispatchAsync.java

package co.uk.hivedevelopment.greet.client;

import java.util.HashMap;
import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;

/**
* Dispatcher which support caching of data in memory
*
*/
public class CachingDispatchAsync implements DispatchAsync {
private DispatchAsync dispatcher;
private Map, Result> cache = new HashMap, Result>();

@Inject
public CachingDispatchAsync(final DispatchAsync dispatcher) {
this.dispatcher = dispatcher;
}

/*
* (non-Javadoc)
* @see net.customware.gwt.dispatch.client.DispatchAsync#execute(A, com.google.gwt.user.client.rpc.AsyncCallback)
*/
public , R extends Result> void execute(final A action, final AsyncCallback callback) {
dispatcher.execute(action, callback);
}

/**
* Execute the give Action. If the Action was executed before it will get fetched from the cache
*
* @param Action implementation
* @param Result implementation
* @param action the action
* @param callback the callback
*/
@SuppressWarnings("unchecked")
public , R extends Result> void executeWithCache(final A action, final AsyncCallback callback) {
final Result r = cache.get(action);

if (r != null) {
callback.onSuccess((R) r);
}
else {
dispatcher.execute(action, new AsyncCallback() {

public void onFailure(Throwable caught) {
callback.onFailure(caught);
}

public void onSuccess(R result) {
cache.put((Action) action, (Result) result);
callback.onSuccess(result);
}

});
}
}

/**
* Clear the cache
*/
public void clear() {
cache.clear();
}
}
GreetingClientModule.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.presenter.client.DefaultEventBus;
import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.gin.AbstractPresenterModule;
import net.customware.gwt.presenter.client.place.PlaceManager;
import co.uk.hivedevelopment.greet.client.CachingDispatchAsync;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingPresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingResponsePresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingResponseView;
import co.uk.hivedevelopment.greet.client.mvp.GreetingView;

import com.google.inject.Singleton;

public class GreetingClientModule extends AbstractPresenterModule {

@Override
protected void configure() {
bind(EventBus.class).to(DefaultEventBus.class).in(Singleton.class);
bind(PlaceManager.class).in(Singleton.class);

bindPresenter(GreetingPresenter.class, GreetingPresenter.Display.class, GreetingView.class);
bindPresenter(GreetingResponsePresenter.class, GreetingResponsePresenter.Display.class, GreetingResponseView.class);

bind(AppPresenter.class).in(Singleton.class);
bind(CachingDispatchAsync.class);
}
}
GreetingGinjector.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.gin.ClientDispatchModule;
import net.customware.gwt.presenter.client.place.PlaceManager;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;

@GinModules({ ClientDispatchModule.class, GreetingClientModule.class })
public interface GreetingGinjector extends Ginjector {

AppPresenter getAppPresenter();

PlaceManager getPlaceManager();

}

Now we tie all this together by replacing the existing module entry class with the following:

GreetMvp.java

package co.uk.hivedevelopment.greet.client;

import co.uk.hivedevelopment.greet.client.gin.GreetingGinjector;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.RootPanel;

public class GreetMvp implements EntryPoint {
private final GreetingGinjector injector = GWT.create(GreetingGinjector.class);

public void onModuleLoad() {
final AppPresenter appPresenter = injector.getAppPresenter();
appPresenter.go(RootPanel.get());

injector.getPlaceManager().fireCurrentPlace();
}
}

Server Components

Okay, now let's define the server side. We need to configure Guice and the dispatch handler plus we need to provide an implementation for the SendGreeting action.

SendGreetingHandler.java

package co.uk.hivedevelopment.greet.server.handler;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.ActionException;
import org.apache.commons.logging.Log;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class SendGreetingHandler implements ActionHandler<SendGreeting, SendGreetingResult> {
private final Log logger;
private final Provider<ServletContext> servletContext;
private final Provider<HttpServletRequest> servletRequest;

@Inject
public SendGreetingHandler(final Log logger,
final Provider<ServletContext> servletContext,
final Provider<HttpServletRequest> servletRequest) {
this.logger = logger;
this.servletContext = servletContext;
this.servletRequest = servletRequest;
}

@Override
public SendGreetingResult execute(final SendGreeting action,
final ExecutionContext context) throws ActionException {
final String name = action.getName();

try {
String serverInfo = servletContext.get().getServerInfo();

String userAgent = servletRequest.get().getHeader("User-Agent");

final String message = "Hello, " + name + "!<br><br>I am running " + serverInfo
+ ".<br><br>It looks like you are using:<br>" + userAgent;


//final String message = "Hello " + action.getName();

return new SendGreetingResult(name, message);
}
catch (Exception cause) {
logger.error("Unable to send message", cause);

throw new ActionException(cause);
}
}

@Override
public void rollback(final SendGreeting action,
final SendGreetingResult result,
final ExecutionContext context) throws ActionException {
// Nothing to do here
}

@Override
public Class<SendGreeting> getActionType() {
return SendGreeting.class;
}
}
DispatchServletModule.java

package co.uk.hivedevelopment.greet.server.guice;

import net.customware.gwt.dispatch.server.service.DispatchServiceServlet;
import com.google.inject.servlet.ServletModule;

public class DispatchServletModule extends ServletModule {

@Override
public void configureServlets() {
// NOTE: the servlet context will probably need changing
serve("/greetmvp/dispatch").with(DispatchServiceServlet.class);
}

}
LogProvider.java

package co.uk.hivedevelopment.greet.server.guice;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.Log4JLogger;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class LogProvider implements Provider<Log>{

public Log get() {
return new Log4JLogger("GreetingLogger");
}

}
MyGuiceServletConfig.java

package co.uk.hivedevelopment.greet.server.guice;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class MyGuiceServletConfig extends GuiceServletContextListener {

@Override
protected Injector getInjector() {
return Guice.createInjector(new ServerModule(), new DispatchServletModule());
}
}
ServerModule.java

package co.uk.hivedevelopment.greet.server.guice;

import net.customware.gwt.dispatch.server.guice.ActionHandlerModule;
import org.apache.commons.logging.Log;
import co.uk.hivedevelopment.greet.server.handler.SendGreetingHandler;
import com.google.inject.Singleton;

/**
* Module which binds the handlers and configurations
*
*/
public class ServerModule extends ActionHandlerModule {

@Override
protected void configureHandlers() {
bindHandler(SendGreetingHandler.class);

bind(Log.class).toProvider(LogProvider.class).in(Singleton.class);
}
}

Finally we need to reconfigure the web.xml with the following:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<!-- Remote logging agent for gwt-log -->
<servlet>
<servlet-name>remoteLoggerServiceImpl</servlet-name>
<servlet-class>com.allen_sauer.gwt.log.server.RemoteLoggerServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>remoteLoggerServiceImpl</servlet-name>
<url-pattern>/greetmvp/gwt-log</url-pattern>
</servlet-mapping>


<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>co.uk.hivedevelopment.greet.server.guice.MyGuiceServletConfig</listener-class>
</listener>

<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>GreetMvp.html</welcome-file>
</welcome-file-list>

</web-app>

You may now launch the application. Surprise surprise, it looks no different but you're now following best practises. Good luck with your own GWT MVP development.

gwt default application screenshot (after)