Integrating WebSockets and JMS with CDI Events in Java EE 7

UPDATE July 15th, 2013
I've updated this blog entry to clarify the issue of integration between WebSockets API 1.0 and other Java EE technologies, and also to link you to the bugs submitted in the WebSockets, JMS, and specially the CDI specification. I want to talk all spec leaders involved in this, specially JJ Snyder, Danny Coward, and Nigel Deakin.

This is a great reminder of how important is your participation during the specification definition process. Please read, test and report any issue you find in a specification before it becomes final. Join the Adopt a JSR program and work with us!

------------------ 

WebSocket is the new kid on the block when you think about Web Development these days. And it is expected that you want to integrate it with whatever is available in your hands. Java EE 7 is coming with cool things beyond this, for example JMS 2.0. And then you wonder: how can I send asynchronous messages to all WebSocket sessions connected to my website? Server push; no polling: for real!

The answer is quite simple: CDI. Also know as the Java EE magic glue. CDI enables a developer to build inter-communication between, apparently, distinct parts of your application. Let's go through all the steps to enable your WebSocket application to send and receive messages through JMS.

1 - Creating the WebSocket Server Endpoint

First we need to build the WebSocket server endpoint that will receive messages from clients, and to also notify clients asynchronously with a server push, with incoming JMS message payloads:

@Named
@ServerEndpoint("/websocket") public class WebSocketEndpoint implements Serializable { // this object will hold all WebSocket sessions connected to this WebSocket // server endpoint (per JVM) private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>()); 

Now you must also add three key methods to this WebSocket: 

@OnOpen public void onOpen(final Session session) { sessions.add(session); } @OnMessage public void onMessage(final String message, final Session client) { ... } @OnClose public void onClose(final Session session) { sessions.remove(session); }

Notice that on onOpen and onClose, we manage all user sessions connected to this endpoint. We will see later how sessions will be used inside onMessage. For now, let's create a SessionBean to send messages to a JMS Queue.

2 - Creating the SessionBean to send JMS messages

Due to missing parts from the specifications, we cannot use @Inject JMSContext inside a @ServerEndpoint WebSocket, and we can't also set a server endpoint as a MessageListener to receive JMS messages. Simply put, integration between JMS and WebSockets (and probably other Java EE APIs) is not straightforward. You can follow the discussion in the following issues:

 

But fortunately, there are two ways to do this: 

  1. Create a stateless SessionBean that sends messages to a JMS queue
  2. Inject JMS resources inside WebSocket Server Endpoint, and create a JMSContext from the ConnectionFactory

 

Solution #2 can be achieved using the following snippet, thanks to Nigel Deakin, spec leader of JMS who helped me discovering this issue.

@Resource(lookup="java:comp/DefaultJMSConnectionFactory") ConnectionFactory cf;
@Resource(lookup = "jms/myQueue") Queue myQueue;
 
@OnMessage
public void onMessage(final String message, final Session client) {
try (JMSContext context = cf.createContext();){
context.createProducer().send(myQueue, message);
}
}

We are going to use solution #1 and create a SessionBean to forward incoming WebSocket messages to a JMS queue. Create a class named QueueSenderSessionBean as it follows:

@Stateless
public class QueueSenderSessionBean { ... }

This is a simple @Stateless SessionBean. Now, let's add a business method to it, called sendMessage:

public void sendMessage(String message) { ... } 

Quite straight-forward, isn't? One of the great things about JMS 2.0 is its simplicity to send messages to a destination. To do that, we need to inject two objects:

@Resource(mappedName = "jms/myQueue")
private Queue myQueue;
@Inject private JMSContext jmsContext; 

JMSContext is one of the new classes added to JMS API, and is documented here. It encapsulates a Connection and a Session, and makes use of a default ConnectionFactory, now a required resource to be provided by all Java EE 7 certified application servers. Next, all you need is to add the logic to the previously added method:

jmsContext.createProducer().send(myQueue, message);

And you are done with the SessionBean. Next we will add some glue between the SessionBean and the WebSocket to send messages to the JMS destination.

3 - Forwarding an incoming WebSocket message to a JMS destination

All you need to do here is to inject the SessionBean into your WebSocket, and call the sendMessage method inside onMessage of your endpoint. Let's start with the injection first, but due to a bug, we must do constructor injection. Open your WebSocket server endpoint class WebSocketEndpoint, and add the following field:

private QueueSenderSessionBean senderBean;

Now add the following constructor to it:
  @Inject
  public WebSocketEndpoint(QueueSenderSessionBean sb) {
     this.senderBean = sb;
  }

Next step is to simply call the method inside onMessage
senderBean.sendMessage(message);

We have finished the first part of this application. With this code, you are now able to send a message from a WebSocket client, to a JMS destination. Next, we will do the opposite. Let's push some data from a JMS queue to all WebSocket clients!

4 - Listening to a JMS Destination with a MessageDriven Bean

Funny fact: some developers have not realized yet, but the MessageDriven annotation is not specified by the JMS API. Instead, it is part of the EJB specification, and it can be used not only for JMS, but for many other things. David Blevins from the awesome Apache TomEE realized that, and proposed a small change to the EJB spec, where resource adapters required connectors to provide a messagelistener-type. His proposal though, suggests that you should be able to use an MDB to listen to different things, and the listener interface should be optional. One example is to listen to Telnet commands. Pretty awesome! But let's focus on our use case here, which is specific to JMS.

Now that we can publish messages into a Queue destination from a WebSocket client, we must process them to later forward to somewhere else. Let's start coding our JMS MDB (remember, not all MDBs are implicitly JMS-related!), implementing the MessageListener interface, required by JMS ResourceAdapter connectors:

