Greg has posted 18 posts at DZone. You can read more from them at their website. View Full User Profile

Pivot: A Practical Example, Part 3 - Web Queries

07.15.2008
| 9038 views |
  • submit to reddit
This is the third in a series of five articles that walk through the implementation of a simple but practical Pivot application called Stock Tracker. The previous article discussed event handling in Pivot. This section focuses on "web queries", Pivot's native means of communicating with remote data services. The next article will cover Pivot's support for data binding. -- Greg Brown

"Web queries" are Pivot's native means of communicating with remote data services. They are designed primarily to facilitate interaction with JSON-based REST services. However, they are sufficiently generic to support communication with any type of HTTP-based service, using any data format.

For example, the data presented by the Stock Tracker application is retrieved from Yahoo! Finance as a comma-separated value (CSV) file:

"AAPL","APPLE INC",171.06,169.59,172.17,166.00,+2.88,12995693
"AMZN","AMAZON.COM INC",72.54,72.35,73.83,70.52,+1.10,2748930
"EBAY","EBAY INC",27.09,27.35,27.44,27.04,-0.02,3426369

This data is returned by submitting an HTTP GET request to http://download.finance.yahoo.com/d/quotes.csv with an appropriate set of query string arguments. For example, the Stock Tracker application passes the following arguments to the service URL:

  • s - A comma-separated list of stock symbols representing the quotes to retrieve.

  • f - the requested format for the resulting CSV file; this is a string of characters representing the various data fields returned by the query. The format used by the Stock Tracker application is "snl1ohgc1v":

    • s - symbol
    • n - company name
    • l1 - most recent value
    • o - opening value
    • h - high value
    • g - low value
    • c1 - change percentage
    • v - volume

Note that this query actually returns more data than can be displayed in the table view. The other data fields are used by the quote detail view, which is discussed in more detail in the data binding section.

Constructing the Web Query

In order to display the stock quotes to the user, Stock Tracker must execute a web query to retrieve the data from the Yahoo! Finance web service and then populate the stock quote table view with the results of the query. The query is executed in the refreshTable() method, and the table is populated by a callback handler implemented as an anonymous inner class defined in this method. This code is defined in the StockTracker class.

First, the web query is created:

getQuery = new GetQuery(SERVICE_HOSTNAME, SERVICE_PATH);

Then, the value of the "s" argument is constructed by joining the values in the symbol list with commas, and the query arguments are applied:

StringBuilder symbolsArgumentBuilder = new StringBuilder();
for (int i = 0, n = symbols.getLength(); i < n; i++) {
if (i > 0) {
symbolsArgumentBuilder.append(",");
}

symbolsArgumentBuilder.append(symbols.get(i));
}

String symbolsArgument = symbolsArgumentBuilder.toString();

getQuery.getArguments().put("s", symbolsArgument);
getQuery.getArguments().put("f", "snl1ohgc1v");

The resulting query URL would be similar to:

http://download.finance.yahoo.com/d/quotes.csv?s=aapl,amzn,ebay&f=snl1ohgc1v

Next, an instance of CSVSerializer is created and configured:

CSVSerializer quoteSerializer = new CSVSerializer();
quoteSerializer.getKeys().add(StockQuote.SYMBOL_KEY);
quoteSerializer.getKeys().add(StockQuote.COMPANY_NAME_KEY);
quoteSerializer.getKeys().add(StockQuote.VALUE_KEY);
quoteSerializer.getKeys().add(StockQuote.OPENING_VALUE_KEY);
quoteSerializer.getKeys().add(StockQuote.HIGH_VALUE_KEY);
quoteSerializer.getKeys().add(StockQuote.LOW_VALUE_KEY);
quoteSerializer.getKeys().add(StockQuote.CHANGE_KEY);
quoteSerializer.getKeys().add(StockQuote.VOLUME_KEY);

