GWT Service Tutorial

This tutorial demonstrates the simplified integration of Google Web Toolkit (GWT) to provide AJAX functionality to the web page.

The example used in this tutorial is to build a High/Low game. This is a simple game where you attempt to guess a number and are told higher or lower to aid in guessing. You win the game by guessing the number within a certain number of tries.

The focus of the tutorial is using GWT RPC to call on the server to determine whether "higher" or "lower". To focus on the GWT RPC handling, the game interface has been kept simple and looks as follows.

GwtService screen shot.

Download Tutorial Source

GWT Service Interfaces

With typical GWT applications two interfaces are required for GWT RPC - the GWT Service Interface and the GWT Async Interface. Please see the GWT RPC tutorial for more details.

WoOF actually only requires the Async Interface. The underlying OfficeFloor dependency injection simplifies the server logic so that the GWT Service Interface is not required.

OfficeFloor will however appropriately interrogate the GWT Service Interface for the necessary details (i.e. remote service relative path).

HighLowGameAsync GWT Async Interface

public interface HighLowGameAsync {

	void attempt(Integer guess, AsyncCallback<Result> callback);
}

HighLowGame GWT Service Interface

As GWT RPC itself requires both interfaces, the GWT Service Interface is also provided.

@RemoteServiceRelativePath("highlow")
public interface HighLowGame extends RemoteService {

	Result attempt(Integer guess);
}

Template.woof.html

The below is the HTML page.

<html>
	<body>
		<h1>Higher or Lower</h1>

		<div id="HighLowGame"></div>

		<form action="#{newGame}">
			<input type="submit" value="New Game" />
		</form>
	</body>
</html>

This simple HTML contains the id attribute for the GWT hook and the WoOF #{newGame} link to start a new game.

Notice that there is mix between HTML and GWT to provide the functionality. Not all functionality on the web page will typically require RIA capabilities and GWT has done a great job in respecting this.

Server side logic

The template logic class handles both the web page requests and the GWT RPC calls.

@HttpSessionStateful
public class TemplateLogic implements Serializable {

	private Integer number;

	private int tryCount;

	public TemplateLogic() {
		this.newGame(); // assigns number for first game
	}

	public void newGame() {
		this.number = Integer.valueOf((int) (Math.random() * 100));
		this.tryCount = 0;
	}

	public void attempt(@Parameter Integer value, AsyncCallback<Result> callback) {
		Answer answer;
		Integer returnNumber = null;
		
		if (value.equals(this.number)) {
			answer = Answer.CORRECT;
			
		} else if (this.tryCount >= 10) {
			answer = Answer.NO_FURTHER_ATTEMPTS;
			returnNumber = this.number;
			
		} else {
			this.tryCount++;
			answer = (this.number < value ? Answer.LOWER : Answer.HIGHER);
		}
		
		Result result = new Result(answer, returnNumber);
		callback.onSuccess(result);
	}

}

The template logic class above has two methods, which respectively:

  • starts a new game
  • attempts a guess

The annotation on the class flags to store the template logic instance within the HTTP Session. This maintains the state of the game play on the server.

Starting a new game follows standard WoOF functionality of mapping the page link to the corresponding template logic class's method. Please see the other tutorials for further details.

The GWT RPC call is also handled by mapping to a template logic class method - in this case attempt.

Unlike traditional GWT applications, the template logic class does not implement either of the GWT interfaces. This is deliberate by design for two reasons:

  1. Only the client side GWT Async interface is required. Handling of the GWT RPC call is by matching on method name.
  2. Implementing the GWT Service Interfaces dictates the method signature. This restricts OfficeFloor from dependency injecting objects into the method.

In servicing the GWT RPC call, there are two parameters to the template logic method:

  • value sent from the client, and
  • the AsyncCallback interface to enable sending a response to the GWT client.

Further parameters may be added for other dependencies.

The @Parameter annotation is required to distinguish which parameter receives the value sent from the client.