  @Named
  @MessageDriven(mappedName = "jms/myQueue")
  public class WebSocketMDB implements MessageListener {
    @Override
    public void onMessage(Message msg) { ... }
  }

This is the basic code to any JMS MDB. Now let's do some magic... 

5 - Firing CDI events with the JMS Message payload

Remember when I told that we cannot listen to JMS destinations directly from the WebSocket server endpoint due to specification restrictions? Well. We can actually, but using a different technique. If you haven't heard about CDI Events, you should read about it before continuing this tutorial. Done? Ok, let's go. First thing we need is an Event qualifier. Create the WSJMSMessage annotation inside your project:

  @Qualifier
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
  public @interface WSJMSMessage {}

 With a defined qualifier, CDI will be able to connect the firing event with the observer object. Go back to the WebSocketMDB and add an Event dispatcher to it, with the qualifier we created above:

    @Inject
    @WSJMSMessage     Event<Message> jmsEvent; 

 Now let's add the logic to the onMessage method:

jmsEvent.fire(msg);

6 - Listening to CDI events within the WebSocket server endpoint

This is the last server-side part of this article, then next you will see how to code Javascript on the client-side. Let's listen to CDI events fired by the MDB, with the Message payload. Open again your WebSocketEndpoint class, and add the following method to it:

public void onJMSMessage(@Observes @WSJMSMessage Message msg) {
        try {
            for (Session s : sessions) {
                s.getBasicRemote().sendText("message from JMS: " + msg.getBody(String.class));
            }
        } catch (IOException | JMSException ex) {
            Logger.getLogger(WebSocketEndpoint.class.getName()).log(Level.SEVERE, null, ex);
        }     } 

Observe the @Observes and the qualifier @WSJMSMessage we defined previously. This is what tells CDI to listen to the fired events by the MDB.

7 - Client-side Javascript to connect with the WebSocket server endpoint

This has been floating around the Internet for a while as it is not Java nor Java EE specific, but anyway it is basically this:

// note the final path is the same defined inside WebSocketEndpoint class at @ServerEndpoint websocketSession = new WebSocket('ws://' + document.location.host + '/your-app-context-root/websocket');

Here is the final Javascript used by this example, as well the HTML interface.

Conclusion

I hope you have found this article useful to begin your development with Java EE 7, and what are the possibilities of integrating CDI, WebSockets, JMS, and EJB. These are the main points about this article:

  • ability to asynchronously communicate with WebSocket clients (although you can also use session.getAsyncRemote() to send messages asynchronously)
  • ability to do a server push to WebSocket clients at any point in your application
  • ability to scale server-pushed communication to WebSocket client sessions across a cluster using JMS Topics
    This is perhaps one of the most interesting thing about this setup. If you use a Topic instead of a Queue, you will be able to push data to all WebSocket sessions connected to your application across a cluster. There's a know limit of roughly 64k client sessions per web server, and in this example we use a static synchronized Set to hold a reference to them. Imagine now a cluster. We change this to a Topic clustered subscriber, and we are able to scale up server pushed data :-)
The source code of this project is available at my GitHub repository javaee7-jms-websocket-example. I hope you liked the article!

Comments:

Nice article!

About the session bean; the @LocalBean annotation is not needed, this is already the default if no business interface is present. @LocalBean is only needed if you want both a no-interface view and either a local- or remote view. Just remove the annotation and test your code again; you'll see it makes no difference ;)

Likewise, @Named does not make a bean part of a CDI context. Every bean in a CDI archive is already part of it. @Named is only needed if you want to refer to the bean via EL. You're not doing that here, so you can safely drop it as well.

Posted by guest on May 03, 2013 at 01:55 AM PDT #

The idea of using CDI events to process JMS messages was first introduced by Seam3 JMS module.

Regarding the comments by guest from May 3, you should really use @ApplicationScoped to indicate the global nature of these objects. Right now they're @Dependent (technically if you read the CDI 1.1 spec these objects would not be registered since they have no scope).

You make claims at the end of the article regarding clustered topics. I have no idea what a clustered topic is, we on the JMS EG spoke about it briefly however when the PAAS options for EE7 were dropped we dropped that discussion. I believe the use of a clustered topic will end up being platform specific.

Posted by John A on May 05, 2013 at 06:31 PM PDT #

@guest, your suggestions were already applied to the example project at github. And you are correct, most of my recommendations about CDI are not necessary. Thanks!

@John, I was never a Seam3 developer, but this is very informative. Now, @ApplicationScoped is not really an issue here. WebSockets and CDI were not quite well aligned in the specification. There are some gaps (see GLASSFISH-20371 and JMS_SPEC-100) that will be fulfilled, hopefully, in the next version.

Regardling my conclusion: what I did want to say is that with a clustered application (not a clustered topic), it will be possible to publish async data across all websocket clients, using a JMS Topic (which then, might vary per vendor, clustered/distribuited/whatever).

My use case is this: an application deployed in a cluster, where users are connected to specific nodes (stick sessions) to a server endpoint websocket. The only way I can see to an application push data to all client endpoints, is by using a JMS Topic, and a Subscriber MDB that will, through CDI event, forward the data to clients. Is it better now?

Posted by Bruno Borges on May 05, 2013 at 07:03 PM PDT #

Is using this,we can implement video conferencing through web? Could you give some suggestion for implementing video conferencing through web using java, without using JMF?

Posted by guest on May 08, 2013 at 08:24 AM PDT #

Hi Bruno

Yes, I agree, WebSocket didn't align much with any spec. Note that in response to the JMS spec issue you mentioned, I created https://java.net/jira/browse/WEBSOCKET_SPEC-196 to try to resolve the underlying problem - the WebSocket spec doesn't indicate active contexts. It's not the JMS spec's job to do this :-)