By default, the GetQuery class uses an instance of pivot.core.serialization.JSONSerializer to deserialize data returned by a GET request. However, the quote data from Yahoo! Finance is returned as a CSV file; CSVSerializer is an implementation of the pivot.core.serialization.Serializer interface that parses a CSV data stream into a sequence of name/value pairs (specifically, an instance of List<Dictionary<String, Object>>). CSVSerializer's key collection maps the columns in the CSV data to keys in the returned dictionary instances.

By default, CSVSerializer creates an instance of HashMap<String, Object> for each row it encounters in the CSV stream. However, a caller can specify the name of a different Dictionary implementation using the setItemClass() method:

quoteSerializer.setItemClass(StockQuote.class);

Since CSV data is untyped, all values are, by default, returned as strings. StockQuote is a custom implementation of the Dictionary interface that converts numeric column data to Float values as it is parsed by the serializer. This avoids the performance penalty of traversing the data twice: once to read it from the CSV stream and again to convert it to the appropriate type. The cell renderers we assigned to the columns in the WTKX file ensure that the data is formatted and presented correctly.

Finally, the query is executed:

getQuery.setSerializer(quoteSerializer);

getQuery.execute(new TaskAdapter<Object>(new TaskListener<Object>() {
...
}

The argument passed to the execute() method is a TaskAdapter wrapped around an anonymous inner class implementation of TaskListener<Object>. TaskListener is an interface defined by the pivot.util.concurrent package and serves as a callback handler for asynchronous operations implemented by the pivot.util.concurrent.Task class, of which GetQuery is a subclass. TaskAdapter is defined by the pivot.wtk package and ensures that the callback occurs on the UI thread (otherwise, the listener is called in the context of the background thread).

TaskListener defines two methods:

public void taskExecuted(Task<V> task);
public void executeFailed(Task<V> task);

The template parameter V is defined by the Task class and represents the (optional) return value of the operation. The first method is called if the task completes successfully, and the second is called if the task failed for any reason.

StockTracker's success handler is defined as follows:

public void taskExecuted(Task<Object> task) {
if (task == getQuery) {
Sequence<Span> selectedRanges = stocksTableView.getSelectedRanges();

List<StockQuote> quotes = (List<StockQuote>)task.getResult();
stocksTableView.setTableData(quotes);

if (selectedRanges.getLength() > 0) {
stocksTableView.setSelectedRanges(selectedRanges);
} else {
if (quotes.getLength() > 0) {
stocksTableView.setSelectedIndex(0);
}
}

ResourceBundle resourceBundle =
ResourceBundle.getBundle(StockTracker.class.getName(), locale);

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
DateFormat.MEDIUM, locale);
String lastUpdateText = resourceBundle.getString("lastUpdate")
+ ": " + dateFormat.format(new Date());
lastUpdateLabel.setText(lastUpdateText);

getQuery = null;
}
}

If the source of the event is the currently executing task, the handler does the following:

  • Caches the current selection state of the quote table view

  • Gets the result of the query and casts it to the appropriate type (List<StockQuote>)

  • Sets the list as the model data for the table view

  • Restores any selection state (which would have been lost when the model data was reset)

  • Updates the value of the "last updated" label to reflect the current time, in a manner appropriate for the current locale

In the case of a failure, the handler simply logs the exception to the console:

public void executeFailed(Task<Object> task) {
if (task == getQuery) {
System.out.println(task.getFault());
getQuery = null;
}
}

This example demonstrates the use of GetQuery only, but Pivot also provides support for HTTP POST, PUT, and DELETE operations using the PostQuery, PutQuery, and DeleteQuery classes, respectively. This makes it very easy to built a complete REST client using Pivot.

Published at DZone with permission of its author, Greg Brown.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Arek Stryjski replied on Tue, 2008/07/15 - 5:56am

Sorry maybe I'm missing something, but relations between Pivot and Groovy and/or Grails are not clear to me. (The head of this DZone says: "Groovy Zone - Everything for the Groovy & Grails developer".)

Are you going to show how all of this Java examples (code) could be simplified in Groovy in some later part of this article?

Greg Brown replied on Tue, 2008/07/15 - 7:18am in response to: Arek Stryjski

The article is cross-posted from the Java zone since it may also be of interest to Groovy developers.

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.