The Trade Monitor has a client that allows dynamic subscriptions to trade summary information for a stock symbol. The data is random buys and sells generated on the server by a timer. The example has GWT & Dojo Cometd integration on the client and Spring Bayeux integration on the server. The GWT part of the client handles display and client interaction, but calls out to external JavaScript to let Dojo Cometd handle Bayeux publish/subscribe on the client. The Spring by Example Web Module is used for it's Bayeux support to configure the Bayeux Trade Monitor Service.
Please see the Spring Bayeux GWT Chat Webapp example for a brief explanation of Comet and Bayeux, and also to see a simpler Bayeux service example.
Note | |
---|---|
Currently this example only runs on Jetty since it uses the Bayeux implementation provided by Jetty. Tomcat also has Comet support and it should be easy to port a Bayeux implementation to it if one doens't already exist. There isn't currently a standard API to suspend and resume HTTP requests, but the Servlet 3.0 Draft Specification is standardizing this. Hopefully it will be finalized relatively soon and most major servlet engines will have a Servlet 3.0 implementation. Jetty already has a pre-release 7.0 version implementing the Servlet 3.0 specification as it currently is. |
This is basically identical to the Spring Bayeux GWT Chat Webapp's
web.xml. The Spring JS ResourceServlet
is defined to serve JavaScript files
like Dojo and the Dojox library. The SpringContinuationCometdServlet
handles
Bayeux publish and subscribe requests and is mapped to '/cometd/*'.
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>monitor</display-name> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/web-application-context.xml </param-value> </context-param> <filter> <filter-name>encoding-filter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding-filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Serves static resource content from .jar files such as spring-faces.jar --> <servlet> <servlet-name>resources</servlet-name> <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet> <servlet-name>cometd</servlet-name> <servlet-class>org.springbyexample.cometd.continuation.SpringContinuationCometdServlet</servlet-class> <init-param> <param-name>asyncDeliver</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>trade-monitor</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <!-- Map all /resources requests to the Resource Servlet for handling --> <servlet-mapping> <servlet-name>resources</servlet-name> <url-pattern>/resources/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>cometd</servlet-name> <url-pattern>/cometd/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>trade-monitor</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
The context:component-scan registers the bayeux server TradeMonitorService
and
the bayeux bean configures the Bayeux
instance used by the Bayeux services and servlet.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.springbyexample.web.cometd" /> <bean id="bayeux" class="org.springbyexample.cometd.continuation.SpringContinuationBayeux" p:timeout="300000" p:interval="0" p:maxInterval="10000" p:multiFrameInterval="2000" p:logLevel="0" p:directDeliver="true"> </bean> </beans>
The Trade Monitor Bayeux Service is just a test class that randomly generates different buys and sells for any registered symbols, keeps track of the summary information for each symbol, and publishes the summary information to anyone subscribed to the symbols channel.
The TradeMonitorService
is annotated with @Component
so it is
registered as a bean by the context:component-scan in the bayeux-context.xml.
It's constructor is marked with @Autowired
the Bayeux
created in the XML configuration file
will be injected into the constructor. There is also an initialization method with @PostConstruct
on it
that means it should be called during Spring's initialization lifecycle. The init()
adds the
starting prices and ranges for symbols. It also initializes the timer to broadcast the server list used for
the menu and also to publish the randomly generated trades.
Example 1. TradeMonitorService
Initialization
Excerpt from src/main/java/org/springbyexample/web/cometd/monitor/TradeMonitorService.java
@Component public class TradeMonitorService extends BayeuxService { ... /** * Constructor */ @Autowired public TradeMonitorService(Bayeux bayeux) { super(bayeux, "monitor"); } /** * Init sending monitor test messages. */ @PostConstruct protected void init() { Bayeux bayeux = getBayeux(); final Channel serversChannel = bayeux.getChannel("/monitor/servers", true); final Client client = getClient(); addChannel(NYSE_GATEWAY, ATT_SYMBOL, "AT&T", 24, 20, 25); addChannel(NYSE_GATEWAY, GM_SYMBOL, "General Motors", 5, 5, 9); addChannel(NYSE_GATEWAY, IBM_SYMBOL, IBM_SYMBOL, 80, 80, 100); addChannel(NYSE_GATEWAY, MS_SYMBOL, "Morgan Stanley", 17, 10, 26); addChannel(NYSE_GATEWAY, NYX_SYMBOL, "NYSE Euronext", 27, 23, 32); addChannel(NYSE_GATEWAY, PG_SYMBOL, "Proctor & Gamble", 63, 57, 64); addChannel(NASDAQ_GATEWAY, JAVA_SYMBOL, "Sun Microsystems, Inc.", 4, 4, 12); addChannel(NASDAQ_GATEWAY, ORCL_SYMBOL, "Oracle Corporation", 17, 16, 23); addChannel(NASDAQ_GATEWAY, GOOG_SYMBOL, "Google Inc.", 331, 330, 510); addChannel(NASDAQ_GATEWAY, MSFT_SYMBOL, "Microsoft Corporation", 21, 21, 28); addChannel(NASDAQ_GATEWAY, YHOO_SYMBOL, "Yahoo! Inc.", 12, 12, 20); // start now, publish every 10 seconds timer.schedule(new TimerTask() { @Override public void run() { publishServerList(serversChannel, client); } }, 0, 5000); // start now, publish every second timer.schedule(new TimerTask() { @Override public void run() { publishTrades(client); } }, 0, 200); } ... }
Trades are randomly generated, occasionally skipping some symbols, with a volume of 500-1000 and a price
movement of up to a dollar. The trade also randomly generates whether or not it is a buy or a sell.
The SummaryInfo
will change the buy to a sell or a sell to a buy if the symbol
has passed it's range set during initialization.
Example 2. TradeMonitorService
Publish Trades
Excerpt from src/main/java/org/springbyexample/web/cometd/monitor/TradeMonitorService.java
protected void publishTrades(Client client) { Random random = new Random(); for (Channel channel : hTrades.keySet()) { SummaryInfo summary = hTrades.get(channel); // randomly skip a symbols trade if it's not IBM, P&G, or Google if (IBM_SYMBOL.equals(summary.getSymbol()) || PG_SYMBOL.equals(summary.getSymbol()) || GOOG_SYMBOL.equals(summary.getSymbol()) || random.nextInt(5) < 4) { int volume = 500 + random.nextInt(500); double price = random.nextDouble(); boolean buy = random.nextBoolean(); summary.increment(volume, price, buy); channel.publish(client, summary.getTradeSummaryMap(), null); } } }