Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
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 call to your local server to retrieve JSON-formatted stock data instead.
This tutorial builds on the GWT concepts and the StockWatcher application created in the Getting Started tutorial. If you have not completed the Getting Started tutorial and are familiar with basic GWT concepts, you can import the StockWatcher project as coded to this point.
File menu, select the Import... menu option.Next button.Finish button.
If you are using ant, edit the gwt.sdk property in StockWatcher/build.xml to point to where you unzipped GWT.
JSON is a universal, language-independent format for data. In this way, it's similar to XML. Whereas XML uses tags, JSON is based on the object-literal notation of JavaScript. Therefore the format is simpler than XML. In general, JSON-encoded data is less verbose than the equivalent data in XML and so JSON data downloads more quickly than XML data.
When you encode the stock data for StockWatcher in JSON format, it will look something like this (but the whitespace will be stripped out).
[
{
"symbol": "ABC",
"price": 87.86,
"change": -0.41
},
{
"symbol": "DEF",
"price": 62.79,
"change": 0.49
},
{
"symbol": "GHI",
"price": 67.64,
"change": 0.05
}
]
In the original StockWatcher implementation, you created a StockPrice class and used the refreshWatchList method to generate random stock data and then call the updateTable method to populate StockWatcher's flex table.
/**
* 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);
}
In this tutorial, you'll create a servlet to generate the stock data in JSON format. Then you'll make an HTTP call to retrieve the JSON data from the server. You'll use JSNI and GWT overlay types to work with the JSON data while writing the client-side code.
To serve up hypothetical stock quotes in JSON format, you'll create a servlet. To use the embedded servlet container (Jetty) to serve the data, add the JsonStockData class to the server directory of your StockWatcher project and reference the servlet in the web application deployment descriptor (web.xml).
Note: If you have a web server (Apache, IIS, etc) installed locally and PHP installed, you could instead write a PHP script to generate the stock data and make the call to your local server. What's important for this example is that the stock data is JSON-encoded and that the server is local.
com.google.gwt.sample.stockwatcher.client
.client to .serverJsonStockData.
package com.google.gwt.sample.stockwatcher.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JsonStockData extends HttpServlet {
private static final double MAX_PRICE = 100.0; // $100.00
private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Random rnd = new Random();
PrintWriter out = resp.getWriter();
out.println('[');
String[] stockSymbols = req.getParameter("q").split(" ");
for (String stockSymbol : stockSymbols) {
double price = rnd.nextDouble() * MAX_PRICE;
double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f);
out.println(" {");
out.print(" \"symbol\": \"");
out.print(stockSymbol);
out.println("\",");
out.print(" \"price\": ");
out.print(price);
out.println(',');
out.print(" \"change\": ");
out.println(change);
out.println(" },");
}
out.println(']');
out.flush();
}
}
The embedded servlet container (Jetty) can host the servlet that generates the stock data in JSON format.
To set this up, add <servlet> and <servlet-mapping> elements to the web application deployment descriptor (web.xml) and point to JsonStockData.
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 path attribute 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>jsonStockData</servlet-name>
<servlet-class>com.google.gwt.sample.stockwatcher.server.JsonStockData</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>jsonStockData</servlet-name>
<url-pattern>/stockwatcher/stockPrices</url-pattern>
</servlet-mapping>
</web-app>
http://localhost:8888/stockwatcher/stockPrices?q=ABC+DEF
At this point, you've verified that you are able to get JSON data from a server. Later in this section, you'll code the HTTP GET request to the server. First, focus on working with the JSON-encoded text that's returned to the client-side code. Two techiques you'll use are JSNI (JavaScript Native Interface) and GWT overlay types.
This is the JSON data coming back from the server.
[
{
"symbol": "ABC",
"price": 47.65563005127077,
"change": -0.4426563818062567
},
]
First, you'll use a JavaScript eval() function to convert the JSON string into JavaScript objects.
private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
return eval(json);
}-*/;
Then, you'll be able to write methods to access those objects.
// JSNI methods to get stock data.
public final native String getSymbol() /*-{ return this.symbol; }-*/;
public final native double getPrice() /*-{ return this.price; }-*/;
public final native double getChange() /*-{ return this.change; }-*/;
In both cases, you'll use JSNI. When the client-side code is compiled to JavaScript, the Java methods are replaced with the JavaScript exactly as you write it inside the tokens.
As you see in the examples above, using JSNI you can call handwritten (as opposed to GWT-generated) JavaScript methods from within the GWT module.
JSNI methods are declared native and contain JavaScript code in a specially formatted comment block between the end of the parameter list and the trailing semicolon. A JSNI comment block begins with the exact token /*-{ and ends with the exact token }-*/. JSNI methods are called just like any normal Java method. They can be static or instance methods.
In Depth: For tips, tricks, and caveats about mixing handwritten JavaScript into your Java source code, see the Developer's Guide, JavaScript Native Interface (JSNI).
First you need to convert the JSON text from the server into JavaScript objects. The simplest and fastest way to do this is by using JavaScript's built-in eval() function, which can correctly parse valid JSON text and produce a corresponding object.
However, because eval() can execute any JavaScript code (not just JSON data) this approach has some serious security implications. Make sure the servers you interact with are absolutely trustworthy, because they will have the ability to execute arbitrary JavaScript code within your application. In this example, since you are using the servlet container to access data on your own machine, this is not an issue.
/**
* Convert the string of JSON into JavaScript object.
*/
private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
return eval(json);
}-*/;
import com.google.gwt.core.client.JsArray;As you might expect, JSON data types correspond to the built-in types of JavaScript. JSON can encode strings, numbers, booleans, and null values, as well as objects and arrays composed of those types. As in JavaScript, an object is actually just an unordered set of name/value pairs. In JSON objects, however, the values can only be other JSON types (never functions containing executable JavaScript code).
Another technique for a converting a JSON string into something you can work with is to use the static JSONParser.parse(String) method. GWT contains a full set of JSON types for manipulating JSON data in the com.google.gwt.json.client package. If you prefer to parse the JSON data, see the Developer's Guide, Working with JSON. Ultimately both techniques rely on the JavaScript eval() function; so you are still responsible for ensuring that you are using a trusted source of JSON data.
Your next task is to replace the existing StockPrice type with the StockData type.
Not only do you want to be access the array of JSON objects, but you want to be able to work with them as if they were Java objects while you're coding. GWT overlay types let you do this.
The new StockData class will be an overlay type which overlays the existing JavaScript array.
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.core.client.JavaScriptObject;
class StockData extends JavaScriptObject { // [1]
// Overlay types always have protected, zero argument constructors.
protected StockData() {} // [2]
// JSNI methods to get stock data.
public final native String getSymbol() /*-{ return this.symbol; }-*/; // [3]
public final native double getPrice() /*-{ return this.price; }-*/;
public final native double getChange() /*-{ return this.change; }-*/;
// Non-JSNI method to return change percentage. // [4]
public final double getChangePercent() {
return 100.0 * getChange() / getPrice();
}
}
[1] StockData is a subclass of JavaScriptObject, a marker type that GWT uses to denote JavaScript objects.
JavaScriptObject gets special treatment from the GWT compiler and hosted browser.
Its purpose is to provide an opaque representation of native JavaScript objects to Java code.
[2] Overlay types always have protected, zero-argument constructors.
[3] Typically methods on overlay types are JSNI.
These getters directly access the JSON fields you know exist.
By design, all methods on overlay types are final and private; thus every method is statically resolvable by the compiler, so there is no need for dynamic dispatch at runtime.
[4] However, methods on overlay types are not required to be JSNI.
Just as you did in the StockPrice class, you calculate the change percentage based on the price and change values.
Using an overlay type creates a normal looking Java type that you can interact with using code completion, refactoring, and compile-time checking. Yet, you also have the flexibility of interacting with arbitrary JavaScript objects, which makes it simpler to access JSON services using RequestBuilder (which you'll do in the next section).
GWT now understands that any instance of StockData is a true JavaScript object that comes from outside this GWT module. You can interact with it exactly as it exists in JavaScript. In this example, you can access directly the JSON fields you know exist: this.Price and this.Change.
Because the methods on overlay types can be statically resolved by the GWT compiler, they are candidates for automatic inlining. Inlined code runs significantly faster. This makes it possible for the GWT compiler to create highly-optimized JavaScript for your application's client-side code.
Now that you have the mechanism for working with the JSON data in place, you'll write the HTTP request that gets the data from the server.
In this example, you're going to replace the current refreshWatchList method with a new implementation that uses HTTP.
First, specify the URL where the servlet lives, that is:http://localhost:8888/stockwatcher/stockPrices?q=ABC+DFD
Note: If you are doing the php example, substitute the corresponding URL.
Then, append the stock codes in the watch list to the base module URL. Rather than hardcoding the URL for the JSON server, add a constant to the StockWatcher class.
private static final String JSON_URL = GWT.getModuleBaseURL() + "stockPrices?q=";
import com.google.gwt.core.client.GWT;
private void refreshWatchList() {
if (stocks.size() == 0) {
return;
}
String url = JSON_URL;
// Append watch list stock symbols to query URL.
Iterator iter = stocks.iterator();
while (iter.hasNext()) {
url += iter.next();
if (iter.hasNext()) {
url += "+";
}
}
url = URL.encode(url);
// TODO Send request to server and handle errors.
}import com.google.gwt.http.client.URL; import java.util.Iterator;
To get the JSON text from the server, you'll use the HTTP client classes in the com.google.gwt.http.client package. These classes contain the functionality for making asynchronous HTTP requests.
The HTTP types are contained within separate GWT modules that StockWatcher needs to inherit.
<!-- Other module inherits -->
<inherits name="com.google.gwt.http.HTTP" />
import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response;
To send a request, you'll create an instance of the RequestBuilder object. You specify the HTTP method (GET, POST, etc.) and URL in the constructor. If necessary, you can also set the username, password, timeout, and headers to be used in the HTTP request. In this example, you don't need to do this.
When you're ready to make the request, you call sendRequest(String, RequestCallback).
The RequestCallback argument you pass will handle the response in its onResponseReceived(Request, Response) method, which is called when and if the HTTP call completes successfully. If the call fails (for example, if the HTTP server is not responding), the onError(Request, Throwable) method is called instead. The RequestCallback interface is analogous to the AsyncCallback interface in GWT remote procedure calls.
// Send request to server and catch any errors.
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
displayError("Couldn't retrieve JSON");
}
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
updateTable(asArrayOfStockData(response.getText()));
} else {
displayError("Couldn't retrieve JSON (" + response.getStatusText()
+ ")");
}
}
});
} catch (RequestException e) {
displayError("Couldn't retrieve JSON");
}To fix the compile errors, modify the updateTable method.
/** * Update the Price and Change fields for all rows in the stock table. * * @param prices Stock data for all rows. */ private void updateTable(JsArray<StockData> prices) { for (int i = 0; i < prices.length(); i++) { updateTable(prices.get(i)); } // Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date())); }
private void updateTable(StockData price) {
// Make sure the stock is still in the stock table.
if (!stocks.contains(price.getSymbol())) {
return;
}
...
}
If something breaks along the way (for example, if the server is offline, or the JSON is malformed), you'll trap the error and display a message to the user. To do this you'll create a Label widget and write a new method, displayError(String).
/**
* If can't get JSON, display error message.
* @param error
*/
private void displayError(String error) {
errorMsgLabel.setText("Error: " + error);
errorMsgLabel.setVisible(true);
}
private void updateTable(JsArray<StockData> 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);
}
In order to display the error, you will need a new UI component; you'll implement a Label widget.
.negativeChange {
color: red;
}
.errorMessage {
color: red;
}
private ArrayList<String> stocks = new ArrayList<String>();
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);
private static final String JSON_URL = GWT.getModuleBaseURL()
+ "stockPrices?q=";private static final String JSON_URL = GWT.getModuleBaseURL()
+ "BADURL?q=";
At this point you've retrieved JSON-encoded stock data from a local server and used it to update the Price and Change fields for the stocks in your watch list. If you'd like to see how to retrieve JSON from web server on another domain, see Making cross-site requests.
To learn more about client-server communication, see the Developer's Guide, Communicating with the Server.
Topics include:
To learn more about JSNI, see the Developer's Guide, JavaScript Native Interface (JSNI).
Topics include: