Flex JMS Chat on Tomcat 5.5


This article explains how to setup JMS chat sample, bundled in Flex Data Services 2.0 beta2, on Tomcat 5.5.16.

The system structure will be as the following figure.




Prepare applications


To use JMS chat sample, it of course requires a JMS provider, but since Tomcat doesn't have a built-in JMS provider, I will introduce ActiveMQ which is one of the famous open source JMS providers.

If you still don't have the following three applications, please download them.

Copy Flex Data Services files and ActiveMQ files to Tomcat


You can find a samples.war in C:\fds directory. Unpack the war file (jar xvf samples.war), then a samples directory will be available. Simply copy the samples directory to C:\apache-tomcat-5.5.16\webapps directory.

Find activemq-4.0-M4.jar in C:\activemq-4.0-M4 directory. The jar file contains all required libraries to use ActiveMQ. Simply copy the activemq-4.0-M4.jar file into C:\apache-tomcat-5.5.16\shared\lib directory.

Setup ActiveMQ


Since Flex Data Services' JMS client (jms adapter) accesses the JMS provider through JNDI, we have to place the following jndi.properties file on the samples webapp's classpath to enable JNDI capability.

Save the following jndi.properties file in the samples webapp's WEB-INF/classes directory (C:\apache-tomcat-5.5.16\webapps\samples\WEB-INF\classes).
Click here to download jndi.properties.
# file name : jndi.properties

java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory

# use the following property to configure the default connector
java.naming.provider.url = tcp://localhost:61616

# use the following property to specify a class path resource or URL
# used to configure an embedded broker using the XML configuration file
#brokerXmlConfig = file:src/conf/sample-conf/default.xml

# use the following property to specify the JNDI name the connection factory
# should appear as. 
connectionFactoryNames = connectionFactory, queueConnectionFactory, topicConnectionFactory

# register some queues in JNDI using the form
# queue.[jndiName] = [physicalName]
# queue.MyQueue = example.MyQueue


# register some topics in JNDI using the form
# topic.[jndiName] = [physicalName]
# topic.MyTopic = example.MyTopic
topic.FlexChatTopic = chat.Topic


There are several ways to run a broker of ActiveMQ such as its own process, within other JVMs. The following instruction explains how to run the broker inside Tomcat's JVM. To run the broker when Tomcat boots up the samples webapp, ServletContextListener is really useful.

I wrote the following ActiveMQBrokerStartListener.java.

You can download the source code and its class file.
/* file name : ActiveMQBrokerStartListener.java */

package com.silverisland.servlet.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.activemq.broker.BrokerService;

public class ActiveMQBrokerStartListener implements ServletContextListener {

    BrokerService broker = new BrokerService();
	
