| <?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&f=snl1ohgc1v">http://download.finance.yahoo.com/d/quotes.csv?s=aapl,amzn,ebay&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<String, Object></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<Object></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<V> task);</tt><br/> |
| <tt>public void executeFailed(Task<V> 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<Object></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> |