Serverless JMS 0.1

Implementation Notes


$Date: 2004/03/14 00:00:45 $
$Revision: 1.7 $






Introduction
Building Instructions
Requirements
Optional Configuration Choices
How to Build
Running Examples
JMS 1.1 Topic Publisher/Subscriber example
Running the example with SLJMS
Running the example with SUNWappserver
JMS 1.1 Queue Sender/Receiver example
Running the example with SLJMS
A Dynamic JMS command line client
Design Notes
What's Next





Introduction


These notes document the first release of the Serverless JMS prototype, a JMS 1.1 provider based on the JGroups toolkit. "Serverless JMS", or SLJMS, is probably a temporary name, but it will be used throughout this document to refer to the provider implementation.

The current release includes support for topics and queues. It does not includes persistence of any kind (persistent delivery or persistent topics), transactional support and many other advanced JMS topics. The implementation is still an early prototype.

The implementation is accompanied by several examples that run out-of-the-box. There are two very simple examples of topic publisher/subscriber and queue sender/receiver; both use the common JMS 1.1 interfaces. They have been tested with SLJMS and also with the JMS provider included with the Sun Microsystems' SUNWappserver 1.4.

A more generic JMS client is also included. It offers a shell-like command line interface that allows a user to initialize and create different JMS components and test different combinations of JMS API calls. Detailed instructions on how to build and run the examples are provided in the "Running Examples" section below.




Building Instructions

Requirements

Optional Configuration Choices

The examples that are included with the SLJMS bundle could run with any other JMS 1.1 compliant provider. The wrapper scripts can be used directly with Sun Microsystems' SUNWappserver 1.4 JMS provider. If you intend to do that, please install SUNWappserver following Sun's instructions and set SUNAPPSERV_HOME environment variable to point to the installation directory. However, setting SUNAPPSERV_HOME is not required for compilation.

By default, the build system uses the library jars provided with the bundle, in the subdirectory lib (jgroups-core-2.2.1.jar, log4j.jar, etc). If you want to use your own classes or jars for compilation and execution, create a file local/config in the project's home and add to it a line similar to this one:

export LOCAL_CLASSPATH=/my-own-JGroups/classes:/my-own-JGroups/conf:/opt/my-own-log4j.jar

How to Build

Untar the bundle in a work directory of your choice. tar extraction will create a sljms subdirectory. Build it by running ./build.sh. For optional targets, consult build.xml.



       gunzip < sljms-0.1.tar.gz | tar xfv -      
       cd sljms
       ./build.sh


The building procedure has been tested on Linux RedHat 7.3. In principle it should work without problems on other UNIX platforms, too. On Windows, use build.bat instead of build.sh.




Running Examples

The files used to configure the JMS runtime are located in etc subdirectory. In principle, the examples should work without any configuration changes, but if you are inclined to tweak configuration parameters, or you need to, here is a list of the configuration files:

jndi.properties - It is a symbolic link made at runtime to one of jndi.properties.jg, jndi.properties.sun or jndi.properties.jboss, depending on the argument you use with your launcher script. It is the standard JNDI property file.

log4j.properties - The log4j configuration file that controls the verbosity of the logging output. If you want a more verbose JGroups runtime, change log4j.category.org.jgroups to INFO or even DEBUG. If you want more SLJMS logging information, change log4j.category.sljms to DEBUG.

sljms.xml - It is the JGroups stack configuration file for the server control channel. If something doesn't work with SLJMS, such as clients not being able to connect to the "server", this is usually the first place you should look. For more information on the channel configuration file, please consult JGroups documentation. If you want to try a different channel configuration, you should place the corresponding xml file somewhere in the example's classpath (the ./etc directory is a good place) and you should modify jndi.properties.jg to list the new file name as value of the property sljms.jndi.connectionFactory.<ConnFactoryJNDIName>. You can find more details about configuring a ConnectionFactory and its associated stack file in JNDI Implementation section.

corba.properties - This file is relevant only if you plan to run the examples with the Sun's SUNWappserver JMS provider.

The first argument of the wrapper scripts must be one of 'jg', 'jboss' or 'sun'. The argument is used by the script to build the correct classpath.

JMS 1.1 Topic Publisher/Subscriber example

This is a very simple JMS 1.1 Pub/Sub example that uses JMS 1.1 common interfaces. One or more clients subscribe to a known topic and wait for messages. Another client publishes a variable number of text messages on the topic and then sends a special end-of-communication message. The number of messages to be sent is specified as a command line parameter. Upon arrival of the last message, the subscribers display statistics and exit. The example can be run with SLJMS or any other JMS provider that supports JMS 1.1 common interfaces. It has been tested with SUNWappserver 1.4 reference implementation.

Running the example with SLJMS