    public void contextInitialized(ServletContextEvent arg0) {
        try{
            broker.addConnector("tcp://localhost:61616?trace=true");
            broker.start();
        }catch(Exception e){
            System.err.println(e.getMessage());
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public void contextDestroyed(ServletContextEvent arg0) {
        try{
            broker.stop();
        }catch(Exception e){
            System.err.println(e.getMessage());
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

}
Please create com/silverisland/servlet/listener directory in the samples webapp's classes directory. (It should be C:\apache-tomcat-5.5.16\webapps\samples\WEB-INF\classes\com\silverisland\servlet\listener)
And place the ActiveMQBrokerStartListener.class file in the directory you just created above.

Finally, we have to add the ActiveMQBrokerStartListener configuration in the deployment descriptor(web.xml) of the samples webapp. (C:\apache-tomcat-5.5.16\webapps\samples\WEB-INF\web.xml)

The listener tag I added is in red and must be put exactly where you see it below. (between filter-mapping and servlet elements)
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Flex Enterprise Services Samples</display-name>
    <description>Flex Enterprise Services Application with Samples</description>

    ...

    <filter-mapping>
      <filter-name>FlexDetectionFilter</filter-name>
      <servlet-name>FlexMxmlServlet</servlet-name>
    </filter-mapping>
  
    <listener>
      <listener-class>com.silverisland.servlet.listener.ActiveMQBrokerStartListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>FileServlet</servlet-name>
        <description>This servlet may override a corresponding FileServlet 
             in SERVER-INF/default-web.xml.If removed, 
             the servlet in default-web.xml will be used.</description>
        <servlet-class>jrun.servlet.file.FileServlet</servlet-class>
        <init-param>
            <param-name>browseDirs</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>

    ...
    
</web-app>

Setup Flex Data Services' JMS client


There is flex-message-service.xml in the flex dir of samples webapp's WEB-INF directory. (C:\apache-tomcat-5.5.16\webapps\samples\WEB-INF\flex\flex-message-service.xml)

In the file, please look for the following destination element having chat-topic-jms as a value of its id attribute.
(WARN: There is also a destination element having chat-topic-amf as an id. BUT NOT THAT ONE !)
    <destination id="chat-topic-jms">

        <properties>

            <server>
                <durable>false</durable>
                <durable-store-manager>flex.messaging.durability.FileStoreManager</durable-store-manager>
            </server>

            <jms>
                <destination-type>Topic</destination-type>
                <message-type>javax.jms.ObjectMessage</message-type>
                <connection-factory>jms/flex/TopicConnectionFactory</connection-factory>
                <destination-jndi-name>jms/topic/flex/simpletopic</destination-jndi-name>
                <destination-name>FlexTopic</destination-name>
                <durable-consumers>false</durable-consumers>
                <delivery-mode>NON_PERSISTENT</delivery-mode>
                <message-priority>DEFAULT_PRIORITY</message-priority>
                <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
                <transacted-sessions>false</transacted-sessions>
            </jms>
        </properties>

        <channels>
            <channel ref="my-rtmp"/>
        </channels>

        <adapter ref="jms"/>

    </destination>
Modify the XML above to the following. (Modifications and Additions are in red.)
    <destination id="chat-topic-jms">

        <properties>

            <server>
                <durable>false</durable>
                <durable-store-manager>flex.messaging.durability.FileStoreManager</durable-store-manager>
            </server>

            <jms>
                <destination-type>Topic</destination-type>
                <message-type>javax.jms.ObjectMessage</message-type>
                <connection-factory>topicConnectionFactory</connection-factory>
                <destination-jndi-name>FlexChatTopic</destination-jndi-name>
                <durable-consumers>false</durable-consumers>
                <delivery-mode>NON_PERSISTENT</delivery-mode>
                <message-priority>DEFAULT_PRIORITY</message-priority>
                <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
                <transacted-sessions>false</transacted-sessions>
                <initial-context-environment>
                    <property>
                        <name>java.naming.factory.initial</name>
                        <value>org.apache.activemq.jndi.ActiveMQInitialContextFactory</value>
                    </property>
                    <property>
                        <name>java.naming.provider.url</name>
                        <value>tcp://localhost:61616</value>
                    </property>
                </initial-context-environment>
             </jms>
        </properties>

        <channels>
            <channel ref="my-rtmp"/>
        </channels>

        <adapter ref="jms"/>

    </destination>
Even though jndi.properties is placed in the samples webapp's classpath, java.naming.factory.initial & java.naming.provider.url properties in jndi.properties are not used when jms adapter instantiates an InitialContext Object. Anyways, because of that, we had to add initial-context-environment element as shown above to force jms adapter to use those properties.

That's it.


Start Tomcat


Finally, start Tomcat, open multiple browsers and access http://localhost:8080/samples/messageservice/chat/chat.mxml

Enjoy !!


Story is not ended yet. :p


The samples directory contains SWING-based Chat client as well. (C:\apache-tomcat-5.5.16\webapps\samples\WEB-INF\src\samples\jms\Chat.java)

Modifications are in red.

package samples.jms;

import java.util.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.*;

public class Chat implements MessageListener, ActionListener {

    private TopicSession _pubSession = null;
    private TopicSession _subSession = null;
    private TopicPublisher _publisher = null;
    private TopicConnection _connection = null;

    private String _providerurl = "tcp://localhost:61616";
    private String _ctxtFactory = "org.apache.activemq.jndi.ActiveMQInitialContextFactory";

    private JTextField tfUser;
    private JTextField tfMessage;
    private JTextArea taChat;

    public static void main(String args[]) {
        new Chat();
    }

    public Chat() {

        try {
            // Obtain JNDI Context
            Properties p = new Properties();
            p.put(Context.PROVIDER_URL, _providerurl);
            p.put(Context.INITIAL_CONTEXT_FACTORY, _ctxtFactory);
            p.put(Context.SECURITY_PRINCIPAL, "admin");
            p.put(Context.SECURITY_CREDENTIALS , "admin");
            Context context = new InitialContext(p);

            // Lookup a JMS connection factory
            TopicConnectionFactory factory = 
                (TopicConnectionFactory) context.lookup("topicConnectionFactory");
                
            // Create a JMS connection
            _connection = factory.createTopicConnection();

            // Create publisher session
            _pubSession = _connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);

            // Create subscriber session
            _subSession = _connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);

            // Lookup a JMS topic
            Topic topic = (Topic) context.lookup("FlexChatTopic");

            // Create a publisher and a subscriber
            _publisher = _pubSession.createPublisher(topic);
            TopicSubscriber subscriber=_subSession.createSubscriber(topic);

            // Set JMS message listener
            subscriber.setMessageListener(this);

            // Start the JMS connection; allows messages to be delivered
            _connection.start();

        } catch (NamingException e) {
            e.printStackTrace();
        } catch (JMSException e) {
            e.printStackTrace();
        }

        // Build user interface
        JFrame frame = new JFrame("JMS Chat");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        taChat = new JTextArea();
        frame.getContentPane().add(taChat, BorderLayout.CENTER);

        Box north = new Box(BoxLayout.X_AXIS);
        north.add(new JLabel("User Name:"));
        tfUser = new JTextField();
        north.add(tfUser);
        frame.getContentPane().add(north, BorderLayout.NORTH);


        Box south = new Box(BoxLayout.X_AXIS);
        south.add(new JLabel("Message:"));
        tfMessage = new JTextField();
        south.add(tfMessage);
        JButton btSend = new JButton("Send");
        btSend.addActionListener(this);
        south.add(btSend);
        frame.getContentPane().add(south, BorderLayout.SOUTH);

        int width = 300;
        int height = 300;
        frame.setSize(width, height);
        frame.setVisible(true);

    }

    public void onMessage(Message obj) {
        try {
            ObjectMessage message = (ObjectMessage) obj;
            String userId = message.getStringProperty("userId");
            String msg = message.getStringProperty("msg");
            taChat.append(userId + ": " + msg + "\n");
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }

    public void actionPerformed(ActionEvent e) {
        try {
            ObjectMessage message = _pubSession.createObjectMessage();
            message.setStringProperty("userId", tfUser.getText());
            message.setStringProperty("msg", tfMessage.getText());
            _publisher.publish(message, Message.DEFAULT_DELIVERY_MODE
                , Message.DEFAULT_PRIORITY, 5 * 60 * 1000);
        } catch (JMSException e1) {
            e1.printStackTrace();
        }

    }

}
When you compile and run this Chat client, don't forget to add activemq-4.0-M4.jar in classpath.
Since JNDI configurations are hardcoded in the source code above, you don't need jndi.properties file.

NOTE: When you run this client, Tomcat of course must be running since the broker of ActiveMQ resides in Tomcat.