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)