Posted by John D. Ament on May 15, 2013 at 05:54 PM PDT #

interrsting article

Posted by guest on May 17, 2013 at 07:58 AM PDT #

Hi,

I am using java EE 7 with the new netbeans and I got some problems with the CDI on the ServerEndpoint Class.
The inject of the session bean and the listening for the CDI event does not work.

Does it work for you on the latest GlassFish

Thanks

Posted by Edwin Biemond on June 16, 2013 at 11:46 AM PDT #

Hi,

Does this example works on the latest netbeans with glassfish 4. My CDI bean inject and CDI events is not working on the websockets class. In my other beans and MDB CDI it working perfectly.

Thanks

Posted by Edwin Biemond on June 16, 2013 at 11:50 AM PDT #

Hi Bruno,
I am trying to see this example working on Glasfish 4.0 (b89). No luck on the Constructor-type Inject and CDI event Observer. Could you suggest any approach for these?

Thanks

Posted by guest on June 28, 2013 at 07:53 AM PDT #

Hi,
Thanks for a nice article, it is a good demonstration how websockets session can be scaled!

Two points related to recent glassfish4:
1. To make source code work you will need to add jms/myQueue to JMS destinations through glassfish admin or change mapped name to "java:app/jms/myQueue"
2. senderBean in WebSocketEndpoint is not being injected (it is null). To make this work I made a WebSocketEndpoint as stateless EJB (added @Stateless).

Posted by Dmytro Polivenok on July 23, 2013 at 08:56 AM PDT #

Bruno, please do not assume websockets is only for browsers. Java clients need a good, robust connection to the java containers as much as browsers do. I will never be able to justify asking my internal users to try to get their work done with something as clumsy and crude as a browser interface. There is a lot of room for enterprise desktop Java applications but connectivity of Netbeans Platform Java clients with Java servers is a dismal affair currently. Websockets could put the fix on that!

Posted by guest on September 07, 2013 at 07:02 PM PDT #

I am trying to get the above code running, using Glassfish v4, and java ee 7, I am keep getting the following:
error Exception while loading the app : EJB Container initialization error
com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: Invalid destination jms/myQueue for MDB: JNDI name not found

Any idea?

Posted by guest on December 07, 2013 at 05:57 PM PST #

In case of "EJB Container initialization error
com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: Invalid destination jms/myQueue for MDB: JNDI name not found" try to add jms/myQueue to JMS destinations through glassfish admin or change mapped name to "java:app/jms/myQueue".

Posted by Dmytro Polivenok on December 08, 2013 at 07:53 AM PST #

Hai,
The JMS 2.0 specificationtells that @OnMessage() fails ,because there is no request scope.(https://java.net/jira/browse/JMS_SPEC-121).
Example
@ServerEndpoint("/websocket")
public class SampleWebSocket {

@Resource(lookup="java:comp/DefaultJMSConnectionFactory") ConnectionFactory cf;
@Resource(lookup = "jms/myQueue") Queue myQueue;

@Inject
private JMSContext jmsContext;

@OnOpen
public void onOpen(final Session session) {
// works OK as there is a valid request scope here
context.createProducer().send(myQueue, message);
}

@OnMessage
public void onMessage(final String message, final Session client) {
// fails as there is no valid request scope here
context.createProducer().send(myQueue, message);
}

@OnClose
public void onClose(final Session session) {
// fails as there is no valid request scope here
context.createProducer().send(myQueue, message);
}

So my doubt is how the solution described above will work,when JMSContext is created within @OnMessage() call.

Posted by guest on January 06, 2014 at 08:00 AM PST #

Post a Comment:
  • HTML Syntax: NOT allowed
About


Bruno has been having fun working with Java since 2000 and now helps Oracle on sharing the technology accross all Latin America. Also plays videogames, does trekking and loves beer.

Follow me on Twitter! @brunoborges

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today