Monday, March 16, 2015

Java Message Service 2.0 with WildFly 8

In a couple of previous messages, I provided examples of Java Message Service (JMS) and JBoss AS 7. In the first, I did the basic, in the following one, I used a number of more advanced features. It's time to move on, and consider the following version of JBoss, which is now WildFly, and, more importantly, the next version of JMS, which is now the 2.0. While WildFly imposed little to no changes in the previous examples, the differences introduce by JMS 2.0 are very worth considering. JMS 2.0 is much less verbose and much easier to use. For details, please refer to this page.

I will write three applications in this example:

  • JMS queue sender
  • JMS synchronous queue receiver
  • JMS asynchronous queue receiver

Configuration

Let us start by launching the server from the bin directory. Note the -c standalone-full.xml option, to enable a configuration where JMS is active:

bin filipius$ ./standalone.sh -c standalone-full.xml
=========================================================================

  JBoss Bootstrap Environment

  JBOSS_HOME: /opt/jboss

  JAVA: java

  JAVA_OPTS:  -server -Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true

=========================================================================

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
11:44:23,563 INFO  [org.jboss.modules] (main) JBoss Modules version 1.3.3.Final
11:44:24,022 INFO  [org.jboss.msc] (main) JBoss MSC version 1.2.2.Final
11:44:24,118 INFO  [org.jboss.as] (MSC service thread 1-6) JBAS015899: WildFly 8.2.0.Final "Tweek" starting
11:44:27,028 INFO  [org.jboss.as.server] (Controller Boot Thread) JBAS015888: Creating http management service using socket-binding (management-http)
11:44:27,064 INFO  [org.xnio] (MSC service thread 1-8) XNIO version 3.3.0.Final
11:44:27,089 INFO  [org.xnio.nio] (MSC service thread 1-8) XNIO NIO Implementation Version 3.3.0.Final
11:44:27,168 WARN  [org.jboss.as.txn] (ServerService Thread Pool -- 52) JBAS010153: Node identifier property is set to the default value. Please make sure it is unique.
11:44:27,169 INFO  [org.jboss.as.jacorb] (ServerService Thread Pool -- 36) JBAS016300: Activating JacORB Subsystem
11:44:27,169 INFO  [org.jboss.as.security] (ServerService Thread Pool -- 51) JBAS013171: Activating Security Subsystem
11:44:27,182 INFO  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 35) JBAS010280: Activating Infinispan subsystem.
11:44:27,183 INFO  [org.jboss.as.naming] (ServerService Thread Pool -- 46) JBAS011800: Activating Naming Subsystem
11:44:27,188 INFO  [org.jboss.as.security] (MSC service thread 1-6) JBAS013170: Current PicketBox version=4.0.21.Final
11:44:27,250 INFO  [org.jboss.as.webservices] (ServerService Thread Pool -- 54) JBAS015537: Activating WebServices Extension
11:44:27,292 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-1) JBAS017502: Undertow 1.1.0.Final starting
11:44:27,294 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 53) JBAS017502: Undertow 1.1.0.Final starting
11:44:27,304 INFO  [org.jboss.as.jsf] (ServerService Thread Pool -- 42) JBAS012615: Activated the following JSF Implementations: [main]
11:44:27,307 INFO  [org.wildfly.extension.io] (ServerService Thread Pool -- 34) WFLYIO001: Worker 'default' has auto-configured to 8 core threads with 64 task threads based on your 4 available processors
11:44:27,330 INFO  [org.jboss.as.connector.logging] (MSC service thread 1-2) JBAS010408: Starting JCA Subsystem (IronJacamar 1.1.9.Final)
11:44:27,398 INFO  [org.jboss.as.naming] (MSC service thread 1-3) JBAS011802: Starting Naming Service
11:44:27,406 INFO  [org.jboss.as.mail.extension] (MSC service thread 1-7) JBAS015400: Bound mail session [java:jboss/mail/Default]
11:44:27,467 INFO  [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 30) JBAS010403: Deploying JDBC-compliant driver class org.h2.Driver (version 1.3)
11:44:27,480 INFO  [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-6) JBAS010417: Started Driver service with driver-name = h2
11:44:27,689 INFO  [org.jboss.remoting] (MSC service thread 1-1) JBoss Remoting version 4.0.6.Final
11:44:27,743 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 53) JBAS017527: Creating file handler for path /opt/jboss/welcome-content
11:44:27,821 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-2) JBAS017525: Started server default-server.
11:44:27,870 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-5) JBAS017531: Host default-host starting
11:44:28,074 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-1) JBAS017519: Undertow HTTP listener default listening on /127.0.0.1:8080
11:44:28,121 WARN  [org.jboss.as.messaging] (MSC service thread 1-8) JBAS011600: AIO wasn't located on this platform, it will fall back to using pure Java NIO. If your platform is Linux, install LibAIO to enable the AIO journal
11:44:28,313 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221000: live server is starting with configuration HornetQ Configuration (clustered=false,backup=false,sharedStore=true,journalDirectory=/opt/jboss/standalone/data/messagingjournal,bindingsDirectory=/opt/jboss/standalone/data/messagingbindings,largeMessagesDirectory=/opt/jboss/standalone/data/messaginglargemessages,pagingDirectory=/opt/jboss/standalone/data/messagingpaging)
11:44:28,318 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221006: Waiting to obtain live lock
11:44:28,391 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221013: Using NIO Journal
11:44:28,419 INFO  [org.jboss.as.jacorb] (MSC service thread 1-6) JBAS016330: CORBA ORB Service started
11:44:28,468 INFO  [org.jboss.as.server.deployment.scanner] (MSC service thread 1-1) JBAS015012: Started FileSystemDeploymentService for directory /opt/jboss/standalone/deployments
11:44:28,540 INFO  [org.jboss.as.connector.subsystems.datasources] (MSC service thread 1-1) JBAS010400: Bound data source [java:jboss/datasources/ExampleDS]
11:44:28,561 INFO  [io.netty.util.internal.PlatformDependent] (ServerService Thread Pool -- 56) Your platform does not provide complete low-level API for accessing direct buffers reliably. Unless explicitly requested, heap buffer will always be preferred to avoid potential system unstability.
11:44:28,686 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221043: Adding protocol support CORE
11:44:28,705 INFO  [org.jboss.as.jacorb] (MSC service thread 1-6) JBAS016328: CORBA Naming Service started
11:44:28,782 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221043: Adding protocol support AMQP
11:44:28,792 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221043: Adding protocol support STOMP
11:44:28,854 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221034: Waiting to obtain live lock
11:44:28,854 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221035: Live Server Obtained live lock
11:44:28,980 INFO  [org.jboss.ws.common.management] (MSC service thread 1-3) JBWS022052: Starting JBoss Web Services - Stack CXF Server 4.3.2.Final
11:44:29,127 INFO  [org.jboss.messaging] (MSC service thread 1-1) JBAS011615: Registered HTTP upgrade for hornetq-remoting protocol handled by http-acceptor-throughput acceptor
11:44:29,127 INFO  [org.jboss.messaging] (MSC service thread 1-5) JBAS011615: Registered HTTP upgrade for hornetq-remoting protocol handled by http-acceptor acceptor
11:44:29,282 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221007: Server is now live
11:44:29,283 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 56) HQ221001: HornetQ Server version 2.4.5.FINAL (Wild Hornet, 124) [f94a27d0-cbd0-11e4-8360-25874cd3b289] 
11:44:29,318 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 56) JBAS011601: Bound messaging object to jndi name java:jboss/exported/jms/RemoteConnectionFactory
11:44:29,340 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 59) HQ221003: trying to deploy queue jms.queue.DLQ
11:44:29,352 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 59) JBAS011601: Bound messaging object to jndi name java:/jms/queue/DLQ
11:44:29,354 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 57) JBAS011601: Bound messaging object to jndi name java:/ConnectionFactory
11:44:29,354 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 58) HQ221003: trying to deploy queue jms.queue.ExpiryQueue
11:44:29,356 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 58) JBAS011601: Bound messaging object to jndi name java:/jms/queue/ExpiryQueue
11:44:29,429 INFO  [org.jboss.as.connector.deployment] (MSC service thread 1-8) JBAS010406: Registered connection factory java:/JmsXA
11:44:29,493 INFO  [org.hornetq.ra] (MSC service thread 1-8) HornetQ resource adaptor started
11:44:29,494 INFO  [org.jboss.as.connector.services.resourceadapters.ResourceAdapterActivatorService$ResourceAdapterActivator] (MSC service thread 1-8) IJ020002: Deployed: file://RaActivatorhornetq-ra
11:44:29,499 INFO  [org.jboss.as.connector.deployment] (MSC service thread 1-7) JBAS010401: Bound JCA ConnectionFactory [java:/JmsXA]
11:44:29,500 INFO  [org.jboss.as.messaging] (MSC service thread 1-7) JBAS011601: Bound messaging object to jndi name java:jboss/DefaultJMSConnectionFactory
11:44:29,578 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015961: Http management interface listening on http://127.0.0.1:9990/management
11:44:29,579 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015951: Admin console listening on http://127.0.0.1:9990
11:44:29,580 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8.2.0.Final "Tweek" started in 6546ms - Started 218 of 267 services (93 services are lazy, passive or on-demand)