The reason for using the AsyncCallback interface in the server code is that it does not mix techniques. In other words, in more conventional GWT applications the client is a GWT Async Interface call back while on the server it is a GWT Service Interface synchronous method. WoOF in keeping the server code as a call back is felt to make the coding more consistent and intuitive.

The implementation of the GWT service method is reasonably self explanatory (determines whether correct, higher, lower or too many tries). As the template object is stored within the HTTP Session, the state of game play is maintained across GWT RPC calls.

application.woof

The following shows the configuration for adding this tutorial's template along with it's GWT RPC configuration.

GwtService Add template dialog screen shot.

The GWT Async Interfaces must be specified. This enables WoOF to bind servicing of the GWT RPC calls to the appropriate template logic class methods.

WoOF will automatically create the appropriate GWT *.gwt.xml configuration file.

Client side logic

The client side logic follows standard GWT coding and involves the following classes.

Result

Object used as payload to communicate response from server.

public class Result implements IsSerializable {

	public static enum Answer implements IsSerializable {
		HIGHER, LOWER, CORRECT, NO_FURTHER_ATTEMPTS
	}

	private Answer answer;

	private Integer number;

	public Result() {
	}

	public Result(Answer answer, Integer number) {
		this.answer = answer;
		this.number = number;
	}

	public Answer getAnswer() {
		return this.answer;
	}

	public Integer getNumber() {
		return this.number;
	}

}

GWT EntryPoint

Provides the client side logic. See GWT getting started for more details.

public class GwtAppEntryPoint implements EntryPoint {

	private final HighLowGameAsync service = GWT.create(HighLowGame.class);

	@Override
	public void onModuleLoad() {

		// Obtain panel to load game
		Panel panel = RootPanel.get("HighLowGame");

		// Add Game widgets
		VerticalPanel gamePanel = new VerticalPanel();
		panel.add(gamePanel);
		gamePanel.add(new Label("Enter a number (between 1 and 100):"));
		HorizontalPanel tryPanel = new HorizontalPanel();
		gamePanel.add(tryPanel);
		final TextBox text = new TextBox();
		tryPanel.add(text);
		final Button button = new Button("Try");
		tryPanel.add(button);
		final Label message = new Label();
		gamePanel.add(message);

		// Handle try button click
		button.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {

				// Obtain the value to try
				Integer value;
				try {
					value = Integer.valueOf(text.getText());
				} catch (NumberFormatException ex) {
					Window.alert("Please enter a valid number");
					return;
				}

				// Attempt try (calls on Server)
				service.attempt(value, new AsyncCallback<Result>() {
					@Override
					public void onSuccess(Result result) {
						switch (result.getAnswer()) {
						case HIGHER:
							message.setText("Higher");
							text.setText("");
							break;
						case LOWER:
							message.setText("Lower");
							text.setText("");
							break;
						case CORRECT:
							message.setText("Correct. Well done!");
							text.setEnabled(false);
							button.setEnabled(false);
							break;
						case NO_FURTHER_ATTEMPTS:
							message.setText("No further attempts. Number was "
									+ result.getNumber());
							text.setEnabled(false);
							button.setEnabled(false);
							break;
						}
					}

					@Override
					public void onFailure(Throwable caught) {
						Window.alert("Technical failure: "
								+ caught.getMessage() + " ["
								+ caught.getClass().getName() + "]");
					}
				});
			}
		});
	}

}

Unit Testing

The following unit test shows the use of SyncProxy to test the servicing of the GWT RPC call by the server.

	public void testCallGwtService() throws Exception {

		// Start Server
		WoofOfficeFloorSource.start();

		// Invoke the service
		HighLowGame game = (HighLowGame) SyncProxy.newProxyInstance(
				HighLowGame.class, "http://localhost:7878/game/", "highlow");
		Result result = game.attempt(new Integer(50));

		// Ensure provide result from attempt
		assertNotNull("Should be successful", result);
	}

Next

The next tutorial looks at using GWT Comet (AJAX push, reverse AJAX).