blob: b85dcfe4aedf98ecb0e7a34faa95482272bff941 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you under the Apache License,
Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<document id="stock-tracker.web-queries">
<properties>
<title>Web Queries</title>
</properties>
<body>
<p>
"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.
</p>
<p>
For example, the data presented by the Stock Tracker application is retrieved from
Yahoo! Finance as a comma-separated value (CSV) file:
</p>
<p>
<tt>"AAPL","APPLE INC",171.06,169.59,172.17,166.00,+2.88,12995693</tt><br/>
<tt>"AMZN","AMAZON.COM INC",72.54,72.35,73.83,70.52,+1.10,2748930</tt><br/>
<tt>"EBAY","EBAY INC",27.09,27.35,27.44,27.04,-0.02,3426369</tt><br/>
</p>
<p>
This data is returned by submitting an HTTP GET request to
<a href="http://download.finance.yahoo.com/d/quotes.csv/">http://download.finance.yahoo.com/d/quotes.csv/</a>
with an appropriate set of query string arguments. For example, the Stock Tracker
application passes the following arguments to the service URL:
</p>
<ul>
<li>
<b>s</b> - A comma-separated list of stock symbols representing the quotes to
retrieve.
</li>
<li>
<b>f</b> - 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":
<ul>
<li><i>s</i> - symbol</li>
<li><i>n</i> - company name</li>
<li><i>l1</i> - most recent value</li>
<li><i>o</i> - opening value</li>
<li><i>h</i> - high value</li>
<li><i>g</i> - low value</li>
<li><i>c1</i> - change percentage</li>
<li><i>v</i> - volume</li>
</ul>
</li>
</ul>
<p>
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 <a href="stock-tracker.data-binding.html">data binding</a> section.
</p>
<h3>Constructing the Web Query</h3>
<p>
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
<tt>refreshTable()</tt> 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 <tt>StockTracker</tt> class.
</p>
<p>
First, the web query is created:
</p>
<source type="java">
<![CDATA[
getQuery = new GetQuery(SERVICE_HOSTNAME, SERVICE_PATH);
]]>
</source>
<p>
Then, the value of the "s" parameter is constructed by joining the values in the symbol
list with commas, and the query arguments are applied:
</p>
<source type="java">
<![CDATA[
StringBuilder symbolsParameterBuilder = new StringBuilder();
for (int i = 0, n = symbols.getLength(); i < n; i++) {
if (i > 0) {
symbolsParameterBuilder.append(",");
}
symbolsParameterBuilder.append(symbols.get(i));
}
// Format:
// s - symbol
// n - company name
// l1 - most recent value
// o - opening value
// h - high value
// g - low value
// c1 - change percentage
// v - volume
String symbolsParameter = symbolsParameterBuilder.toString();
getQuery.getParameters().put("s", symbolsParameter);
getQuery.getParameters().put("f", "snl1ohgc1v");
]]>
</source>
<p>
The resulting query URL would be similar to:
</p>
<p style="padding-left:24px">
<a href="http://download.finance.yahoo.com/d/quotes.csv?s=aapl,amzn,ebay&amp;f=snl1ohgc1v">http://download.finance.yahoo.com/d/quotes.csv?s=aapl,amzn,ebay&amp;f=snl1ohgc1v</a>
</p>
<p>
Next, an instance of CSVSerializer is created and configured:
</p>
<source type="java">
<![CDATA[
CSVSerializer quoteSerializer = new CSVSerializer(StockQuote.class);
quoteSerializer.setKeys("symbol",
"companyName",
"value",
"openingValue",
"highValue",
"lowValue",
"change",
"volume");
]]>
</source>
<p>
By default, the <tt>GetQuery</tt> class uses an instance of
<tt>org.apache.pivot.core.serialization.JSONSerializer</tt> to deserialize data
returned by a GET request. However, the quote data from Yahoo! Finance is returned as
a CSV file; <tt>CSVSerializer</tt> is an implementation of the
<tt>org.apache.pivot.core.serialization.Serializer</tt> interface that parses a CSV
data stream into a sequence of name/value pairs.
</p>
<p>
By default, <tt>CSVSerializer</tt> will create an instance of
<tt>org.apache.pivot.collections.HashMap&lt;String, Object&gt;</tt> for each row it
encounters in the CSV stream. However, a caller can specify the name of a different
class as a constructor argument to <tt>CSVSerializer</tt>. 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.
</p>
<p>
If the item class implements the <tt>org.apache.pivot.collections.Dictionary</tt>
interface, the parsed values are <tt>put()</tt> directly into the item instance;
otherwise, the item is wrapped in a <tt>org.apache.pivot.beans.BeanAdapter</tt> into
which the values are <tt>put()</tt> (<tt>BeanAdapter</tt> is a handy class that allows
a caller to treat a Java bean object as if it were a dictionary).
</p>
<p>
Stock Tracker uses the <tt>StockQuote</tt> class to represent the rows in the CSV file:
</p>
<source type="java">
<![CDATA[
public class StockQuote {
private String symbol = null;
private String companyName = null;
private float value = 0;
private float openingValue = 0;
private float highValue = 0;
private float lowValue = 0;
private float change = 0;
private float volume = 0;
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public void setValue(String value) {
try {
setValue(Float.parseFloat(value));
} catch(NumberFormatException exception) {
setValue(Float.NaN);
}
}
public float getOpeningValue() {
return openingValue;
}
public void setOpeningValue(float openingValue) {
this.openingValue = openingValue;
}
public void setOpeningValue(String openingValue) {
try {
setOpeningValue(Float.parseFloat(openingValue));
} catch(NumberFormatException exception) {
setOpeningValue(Float.NaN);
}
}
public float getHighValue() {
return highValue;
}
public void setHighValue(float highValue) {
this.highValue = highValue;
}
public void setHighValue(String highValue) {
try {
setHighValue(Float.parseFloat(highValue));
} catch(NumberFormatException exception) {
setHighValue(Float.NaN);
}
}
public float getLowValue() {
return lowValue;
}
public void setLowValue(float lowValue) {
this.lowValue = lowValue;
}
public void setLowValue(String lowValue) {
try {
setLowValue(Float.parseFloat(lowValue));
} catch(NumberFormatException exception) {
setLowValue(Float.NaN);
}
}
public float getChange() {
return change;
}
public void setChange(float change) {
this.change = change;
}
public void setChange(String change) {
try {
setChange(Float.parseFloat(change));
} catch(NumberFormatException exception) {
setChange(Float.NaN);
}
}
public float getVolume() {
return volume;
}
public void setVolume(float volume) {
this.volume = volume;
}
public void setVolume(String volume) {
try {
setVolume(Float.parseFloat(volume));
} catch(NumberFormatException exception) {
setVolume(Float.NaN);
}
}
}
]]>
</source>
<p>
The cell renderers assigned to the columns in the BXML file ensure that the data
represented by this class is formatted and presented correctly.
</p>
<p>
Finally, the query is executed:
</p>
<source type="java">
<![CDATA[
getQuery.setSerializer(quoteSerializer);
getQuery.execute(new TaskAdapter<Object>(new TaskListener<Object>() {
...
}
]]>
</source>
<p>
The argument passed to the <tt>execute()</tt> method is a <tt>TaskAdapter</tt> wrapped
around an anonymous inner class implementation of <tt>TaskListener&lt;Object&gt;</tt>.
<tt>TaskListener</tt> is an interface defined in the <tt>org.apache.pivot.util.concurrent</tt>
package and serves as a callback handler for asynchronous operations implemented by the
<tt>org.apache.pivot.util.concurrent.Task</tt> class, of which <tt>GetQuery</tt> is a subclass.
<tt>TaskAdapter</tt> is defined in the <tt>pivot.wtk</tt> package and ensures that the
callback occurs on the UI thread (otherwise, the listener is called in the context of
the background thread, which can produce non-deterministic results).
</p>
<p>
<tt>TaskListener</tt> defines two methods:
</p>
<p>
<tt>public void taskExecuted(Task&lt;V&gt; task);</tt><br/>
<tt>public void executeFailed(Task&lt;V&gt; task);</tt><br/>
</p>
<p>
The template parameter <tt>V</tt> is defined by the <tt>Task</tt> 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.
</p>
<p>
StockTracker's success handler is defined as follows:
</p>
<source type="java">
<![CDATA[
@Override
public void taskExecuted(Task<Object> task) {
if (task == getQuery) {
List<Object> quotes = (List<Object>)task.getResult();
// Preserve any existing sort and selection
Sequence<?> selectedStocks = stocksTableView.getSelectedRows();
List<Object> tableData = (List<Object>)stocksTableView.getTableData();
Comparator<Object> comparator = tableData.getComparator();
quotes.setComparator(comparator);
stocksTableView.setTableData(quotes);
if (selectedStocks.getLength() > 0) {
// Select current indexes of selected stocks
for (int i = 0, n = selectedStocks.getLength(); i < n; i++) {
Object selectedStock = selectedStocks.get(i);
int index = 0;
for (Object stock : stocksTableView.getTableData()) {
String symbol = JSON.get(stock, "symbol");
String selectedSymbol = JSON.get(selectedStock, "symbol");
if (symbol.equals(selectedSymbol)) {
stocksTableView.addSelectedIndex(index);
break;
}
index++;
}
}
} else {
if (quotes.getLength() > 0) {
stocksTableView.setSelectedIndex(0);
}
}
refreshDetail();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
DateFormat.MEDIUM, Locale.getDefault());
lastUpdateLabel.setText(dateFormat.format(new Date()));
getQuery = null;
}
}
}
]]>
</source>
<p>
If the source of the event is the currently executing task, the handler does the
following:
</p>
<ul>
<li>
<p>
Caches the current sort and selection state of the quote table view
</p>
</li>
<li>
<p>
Gets the result of the query and casts it to the appropriate type
(<tt>List&lt;Object&gt;</tt>)
</p>
</li>
<li>
<p>
Sets the list as the model data for the table view
</p>
</li>
<li>
<p>
Restores any selection state (which would have been lost when the model data
was reset)
</p>
</li>
<li>
<p>
Updates the value of the "last updated" label to reflect the current time, in
a manner appropriate for the current locale
</p>
</li>
</ul>
<p>
In the case of a failure, the handler simply logs the exception to the console:
</p>
<source type="java">
<![CDATA[
@Override
public void executeFailed(Task<Object> task) {
if (task == getQuery) {
System.err.println(task.getFault());
getQuery = null;
}
}
]]>
</source>
<p>
This example demonstrates the use of <tt>GetQuery</tt> only, but Pivot also provides
support for HTTP POST, PUT, and DELETE operations using the <tt>PostQuery</tt>,
<tt>PutQuery</tt>, and <tt>DeleteQuery</tt> classes, respectively. This makes it very
easy to built a complete REST client using Pivot.
</p>
</body>
</document>