Still in the bin directory, the second thing to do, if you still don't have a user is to create one. For this we run the add-user.sh command (in windows the extension is .bat). Beware of one detail: the password you enter here will imply changes to the code below. I used the password "br1o+sa*" (without the quotes), if you don't want to change any source code:

bin filipius$ ./add-user.sh 

What type of user do you wish to add? 
 a) Management User (mgmt-users.properties) 
 b) Application User (application-users.properties)
(a): b

Enter the details of the new user to add.
Using realm 'ApplicationRealm' as discovered from the existing property files.
Username : joao
Password recommendations are listed below. To modify these restrictions edit the add-user.properties configuration file.
 - The password should not be one of the following restricted values {root, admin, administrator}
 - The password should contain at least 8 characters, 1 alphabetic character(s), 1 digit(s), 1 non-alphanumeric symbol(s)
 - The password should be different from the username
Password : 
Re-enter Password : 
What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]: guest
About to add user 'joao' for realm 'ApplicationRealm'
Is this correct yes/no? yes
Added user 'joao' to file '/opt/wildfly-8.2.0.Final/standalone/configuration/application-users.properties'
Added user 'joao' to file '/opt/wildfly-8.2.0.Final/domain/configuration/application-users.properties'
Added user 'joao' with groups guest to file '/opt/wildfly-8.2.0.Final/standalone/configuration/application-roles.properties'
Added user 'joao' with groups guest to file '/opt/wildfly-8.2.0.Final/domain/configuration/application-roles.properties'
Is this new user going to be used for one AS process to connect to another AS process? 
e.g. for a slave host controller connecting to the master or for a Remoting connection for server to server EJB calls.
yes/no? no

