Creating Live Stock Quote Sample Applications using the Message service

Creating Live Stock Quote Sample Applications using the
Message service
You can programmatically create a Rich Internet Application (RIA) that displays stock data
by using LiveCycle Data Services. You use the Flex client-side API to create the client
application. You use the Java API to create server-side classes that retrieve financial data
from a content provider and sends messages to a destination. When the client application
detects new messages, the application is updated in real time with financial data. For
example, assume that the client application tracks Adobe stock. When the server retrieves
updated data from the financial content provider, it creates an updated message and pushes
the message to the destination. The client application detects the new message and displays
changes to the stock price.
The following illustration shows the stock quote application that is created in this
development article.
Creating Live Stock Quote Sample Applications using the Message service
The stock quote application displays data related to the stock symbol that a user entered. In
the previous illustration, notice that the Adobe stock price is displayed. This application
displays the following data:

The first section displays current stock activity within a data grid control. The data
grid control displays information such as the stock name, its symbol, the change in
the price, and so on.

The second section displays daily stock activity within a line chart control. As a stock
price either moves up or down, the activity is plotted within this control. The user
can modify the zoom perspective by adjusting the slider control.

The third section displays stock history information within an area chart control. The
user can view different ranges by clicking different time range buttons. For example,
the user can view the stock activity over the past year by clicking the 1y button.
LiveCycle Data Services retrieves stock data that is displayed within this client application.
The following illustration shows LiveCycle Data Services retrieving stock data from a third
party financial content provider.
LiveCycle Data Services retrieves stock data from the content provider by using a Java API.
As the server receives stock data, it creates messages and sends the messages to a
destination. A client application written using Flash Builder can receive messages from a
destination and display message data. In this workflow, LiveCycle Data Services is a
message producer and the client applications are message consumers.
Creating Live Stock Quote Sample Applications using the Message service
Note: For more information about message consumers and message producers, see the
Messaging Service section in Using Adobe LiveCycle Data Services Guide.
For this development article, the financial content provider that is used is Yahoo Finance.
This service returns financial data within a comma delimited format, as shown in the
following example:
Date,Open,High,Low,Close,Volume,Adj Close 2010-0812,27.80,28.33,27.76,28.08,7694000,28
The Java server classes download stock data from Yahoo Finance and then parses the
comma delimited data. The data is placed within messages and sent to the destination
located on the server. Each stock has its own subtopic within the destination. A subtopic is a
specific category within a destination. For example, the ADBE subtopic tracks information
about the Adobe stock.
Note: The free Yahoo Finance data provider only returns data when the financial markets
are open from 9:30 to 4 pm EST. If you are following along with this development article,
keep this issue in mind. Also, the data that is displayed is subject to a 15-minute delay.
That is, the client application displays data that is 15 minutes old.
To create the stock quote application by using LiveCycle Data Services, perform the
following tasks:
1. Configure the destinations on the server.
2. Create the server-side classes.
3. Deploy the stock quote application server classes.
4. Create the client application using Flash Builder.
5. Create the JSP that starts the server thread.
Note: This development article contains all of the Java and ActionScript logic that is
required to create this application. The code is explained in the corresponding sections. For
example, if you are interested in knowing how to create the server-side classes, then read
the section titled Create the server-side classes. However, if you want to develop this
application, then it is recommended that you read tasks 1-5.
Creating Live Stock Quote Sample Applications using the Message service
Configure the destinations on the server
The stock quote application uses both the Message service and the RPC service. The
Message service is used to retrieve real-time stock information that is displayed in the data
grid control and the linechart control. The RPC service is used to retrieve historical data and
stock quote prices that are displayed in the area chart control. As a result, you define the
following three destinations in XML configuration files:

stockFeed - this destination provides real-time financial information for the stock.
This destination is a Message service destination and is defined in the messagingconfig.xml file.

setQuotes - this destination is used to set which stock price is displayed. This
destination is an RPC service destination and is defined in the remoting-config.xml
file.

getStockHistory - this destination is used to retrieve historical information for the
stock. This destination is an RPC service destination and is defined in the remotingconfig.xml file.
These XML configuration files are located in the following folder:
[Install directory]\lcds\tomcat\webapps\[web application name]\WEB-INF\flex
where [Install directory] is the directory on which LiveCycle Data Services is installed
and [web application name] is your web application name. For example, if the name of
your web application is stockDashboard, place these files in the following folder:
[Install directory]\lcds\tomcat\webapps\stockDashboard\WEB-INF\flex
Modifying the messaging-config.xml file
In the messaging-config.xml file, define a destination named stockFeed by using the
destination tag. Both the Java server class and the client application created using Flash
Builder references this destination. The server pushes messages (changes to a stock price)
to this destination and the client retrieves messages from this destination.
The following XML code represents the stockFeed destination.
<destination id="stockFeed">
<properties>
Creating Live Stock Quote Sample Applications using the Message service
<network>
<session-timeout>0</session-timeout>
</network>
<server>
<message-time-to-live>0</message-time-to-live>
<allow-subtopics>true</allow-subtopics>
<subtopic-separator>.</subtopic-separator>
</server>
</properties>
</destination>
The id attribute defines the name of the destination. In this example, the name of the
destination is stockFeed. The allow-subtopics attribute enables the server (the message
producer) to send messages to a specific category, called a subtopic, within the stockFeed
destination. The client application retrieves messages from just that subtopic. As a result,
only part of a client application is updated, such as a specific data grid, not the entire client
application.
The subtopic-separators attribute lets you change the separator character. The default
value is a period character (.). The following XML code represents the entire messagingconfig.xml file for the stock quote application. Notice that the default channel is my-rtmp.
Note: The my-rtmp channel is only supported by LiveCycle Data Services. If you are using
BlazeDS, you can use Streaming-AMF. For information about channels, see the Using Adobe
LiveCycle Data Services ES2 guide.
<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service"
class="flex.messaging.services.MessageService">
<adapters>
<adapter-definition id="actionscript"
class="flex.messaging.services.messaging.adapters.ActionScriptAdapter"
default="true" />
<adapter-definition id="jms"
class="flex.messaging.services.messaging.adapters.JMSAdapter"/>
</adapters>
<default-channels>
<channel ref="my-rtmp"/>
</default-channels>
<destination id="stockFeed">
<properties>
Creating Live Stock Quote Sample Applications using the Message service
<network>
<session-timeout>0</session-timeout>
</network>
<server>
<message-time-to-live>0</message-time-to-live>
<allow-subtopics>true</allow-subtopics>
<subtopic-separator>.</subtopic-separator>
</server>
</properties>
</destination>
</service>
Modifying the remoting-config.xml file
In the remoting-config.xml file, define two destinations named setQuotes and
getStockHistory by using the destination tag. Both these destinations reference the Java
server classes that are created within this development article. These destinations let
objects defined in the client application to call methods defined in Java server classes. That
is, an ActionScript object can call a Java method that is located on the server.
The following XML code represents the entire remoting-config.xml file for the stock quote
application. (The bolded code represents the new destinations.)
<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service"
class="flex.messaging.services.RemotingService">
<adapters>
<adapter-definition id="java-object"
class="flex.messaging.services.remoting.adapters.JavaAdapter"
default="true"/>
</adapters>
<default-channels>
<channel ref="my-amf"/>
</default-channels>
<destination id="setQuotes">
<properties>
<source>httpDownloader.RealTimeHttpDownloader</source>
</properties>
</destination>
<destination id="getStockHistory">
<properties>
<source>httpDownloader.HistoryHttpDownloader</source>
Creating Live Stock Quote Sample Applications using the Message service
</properties>
</destination>
</service>
The setQuotes destination references a Java class named
httpDownloader.RealTimeHttpDownloader. Likewise, the getStockHistory destination
references a Java class named httpDownloader.HistoryHttpDownloader. Both of these
class return data from the financial content provider. For information about creating these
classes, see Create the server-side classes.
Create the server-side classes
The LiveCycle Data Services stock quote application consists of server-side classes that
download financial information from Yahoo finance. These classes creates messages that
contain financial data, and pushes the messages to the stockFeed destination. You can
create these classes in a Java IDE such as Eclipse.
The stock quote application consists of the following server-side classes:

StockQuoteFeed - Creates a StockQuoteFeedThread instance and starts the
thread. A JSP references this class to start the server thread. (See Create the JSP
that starts the server thread.)

StockQuoteFeedThread - Creates a server thread that produces message and
pushes the messages to the stockFeed destination.

HttpDownloader - Represents the base download class that is able to download
financial data from Yahoo finance.

RealTimeHttpDownloader - Represents the class that downloads real-time stock
information. Real-time information populates the data grid control located in the
client application.

HistoryHttpDownloader - Represents the class that downloads stock history
information. History information populates the area control located in the client
application.

Stock - Represents a stock that contains data members that store information such
as high value, low value, open value, and so on. A Stock instance is the complex
Creating Live Stock Quote Sample Applications using the Message service
Java type that is used as the body of the message that is sent to the stockFeed
destination.

