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);
}
}
}