From the home directory of the project, go to bin and start the subscriber:


UNIX:

       cd bin
       ./subscriber jg

Windows:

      cd bin
      subscriber.bat jg


After a while you should see the message "Connection started, waiting for messages ...", which means the subscriber is ready.

Start the producer only when the subscriber is up and running. It can be started on the same machine or on a different machine that has access to the project directory and can reach by multicast the machine with the subscriber.


UNIX:

       cd bin
       ./publisher jg [number_of_messages]

Windows:

      cd bin
      publisher.bat jg [number_of_messages]


If not specified, the default number of messages is 10.

The subscriber displays the number of messages received and time statistics, and then exits. The publisher has to be explicitly killed with Ctrl-C.

Running the example with SUNWappserver

Before running the examples, make sure the server is up and running. Use Sun's administrative interface to create a topic connection factory named "ConnectionFactory" and a topic named "Topic1". A recipe on how to do it is provided with the J2EE tutorial: http://java.sun.com/j2ee/1.4/docs/tutorial/doc/index.html, Chapter 29, The Java Message Service API.

Modify the file etc/corba.properties to contain a valid ORBInitialHost and ORBInitialPort, which are the host running the application server and the port JNDI runtime is listening on (usually 3700).

From the home directory of the project, go to bin and start the subscriber:



       cd bin
       export SUNWAPPSERV_HOME=<your_SUNWappserver_home>
       ./subscriber sun


After a while you should see the message "Connection started, waiting for messages ...".

Start the producer only when the subscriber is up and running. It can be started on the same machine or on a different machine that has access to the project directory and the application server.



       ./publisher sun [number_of_messages]



If not specified, the default number of messages is 10.

The subscriber displays the number of messages received and time statistics, and then exits. The publisher has to be explicitly killed with Ctrl-C.

JMS 1.1 Queue Sender/Receiver example

This is a very simple JMS 1.1 P2P example that uses JMS 1.1 common interfaces. A client creates two queue receivers for a known queue and then waits for messages. Other client sends a variable number of text messages to the queue and stops. The number of messages to be sent is specified on command line. The example can be run with SLJMS or any other JMS provider that supports JMS 1.1 common interfaces. It has been tested with SUNWappserver 1.4 reference implementation.

Running the example with SLJMS

From the home directory of the project, go to bin and start the receiver:


UNIX:

       cd bin
       ./queueReceiver jg

Windows:

      cd bin
      queueReceiver.bat jg


After a while you should see the message "Connection started, waiting for messages ...".

Start the sender only when the receiver is up and running (the queues don't persist anything yet). It can be started on the same machine or on a different machine that has access to the project directory, and can reach by multicast the machine with the receiver.


UNIX:

       cd bin
       ./queueSender jg [number_of_messages]

Windows:

      cd bin
      queueSender.bat jg


If not specified, the default number of messages is 10.

The receiver reports the messages it has received. Both the sender and the receiver have to be explicitly killed with Ctrl-C.

A Dynamic JMS command line client

A third example can be used to interact more dynamically the SLJMS runtime. The launcher script name is interactive. This programs has a shell-type command line interface that accepts commands very similar to JMS 1.1 API calls. The program can be used to dynamically look up ConnectionFactories and Destinations, create a Connection to the server, create one or more Sessions, MessageProducers and MessageConsumers and then send text messages and display received text messages. It can be used to try and test different combinations of JMS API calls.

The program should work with any JMS 1.1 compliant provider, according to the same usage patterns presented in the previous examples.

Due to the limitations of the current version of the prototype, there is only one hardcoded "ConnectionFactory" JNDI name that can be used to lookup a ConnectionFactory, and only two destinations: "Topic1" and "Queue1".

A list with the supported "API calls" can be obtained at any time by typing help.
A dump of the current JMS runtime (the list of active connections, sessions, producers and consumers) can be generated at any time by typing runtime.

The complete documentation for this client is out of the scope of this document. However, relatively self-explanatory screen dumps of two different interactive sessions  (one that configures and uses a topic publisher, and the other one that configures a topic subscriber to receives the messages sent by the publisher), are presented below.

To configure the topic subscriber:


UNIX:

       cd bin
       ./interactive jg

Windows:

      cd bin
      interactive.bat jg



       > lookupConnectionFactory ConnectionFactory
       > ok
       > createConnection
       > ok
       > createSession false, AUTO_ACKNOWLEDGE
       > ok
       > createConsumer 0 Topic1
       > ok
       > setMessageListener 0.0
       > start
       > ok




To configure the topic publisher:


UNIX:

       cd bin
       ./interactive jg

Windows:

      cd bin
      interactive.bat jg



       > lookupConnectionFactory ConnectionFactory
       > ok
       > createConnection
       > ok
       > createSession false, AUTO_ACKNOWLEDGE
       > ok
       > createConsumer 0 Topic1
       > ok
       > setMessageListener 0.0
       > start
       > ok



After sending the text message, the subscriber should display something similar to:

Consumer 0.0: this is the content of the message

The program can be used to mix and match different runtime configurations and compare the behavior of different JMS providers.




Design Notes

A SLJMS server instance is virtually a JGroups group. The client runtime is built around a JChannel that connects to the server's "control group". The main JGroups control channel provides the heavyweight support (sockets, threads) for communication with the "server".

The current implementation uses one channel to both maintain the state of the server and to exchange regular JMS messages. Performance profiling will probably show that only one JChannel per client runtime is not enough, so secondary channels used for message traffic could be added.



A simplified diagram of the SLJMS runtime is presented below:





Fig. 1 The SLJMS runtime architecture.



A SLJMS runtime connects to the server and starts exchange messages in the following sequence:

- Get a JNDI InitialContext.

Context initialContext = new InitialContext();

JNDI Implementation

The prototype is designed to work standalone, so far, so it doesn't rely on the presence of an application server with its associated JNDI namespace. To provide a consistent interface to the client code, the runtime relies on its own InitialContextFactory that  returns GroupContexts. GroupContexts are very simple Context implementations that do not try to look up names externally, but rely on local configuration files. While this solution is good enough for a prototype, the next implementation of the standalone JNDI runtime will possibly try to connect to the server group and lookup names using the group state. The group state (a common set of data that is stored by all members of the group and is kept synchronized) is a good candidate as the source of the JNDI namespace, because even for the current implementation, the group state keeps detailed information about the existing Queues, for example.

JMS relies on JNDI to lookup two types of adminstered objects: ConnectionFactories and Destinations. These two categories of objects are handled differently.

A ConnectionFactory incapsulates the knowledge to connect to a certain provider instance, in this case a server group. Since a group is essentially uniquely identified by its group name and stack configuration, a ConnectionFactory needs exactly these two elements to be able to create connections to that "server".

For the standalone implementation, no centralized JNDI nampespace is available to register the associations, so it has to be locally specified. The current mechanism relies on jndi.properties file and a set of provider-specific properties. To "bind" a a certain ConnectionFactroy in the JNDI namespace, specify a property

sljms.jndi.connectionFactory.<ConnectionFactoryJNDIName>=<stack_config_file>

A JMS client would then be able to lookup <ConnectionFactoryJNDIName> in JNDI and obtain a ConnectionFactory instace that produces Connections based on the stack configuration specified in the is the <stack_config_file><stack_config_file> must be the name of a JGroups stack configuration XML file, available somewhere in the current classpath.

Destinations are similarily handled. To make a Topic/Queue "available" in JNDI, add

sljms.jndi.destination.<TopicJNDIName>=topic
sljms.jndi.destination.<QueueJNDIName>=queue

- Look up the ConnectionFactory.

ConnectionFactory connectionFactory = (ConnectionFactory)initialContext.lookup("ConnectionFactory");

The class that implements ConnectionFactory interface is GroupConnectionFactory. Its main role is to get the JGroups channel configuration from the JNDI runtime and pass it to the Connection instances it creates.

- Create a Connection, one or more Sessions,MessageProducers,MessageConsumers and start sending and receiving messages.

Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
TextMessage message = session.createTextMessage();
message.setText("...");
producer.send(message);

The Connection implementation is the central piece of the SLJMS runtime. The actual class that implements the interface is GroupConnection. GroupConnection controls the thread ("Connection Management Thread") that pulls JGroups Events and Messages from the channel. It makes a first selection based on the message/event semantics, either updating the group state or forwarding the message to the delivery queue.

The messages are pulled out of the delivery queue by the SessionManager. There is only one SessionManager instance per connection and it has two responsibilities: it synchronizes access to the session list, making sure that only one thread operates with the session list at a time, and handles message delivery to and from its sessions. It uses its own thread(s) to extract messages from the queue and distribute them to the topic subscribers / queue receivers, according to their destinations.

The "Connection Management Thread" is always on for the life time of the connection, because it has to asynchronously handle JG events and messages that are essential to group management. A connection can be "stopped" by just stopping the message delivery to the queue (the current implementation just discards the messages arrived when the connection is stopped). During this time, the SessionManager thread blocks on read on an empty queue.

JMS specification mentions that a stopped connection can still be used to send messages. Even if the connection is stopped, a message producer can accept a message for delivery. It delegates the delivery to its session, which in turn delegates it to the SessionManager, which immediately submits the message to the connection. The GroupConnection puts the message on the channel.


What's Next

Add support for:

- persistent delivery (topics and queues) (release 0.2)
- persistent topics (release 0.3)
- transactions (release 0.4)