StockInTime - Represents a save each point of data in a stock's history. The date
and close data members are used as (x,y) coordinates that can be plotted in the line
chart control located in the client application.
LiveCycle Data Services Library Files
In your Java server project, include LiveCycle Data Services JAR files in your project’s class
path. Ensure that you include LiveCycle Data Services JAR files located in the following
directory:
[Install directory]\lcds\tomcat\webapps\lcds-samples\WEB-INF\lib
where [Install directory] is the directory on which LiveCycle Data Services is installed.
Understanding the StockQuoteFeed class
The StockQuoteFeed class starts the thread on the server that downloads financial data
from the content provider, creates messages, and sends them to the stockFeed
destination. The StockQuoteFeed class contains a private static data member named
thread of type StockQuoteFeedThread, as shown in the following example.
private static StockQuoteFeedThread thread ;
This thread represents the server thread. This StockQuoteFeed class contains a static
method named main that creates a StockQuoteFeed class (an instance of itself) and calls
the start method. Within the start method, the thread data member is checked to see if it
is null. If this data member is null, then memory is allocated to the thread data member and
its start method is invoked.
The following code example represents the start method that belongs to the
StockQuoteFeed class. This method is called from the JSP that starts the server thread.
(See Create the JSP that starts the server thread.)
//If thread is null, allocate memory to thread, and call its start method
public void start() {
if (thread == null) {
thread = new StockQuoteFeedThread();
thread.start();
}
Creating Live Stock Quote Sample Applications using the Message service
}
Understanding the StockQuoteFeedThread class
The StockQuoteFeedThread class extends java.lang.thread and contains a method
named run. The run method performs the following tasks:

Checks to see if the thread is running. By default, the thread is in a running state.

Checks to see if the client application has request information about a specific stock.
If a client application has not requested information about a stock, nothing happens
and the thread remains in a running state.

If a client application has requested information about a specific stock, the thread
starts to create a message. (Proceed to the next bullet.)

The thread downloads CSV data that contains financial information about the stock
and saves the data to a string variable. To download CSV data, the
RealTimeHttpDownloader object’s static getRealTimeQuotes method is called.

New stock information is placed in an ArrayList instance. To place the stock
information into an ArrayList instance, the RealTimeHttpDownloader object’s static
parseRealTimeData method is called. This method accepts a string variable that
contains financial data and returns the ArrayList instance. Each element within the
ArrayList instance is a Stock instance.

New stock information is compared with existing stock information. That is, two
ArrayList instances are compared. The new stock list and the current stock list.
Only new stock information is placed into messages and sent to the destination. As a
result, the client application is only getting messages that represent updated stock
information. If information has not changed, then it is not placed within a message.
To compare the two ArrayList instances, the compareStocks method is called. This
method accepts two ArrayList instances and returns an ArrayList instance that
represents the changes. Also the changes ArrayList instance is used to create the
currentStock ArrayList instance that is used for the next call to compareStocks.

