At this point, you've created the initial implementation of the StockWatcher application, simulating stock data in the client-side code.
In this section, you'll make a GWT remote procedure call to a server-side method which returns the stock data. The server-side code that gets invoked from the client is also known as a service; the act of making a remote procedure call is referred to as invoking a service.
You'll learn to:
Note: For a broader guide to RPC communication in a GWT application, see Communicate with a Server - Remote Procedure Calls.
The GWT RPC framework makes it easy for the client and server components of your web application to exchange Java objects over HTTP. The server-side code that gets invoked from the client is often referred to as a service. The implementation of a GWT RPC service is based on the well-known Java servlet architecture. Within the client code, you'll use an automatically-generated proxy class to make calls to the service. GWT will handle serialization of the Java objects passing back and forth—the arguments in the method calls and the return value.
Important: GWT RPC services are not the same as web services based on SOAP or REST. They are simply as a lightweight method for transferring data between your server and the GWT application on the client. To compare single and multi-tier deployment options for integrating GWT RPC services into your application, see the Developer's Guide, Architectural Perspectives.
When setting up GWT RPC, you will focus on these three elements involved in calling procedures running on remote servers.
In order to define your RPC interface, you need to write three components:
A service implementation must extend RemoteServiceServlet and must implement the associated service interface. Notice that the service implementation does not implement the asynchronous version of the service interface. Every service implementation is ultimately a servlet, but rather than extending HttpServlet, it extends RemoteServiceServlet instead. RemoteServiceServlet automatically handles serialization of the data being passed between the client and the server and invoking the intended method in your service implementation.
In this tutorial, you are going to take the functionality in the refreshWatchList method and move it from the client to the server. Currently you pass the refreshWatchList method an array of stock symbols and it returns the corresponding stock data. It then calls the updateTable method to populate the FlexTable with the stock data.
/** * Generate random stock prices. */ private void refreshWatchList() { final double MAX_PRICE = 100.0; // $100.00 final double MAX_PRICE_CHANGE = 0.02; // +/- 2% StockPrice[] prices = new StockPrice[stocks.size()]; for (int i = 0; i < stocks.size(); i++) { double price = Random.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (Random.nextDouble() * 2.0 - 1.0); prices[i] = new StockPrice(stocks.get(i), price, change); } updateTable(prices); }
To create the service, you will:
In GWT, an RPC service is defined by an interface that extends the GWT RemoteService interface. For the StockPriceService interface, you need to define only one method: a method that will accept an array of stock symbols and return the data associated with each stock as an array of StockPrice objects.
com.google.gwt.sample.stockwatcher.client
File > New > Interface
StockPriceService
Finish
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @RemoteServiceRelativePath("stockPrices") public interface StockPriceService extends RemoteService { StockPrice[] getPrices(String[] symbols); }
Now create the class (StockPriceServiceImpl) that lives on the server. As defined in the interface, StockPriceServiceImpl will contain only one method, the method that returns the stock price data.
To do this, implement the service interface by creating a class that also extends the GWT RemoteServiceServlet class. RemoteServiceServlet does the work of deserializing incoming requests from the client and serializing outgoing responses.
The service implementation runs on the server as Java bytecode; it's not translated into JavaScript. Therefore, the service implementation does not have the same language constraints as the client-side code. To keep the code for the client separate from the code for the server, you'll put it in a separate package (com.google.gwt.sample.stockwatcher.server).
.client
to .server
StockPriceServiceImpl
com.google.gwt.user.server.rpc.RemoteServiceServlet
com.google.gwt.sample.stockwatcher.client.StockPriceService
Finish
com.google.gwt.sample.stockwatcher.server
package com.google.gwt.sample.stockwatcher.server; import com.google.gwt.sample.stockwatcher.client.StockPrice; import com.google.gwt.sample.stockwatcher.client.StockPriceService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; public class StockPriceServiceImpl extends RemoteServiceServlet implements StockPriceService { public StockPrice[] getPrices(String[] symbols) { // TODO Auto-generated method stub return null; } }
Replace the client-side implementation that returns random stock prices.
private static final double MAX_PRICE = 100.0; // $100.00 private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
public StockPrice[] getPrices(String[] symbols) { Random rnd = new Random(); StockPrice[] prices = new StockPrice[symbols.length]; for (int i=0; i<symbols.length; i++) { double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); prices[i] = new StockPrice(symbols[i], price, change); } return prices; }
import java.util.Random;
package com.google.gwt.sample.stockwatcher.server; import com.google.gwt.sample.stockwatcher.client.StockPrice; import com.google.gwt.sample.stockwatcher.client.StockPriceService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import java.util.Random; public class StockPriceServiceImpl extends RemoteServiceServlet implements StockPriceService { private static final double MAX_PRICE = 100.0; // $100.00 private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2% public StockPrice[] getPrices(String[] symbols) { Random rnd = new Random(); StockPrice[] prices = new StockPrice[symbols.length]; for (int i=0; i<symbols.length; i++) { double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); prices[i] = new StockPrice(symbols[i], price, change); } return prices; } }
The embedded servlet container (Jetty) can host the servlets that contain your service implementation. This means you can take advantage of running your application in development mode while testing and debugging the server-side Java code.
To set this up, add <servlet> and <servlet-mapping> elements to the web application deployment descriptor (web.xml) and point to the implementation class (StockPriceServiceImpl).
Starting with GWT 1.6, servlets should be defined in the web application deployment descriptor (web.xml) instead of the GWT module (StockWatcher.gwt.xml).
In the <servlet-mapping> element, the url-pattern can be in the form of an absolute directory path (for example, /spellcheck
or /common/login
).
If you specify a default service path with a @RemoteServiceRelativePath annotation on the service interface (as you did with StockPriceService), then make sure the url-pattern matches the annotation value.
Because you've mapped the StockPriceService to "stockPrices" and the module rename-to attribute in StockWatcher.gwt.xml is "stockwatcher", the full URL will be:
http://localhost:8888/stockwatcher/stockPrices
<?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>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>StockWatcher.html</welcome-file>
</welcome-file-list>
<!-- Servlets -->
<servlet>
<servlet-name>stockPriceServiceImpl</servlet-name>
<servlet-class>com.google.gwt.sample.stockwatcher.server.StockPriceServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>stockPriceServiceImpl</servlet-name>
<url-pattern>/stockwatcher/stockPrices</url-pattern>
</servlet-mapping>
</web-app>
You need to add an AsyncCallback parameter to all your service methods.
To add an AsyncCallback parameter to all of our service methods, you must define a new interface as follows:
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.AsyncCallback; public interface StockPriceServiceAsync { void getPrices(String[] symbols, AsyncCallback<StockPrice[]> callback); }
Tip: The Google Plugin for Eclipse will generate an error when it finds a synchronous remote service without a matching asynchronous interface. You can right click on the error in Eclipse, select Quick Fix, and choose the "Create asynchronous RemoteService interface" option to automatically generate the asynchronous interface.
When you call a remote procedure, you specify a callback method which executes when the call completes. You specify the callback method by passing an AsyncCallback object to the service proxy class. The AsyncCallback object contains two methods, one of which is called depending on whether the call failed or succeeded: onFailure(Throwable) and onSuccess(T).
private ArrayList<String> stocks = new ArrayList<String>();
private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class);
private void refreshWatchList() {
// Initialize the service proxy.
if (stockPriceSvc == null) {
stockPriceSvc = GWT.create(StockPriceService.class);
}
// Set up the callback object.
AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() {
public void onFailure(Throwable caught) {
// TODO: Do something with errors.
}
public void onSuccess(StockPrice[] result) {
updateTable(result);
}
};
// Make the call to the stock price service.
stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
}
import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback;
At this point, you've created a service and pointed to it in the module XML file, set up the mechanism for making asynchronous calls, and generated a service proxy on the client side to call the service. However there's a problem.
[ERROR] Type 'com.google.gwt.sample.stockwatcher.client.StockPrice' was not serializable
and has no concrete serializable subtypes
In the service implementation (StockPriceServiceImpl), you inherited the code that serializes and deserializes Java objects by extending the RemoteServiceServlet class. However the problem is that we did not also edit the StockPrice class to indicate it was serializable.
Serialization is the process of packaging the contents of an object so that it can moved from one application to another application or stored for later use. Anytime you transfer an object over the network via GWT RPC, it must be serialized. Specifically, GWT RPC requires that all service method parameters and return types be serializable.
A type is serializable and can be used in a service interface if one of the following is true:
GWT honors the transient keyword, so values in those fields are not serialized (and thus, not sent over the wire when used in RPC calls).
Note: GWT serialization is not the same as serialization based on the Java Serializable interface.
For more information on what is and is not serializable in GWT, see the Developer's Guide, Serializable Types.
Based on the requirements for serialization, what do you need to do to make the StockPrice class ready for GWT RPC? Because all its instance fields are primitive types, all you need to do in this case is implement the Serializable or IsSerializable interface.
package com.google.gwt.sample.stockwatcher.client; import java.io.Serializable; public class StockPrice implements Serializable { private String symbol; private double price; private double change; ...
At this point, the basic RPC mechanism is working. However, there is one TODO left. You need to code the error handling in case the callback fails.
When a remote procedure call fails, the cause falls into one of two categories: an unexpected exception or a checked exception. In either case you want to handle the exception and, if necessary, provide feedback to the user.
Unexpected exceptions: Any number of unexpected occurrences could cause the call to a remote procedure to fail: the network could be down; the HTTP server on the other end might not be listening; the DNS server could be on fire, and so forth.
Another type of unexpected exception can occur if GWT is able to invoke the service method, but the service implementation throws an undeclared exception. For example, a bug may cause a NullPointerException.
When unexpected exceptions occur in the service implementation, you can find the full stack trace in the development mode log. On the client-side, the onFailure(Throwable) callback method will receive an InvocationException with the generic message: The call failed on the server; see server log for details.
Checked exceptions: If you know a service method might throw a particular type of exception and you want the client-side code to be able to handle it, you can use checked exceptions. GWT supports the throws keyword so you can add it to your service interface methods as needed. When checked exceptions occur in an RPC service method, GWT will serialize the exception and send it back to the caller on the client for handling.
To learn how to handle errors in RPC, you will throw an error when the user adds a stock code which you've defined as "delisted"—causing the call to fail. Then you'll handle the failure by displaying an error message to the user.
DelistedException
Finish
package com.google.gwt.sample.stockwatcher.client; import java.io.Serializable; public class DelistedException extends Exception implements Serializable { private String symbol; public DelistedException() { } public DelistedException(String symbol) { this.symbol = symbol; } public String getSymbol() { return this.symbol; } }
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("stockPrices")
public interface StockPriceService extends RemoteService {
StockPrice[] getPrices(String[] symbols) throws DelistedException;
}
You need to make two changes to the service implementation (StockPriceServiceImpl).
import com.google.gwt.sample.stockwatcher.client.DelistedException; ... public StockPrice[] getPrices(String[] symbols) throws DelistedException { Random rnd = new Random(); StockPrice[] prices = new StockPrice[symbols.length]; for (int i=0; i<symbols.length; i++) { if (symbols[i].equals("ERR")) { throw new DelistedException("ERR"); } double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); prices[i] = new StockPrice(symbols[i], price, change); } return prices; }
At this point you've created the code that will throw the exception. You don't need to add the throws declaration to the service method in StockPriceServiceAsync.java. That method will always return immediately (remember, it's asynchronous). Instead you'll receive any thrown checked exceptions when GWT calls the onFailure(Throwable) callback method.
In order to display the error, you will need a new UI component. First think a moment about how you want to display the error. An obvious solution would be to display a pop-up alert using Window.alert(String). This could work in the case where the user receives the error while entering stock codes. But in a more real-world example, what would you want to happen if stock was delisted after the user had added it to the watch list? or if you need to display multiple errors? In these cases, a pop-up alert is not the best approach.
So, to display any messages about failing to retrieve stock data, you'll implement a Label widget.
.negativeChange {
color: red;
}
.errorMessage {
color: red;
}
private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class);
private Label errorMsgLabel = new Label();
// Assemble Add Stock panel.
addPanel.add(newSymbolTextBox);
addPanel.add(addButton);
addPanel.addStyleName("addPanel");
// Assemble Main panel.
errorMsgLabel.setStyleName("errorMessage");
errorMsgLabel.setVisible(false);
mainPanel.add(errorMsgLabel);
mainPanel.add(stocksFlexTable);
mainPanel.add(addPanel);
mainPanel.add(lastUpdatedLabel);
Now that you've built a Label widget to display the error, you can finish writing the error handling code.
You will also want to hide the error message if the error is corrected (for example, if the user removes the ERR code from the stock table).
public void onFailure(Throwable caught) {
// If the stock code is in the list of delisted codes, display an error message.
String details = caught.getMessage();
if (caught instanceof DelistedException) {
details = "Company '" + ((DelistedException)caught).getSymbol() + "' was delisted";
}
errorMsgLabel.setText("Error: " + details);
errorMsgLabel.setVisible(true);
}
private void updateTable(StockPrice[] prices) {
for (int i=0; i < prices.length; i++) {
updateTable(prices[i]);
}
// Display timestamp showing last refresh.
lastUpdatedLabel.setText("Last update : " +
DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
// Clear any errors.
errorMsgLabel.setVisible(false);
}
At this point, you've created and checked for an exception—the delisted stock code "ERR". When StockWatcher makes a remote procedure call to retrieve stock data for the delisted stock code, the call fails, an exception is thrown, and then handled by displaying an error message.
At this point, you've made remote procedure calls to get stock data from the server.
For more information about GWT RPC, see the Developer's Guide, Remote Procedure Calls.
During testing, you can use development mode's built-in servlet container to test the server-side code for your RPC services—just as you did in this tutorial. When you deploy your GWT application, you can use any servlet container to host your services. Make sure your client code invokes the service using the URL the servlet is mapped to in the web.xml configuration file.
To learn how to deploy GWT RPC servlets to a production server, see the Developer's Guide, Deploying RPC