We now need to create a queue, if we are going to use it. One might edit the configuration file standalone-full.xml directly, but it is possibly simpler to use the command line interface, as follows (note that this only works if the server is running):

bin filipius$ ./jboss-cli.sh 
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9990 /] jms-queue add --queue-address=PlayQueue --entries=java:jboss/exported/jms/queue/PlayQueue
[standalone@localhost:9990 /]

The server outputs the following message:

14:22:06,037 INFO  [org.hornetq.core.server] (ServerService Thread Pool -- 64) HQ221003: trying to deploy queue jms.queue.PlayQueue
14:22:06,038 INFO  [org.jboss.as.messaging] (ServerService Thread Pool -- 64) JBAS011601: Bound messaging object to jndi name java:jboss/exported/jms/queue/PlayQueue

If you need to remove this queue just do jms-queue remove --queue-address=PlayQueue. We now may see the result of this action in the file standalone-full.xml directly (look for this file in the directory standalone/configuration). The entry "PlayQueue" was not there before we added it. You can take my word on this.

<jms-destinations>
   <jms-queue name="ExpiryQueue">
      <entry name="java:/jms/queue/ExpiryQueue"/>
   </jms-queue>
   <jms-queue name="DLQ">
      <entry name="java:/jms/queue/DLQ"/>
   </jms-queue>
   <jms-queue name="PlayQueue">
      <entry name="java:jboss/exported/jms/queue/PlayQueue"/>
   </jms-queue>