For each instance in the changed ArrayList instance, a new AsyncMessage instance
is created.
Creating Live Stock Quote Sample Applications using the Message service
The following code example represents the run method that performs these tasks.
public void run() {
ArrayList<Stock> currentStocks = new ArrayList<Stock>();
MessageBroker msgBroker = MessageBroker.getMessageBroker(null);
String clientID = UUIDUtils.createUUID();
while (running) {
//Check to see if a client application has
//request stock symbols
if (!RealTimeHttpDownloader.currentSymbolsIsEmpty()) {
//Download a CSV data file and save to string
String data = RealTimeHttpDownloader.getRealTimeQuotes();
//Parse data and put each stock in the newStocks ArrayList instance
ArrayList<Stock> newStocks =
RealTimeHttpDownloader.parseRealTimeData(data);
ArrayList<Stock> changes = compareStocks(currentStocks, newStocks);
currentStocks = (ArrayList<Stock>) newStocks.clone();
//Iterate thought the changed ArrayList
for (int i = 0; i < changes.size(); i++) {
AsyncMessage msg = new AsyncMessage();
msg.setDestination("stockFeed");
msg.setHeader("DSSubtopic", changes.get(i).getSymbol());
msg.setClientId(clientID);
msg.setMessageId(UUIDUtils.createUUID());
msg.setTimestamp(System.currentTimeMillis());
msg.setBody(changes.get(i));
msgBroker.routeMessageToService(msg, null);
}
}
}
}
Notice that for each element in the changed ArrayList instance, a new AsyncMessage
instance is created. There is a 1-1 relationship between an AsyncMessage instance and a
message that is sent. That is, the AsyncMessage instance is the message that is sent to the
stockFeed destination.
The AsyncMessage object’s setDestination method is called that defines the destination on
the server to which the message is sent. The argument for the setDestination method is a
Creating Live Stock Quote Sample Applications using the Message service
string value that specifies the destination name. In this example, the stockFeed destination
was configured by using XML files. (See Configure the destinations on the server.)
Next, the AyncMessage object’s setHeader method is called. This method accepts a string
value whose value is DSSubtopic. This value informs LiveCycle Data Services to use
subtopics. That is, messages are sent to a subtopic of the stockFeed destination. In this
example, the subtopic is the symbol name that corresponds to the stock. When the
stockFeed destination was defined, the allow-subtopics attribute was set to true.
The setClientID method sets the identifier value of the message. The identifier value is a
universally unique value that was obtained by calling the UUIDUtils object’s static
createUUID method. (This application logic is shown in the run method.) A timestamp is
defined for the message by calling the AsyncMessage instance’s setTimeStamp method. The
value of the System.currentTimeMillis is passed as an argument value.
The message body is a Stock instance that is retrieved from the ArrayList instance that
contains the changes. The index value is used to retrieve the correct Stock instance in the
ArrayList instance. The MessageBroker object’s routeMessageToService method is called.
This method sends the message to the stockFeed destination.
StockQuoteFeed and StockQuoteFeedThread classes
The following code example represents a Java file that contains both the StockQuoteFeed
and StockQuoteFeedThread classes. Notice that these classes belong to a Java package
named feed.
package feed;
import
import
import
import
import
import
httpDownloader.RealTimeHttpDownloader;
java.util.ArrayList;
flex.messaging.MessageBroker;
flex.messaging.messages.AsyncMessage;
flex.messaging.util.UUIDUtils;
stock.Stock;
public class StockQuoteFeed {
private static StockQuoteFeedThread thread;
public StockQuoteFeed() {
}
Creating Live Stock Quote Sample Applications using the Message service
//If thread is null, allocate memory to thread and invoke its start memory
public void start() {
if (thread == null) {
thread = new StockQuoteFeedThread();
thread.start();
}
}
public void stop() {
thread.running = false;
thread = null;
}
public static void main (String arg[]) {
StockQuoteFeed feed = new StockQuoteFeed();
feed.start();
System.out.println("StockQuoteFeed Started");
RealTimeHttpDownloader.addStockQuote("ADBE");
}
public static class StockQuoteFeedThread extends Thread {
public boolean running = true;
public static int test = 0;
public static ArrayList<Stock> compareStocks (ArrayList<Stock> currentStocks,
ArrayList<Stock> newStocks) {
ArrayList<Stock> changes = new ArrayList<Stock>();
//Clone so we don't lose the values in newStock
//(will be needed to update current
changes = (ArrayList<Stock>) newStocks.clone();
//Remove all values that did not change
//since last check (all those in newStocks that are also in currentStocks)
changes.removeAll(currentStocks);
return changes;
}
public void run() {
ArrayList<Stock> currentStocks = new ArrayList<Stock>();
MessageBroker msgBroker = MessageBroker.getMessageBroker(null);
String clientID = UUIDUtils.createUUID();
while (running) {
//Check to see if a client application has request stock symbols
if (!RealTimeHttpDownloader.currentSymbolsIsEmpty()) {
Creating Live Stock Quote Sample Applications using the Message service
//Download a CSV data file and save to string
String data = RealTimeHttpDownloader.getRealTimeQuotes();
//Parse data and put each stock in the newStocks ArrayList instance
ArrayList<Stock> newStocks =
RealTimeHttpDownloader.parseRealTimeData(data);
ArrayList<Stock> changes = compareStocks(currentStocks, newStocks);
currentStocks = (ArrayList<Stock>) newStocks.clone();
//Iterate through the change
for (int i = 0; i < changes.size(); i++) {
AsyncMessage msg = new AsyncMessage();
msg.setDestination("stockFeed");
msg.setHeader("DSSubtopic", changes.get(i).getSymbol());
msg.setClientId(clientID);
msg.setMessageId(UUIDUtils.createUUID());
msg.setTimestamp(System.currentTimeMillis());
msg.setBody(changes.get(i));
msgBroker.routeMessageToService(msg, null);
}
}
}
}
}
}
Understanding the HttpDownloader class
The HttpDownloader class represents the base class that is used to download information
from Yahoo finance. This class contains a method named downloadFile that takes a string
parameter named uri. The uri parameter represents the URL from which the data is
downloaded. The downloadFile method returns a string value that represents the download
response. The following Java code represents the downloadFile method.
protected static String downloadFile(String uri) {
HttpClient httpClient = new HttpClient();
HttpMethod getMethod = new GetMethod(uri);
try {
int response = httpClient.executeMethod(getMethod);
if (response != 200) {
throw new HttpException ("HTTP problem, httpcode: " + response);
}
InputStream inputStream = getMethod.getResponseBodyAsStream();
Creating Live Stock Quote Sample Applications using the Message service
String responseText = streamToString(inputStream);
return responseText;
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null; //returns null if error was thrown
}
The following Java code represents the entire HttpDownloader class.
package httpDownloader;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import
import
import
import
org.apache.commons.httpclient.HttpClient;
org.apache.commons.httpclient.HttpException;
org.apache.commons.httpclient.HttpMethod;
org.apache.commons.httpclient.methods.GetMethod;
/**
* Superclass containing only static methods used by
*both <code>RealTimeHttpDownloader</code>
* and <code>HistoryHttpDownloader</code>.
* This class and its subclasses are based on the
* YahooDownloader class located on wikiJava.
*
*/
public class HttpDownloader {
/**
* Connects to the given URI using an <code>HttpClient</code> and a
* <code>HttpMethod</code> object. Receives the downloaded information
* in an <code>inputStream</code> and sends it to the
* <code>streamToString()</code> method to convert it to a String.
*
* @param uri : The uri where the method will download the information
* @return The downloaded response in String format
*/
protected static String downloadFile(String uri) {
HttpClient httpClient = new HttpClient();
HttpMethod getMethod = new GetMethod(uri);
Creating Live Stock Quote Sample Applications using the Message service
try {
int response = httpClient.executeMethod(getMethod);
if (response != 200) {
throw new HttpException ("HTTP problem, httpcode: " + response);
}
InputStream inputStream = getMethod.getResponseBodyAsStream();
String responseText = streamToString(inputStream);
return responseText;
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null; //returns null if error was thrown
}
/**
* Converts the given InputStream to a String using
* the StringBuilder object.
*
* @param inputStream : The InputStream to be converted
* @return A String representation of the given <code>InputStream</code>
* @throws IOException Thrown by the
* bufferedInputStream.read() method
*/
private static String streamToString(InputStream inputStream) throws
IOException {
BufferedInputStream bufferedInputStream = new
BufferedInputStream(inputStream);
StringBuilder stringBuilder = new StringBuilder();
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
stringBuilder.append(new String(buffer, 0, bytesRead));
}
return stringBuilder.toString();
}
/**
* Converts a String to a Double using the Double.parseDouble()
Creating Live Stock Quote Sample Applications using the Message service
* method. If the conversion fails, will simply return 0.
* The conversion will
* only fail when the String is equal to "N\A", as
* this is the only non-numerical
* value the downloaded file may have in a field where a number is expected.
*
* @param number : The String representation of the number to convert
* @return The given number as a Double. Returns 0 if the String cannot be
* converted to a Double.
*/
protected static Double parseDouble(String number) {
number = number.replace("\"", "");
try {
return Double.parseDouble(number);
} catch (NumberFormatException e) {
return 0.0;
}
}
}
Understanding the RealTimeHttpDownloader class
The RealTimeHttpDownloader class extends the HttpDownloader class and downloads realtime stock information from Yahoo finance. This data is used to populate the data grid
control located in the client application. This class creates ArrayList instances that are
used to create messages that are sent to the stockFeed destination. All methods located in
this class are static.
Within the StockQuoteFeedThread object’s run method, the RealTimeHttpDownloader
object’s static getRealTimeQuotes method is called. The getRealTimeQuotes method calls
the HttpDownloader object’s downloadFile method. Remember that the downloadFile
method takes a uri parameter value that specifies the URL from which to download
information. The downloadFile method returns CSV data that represents stock information
within a string value.
The following Java code example shows the getRealTimeQuotes method.
public static String getRealTimeQuotes() {
return downloadFile(getRealTimeURI());
}
Creating Live Stock Quote Sample Applications using the Message service
The uri argument is obtained by calling the getRealTimeURI method. The getRealTimeURI
method constructs the URL that is required to retrieve CSV data from Yahoo finance. The
following Java code example represents the getRealTimeURI method.
private static String getRealTimeURI() {
String uri = "http://finance.yahoo.com/d/quotes.csv?s=";
for (int i = 0; i < currentSymbols.size(); i++) {
uri += currentSymbols.get(i);
if (i+1 < currentSymbols.size()) {
uri += "+";
}
}
return uri += "&f=snl1c6ohgd1t1";
}
In this example, currentSymbols is an ArrayList data member that belongs to the
RealTimeHttpDownloader class. The currentSymbols data member tracks all of the
symbols that a client application tracks. Therefore each symbol that is tracked by a client
application is specified within the URL that is sent to the Yahoo content provider.
Stock symbols that are tracked are added to the currentSymbols data member by calling
the static addStockQuote method. This method accepts a string value that represents the
stock symbol. If the input argument is not a valid stock symbol, then an exception is thrown
when the CSV data is parsed and this method returns null.
Note: The addStockQuote method is called by an ActionScript object defined in the client
application that references the setQuotes destination. (See Develop the client application
logic.)
The following Java code represents the addStockQuote method.
public static Stock addStockQuote (String symbol) {
String initData = downloadFile("http://finance.yahoo.com/d/quotes.csv?s=" +
symbol + "&f=snl1c6ohgd1t1");
try {
Stock aStock = (parseRealTimeData(initData)).get(0);
if (!currentSymbols.contains(symbol)) {
currentSymbols.add(symbol);
}
System.out.println(currentSymbols.toString());
return aStock;
} catch (Exception e) {
Creating Live Stock Quote Sample Applications using the Message service
return null;
}
}
Another important task that the RealTimeHttpDownloader class performs is to create an
ArrayList object where each element is a Stock instance. The run method located in the
StockQuoteFeedThread class calls the static parseRealTimeData method. This method
accepts a string variable that represents the CSV data returned by the content provider.
This method constructs the ArrayList object where each element is a Stock instance.
The following Java code represents the parseRealTimeData method.
public static ArrayList<Stock> parseRealTimeData (String data) throws
ParseException {
ArrayList<Stock> newStocks = new ArrayList<Stock>();
String[] csvRows = data.split("\n");
for (int i = 0; i < csvRows.length; i++) {
String[] stockInfo = csvRows[i].split(",");
Stock aStock = new Stock(stockInfo[0].replace("\"", ""),
stockInfo[1].replace("\"", ""),
parseDouble(stockInfo[2]),
parseDouble(stockInfo[3]),
parseDouble(stockInfo[4]),
parseDouble(stockInfo[5]),
parseDouble(stockInfo[6]),
convertToDate(stockInfo[7], stockInfo[8]));
newStocks.add(aStock);
}
return newStocks;
}
The first task that this method performs is to create an ArrayList object that contains
stock information. This method splits data located in the string input parameter that
contains CSV data into rows by using a new line character (\n). Each row represents a
different stock and is stored as an element in a string array named csvRows. For each row
(or element in the csvRows array), a new string array named stockInfo is declared by
splitting the data using a comma character.
A new Stock instance is created and its data members are populated with data located the
stockInfo string array. The Stock instance is added to the ArrayList object. The
parseRealTimeData method returns the ArrayList object that contains a Stock object for
each stock located in the CSV data.
Creating Live Stock Quote Sample Applications using the Message service
RealTimeHttpDownloader class
The following Java code represents the entire RealTimeHttpDownloader class. Notice that
this class belongs to a package named httpDownloader
package httpDownloader;
import
import
import
import
import
import
java.text.DateFormat;
java.text.ParseException;
java.text.SimpleDateFormat;
java.util.ArrayList;
java.util.Date;
stock.Stock;
public class RealTimeHttpDownloader extends HttpDownloader {
/** Static ArrayList containing the symbols that are being queried
* by the StockQuoteFeed
*/
private static ArrayList<String> currentSymbols = new ArrayList<String>();
public static boolean currentSymbolsIsEmpty() {
return currentSymbols.isEmpty();
}
public static String getRealTimeQuotes() {
return downloadFile(getRealTimeURI());
}
public static String addStockQuote (String symbol) {
if (!currentSymbols.contains(symbol)) {
currentSymbols.add(symbol);
}
System.out.println(currentSymbols.toString());
return symbol;
}
public static void removeStockQuote (String symbol) {
currentSymbols.remove(symbol);
}
private static String getRealTimeURI() {
String uri = "http://finance.yahoo.com/d/quotes.csv?s=";
for (int i = 0; i < currentSymbols.size(); i++) {
uri += currentSymbols.get(i);
if (i+1 < currentSymbols.size()) {
Creating Live Stock Quote Sample Applications using the Message service
uri += "+";
}
}
return uri += "&f=snl1c6ohgd1t1";
}
//This method parses the CSV data, creates a Stock instance and
//places the Stock instance in an ArrayList
public static ArrayList<Stock> parseRealTimeData (String data) {
ArrayList<Stock> newStocks = new ArrayList<Stock>();
String[] csvRows = data.split("\n");
for (int i = 0; i < csvRows.length; i++) {
String[] stockInfo = csvRows[i].split(",");
Stock aStock = new Stock(stockInfo[0].replace("\"", ""),
stockInfo[1].replace("\"", ""),
parseDouble(stockInfo[2]),
parseDouble(stockInfo[3]),
parseDouble(stockInfo[4]),
parseDouble(stockInfo[5]),
parseDouble(stockInfo[6]),
convertToDate(stockInfo[7], stockInfo[8]));
newStocks.add(aStock);
}
return newStocks;
}
private static Date convertToDate (String sDate, String sTime) {
sDate = sDate.replace("\"", "");
sDate = sDate.replace("\r", "");
sTime = sTime.replace("\"", "");
sTime = sTime.replace("\r", "");
sDate += " " + sTime;
try {
DateFormat dateformater = new SimpleDateFormat("M/d/yyyy KK:mmaa");
return dateformater.parse(sDate);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
Creating Live Stock Quote Sample Applications using the Message service
Understanding the HistoryHttpDownloader class
The HistoryHttpDownloader class extends the HttpDownloader class and downloads
history stock information from Yahoo finance. This data populates the area chart and line
chart controls located in the client application. For example, if the client application requests
to view data that represents stock activity during the past five years, this class creates the
data.
The StockQuoteFeedThread class does not call methods that belong to the
HistoryHttpDownloader class. Instead, the client application calls methods exposed by this
class by using Remoting functionality supported by LiveCycle Data Services.
This class contains a method named getHistoricalQuotes that accepts three parameter
values:

A string value that specifies the stock symbol for which historical data is retrieved.

A date value that specifies the start dare.

A date value that specifies the end date.
The getHistoricalQuotes method is called by an ActionScript object that is defined in
the client application that references the getStockHistory destination.
The following Java code represents the getHistoricalQuotes method.
public static ArrayList<StockInTime> getHistoricalQuotes(String symbol, Date
from, Date to) {
String data = downloadFile(getHistoryURI(symbol, from, to));
ArrayList<StockInTime> stockHistory = parseHistoryData(data);
return stockHistory;
}
The first task that this method performs is to call the downloadFile method located in the
HttpDownloader class. The URL value that is passed to the downloadFile method is created
by calling the getHistoryURI method. This method requires the input parameter values
that are passed to the HistoricalQuotes method. The HistoricalQuotes method
constructs a URL value that is passed to Yahoo finance. This URL value includes date
information along with the stock symbol.
The following Java code represents the getHistoryURI method.
Creating Live Stock Quote Sample Applications using the Message service
private static String getHistoryURI (String symbol, Date from, Date to) {
Calendar fromDate = new GregorianCalendar();
fromDate.setTime(from);
Calendar toDate = new GregorianCalendar();
toDate.setTime(to);
String uri = "http://ichart.finance.yahoo.com/table.csv?s=";
uri += symbol;
uri += "&a=" + fromDate.get(Calendar.MONTH);
uri += "&b=" + fromDate.get(Calendar.DAY_OF_MONTH);
uri += "&c=" + fromDate.get(Calendar.YEAR);
uri += "&d=" + toDate.get(Calendar.MONTH);
uri += "&e=" + toDate.get(Calendar.DAY_OF_MONTH);
uri += "&f=" + toDate.get(Calendar.YEAR);
return uri += "&g=d";
}
The return value of getHistoryURI is passed to the downloadFile method in the
HttpDownloader class. The return value of the downloadFile method is CSV data returned
by Yahoo finance. This data represents the historical data that is displayed in the area chart
control located in the client application.
The CSV data returned by the downloadFile method is passed to the parseHistoryData
method. This method constructs the ArrayList object where each element is a
StockInTime instance. A StockInTime instance represents a save point of data in a stock's
history. The date and close data members are used as (x,y) coordinates that are plotted in
the line chart control located in the client application.
The following Java code represents the parseHistoryData method.
public static ArrayList<StockInTime> parseHistoryData (String data) {
ArrayList<StockInTime> stockHistory = new ArrayList<StockInTime>();
String[] csvRows = data.split("\n");
//First row contains headers, ignored
for (int i = 1; i < csvRows.length; i++) {
String[] stockInfo = csvRows[i].split(",");
StockInTime stockPoint = new StockInTime(convertToDate(stockInfo[0]),
parseDouble(stockInfo[4]));
stockHistory.add(stockPoint);
}
return stockHistory;
}
Creating Live Stock Quote Sample Applications using the Message service
The first task that this method performs is to create an ArrayList object named
parseHistoryData that contains stock information. This method splits data located in the
CSV input parameter into rows by using a new line character (\n). Each row represents a
different stock and is stored as an element in a string array named csvRows. For each row
(or element in the csvRows array), a new string array named stockInfo is created by
splitting the data by using a comma character.
A new StockInTime instance is created and its data members are populated with data
located the stockInfo string array. The StockInTime instance is added to the
parseHistoryData object. The parseHistoryData method returns the ArrayList object
that contains a StockInTime object for each stock located in the CSV data.
The return value of the getHistoricalQuotes method is the ArrayList object that contains
a StockInTime object for each stock located in the CSV data.
HistoryHttpDownloader class
The following Java code represents the entire HistoryHttpDownloader class. Notice that
this class belongs to a package named httpDownloader.
package httpDownloader;
import
import
import
import
import
import
import
java.text.DateFormat;
java.text.ParseException;
java.text.SimpleDateFormat;
java.util.ArrayList;
java.util.Calendar;
java.util.Date;
java.util.GregorianCalendar;
import stock.StockInTime;
public class HistoryHttpDownloader extends HttpDownloader {
public static ArrayList<StockInTime> getHistoricalQuotes(String symbol, Date
from, Date to) {
String data = downloadFile(getHistoryURI(symbol, from, to));
ArrayList<StockInTime> stockHistory = parseHistoryData(data);
return stockHistory;
}
private static String getHistoryURI (String symbol, Date from, Date to) {
Calendar fromDate = new GregorianCalendar();
Creating Live Stock Quote Sample Applications using the Message service
fromDate.setTime(from);
Calendar toDate = new GregorianCalendar();
toDate.setTime(to);
String uri = "http://ichart.finance.yahoo.com/table.csv?s=";
uri += symbol;
uri += "&a=" + fromDate.get(Calendar.MONTH);
uri += "&b=" + fromDate.get(Calendar.DAY_OF_MONTH);
uri += "&c=" + fromDate.get(Calendar.YEAR);
uri += "&d=" + toDate.get(Calendar.MONTH);
uri += "&e=" + toDate.get(Calendar.DAY_OF_MONTH);
uri += "&f=" + toDate.get(Calendar.YEAR);
return uri += "&g=d";
}
public static ArrayList<StockInTime> parseHistoryData (String data) {
ArrayList<StockInTime> stockHistory = new ArrayList<StockInTime>();
String[] csvRows = data.split("\n");
//First row contains headers, ignored
for (int i = 1; i < csvRows.length; i++) {
String[] stockInfo = csvRows[i].split(",");
StockInTime stockPoint = new StockInTime(convertToDate(stockInfo[0]),
parseDouble(stockInfo[4]));
stockHistory.add(stockPoint);
}
return stockHistory;
}
private static Date convertToDate (String sDate) {
try {
DateFormat dateformater = new SimpleDateFormat("yyyy-MM-dd");
return dateformater.parse(sDate);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
Understanding the Stock class
The Stock class contains data members that store stock information such as high value, low
value, open value, and so on. The following Java code represents the Stock class. Notice
that this class belongs to a package named stock.
Creating Live Stock Quote Sample Applications using the Message service
package stock;
import java.io.Serializable;
import java.util.Date;
/**
* This POJO stores detailed information concerning a stock
* at a particular time.
*
*/
public class Stock implements Serializable {
private
private
private
private
private
private
private
private
private
static final long serialVersionUID = -8334804402463267285L;
String symbol;
String name;
double low;
double high;
double open;
double last;
double change;
Date tradeTime;
public Stock (String symbol, String name, double last,
double change, double open, double high,
double low, Date tradeTime) {
this.symbol = symbol;
this.last = last;
this.name = name;
this.low = low;
this.high = high;
this.open = open;
this.change = change;
this.tradeTime = tradeTime;
}
public double getChange() {
return change;
}
public void setChange(double change) {
this.change = change;
}
public double getHigh() {
return high;
Creating Live Stock Quote Sample Applications using the Message service
}
public void setHigh(double high) {
this.high = high;
}
public double getLast() {
return last;
}
public void setLast(double last) {
this.last = last;
}
public double getLow() {
return low;
}
public void setLow(double low) {
this.low = low;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getOpen() {
return open;
}
public void setOpen(double open) {
this.open = open;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
Creating Live Stock Quote Sample Applications using the Message service
public Date getTradeTime() {
return tradeTime;
}
public void setTradeTime(Date tradeTime) {
this.tradeTime = tradeTime;
}
@Override
public boolean equals(Object aStock) {
//Check self comparison
if ( this == aStock ) return true;
//Check instance
if ( !(aStock instanceof Stock) ) return false;;
//Cast to object is safe due to above check
Stock stock = (Stock) aStock;
//Field-by-field comparison
return
this.symbol.equals(stock.symbol) &&
this.last == stock.last &&
this.name.equals(stock.name) &&
this.low == stock.low &&
this.high == stock.high &&
this.open == stock.open &&
this.change == stock.change;
}
@Override
public String toString() {
return this.name + " (" + this.symbol + "): " + this.last + (this.change>0?
" +" + this.change : " " + this.change);
}
}
Understanding the StockInTime class
The StockInTime class represents a save point of data in a stock's history. The following
Java code represents the StockInTime class. Notice that this class belongs to a package
named stock.
package stock;
Creating Live Stock Quote Sample Applications using the Message service
import java.util.Date;
/**
* This POJO is used to save each point of data in a stock's history. The
* attributes "date" and "close" are used as (x,y) coordinates that can be
* plotted on a graph.
*
*/
public class StockInTime {
/** The date value represent the day associated
* with a particular closing value of a stock. */
public Date date;
/** The close values represent the closing value of a
* stock on a particular day. */
public Double close;
/** Simple constructor that associates given parameters to attributes */
public StockInTime(Date date, Double close) {
this.date = date;
this.close = close;
}
}
Deploy the stock quote application server classes
After you create and compile the Java server-based classes, you deploy them to the server
hosting LiveCycle Data Services. The compiled Java CLASS files are deployed to the WEBINF\classes folder located in your web application. For example, assume that the name of
your web application is stockDashboard. In this situation, deploy the CLASS files in the
following directory.
[Install directory]lcds\tomcat\webapps\stockDashboard\WEB-INF\classes
where [Install directory] is the directory on which LiveCycle Data Services is installed.
Under the classes folder, create a folder structure that matches the package names. The
StockQuoteFeed and StockQuoteFeedThread classes both belong to the feed package. As a
result, create the following folder structure:
WEB-INF\classes\feed
Place the StockQuoteFeed.class and StockQuoteFeedThread.class files in this folder.
Creating Live Stock Quote Sample Applications using the Message service
Because the HistoryHttpDownloader, HttpDownloader, RealTimeHttpDownloader classes
belong to the httpDownloader package, create the following folder structure:
WEB-INF\classes\httpDownloader
Place these CLASS files in this folder.
Lastly, create the following folder structure:
WEB-INF\classes\stock
Place the Stock.class and StockInTime.class files in this folder
Ensure that the LiveCycle Data Service JAR files are located in the WEB-INF\lib folder.
Create the client application using Flash Builder
Create a Flash Builder project that is used to create the client application. This project
references the J2EE application server hosting LiveCycle Data Services. That is, when you
create the project, select J2EE as the Application Server type and LiveCycle Data Services
as the application server. After you create the project, all of the client libraries required to
interact with the J2EE application server are added to your project’s class path.
To create a client project by using Flash Builder 4, perform the following steps:
1. Start Flash Builder 4 by clicking Start, All Programs, Adobe Flash Builder 4.
2. Create a new project.
3. In the Project Name box, specify a name for your project.
4. Under Application Type, select Web.
5. Specify version 4.0 for the Flex SDK version.
6. In the Application Server list, select J2EE.
7. Select the Use Remote Access Service check box.
8. Select LiveCycle Data Services check box.
Creating Live Stock Quote Sample Applications using the Message service
9. In the Root folder box, specify the root folder value. For example, specify
C:\lcds\tomcat\webapps\stockDashboard.
10. In the Root URL box, specify the root URL folder value. For example, specify
http://localhost:8400/stockDashboard/.
11. In the Content root box, specify the Context root value. For example, specify
/stockDashboard.
12. Accept the Output folder default value.
13. Click Finish.
Develop the client application logic
After you create a project for the client application, add the required files to the project. The
stock quote application consists of the following files:

financeDashboard.mxml - represents the main application file. This file defines the
Consumer objects that subscribe to the feedStock destination.

historyChart.mxml - displays the finance data within an AreaChart control.

liveChart.mxml - displays the finance data within a LineChart control.

DateRange.as - represents a date range that is used to set slider controls.

Stock.as - represents the client implementation of the Stock server-side Java class.

StockInTime.as - represents the client implementation of the StockInTime serverside Java class.
The following illustration shows the Flash Builder project that creates the stock quote
application.
Creating Live Stock Quote Sample Applications using the Message service
Note: The SWC files are automatically added to the project’s class path when you create a
new project as described in the Create a Flash Builder project topic.
Create the financeDashboard file
The financeDashboard.mxml file is the main application file for the stock quote application.
This client application displays message information (stock information) within a DataGrid
control. This control consists of the following columns:

Name - displays the name of the company

Symbol - displays the stock symbol

Last - displays the stock’s last price

Change - displays the change to the stock price

Open - displays the stock’s opening price

Day High - displays the stock’s high price

Day Low - displays the stocks low price
Creating Live Stock Quote Sample Applications using the Message service
The DataGrid control, named stockGrid, is populated by an ArrayCollection instance
named gridData. This object is a public data member that belongs to the
financeDashboard.mxml file. Because this object is used to populate the data grid control, it
is declared as Bindable, as shown in the following example.
[Bindable]
public var gridData:ArrayCollection = new ArrayCollection();
Setting up a multi topic consumer
The client application receives messages from different subtopics that are located in the
stockFeed destination. As a result, a MultiTopicConsumer object named consumer is
defined. The following code example shows the MultiTopicConsumer object being created.
<mx:MultiTopicConsumer id="consumer" destination="stockFeed"
message="handleMessage(event)" />
This object type enables you to register subscriptions from a list of subtopics within a
destination. A MultiTopicConsumer dispatches a MessageEvent for each message it
receives. That is, each time a message is received, an event is dispatched to
handleMessage as defined by the MultiTopicConsumer object’s message property.
Adding new symbols by using a RemoteObject
A RemoteObject instance named roStockModifier is defined that interacts with the
setQuotes destination, as shown in the following code example.
<s:RemoteObject id="roStockModifier" destination="setQuotes"
result="resultAddStockQuote(event)"/>
The roStockModifier object is responsible for adding symbols to the thread running on the
server. No messages are returned back to the client application until a symbol is set by
calling this method. Symbols are added to the thread running on the server as a result of a
user entering the name of the symbol into the TextInput control named inputSymbol. When
the user clicks the Add Symbol button, a call to the addSymbol method is made.
The following ActionScript code represents the addSymbol method.
protected function addSymbol(event:MouseEvent):void
{
roStockModifier.addStockQuote(inputSymbol.text);
consumer.addSubscription(inputSymbol.text);
cursorManager.setBusyCursor();
}
Creating Live Stock Quote Sample Applications using the Message service
The roStockModifier remote object calls the method named addStockQuote. This method
is defined in the RealTimeHttpDownloader Java server-side class. This method accepts a
string argument that represents the symbol to add to the server thread. (See Understanding
the RealTimeHttpDownloader class.)
Also notice that the MultiTopicConsumer object’s addSubscription method is called. This
method results in the client application monitoring the subtopic within the destination. The
name of the subtopic corresponds to the symbol value. For example, assume that a user
enters ADBE into the inputSymbol text input control and clicks the Add Symbol button. This
user action results in the followng two actions:

The server thread creates messages that contain financial information for Adobe. The
server thread sends the messages to the ADBE subtopic within the stockFeed
destination.

The client application is set to monitor the ADBE subtopic within the stockFeed
destination for new messages.
Handling incoming messages
Now that a symbol is added to the server thread, the server starts creating messages and
sending them to the stockFeed destination. When the client application detects new
messages that are sent to the stockFeed destination, the handleMessage method is called.
Messages that are retrieved from the stockFeed destination are Stock instances. A Stock
instance is defined on the client as instances of the Stock class.
The following ActionScript code represents the handleMessage method.
private function handleMessage(event:MessageEvent):void
{
var newStock:Stock = event.message.body as Stock;
var index:int = findStock(newStock.symbol);
if (index >= 0) { //Stock already exists
var targetStock:Stock = gridData.getItemAt(index) as Stock;
if (targetStock.tradeTime.minutes < newStock.tradeTime.minutes) {
targetStock.daily.addItem({Time:newStock.tradeTime, Last:newStock.last});
}
targetStock.last = newStock.last;
targetStock.change = newStock.change;
Creating Live Stock Quote Sample Applications using the Message service
targetStock.open = newStock.open;
targetStock.high = newStock.high;
targetStock.low = newStock.low;
targetStock.tradeTime = newStock.tradeTime;
else {
//Stock is new
newStock.daily.addItem({Time:newStock.tradeTime, Last:newStock.last});
gridData.addItem(newStock);
}
gridData.refresh();
}
In the handleMessage method, a new Stock instance named newStock is created by getting
the value of event.message.body. This value is cast to a Stock instance. The findStock
method is called that returns the index value of the stock with the indicated symbol in the
gridData array collection. If the symbol exists, then a new Stock instance named
targetStock is created that represents the existing Stock in the array collection. If the
stock symbol does not exist, then the findStock method returns -1.
The properties of targetStock are modified with the properties of new newStock. As a
result, the changes represented by newStock are displayed in the data grid control. If the
stock symbol does not exist in the array collection, then newStock is added to the array
collection. The data grid control displays the new information represented by newStock.
financeDashboard file
The following code represents the entire financeDashboard.mxml file.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
creationComplete="init(event)" height="1058" width="1544"
xmlns:Components="Components.*">
<fx:Style source="financeDashboard.css"/>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
<mx:MultiTopicConsumer id="consumer" destination="stockFeed"
message="handleMessage(event)" />
<s:RemoteObject id="roStockModifier" destination="setQuotes"
result="resultAddStockQuote(event)"/>
Creating Live Stock Quote Sample Applications using the Message service
<s:Sequence id="newpanel" target="{chartPanel}">
<s:Fade id="movein" duration="500" alphaFrom="0" alphaTo="1.0"/>
</s:Sequence>
<fx:Date id="today"/>
</fx:Declarations>
<fx:Script>
<![CDATA[
import
import
import
import
import
import
import
import
import
import
import
import
import
mx.collections.ArrayCollection;
mx.collections.ListCollectionView;
mx.controls.Alert;
mx.events.FlexEvent;
mx.messaging.ChannelSet;
mx.messaging.Consumer;
mx.messaging.MultiTopicConsumer;
mx.messaging.config.ServerConfig;
mx.messaging.events.MessageEvent;
mx.messaging.messages.IMessage;
mx.rpc.events.ResultEvent;
stock.DateRange;
stock.Stock;
/** Data Provider for the dataGrid */
[Bindable] public var gridData:ArrayCollection = new ArrayCollection();
/** Stock currently displayed in the chartPanel */
[Bindable] public var detailStock:Stock;
private function init(event:FlexEvent):void {
chartPanel.visible = false;
consumer.subscribe();
}
/**
* When a message is received form the server with new stock data,
* this funciton will either add it to the data provider (if new)
* of it will update the value in the data provider (if it is
* already there)
*/
private function handleMessage(event:MessageEvent):void
{
var newStock:Stock = event.message.body as Stock;
var index:int = findStock(newStock.symbol);
Creating Live Stock Quote Sample Applications using the Message service
if (index >= 0) { //Stock already exists
var targetStock:Stock = gridData.getItemAt(index) as Stock;
//Only add the data for the live chart every minute
if (targetStock.tradeTime.minutes < newStock.tradeTime.minutes) {
targetStock.daily.addItem({Time:newStock.tradeTime, Last:newStock.last});
}
//Set the new values of the received stock
targetStock.last = newStock.last;
targetStock.change = newStock.change;
targetStock.open = newStock.open;
targetStock.high = newStock.high;
targetStock.low = newStock.low;
targetStock.tradeTime = newStock.tradeTime;
} else { //Stock is new
//Add stock to the datProvider
newStock.daily.addItem({Time:newStock.tradeTime, Last:newStock.last});
gridData.addItem(newStock);
}
gridData.refresh();
}
/** Returns the index of the stock with the indicated symbol
* in the gridData array collection. If it is not in this
* array collection, the value -1 is returned
*
* @param symbol: The symbol of the stock
* @return Index of this stock in stockValues. -1 if not found
*/
private function findStock(symbol:String):int {
for (var i:int = 0; i < gridData.length; i++) {
if (gridData.getItemAt(i).symbol == symbol) {
return i;
}
}
return -1;
}
/**
* Remote method call to add subscribe to a new symbol. The server will
* return intial data that will be added to the dataGrid, or will return
* null to indicate this symbol is not supported.
Creating Live Stock Quote Sample Applications using the Message service
*
* The remote object method call is handled in the function
* "resultAddStockQuote"
*/
protected function addSymbol(event:MouseEvent):void
{
roStockModifier.addStockQuote(inputSymbol.text);
consumer.addSubscription(inputSymbol.text);
cursorManager.setBusyCursor();
}
/**
* Handles data returned by the server after attemtping to add a new
* stock symbol. If null is received than the stock symbol is not
* available. If the symbol is already in the data provider than it
* is ignored and the proper message is displayed. If the symbol was
* valid than it is added to the dataprovider.
*/
private function resultAddStockQuote(event:ResultEvent):void {
if (event.result != null) {
var newStock:Stock = event.result as Stock;
if (findStock(newStock.symbol) < 0) {
newStock.daily.addItem({Time:newStock.tradeTime, Last:newStock.last});
gridData.addItem(newStock);
gridData.refresh();
} else {
Alert.show("This stock symbol has already been added!", "Symbol already
exists",4,this);
}
} else {
Alert.show("This stock symbol is not available.", "Wrong Stock
Symbol",4,this);
}
cursorManager.removeBusyCursor();
}
/**
* This mehtod simply unsubscribes the user from the given
* stock symbol and removes the entry form the data provider.
* This does not make any changes on the server side. As an
* exercise, the reader could extend this application to
* dynamically change the queried symbols to remove symbol
* that no client is currently viewing.
*/
protected function removeSymbol(event:MouseEvent):void {
consumer.removeSubscription(stockGrid.selectedItem.symbol);
Creating Live Stock Quote Sample Applications using the Message service
var index:int = findStock(stockGrid.selectedItem.symbol);
gridData.removeItemAt(index);
chartPanel.visible = false;
}
/**
* Displays the charPanel containing the detailed information
* of the selected stock.
*/
protected function displayDetails(event:Event):void {
removeBtn.enabled = true;
chartPanel.visible = true;
newpanel.play();
detailStock = stockGrid.selectedItem as Stock;
historyChart.getStockHistory(new DateRange());
}
]]>
</fx:Script>
<!-- DataGrid Panel -->
<s:Panel x="6" y="10" width="901" height="249">
<mx:DataGrid id="stockGrid" x="9" y="10" width="684" height="196"
fontSize="10"
dataProvider="{gridData}" itemClick="displayDetails(event)">
<mx:columns>
<mx:DataGridColumn dataField="name" headerText="Name"/>
<mx:DataGridColumn dataField="symbol" headerText="Symbol"/>
<mx:DataGridColumn dataField="last" headerText="Last"/>
<mx:DataGridColumn dataField="change" headerText="Change"/>
<mx:DataGridColumn dataField="open" headerText="Open"/>
<mx:DataGridColumn dataField="high" headerText="Day High"/>
<mx:DataGridColumn dataField="low" headerText="Day Low"/>
</mx:columns>
</mx:DataGrid>
<s:Label id="asd" x="11" y="-22" text="Financial Dashboard"
fontFamily="Verdana" fontSize="20" fontWeight="normal" fontStyle="normal"
textDecoration="none"/>
<s:Button x="701" y="52" label="Add Symbol" height="20" width="109"
click="addSymbol(event)"/>
<s:TextInput x="818" y="52" width="76" id="inputSymbol"/>
<s:Button id="removeBtn" x="701" y="81" label="Remove Symbol" enabled="false"
width="110" click="removeSymbol(event)"/>
</s:Panel>
Creating Live Stock Quote Sample Applications using the Message service
<!-- Chart Panel -->
<s:Panel id="chartPanel" x="6" y="267" width="901" height="602">
<s:Label id="dstockSymbol" text="{detailStock.symbol}" x="12" y="-21"
fontFamily="Verdana" fontSize="20"/>
<s:Label id="dstockLast" text="{String(detailStock.last)} (
{String(detailStock.change)} )" x="118" y="-21" fontFamily="Verdana"
fontSize="20"/>
<Components:liveChart id="liveChart"
x="15" y="10"
stockData="{detailStock.daily}"
low="{detailStock.low}"
high="{detailStock.high}"
symbol="{detailStock.symbol}"/>
<Components:historyChart id="historyChart"
x="13" y="288"
symbol="{detailStock.symbol}"/>
</s:Panel>
</s:Application>
Create the historyChart file
The historyChart.mxml file displays the finance data within an AreaChart control. The name
of the AreaChart instance is stockHistoryChart, as shown in the following code snippet.
<!-- stock history chart -->
<mx:AreaChart id="stockHistoryChart"
x="0" y="0"
width="883"
height="223"
dataProvider="{stockHistory}"
showDataTips="true">
<!-- seriesFilters="[]"-->
<!-- horizontal axis -->
<mx:horizontalAxis>
<mx:DateTimeAxis id="dtAxis"
dataUnits="days"
displayLocalTime="true" />
</mx:horizontalAxis>
<!-- vertical axis -->
<mx:verticalAxis>
<mx:LinearAxis id="lAxis"/> <!--minimum="{low}" maximum="{high}"-->
</mx:verticalAxis>
Creating Live Stock Quote Sample Applications using the Message service
<!-- series -->
<mx:series>
<mx:AreaSeries displayName="{symbol}" xField="date" yField="close"
alpha="0.8"/>
</mx:series>
</mx:AreaChart>
As defined in this tag, the data provider for the AreaChart control is an ArrayCollection
instance named stockHistory. The stockHistory instance is a public data member, as
shown in the following code example.
[Bindable]
public var stockHistory:ArrayCollection = new ArrayCollection;
The historyChart.mxml file defines a RemoteObject named roStockHistory. The
destination for this remote object is getStockHistory. The result handler is a method
named resultStockHistory, as shown in the following example.
<s:RemoteObject id="roStockHistory" destination="getStockHistory"
result="resultStockHistory(event)"/>
The roStockHistory instance is used within a method named getStockHistory. The
roStockHistory instance calls the getHistoricalQuotes method, as shown in the
following code example.
public function getStockHistory(range:DateRange):void {
showLoading();
roStockHistory.getHistoricalQuotes(symbol, range.getFromDate(),
range.getToDate());
}
The getHistoricalQuotes method is defined in the HistoryHttpDownloader Java class.
(See Understanding the HistoryHttpDownloader class.)
This method accepts three parameter values:

A string value that specifies the stock symbol for which historical data is retrieved.

A date value that specifies the start date.

A date value that specifies the end date.
The resultStockHistory method is called when the getHistoricalQuotes method returns
data. The getHistoricalQuotes method returns an ArrayCollection where each element
Creating Live Stock Quote Sample Applications using the Message service
is a StockInTime instance. The following ActionScript code example represents the
resultStockHistory method.
private function resultStockHistory(event:ResultEvent):void {
stockHistory = event.result as ArrayCollection;
timeSlider.values = [currentDateRange.getFromDate().getTime(),
currentDateRange.getToDate().getTime()];
timeSlider.minimum = stockHistory.getItemAt(stockHistory.length1).date.getTime(); //get date of first available stock quote
timeSlider.maximum = currentDateRange.getToDate().getTime();
dtAxis.minimum = new Date(timeSlider.minimum);
dtAxis.maximum = new Date(timeSlider.maximum);
sortArrayCollection(stockHistory, "close");
var axisBounds:Array = getMinMax();
lAxis.maximum = axisBounds[1];
lAxis.minimum = axisBounds[0];
stockHistory.refresh();
removeLoading();
}
A stockHistory instance is created by getting event.result and casting the value to
ArrayCollection. The stockHistory ArrayCollection is used to populate the AreaChart
control.
The following code represents the entire historyChart.mxml file. This file is placed within a
package named Components.
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="883" height="281">
<fx:Declarations>
<s:RemoteObject id="roStockHistory" destination="getStockHistory"
result="resultStockHistory(event)"/>
<s:BlurFilter id="blurFilter" quality="4"/>
</fx:Declarations>
<fx:Script>
<![CDATA[
Creating Live Stock Quote Sample Applications using the Message service
import
import
import
import
import
import
mx.collections.ArrayCollection;
mx.collections.Sort;
mx.collections.SortField;
mx.rpc.events.ResultEvent;
stock.DateRange;
stock.StockInTime;
/** dataProvider for the chart */
[Bindable] public var stockHistory:ArrayCollection = new ArrayCollection;
/** Symbol of the stock currently displayed */
[Bindable] public var symbol:String;
/** Current data range selected */
Bindable] public var currentDateRange:DateRange = new DateRange();
/** Represents one day in seconds. Used to set the steps of the slider
control */
[Bindable] public var dailyStep:uint = 1000*60*60*24;
/**
* Remote prodecure call to obtain historical stock data. Handled in the
* "resultStockHistory" function
*/
public function getStockHistory(range:DateRange):void {
showLoading();
roStockHistory.getHistoricalQuotes(symbol, range.getFromDate(),
range.getToDate());
}
/** Sets the data provider of the chart and initializes the slider */
private function resultStockHistory(event:ResultEvent):void {
stockHistory = event.result as ArrayCollection;
timeSlider.values = [currentDateRange.getFromDate().getTime(),
currentDateRange.getToDate().getTime()]; //set bounds
timeSlider.minimum = stockHistory.getItemAt(stockHistory.length1).date.getTime(); //get date of first available stock quote
timeSlider.maximum = currentDateRange.getToDate().getTime();
dtAxis.minimum = new Date(timeSlider.minimum);
dtAxis.maximum = new Date(timeSlider.maximum);
sortArrayCollection(stockHistory, "close"); //sort for faster axis resizing
var axisBounds:Array = getMinMax(); //quicker due to sort
lAxis.maximum = axisBounds[1];
lAxis.minimum = axisBounds[0];
stockHistory.refresh();
Creating Live Stock Quote Sample Applications using the Message service
removeLoading();
}
/** Sorts given arrayCollection by the given field in increasing numerical
order*/
private function sortArrayCollection(aColl:ArrayCollection,
field:String):ArrayCollection {
var sField:SortField = new SortField();
sField.name = field;
sField.numeric = true;
var numSort:Sort = new Sort();
numSort.fields = [sField];
aColl.sort = numSort;
aColl.refresh();
return aColl;
}
/** Change slider bounds and displayed information based on the toggle button
pressed */
private function toggleButtonClick(event:MouseEvent):void {
toggleButtons(event.target as ToggleButton);
switch(event.target.id) {
case "tb1M": {
getStockHistory(currentDateRange.set1M());
break;
case "tb3M": {
getStockHistory(currentDateRange.set3M());
break;
}
case "tb6M": {
getStockHistory(currentDateRange.set6M());
break;
}
case "tbYTD": {
getStockHistory(currentDateRange.setYTD());
break;
}
case "tb1Y": {
getStockHistory(currentDateRange.set1Y());
break;
}
Creating Live Stock Quote Sample Applications using the Message service
case "tb5Y": {
getStockHistory(currentDateRange.set5Y());
break;
}
case "tbAll": {
getStockHistory(currentDateRange.setAll());
break;
}
}
}
/** Intialize slider thumb positions */
private function timeSlider_creationComplete(event:Event):void {
timeSlider.values=[0,(new Date()).getTime()];
}
/** Change chart according to slider values */
private function sliderRangeChange(event:Event):void
{
if (event.target.values[0] != dtAxis.minimum.getTime() ||
event.target.values[1] != dtAxis.maximum.getTime()) {
sortArrayCollection(stockHistory, "close"); //Sort array by closing value
var axisBounds:Array = getMinMax(); //This step is quicker due to the sort
lAxis.maximum = axisBounds[1];
lAxis.minimum = axisBounds[0];
dtAxis.minimum = new Date(event.currentTarget.values[0]);
dtAxis.maximum = new Date(event.currentTarget.values[1]);
}
}
/** Ensures only the last clicked button is selected */
private function toggleButtons(tButton:ToggleButton):void {
tb1D.selected = false;
tb5D.selected = false;
tb1M.selected = false;
tb3M.selected = false;
tb6M.selected = false;
tbYTD.selected = false;
tb1Y.selected = false;
tb5Y.selected = false;
tbAll.selected = false;
tButton.selected = true;
}
Creating Live Stock Quote Sample Applications using the Message service
/**
* Get the maximum and minimum "close" values in the data provider array.
* This is used to dynamically change the
* axis as the slider values are changed .
*/
private function getMinMax():Array {
var highestClose:int = 0;
var lowestClose:int = 0;
for (var i:int = 0; i < stockHistory.length; i++) {
var point1:StockInTime = stockHistory.getItemAt(i) as StockInTime;
if (point1.date.getTime() > timeSlider.values[0] && point1.date.getTime() <
timeSlider.values[1]) {
lowestClose = point1.close;
break;
}
}
for (var j:int = stockHistory.length-1; j >= 0; j--) {
var point2:StockInTime = stockHistory.getItemAt(j) as StockInTime;
if (point2.date.getTime() > timeSlider.values[0] && point2.date.getTime() <
timeSlider.values[1]) {
highestClose = point2.close;
break;
}
}
return new Array(lowestClose, highestClose);
}
private function showLoading():void {
stockHistoryChart.filters = [blurFilter];
loading.visible=true;
}
private function removeLoading():void {
stockHistoryChart.filters = new Array();
loading.visible=false;
}
]]>
/fx:Script>
<!-- stock history chart -->
<mx:AreaChart id="stockHistoryChart"
Creating Live Stock Quote Sample Applications using the Message service
x="0" y="0"
width="883"
height="223"
dataProvider="{stockHistory}"
showDataTips="true">
<!-- seriesFilters="[]"-->
<!-- horizontal axis -->
<mx:horizontalAxis>
<mx:DateTimeAxis id="dtAxis"
dataUnits="days"
displayLocalTime="true" />
</mx:horizontalAxis>
<!-- vertical axis -->
<mx:verticalAxis>
<mx:LinearAxis id="lAxis"/> <!--minimum="{low}" maximum="{high}"-->
</mx:verticalAxis>
<!-- series -->
<mx:series>
<mx:AreaSeries displayName="{symbol}" xField="date" yField="close"
alpha="0.8"/>
</mx:series>
</mx:AreaChart>
<!-- ToggleButton bar -->
<s:HGroup id="toggleButtonGroup" x="224" y="251" width="436" height="24"
paddingLeft="0" paddingRight="0" paddingTop="0" paddingBottom="0"
textAlign="center" verticalAlign="middle">
<s:ToggleButton id="tb1D" label="1D" width="43" fontFamily="Verdana"
fontSize="10" enabled="false"/>
<s:ToggleButton id="tb5D" label="5D" width="43" fontFamily="Verdana"
fontSize="10" enabled="false"/>
<s:ToggleButton id="tb1M" label="1M" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
<s:ToggleButton id="tb3M" label="3M" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
<s:ToggleButton id="tb6M" label="6M" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
<s:ToggleButton id="tbYTD" label="YTD" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
<s:ToggleButton id="tb1Y" label="1Y" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
<s:ToggleButton id="tb5Y" label="5Y" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
Creating Live Stock Quote Sample Applications using the Message service
<s:ToggleButton id="tbAll" label="All" width="43" fontFamily="Verdana"
fontSize="10" click="toggleButtonClick(event)"/>
</s:HGroup>
<mx:HSlider id="timeSlider"
x="28" y="227"
width="827"
thumbCount="2"
liveDragging="true"
showDataTip="false"
maximum="{currentDateRange.getToDate().getTime()}"
minimum="{currentDateRange.getFromDate().getTime()}"
snapInterval="{dailyStep}"
change = "sliderRangeChange(event)"
creationComplete = "timeSlider_creationComplete(event)" />
<s:Label id="loading"
text="Loading..."
visible="false"
fontSize="20"
color="#FFFFFF"
y="{281/2 - loading.height/2}"
x="{883/2 - loading.width/2}" />
</s:Group>
Create the LiveChart file
The liveChart.mxml displays the data within a LineChart control. The column chart control is
defined by using a mx:LineChart MXML tag. The data source is an ArrayCollection named
stockData, as shown in the following code snippet.
<!-- Line Chart -->
<mx:LineChart id="linechart1"
x="-1" y="0"
height="227"
width="400"
dataProvider="{stockData}"
showDataTips="true"
seriesFilters="[]">
<!-- horizontal axis -->
<mx:horizontalAxis>
<mx:DateTimeAxis id="dtAxis"
maximum="{maxDate}"
Creating Live Stock Quote Sample Applications using the Message service
minimum="{minDate}"
dataUnits="seconds"
displayLocalTime="true" />
</mx:horizontalAxis>
<!-- vertical axis -->
<mx:verticalAxis>
<mx:LinearAxis minimum="{low}" maximum="{high}"/>
</mx:verticalAxis>
<!-- series -->
<mx:series>
<mx:LineSeries xField="Time" yField="Last"
displayName="{symbol}">
<mx:lineStroke>
<mx:SolidColorStroke weight="1"/>
</mx:lineStroke>
</mx:LineSeries>
</mx:series>
</mx:LineChart>
The stockData array created in the ActionScript section of liveChart.mxml file is a bindable
array, as shown in the following code snippet.
[Bindable]
public var stockData:ArrayCollection;
The following code represents the entire liveChart.mxml file. This file is located in a package
named Components.
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="270">
<fx:Declarations>
<fx:Date id="minDate" hours="9" minutes="0" seconds="0" milliseconds="0"/>
<fx:Date id="maxDate" hours="16" minutes="0" seconds="0" milliseconds="0"/>
</fx:Declarations>
<fx:Script>
<![CDATA[
Creating Live Stock Quote Sample Applications using the Message service
import mx.collections.ArrayCollection;
/** dataProvider for the chart */
[Bindable] public var stockData:ArrayCollection;
/** Day's low. used to set the chart's axis bounds. */
[Bindable] public var low:Number;
/** Day's high. used to set the chart's axis bounds. */
[Bindable] public var high:Number;
/** Two minutes represented in seconds. Used to set the steps of the slider.
*/
[Bindable] public var twoMinStep:uint = 1000*60*2;
/** Symbol of the currently displayed stock. */
[Bindable] public var symbol:String;
/** Change the axis of the chart based on the values of the slider */
private function sliderRangeChange(event:Event):void {
dtAxis.minimum = new Date(event.currentTarget.values[0]);
dtAxis.maximum = new Date(event.currentTarget.values[1]);
}
/** Intialize slider thumb positions */
private function timeSlider_creationComplete(event:Event):void {
timeSlider.values=[maxDate.getTime(),minDate.getTime()];
}
/** Format the dataTip to display the time. */
private function dataTipFormat(ms:Number):String {
return new Date(ms).toTimeString();
}
]]>
</fx:Script>
<!-- Line Chart -->
<mx:LineChart id="linechart1"
x="-1" y="0"
height="227"
width="400"
dataProvider="{stockData}"
showDataTips="true"
seriesFilters="[]">
Creating Live Stock Quote Sample Applications using the Message service
<!-- horizontal axis -->
<mx:horizontalAxis>
<mx:DateTimeAxis id="dtAxis"
maximum="{maxDate}"
minimum="{minDate}"
dataUnits="seconds"
displayLocalTime="true" />
</mx:horizontalAxis>
<!-- vertical axis -->
<mx:verticalAxis>
<mx:LinearAxis minimum="{low}" maximum="{high}"/>
</mx:verticalAxis>
<!-- series -->
<mx:series>
<mx:LineSeries xField="Time" yField="Last"
displayName="{symbol}">
<mx:lineStroke>
<mx:SolidColorStroke weight="1"/>
</mx:lineStroke>
</mx:LineSeries>
</mx:series>
</mx:LineChart>
<!-- hSlider for horizontal axis range -->
<mx:HSlider id="timeSlider"
x="28" y="228"
width="362"
thumbCount="2"
liveDragging="true"
maximum="{maxDate.getTime()}"
minimum="{minDate.getTime()}"
snapInterval="{twoMinStep}"
dataTipFormatFunction = "dataTipFormat"
change = "sliderRangeChange(event)"
creationComplete = "timeSlider_creationComplete(event)" />
<s:Label x="28" y="254" text="Time Range"/>
</s:Group>
Creating Live Stock Quote Sample Applications using the Message service
Create the Stock file
The Stock.as file represents a stock message that is retrieved from the stockFeed
destination. This class contains a RemoteClass tag that references the Java class named
Stock that is located in the stock package. The data members in this class match the data
members that are located in the Stock Java class. For information about the Stock Java
class, see Understanding the Stock class.
The following ActionScript code represents the entire Stock.as file. This class is located in a
package named stock.
package stock
{
import mx.collections.ArrayCollection;
[RemoteClass(alias="stock.Stock")]
[Bindable]
public class Stock
{
public var symbol:String;
public var name:String;
public var low:Number;
public var high:Number;
public var open:Number;
public var last:Number;
public var change:Number = 0;
public var tradeTime:Date;
public var daily:ArrayCollection = new ArrayCollection();
}
}
Create the StockInTime file
The StockInTime.as file represents a message that is retrieved from the setQuotes
destination. This class contains a RemoteClass tag that references the Java class named
StockInTime that is located in the stock package. The data members in this class match
the data members that are located in the StockInTime Java class. For information about the
StockInTime Java class, see Understanding the StockInTime class.
The following ActionScript code represents the entire StockInTime.as file. This class is
located in a package named stock.
package stock
Creating Live Stock Quote Sample Applications using the Message service
{
[RemoteClass(alias="stock.StockInTime")]
[Bindable]
public class StockInTime
{
public var close:Number;
public var date:Date;
}
}
Create the DateRange file
The DateRange.as file represents a date range that is used by the client application. By
default the date range is set from one month ago to the present. The following ActionScript
code represents the entire DateRange.as file. This class is located in a package named
stock.
package stock
{
/**
* Object with two dates representing a range.
* By default the range is set from
* 1 month ago to today.
*/
public class DateRange
{
private static const today:Date = new Date();
private var fromDate:Date;
private var toDate:Date;
public function DateRange() {
set5Y();
}
public
return
}
public
return
}
function getFromDate():Date {
this.fromDate;
function getToDate():Date {
this.toDate;
/** Set range: 1 month ago to today
public function set1M():DateRange {
this.toDate = today;
this.fromDate = substractMonths(1);
*/
Creating Live Stock Quote Sample Applications using the Message service
return this;
}
/** Set range: 3 months ago to today
public function set3M():DateRange {
this.toDate = today;
this.fromDate = substractMonths(3);
return this;
}
*/
/** Set range: 6 months ago to today
public function set6M():DateRange {
this.toDate = today;
this.fromDate = substractMonths(6);
return this;
}
*/
/** Set range: January 1 this year to today
public function setYTD():DateRange {
this.toDate = today;
this.fromDate = new Date();
this.fromDate.month = 0;
return this;
}
*/
/** Set range: 1 year ago to today */
public function set1Y():DateRange {
this.toDate = today;
this.fromDate = substractMonths(12);
return this;
}
/** Set range: 5 years ago to today */
public function set5Y():DateRange {
this.toDate = today;
this.fromDate = substractMonths(5*12);
return this;
}
/** Set range: 1/1/1970 to today */
public function setAll():DateRange {
this.toDate = today;
this.fromDate = new Date(0);
return this;
}
Creating Live Stock Quote Sample Applications using the Message service
/** Set range between indicated dates */
public function setCustom(from:Date, to:Date):DateRange {
this.toDate = to;
this.fromDate = from;
return this;
}
/**
* Substracts a given number of months from a given date. Returns
* Date object with "aNumber" of months less. If no date is specified,
* the current date is used.
*/
private function substractMonths(aNumber:int, aDate:Date = null):Date {
var toReturn:Date;
var months:int;
var years:int;
if (aDate == null) {
toReturn = new Date(today.getTime());
months = today.month;
years = today.fullYear;
} else {
toReturn = new Date(aDate.getTime());
months = aDate.month;
years = aDate.fullYear;
}
months -=aNumber;
while (months < 0) {
years -= 1;
months += 12;
}
toReturn.month = months;
toReturn.fullYear = years;
return toReturn;
}
}
}
Create the JSP that starts the server thread
Before the client application can display financial data, the server thread must be started.
To start the server thread, a JSP is created, whose only purpose is to start the server
Creating Live Stock Quote Sample Applications using the Message service
thread. For information about the server thread, see Understanding the StockQuoteFeed
class.
The JPS page imports the feed.StockQuoteFeed class and calls its start method. The
following code represents the JSP named startfeed.jps that starts the server thread.
<%@page import="feed.StockQuoteFeed"%>
<%
try {
StockQuoteFeed feed = new StockQuoteFeed();
feed.start();
out.println("StockQuoteFeed Started");
} catch (Exception e) {
out.println("A problem occured while starting the feed: "+e.getMessage());
}
%>
This JSP can be deployed the root of your web application. For example, assume that the
name of your web application is named stockDashBoard. In this situation, deploy the JSP to
the following location.
[Install directory]lcds\tomcat\webapps\stockDashboard
where [Install directory] is the directory on which LiveCycle Data Services is installed.
To start the server thread, enter the following URL into a web browser.
http://[server]:[port]/stockDashboard/startfeed.jsp
Stopping the server thread
You can also create a JPS that stops the server thread. The JPS page imports the
feed.StockQuoteFeed class and calls its stop method. The following code represents the
JSP named stopfeed.jps that stops the server thread.
<%@page import="feed.StockQuoteFeed"%>
<%
try {
StockQuoteFeed feed = new StockQuoteFeed();
feed.stop();
out.println("StockQuoteFeed Stopped");
} catch (Exception e) {
out.println("A problem occured while starting the feed: "+e.getMessage());
}
%>
Creating Live Stock Quote Sample Applications using the Message service
This JSP can be deployed to the root of your web application. To stop the server thread,
enter the following URL into a web browser.
http://[server]:[port]/stockDashboard/stopfeed.jsp
After you start the server thread, you can run the client application and enter a stock
symbol and click the Add Symbol button. The data is displayed in the client application.
Click a row of data to view additional details. In time, data is plotted in the Line chart
control.
Creating Live Stock Quote Sample Applications using the Message service