Spring Bayeux GWT Trade Monitor Webapp

David Winterfeldt

2008


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]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.

1. Server Configuration

Web Configuration

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/*'.

/WEB-INF/web.xml
                    
<?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>
                    
                

Spring Configuration

The context:component-scan registers the bayeux server TradeMonitorService and the bayeux bean configures the Bayeux instance used by the Bayeux services and servlet.

/WEB-INF/bayeux-context.xml
                    
<?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>
                    
                

Code Example

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);  1
            }
        }, 0, 5000); 
        
        // start now, publish every second
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                publishTrades(client);  2
            }
        }, 0, 200);
    }

    ...

}
                    
                
1 The server list is published every 5 seconds.
2 The different trades are generated every 200 milliseconds.

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);  1
            
            channel.publish(client, summary.getTradeSummaryMap(), null);  2
        }
    }
}
                    
                
1 Increments the summary information for a symbol by passing in this trades volume, movement in price, and whether or not it is a buy or a sell.
2 Publishes the summary info to the symbol's channel.