</jms-destinations>

The Sender

We are now going to write three applications, a sender and two receivers that use this queue. As usual, I'm assuming that we are using Eclipse. Let us start by creating a standard Java project:



and name it BasicJMS2. I'm using Java 8:



We may now create the Sender (File-->New-->Class):

with the following contents:

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSContext;
import javax.jms.JMSProducer;
import javax.jms.JMSRuntimeException;
import javax.naming.InitialContext;
import javax.naming.NamingException;


public class Sender {
private ConnectionFactory cf;
private Destination d;

public Sender() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/PlayQueue");
}

private void send(String text) {
try (JMSContext jcontext = cf.createContext("joao", "br1o+sa*");) {
JMSProducer mp = jcontext.createProducer();
mp.send(d, text);
} catch (JMSRuntimeException re) {
re.printStackTrace();
}
}

public static void main(String[] args) throws NamingException {
Sender s = new Sender();
s.send("Hello Receiver!");
}
}


What is different comparing to this? The Connection and the Session objects are gone! We now have a JMSContext instead, which replaces both for most cases (actually, the Connection and Session still exist and are not deprecated, but they were - are! - much more complicated to use). One interesting detail is that we do not need to explicitly close the JMSContext using this try-with-resources block. It closes automatically when the block finishes. This will certainly save a lot of time for inexperienced users that forget to close resources. This would make applications unable to receive messages from a specific queue for a while in the subsequent tries. I was used to see students wasting their time looking for non-existing bugs, when the only problem was a missing close(). The new JMSContext still needs a username and a password, but we've already gone through the creation of the user before. Don't forget to use your own password!

The JMSProducer replaces the MessageProducer with another simplification in the message send.

This said, you should not get a very impressive result:


We need to add a library that exists in WildFly: the jboss-client.jar in the directory bin/client. For this, we first use the contextual menu over the name of the project, and then go to the option Build Path-->Configure Build Path. Pick the Libraries tab and navigate to the jboss-client.jar. Once you add it, the red underlines should disappear.



The Synchronous Receiver

Let us now write the synchronous Receiver:

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSRuntimeException;
import javax.naming.InitialContext;
import javax.naming.NamingException;


public class Receiver {
private ConnectionFactory cf;
private Destination d;

public Receiver() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/PlayQueue");
}
private String receive() {
String msg = null;
try (JMSContext jcontex = cf.createContext("joao", "br1o+sa*");) {
JMSConsumer mc = jcontex.createConsumer(d);
msg = mc.receiveBody(String.class);
} catch (JMSRuntimeException re) {
re.printStackTrace();
}
return msg;
}

public static void main(String[] args) throws NamingException {
Receiver r = new Receiver();

String msg = r.receive();
System.out.println("Message: " + msg);
}


}


And we need an extra file: jndi.properties. This file contains the properties for Java Naming and Directory Interface (JNDI), which we use to lookup the ConnectionFactory and the Destination in the constructors of both examples:

java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory
java.naming.provider.url=http-remoting://localhost:8080
jboss.naming.client.ejb.context=false

To create this file just select the same folder of Receiver. java and Sender.java, right-click and pick (New-->File). This is the tree structure we get in the end:



You can now run the receiver and the sender in either order (select the File and go to the Run Menu). When you finish running both applications, the console should show the following on the receiver side:

Mar 16, 2015 3:05:58 PM org.xnio.Xnio <clinit>
INFO: XNIO version 3.3.0.Final
Mar 16, 2015 3:05:58 PM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.3.0.Final
Mar 16, 2015 3:05:58 PM org.jboss.remoting3.EndpointImpl <clinit>
INFO: JBoss Remoting version 4.0.6.Final
Mar 16, 2015 3:06:00 PM org.jboss.ejb.client.remoting.VersionReceiver handleMessage
INFO: EJBCLIENT000017: Received server version 2 and marshalling strategies [river]
Mar 16, 2015 3:06:00 PM org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver associate
INFO: EJBCLIENT000013: Successful version handshake completed for receiver context EJBReceiverContext{clientContext=org.jboss.ejb.client.EJBClientContext@68c4039c, receiver=Remoting connection EJB receiver [connection=Remoting connection <57577092>,channel=jboss.ejb,nodename=filipius-portatil]} on channel Channel ID c21f8d4f (outbound) of Remoting connection 2b568ca9 to localhost/127.0.0.1:8080
Mar 16, 2015 3:06:00 PM org.jboss.ejb.client.EJBClient <clinit>
INFO: JBoss EJB Client version 2.0.1.Final
Message: Hello Receiver!

The Asynchronous Receiver

Let us now write the asynchronous receiver:

import java.io.IOException;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;


public class ReceiverAsync implements MessageListener {
private ConnectionFactory cf;
private Destination d;

public ReceiverAsync() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/PlayQueue");
}
@Override
public void onMessage(Message msg) {
TextMessage tmsg = (TextMessage) msg;
try {
System.out.println("Got message: " + tmsg.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
public void launch_and_wait() {
try (JMSContext jcontext = cf.createContext("joao", "br1o+sa*");) {
JMSConsumer consumer = jcontext.createConsumer(d);
consumer.setMessageListener(this);
System.out.println("Press enter to finish...");
System.in.read();
} catch (JMSRuntimeException | IOException re) {
re.printStackTrace();
}
}

public static void main(String[] args) throws NamingException {
ReceiverAsync r = new ReceiverAsync();
r.launch_and_wait();
}



}


We can now run it. Differently from the synchronous case, this receiver keeps reading the messages that come into the queue, until the user presses enter. Notice that we do not need to create any thread, as the JMS library will create one for you (most likely in the setMessageListener() invocation). This thread, however, cannot survive the end of the JMS context where it lives. For this reason, we must wait for the enter key inside the try-with-resources block.

In the following example, I ran the Sender three times, before finishing the receiver.

Mar 16, 2015 8:54:29 PM org.xnio.Xnio <clinit>
INFO: XNIO version 3.3.0.Final
Mar 16, 2015 8:54:29 PM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.3.0.Final
Mar 16, 2015 8:54:29 PM org.jboss.remoting3.EndpointImpl <clinit>
INFO: JBoss Remoting version 4.0.6.Final
Mar 16, 2015 8:54:30 PM org.jboss.ejb.client.remoting.VersionReceiver handleMessage
INFO: EJBCLIENT000017: Received server version 2 and marshalling strategies [river]
Mar 16, 2015 8:54:30 PM org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver associate
INFO: EJBCLIENT000013: Successful version handshake completed for receiver context EJBReceiverContext{clientContext=org.jboss.ejb.client.EJBClientContext@6f7fd0e6, receiver=Remoting connection EJB receiver [connection=Remoting connection <7604159b>,channel=jboss.ejb,nodename=ovelha]} on channel Channel ID d9bffeb7 (outbound) of Remoting connection 2ef23fb4 to localhost/127.0.0.1:8080
Mar 16, 2015 8:54:30 PM org.jboss.ejb.client.EJBClient <clinit>
INFO: JBoss EJB Client version 2.0.1.Final
Press enter to finish...
Got message: Hello Receiver!
Got message: Hello Receiver!
Got message: Hello Receiver!

That's it. Long live JMS 2.0!

8 comments:

  1. Everything is very much clear to me but I'm stuck at one thing, when I run the sender class it gives me the following error:

    Exception in thread "main" javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file:

    java.naming.factory.initial
    at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662)
    at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
    at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:350)
    at javax.naming.InitialContext.lookup(InitialContext.java:417)
    at javax.naming.InitialContext.doLookup(InitialContext.java:290)
    at wildfly_jms.Sender.(Sender.java:16)
    at wildfly_jms.Sender.main(Sender.java:30)
    Picked up _JAVA_OPTIONS: -Xmx512M

    not sure why is this giving out this error

    ReplyDelete
    Replies
    1. It seems that you don't have the jndi.properties file (see above) in the classpath.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete