The Lightstreamer JMS Extender is a new product aimed at porting the JMS interface to the Web and Node.js environments.
For those who don’t know the JMS acronym, it stands for Java Message Service and it is a well-known set of APIs used in Java-based environments to provide publish/subscribe and message queuing paradigms. Not only does the JMS Extender provide that same set of APIs as a JavaScript client library (and other languages in the future), but it also extends the reachability and scalability of a JMS broker (typically a local-area-network component) to the entire Web.
Brief History of the Product
Taking JMS to the web by means of Lightstreamer is not something new. Originally, a sample data adapter source code was provided to show that the adapter pattern used by Lightstreamer could let you easily expose typical back-end services, like JMS, to the web. Anyway, moving it to production meant writing lots of server-side code to map topics and queues to Lightstreamer items.
Inquiries from customers suggested us to push the idea further. So we started developing around that sample and built a new adapter, able to encapsulate most of the JMS APIs within the standard Lightstreamer text protocol. With it you could subscribe and publish to topics from a web browser without writing a single line of server-side code: the adapter translated topics and queues to items for you, dynamically. It was called JMS Gateway, supported all mainstream JMS brokers and was a perfect companion for an existing installation of Lightstreamer Server.
One step further could still be done: integrate the JMS Gateway adapter deeply into the Lightstreamer kernel to obtain better performance, better monitoring and easier deployment. And here it is, the Lightstreamer JMS Extender is exactly this: a standalone server dedicated to taking JMS to the web with the usual reliability, scalability and performance of Lightstreamer.
Using JMS on the Web and With Node.js
Today there a lot of JMS brokers deployed in the back-end of big and small companies. These infrastructures carry important informations and notifications, like stock quotes, order confirmations, flight bookings, etc. Is there an easy way to let these informations reach a web application in real time?
Some of these brokers already provide HTTP-accessible APIs, but while they are designed to withstand huge numbers of messages dispatched each second, they are not designed to withstand a huge number of clients connected at the same time. Moreover, it would be quite risky to expose such key infrastructure components directly on the web.
This is where Lightstreamer JMS Extender plays its role: it lets your web application receive (and send) this data in real time, and puts itself in the middle between the web and your infrastructure. The same thing that Lightstreamer has always done, but this time with JMS APIs on the client side.
This is how you open a topic connection with Lightstreamer JMS Extender:
TopicConnectionFactory.createTopicConnection(
"http://my.server.com/", "brokerName",
"user", "password", {
onConnectionCreated: function(conn) {
// Connection succeeded
},
onConnectionFailed: function(errorCode, errorMessage) {
// Connection failed
},
onLSClient: function(lsClient) {
// Underlying LightstreamerClient available
}
});
You may notice the createTopicConnection call has two more parameters than the JMS API would require: an URL and a name. This is because in a common JMS application most connection parameters are defined outside of the code, while with the JMS Extender these parameters are defined on the JMS Extender itself. Thus, additionally to the user and password of the JMS API, creating a connection requires the URL of the JMS Extender and the name of the JMS connector (more on this later).
Recall also that JavaScript doesn’t have multithreading, so some typically synchronous calls of JMS had to be refactored as asynchronous ones. Here, in particular, we had to refactor the return value of the API call to a callback interface.
Last but not least, note that JMS Extender APIs also offer you an opportunity to use the LightstreamerClient instance of the underlying Lightstreamer JavaScript APIs. You may want to use it to monitor the connection status, for example.
This is how you set up a consumer:
var topicSession= conn.createSession(false, "AUTO_ACK");
var topic= topicSession.createTopic("stocksTopic");
var consumer= topicSession.createConsumer(topic, null);
// Add listener to the message consumer
consumer.setMessageListener({
onMessage: function(message) {
// Message received
}
});
// Start the connection
conn.start();
If you used JMS before, this should look pretty familiar. And finally, this is how you send a message:
var producer = topicSession.createProducer(topic, null);
var message = topicSession.createTextMessage(text);
// Send the message
producer.send(message);
Easy, isn’t it? Of course queue connections, sessions, receivers and senders are all supported, as well as durable subscriptions, map messages, object messages, transactions and 5 types of acknowledge modes. And did we mention it supports all the mainstream JMS brokers?
Configuration
To configure a JMS Extender instance you just need the typical parameters of a JMS connection:
- the JNDI factory fully-qualified name;
- the JMS connection URL;
- the JMS connection factory name.
The JMS Extender can connect simultaneously to different brokers. Each broker must be specified by a separate XML file named “JMS connector”. In the connection configuration you specify the parameters above and other auxiliary parameters like credentials, connection pool size, connection timeouts, etc.
An example of a JMS connector configuration for HornetQ is the following:
<jms_connector name="HornetQ">
<install_dir>HornetQ</install_dir>
<param name="jndi_factory">org.jnp.interfaces.NamingContextFactory</param>
<param name="url_pkg_prefixes">org.jboss.naming:org.jnp.interfaces</param>
<param name="jms_url">jnp://localhost:1099</param>
<param name="conn_factory_name">ConnectionFactory</param>
<param name="connect_timeout_millis">5000</param>
<param name="retry_delay_millis">2000</param>
</jms_connector>
Once JMS connectors have been configured, on the client side you just name them. Suppose, for example, that you configured two JMS connectors, one for HornetQ and one for TIBCO EMS, naming them “HornetQ” and “TIBCO” respectively. On the client you would connect to the first with:
TopicConnectionFactory.createTopicConnection(
"http://my.server.com/", "HornetQ",
"user", "password", {
// ...
});
And to the second with:
TopicConnectionFactory.createTopicConnection(
"http://my.server.com/", "TIBCO",
"user", "password", {
// ...
});
Note that user and password are not the credentials used to connect to the broker (which are specified in the JMS connector), but rather a separate set of credentials that may be checked directly on the JMS Extender. To allow for this and other authentication and authorization purposes, an extension mechanism called the hook is provided. More on this later.
Implementing a Chat
Typical implementation of a chat system with JMS requires just a dedicated topic. Clients would subscribe to the topic and show messages received in the chat window. Correspondingly, a new chat line would simply be a new message published on the topic.
With the JMS Extender and a bit of jQuery this can be implemented with just 20 lines of code:
TopicConnectionFactory.createTopicConnection(
"http://localhost:8080/", "HornetQ",
null, null, {
onConnectionCreated: function(conn) {
// Connection succeeded, topic subscription
var topicSession = conn.createSession(false, "AUTO_ACK");
var topic = topicSession.createTopic("chatTopic");
var consumer = topicSession.createConsumer(topic, null);
var producer = topicSession.createProducer(topic, null);
// Add a listener to the message consumer
consumer.setMessageListener({
onMessage: function(message) {
// Message received
$("#messages").append($("<div>").
text(message.getText()).
addClass("messageContainer"));
}
});
// Start the connection
conn.start();
// Event to send new messages
$("#submitForm").submit(function(event) {
event.preventDefault();
// Get value from field and send to JMS topic
var text = $("#user_message").val();
$("#user_message").val("");
var message = topicSession.createTextMessage(text);
producer.send(message);
});
}
});
Check out the full source code for our JMS Extender sample chat client for HTML/Javascript on GitHub.
Implementing a Stock-List with Custom Object Messages
An implementation of a stock watchlist with JMS usually employs one topic for each stock symbol. For the sake of simplicity, let’s imagine to use a single topic for the whole list. Similarly to the chat example, the configuration of the JMS broker requires a dedicated topic, but in this case, messages carry much more information than just a line of text. A typical stock-quote message may be composed of a timestamp, current price, minimum price of the day, maximum price of the day, bid size, ask size, and so on.
JMS provides a simple way to send object messages, i.e. messages whose structure is defined by a Java object. The JMS Extender supports this feature and automatically translates these messages to JSON objects and viceversa. All it needs is the jar file defining the object’s structure deployed in the lib subdirectory of the appropriate JMS connector.
So, suppose the stock-quote message is defined as follows:
public class FeedMessage implements Serializable {
private static final long serialVersionUID = 1L;
// The item name
public String itemName = null;
// An HashMap containing the updates for the item
// (the field names are the keys)
public Map<String, String> currentValues = null;
public FeedMessage() {}
public FeedMessage(String itemName,
final Map<String, String> currentValues) {
this.itemName = itemName;
this.currentValues = currentValues;
}
}
With the JMS Extender, a Node.js application may subscribe and process these messages with the following code:
var jms = require('lightstreamer-jms-client');
jms.TopicConnectionFactory.createTopicConnection(
"http://localhost:8080/", "HornetQ",
null, null, {
onConnectionCreated: function(conn) {
// Connection succeeded, topic subscription
var topicSession= conn.createSession(false, "AUTO_ACK");
var topic= topicSession.createTopic("stocksTopic");
var consumer= topicSession.createConsumer(topic, null);
// Add listener to message consumer
consumer.setMessageListener({
onMessage: function(message) {
// Message received, extract the object
var feedMessage= message.getObject();
var stock= feedMessage.itemName;
var values= feedMessage.currentValues;
// Log the received data
console.log(values["stock_name"] + ": " +
values["last_price"]);
}
});
// Start the connection
conn.start();
}
});
Of course the same code would work in a web browser. Check out the full source code on GitHub for:
- JMS Extender Sample Stock-List Client for HTML/JavaScript
- JMS Extender Sample Stock-List Client for Node.js
- JMS Sample Stock-List Back-End Service
Adding Authorization and Authentication With the Hook
As introduced above, an extension mechanism called the hook is provided, loosely based on the adapter pattern. A hook is a Java class that implements the JmsExtenderHook interface and is deployed system-wide on the JMS Extender.
This class must implement a set of event callbacks that you may use to authenticate and/or authorize almost every operation that a client may request, e.g.
- opening a session on a certain JMS connector;
- creating a consumer for a certain destination;
- creating a producer for a certain destination;
- etc.
The rationale behind this mechanism is that, for performance reasons, JMS connections to the back-end broker are pooled. Thus, clients of the JMS Extender, when they connect, rely upon a connection that is already open since long. Canonical JMS user/password pairs (or JNDI credentials) are specified on the JMS connector configuration and are used by the JMS Extender when starting up, while user/password pairs provided by clients must be checked by the hook implementation.
This also provides room for typical authentication procedures of web applications, such as OAuth, shared token single sign-ons, etc. Full source code for a web application with authentication and authorization, together with its JMS Extender hook, are available on GitHub:
Summing Things Up
With this blog post we have shown the similarity between the JMS Extender client APIs and the original JMS specifications. We have also shown how a connection can be created, a topic can be subscribed and a message can be sent. Finally, we have shown how to combine these typical JMS operations in a chat or stock-list example.
By now, it should be clear how easy it is to port your applications to the JMS Extender. But simplicity of coding is just one aspect of this product, the other being the improved scalability and performance it may bring to your JMS infrastructure, which by itself may never be able to sustain the numbers of the Web.
Take a look to our web site for a full list of features of the JMS Extender, and if you are searching for a solution to bring JMS to your web application, look no further: you have found it.
You can start playing with the Lightstreamer JMS Extender right now. Just download it and use it. You will find full documentation and working examples.