The key difference between an Asynchronous JavaScript + XML (Ajax)-enabled Web application and a traditional Web application is the A in Ajax: asynchronous. An Ajax application allows the browser to update a specific part of the page without having to completely refresh the entire page. This simple trick enables a much more interactive user experience, as a simple Web page now behaves much more like a desktop application.
From the developer's point of view, this asynchronous behavior has two key components:
- The
XMLHttpRequestobject is a JavaScript object defined by the browser that enables a Web page to send HTTP requests and receive a response in a background thread. Unlike a typical page request, the call doesn't interrupt the user experience, and the browser doesn't pause while waiting for the response. - Some kind of callback is executed after the response is complete. That callback usually uses the JavaScript Document Object Model (DOM) to manipulate the elements of the page based on the new data. Something cool happens on screen, and the user is happy.
So that's the basic flow: a call to the server, a response back, and actions taken to the page based on the response. Again, the important thing to remember is that all of this takes place behind the scenes without the busy waiting and refreshing associated with a typical change of page in a browser.
There's only one problem with the whole XMLHttpRequest/DOM
method: It's kind of a pain in the neck. Each browser implements the relevant
JavaScript objects differently, for one thing. For another, getting data to the
server can be awkward, and so can converting the response into useful data. As a
result, every Ajax framework worthy of the name has some kind of simplification
wrapper around the creation, calling, and management of RPCs. GWT is no exception.
GWT manages RPCs with a multiple-interface infrastructure somewhat reminiscent of Enterprise JavaBeans (EJB) technology, but -- thankfully -- a lot simpler. The deal is that you define the list of remote calls that your system makes. GWT implements the behind-the-scenes plumbing for converting data to go back to the server, making the server call, and converting the returned data back to client data. You then define what to do after the remote call has completely returned. It's not quite as easy as making a normal Java method call, but it's not difficult.
The change you're going to make to the Slicr application is to retrieve the initial list of toppings from the server database rather than hardcoding them into the client code. This admittedly simplistic example walks you through all the steps needed to make an RPC call. To run this example easily, run in hosted mode. In hosted mode, GWT automatically simulates your remote calls. You'll get some support for working in hosted mode if you use an integrated development environment (IDE), such as Eclipse or IntelliJ IDEA.
Note: In the final article in this series, I'll show you how to run your server side in a normal servlet environment.
The bulk of the work in your GWT procedure call happens in two classes. On the
server side, you define a subclass of RemoteServiceServlet.
In this class, you manipulate the server data and return a value to the client
(or throw an exception, but let's stay optimistic for the moment). On the client
side, you define a class that implements the interface AsyncCallback.
In this class, you define what the client page does with the data (or the
exception) when the server is done. In addition to these two classes, you must
write some glue code that allows GWT to bind the client-side class and the
server-side class together. OK, there's rather a lot of glue code. The glue
code consists of two different interfaces plus some client-side code and a
setting or two. But don't worry; most of the code is boilerplate, and if you just
take it step by step you'll be fine.
Start on the server side. Your goal here is merely to generate a list of all the
toppings you placed in the database at the end of the previous article. The server
uses the simple ObjectFactory from that article (see
Listing 1) to help you out.
Listing 1. Topping service implementation
public class ToppingServiceImpl extends RemoteServiceServlet
implements ToppingService {
public static final String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
public static final String PROTOCOL = "jdbc:derby:slicr;";
public List getAllToppings() {
try {
Class.forName(DRIVER).newInstance();
Connection con = DriverManager.getConnection(PROTOCOL);
Statement s = con.createStatement();
ResultSet rs = s.executeQuery(
"SELECT * FROM toppings order by name");
return ObjectFactory.convertToObjects(rs, Topping.class);
} catch (Exception e) {
e.printStackTrace();
return new ArrayList();
} finally {
try {
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ignore) {}
}
}
}
|
The first thing to notice about this code is that there really isn't anything all
that fancy about it. The requirements for a GWT remote service are simply that it must extend RemoteServiceServlet and implement an interface
that you'll create in about two paragraphs from here.
Note: It seems as though most GWT documentation has you create the interfaces first. That's fine, it really doesn't matter. I just thought it would be clearer with the concrete code first.
Other than that, this code has much the same functionality as the
ToppingTestr example from the previous article,
repackaged for use within GWT. You make a call to the Derby database, use the
object factory to create topping objects, and return them.
To make your new service available to your client-side application, you must
define two related interfaces. The first is the ToppingService
interface used, but not described, in Listing 1. It's
really simple, shown here in Listing 2.
Listing 2. Defining the first related interface,
ToppingServicepublic interface ToppingService extends RemoteService {
public List getAllToppings();
}
|
All you've done here is take the same signature you used for the method in the
actual concrete class. The main constraint here is that your interface must
extend com.google.gwt.user.client.rpc.RemoteService;.
Also, your parameters and return value must be of types that GWT can serialize.
A list of those types is provided in
Part 2.
However, it's not enough to have only one version of your service interface.
You must also define an asynchronous version of the interface, as shown in Listing 3.
Listing 3. Defining an asynchronous version of the interface
public interface ToppingServiceAsync {
public void getAllToppings(AsyncCallback callback);
}
|
The asynchronous version of the service interface is derived from the primary
version described above. The two versions must be in the same package, and that
package must be visible to your GWT client code. (I used
com.ibm.examples.client ). The class name in the
asynchronous version must be the name of your original interface with the
string Async added to the end. For each method in your original interface,
the asynchronous version must have a matching method with the return type
changed to void and an additional parameter of type
AsyncCallback. The client-side code uses
AsyncCallback to act on the server response.
You are perfectly free to have as many methods in a single service interface as you like, as long as they all have siblings in the asynchronous version and are all implemented in the same remote service class. How you organize the services is largely an aesthetic decision, although I suppose there might be practical value in having service methods able to share common data on the server side.
One more thing, and then your server code will be registered. Add the following line to the Slicr.gwt.xml file:
<servlet path="/toppings" class="com.ibm.examples.server.ToppingServiceImpl"/>
The above line pairs the fully qualified class name of the concrete remote service class with a path name that is essentially the URL for this service. You can use any name you want as long as you remember it and are consistent later. Technically, you need to include only this line in the .xml file if you are going to run your application in hosted mode. As you'll see next time, in a Web deployment, you need something similar in your web.xml file.
That's a lot of things to keep consistent for a single call -- the interface, the asynchronous interface, the actual implementation, and the module file. Missing one or getting the match incorrect leads to errors when you try to actually invoke the service. As of this writing, at least one IDE (IntelliJ IDEA) provides support in the editor for keeping all these items in a consistent state. If you're using Eclipse, the Googlipse plug-in also provides similar support.
With the server side taken care of, it's now time for the client to make the
procedure call. The basic idea here is that you carefully tell the GWT system which
remote service you're calling. You then send an
AsyncCallback object out into the ether; eventually, GWT
sends it back to you, and you can act on the result. Listing 2
shows the code for the setup and call. This method is in the Slicr
class from Part 1.
Specifically, the call to this method replaces the line in
Slicr.onModuleLoad() that added the topping panel.
Listing 4. Setup and call
public void callForToppings() {
ToppingServiceAsync toppingService =
(ToppingServiceAsync) GWT.create(ToppingService.class);
ServiceDefTarget target = (ServiceDefTarget) toppingService;
String relativeUrl = GWT.getModuleBaseURL() + "toppings";
target.setServiceEntryPoint(relativeUrl);
toppingService.getAllToppings(new ToppingCallback());
}
|
Each line is a critical step in your GWT call. Here's the checklist:
- Create an instance of the asynchronous interface. Typically, of course, you can't create an instance of an interface,
which is where the
GWT.create()call comes in. TheGWTclass is a catch-all for several utilities. In this case, it allows you to create a proxy object that implements a given interface. Note that in deployment, the argument to this method must be a literal rather than a variable. Variables work in hosted mode, though, so be careful. - The proxy object that
GWT.create()returns also implements another interface,ServiceDefTarget. In a line or two, you're going to need a method in that interface. - Calculate the URL that you need to send the message to.
The URL has two components:
- The base URL for the system,
which you get by calling
GWT.getModuleBaseURL() - The same string you used as the path back when you added the servlet to the .xml file
- The base URL for the system,
which you get by calling
- Armed with the full URL, you can inform GWT that this particular URL is the
place to go for this particular service by calling the
setServiceEntryPoint()method. The interface also contains the related getter method. At this point, the service is fully initialized for use in this client page. - And finally -- drum roll, please -- you can make the actual call to the
service, just as the service defines it (except for that
ToppingCallbackobject, which is the subject of the next section).
You need to initialize only the service (the first four lines of the example) once in your client page. After the service is created and associated with its entry point, you can reuse the same service object to make multiple calls back to the server. You can also create some helper methods to simplify the boilerplate initialization of the server object.
As I alluded to before, a big difference between a GWT RPC and a regular method call is that you don't know when or if the remote call is going to be completed. Because Web users don't normally take kindly to having entire pages freeze up while you refresh the select box in the corner, your GWT program makes its remote call and continues on its merry way. Eventually, the server will eke out a response, and only at that point does your GWT code do the wonderful thing you have planned.
Often, keeping track of the background threads in this kind of environment can be a
challenge. However, in your case, GWT does most of the work with the mechanism
described above and the AsyncCallback interface that
you've already seen. GWT effectively simplifies the overhead of dealing with
multiple threads.
The AsyncCallback interface defines two methods:
OnSuccess(Object obj) and
OnFailure(Throwable t). You must define a class that
implements both. You create an instance of the class and pass it to your
asynchronous service method when you make your remote call, as seen in
Listing 4. Eventually, the server-side resource is complete,
and one of the two methods in the code is called. The argument to the success
method is the return value of the call in the interface and implementation. In
your case, that's the list of toppings. You'd cast it to the expected type, and
then do something useful with the new data. Hopefully, GWT will support Java 1.5
generics sometime soon, which would reduce the need for a lot of casting. If the
server-side code fails, the failure method is called with the thrown exception as
its argument. Again, you're free to do what you want to respond to the exception.
The code shown in Listing 5 shows the
ToppingCallback class referred to in
Listing 4. As I probably mentioned before, you'll frequently
see AsyncCallback classes defined as anonymous inner
classes. I'd recommend avoiding that. Your code will be much easier to read and
test if the class has a name attached and isn't placed right in the middle of an
entirely different block of code.
Listing 5. The ToppingCallback class
public class ToppingCallback implements AsyncCallback {
public void onFailure(Throwable caught) {
GWT.log("Error ", caught);
caught.printStackTrace();
}
public void onSuccess(Object result) {
List allToppings = (List) result;
VerticalPanel toppings = new VerticalPanel();
toppings.add(new HTML("<h2>Toppings</h2>"));
Grid topGrid = new Grid(allToppings.size() + 1, 3);
topGrid.setText(0, 0, "Topping");
topGrid.setText(0, 1, "Left");
topGrid.setText(0, 2, "Right");
for (int i = 0; i < allToppings.size(); i++) {
Topping t = (Topping) allToppings.get(i);
Button button = new Button(t.getName() );
CheckBox leftCheckBox = new CheckBox();
CheckBox rightCheckBox = new CheckBox();
clearables.add(leftCheckBox);
clearables.add(rightCheckBox);
button.addClickListener(new ToppingButtonListener(leftCheckBox,
rightCheckBox));
topGrid.setWidget(i + 1, 0, button);
topGrid.setWidget(i + 1, 1, leftCheckBox);
topGrid.setWidget(i + 1, 2, rightCheckBox);
}
toppings.add(topGrid);
panel.add(toppings, DockPanel.EAST);
}
}
|
An instance of the ToppingCallback class is sent
by means of the asynchronous interface. GWT manages things behind the scenes so
that first, the proper call is made to the server, then that the result of the
server call is given to the callback object, and finally that the appropriate
response method is invoked.
In this case, the failure option is easier to describe. If the server-side method
throws an exception for any reason, control in the callback passes to the
onFailure() method. The argument to this method is
the exception or other throwable object that the server side has returned. This
argument gives you a chance to respond gracefully to the failure, if possible. I
use the method GWT.log(), which takes a string and a
Throwable as arguments. This method prints a log message
to the shell window for GWT hosted mode. As such, it only works in hosted mode; in
Web mode, the method is ignored. The GWT.log() method is
really meant for tracking during development. You'll probably want to use this
method to put a nice default or error message in the part of the screen you were
about to refresh, or perhaps redirect the entire browser to an error screen.
If you've read the first article in this series, you should notice almost
immediately that the onSuccess() code is nearly
identical to the buildToppingTypePanel() method in the
original version. One of the differences is trivial: You're now looping over a
list rather than an array, which causes minor changes in the loop control. I've
also added the price of the topping to the display, because that information is
also available in the database.
More profoundly, the previous version of the code was able to return the
toppings panel back to the calling code, and the calling
code was then able to add the toppings panel to the
parent panel. In this code, you can't return a value, so you must add the newly
created toppings panel to the parent as part of this method. When you run it,
you'll see the Pizza panel show up first; then, after a small delay for GWT to
simulate the server call, the Toppings panel will appear, as shown in
Figure 1.
Figure 1. The New Topping panel
Even in this small example, moving to an asynchronous structure starts to have serious effects on the structure of the code. Just to start, the order in which the subpanels are added to the main panel was important in determining the final shape of each subpanel within the parent. As you move more of the initialization to RPC calls, you can no longer depend on the subpanels being created in order. You must separate the initial creation of the panels away from inserting the widgets inside them. (This should have the added benefit of reducing flickering when the new data is added.) The general goal is to keep the code inside the asynchronous callbacks as simple as possible and as disconnected from each other as possible. Having threads that depend on one another dramatically increases the complexity of your code.
In this article, you worked through the process of creating a single RPC using GWT. At this point, you now have a legitimate GWT application. However, as of yet, that application runs only in hosted mode on your development machine. To show it to the great wide world, you must move over to Web mode and deploy your Web application within a servlet environment. Stay tuned for the final installment of this series, which will cover deployment.
Learn
-
Visit the Google Groups
forum for GWT.
-
Check out the official GWT blog.
-
Learn more about the Eclipse Foundation and its many projects.
- Get more information about the Googlipse plug-in.
- Check out the developerWorks Apache Derby project area for articles, tutorials, and other resources to help you get started with Derby today.
- Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
- Browse all the Apache articles and free Apache tutorials available in the developerWorks Open source zone.
- Browse for books on these and other technical topics at the Safari bookstore.
- Visit the Apache Derby Project Web site.
- Stay current with developerWorks technical events and webcasts.
- Get an RSS feed for this series. (Find out more about RSS.)
Get products and technologies
-
Download the Google Web Toolkit.
-
Download Eclipse, the open source, freely
available, and extensible IDE.
- Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
- Get involved in the developerWorks community by participating in developerWorks blogs.
Noel Rappin, a Ph.D. from the Graphics, Visualization, and Usability Center at the Georgia Institute of Technology, is a senior software engineer at Motorola. He is also the coauthor of wxPython in Action and Jython Essentials. You can check out Noel's blog at 10printhello.blogspot.com.
Comments (Undergoing maintenance)





