Monday, September 25, 2017

Java Message Service 2.0, WildFly 11 and Maven

In this message I'm going to run a basic example of Java Message Service 2.0 using Maven to manage the project and WildFly 11 as the server.

First, you should create a Maven project in your favorite IDE (Eclipse or IntelliJ, for example) using the maven-archetype-quickstart. Later, I will give a complete pom.xml that supersedes the default one. In this project, let us write a bare-bones synchronous message sender:

package blog.example_JMS;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSContext;
import javax.jms.JMSProducer;
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/QueueExample");
}
public void send(String msg) {
try (JMSContext cntx = this.cf.createContext("joao", "br1o+sa*")) {
JMSProducer prod = cntx.createProducer();
prod.send(d, msg);
}
}

public static void main(String[] args) throws NamingException {
new Sender().send("Hello World, JMS!");
}


}


and a receiver:

package blog.example_JMS;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
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/QueueExample");
}

public String receive() {
try (JMSContext cntxtthis.cf.createContext("joao", "br1o+sa*")) {
JMSConsumer cons = cntxt.createConsumer(d);
return cons.receiveBody(String.class);
}
}
public static void main(String[] args) throws NamingException {
System.out.println(new Receiver().receive());
}


}


Please be aware of the package: these files should go to the directory src/main/java/blog/example_JMS/.

An issue now is that you should see your IDE pointing out lots of errors in this code, because we haven't changed the pom.xml yet. This a tricky step, as we must find out the right JMS APIs, the ActiveMQ client implementations, and the Java Naming and Directory Interface (JNDI) client. Besides the previous dependencies, we need to correct the default Java version to 1.8 using a plugin (or whatever more recent version you might want to use; Java 9 was just released just four days ago, as I write this message). Overall, this new pom.xml should do the it:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>blog</groupId>
<artifactId>example-JMS</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>example-JMS</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

<!-- BOM with wildfly JEE7 implementations -->
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-messaging-activemq</artifactId>
<version>11.0.0.CR1</version>
</dependency>

<!-- JMS dependency we need from the BOM -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>

<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-jms-client-bom</artifactId>
<version>11.0.0.CR1</version>
<type>pom</type>
</dependency>

<!-- JNDI dependency we need from the BOM -->
<!-- https://mvnrepository.com/artifact/org.wildfly/wildfly-ejb-client-bom -->
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-ejb-client-bom</artifactId>
<version>11.0.0.CR1</version>
<type>pom</type>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>


</project>


If you do a Maven-->update or something similar in your IDE the compiling errors should disappear, as the IDE updates the libraries of the project according to the indications of the pom.xml. To compile your project, you need to define the compile goal. For Eclipse, the following figure should help:


You may press the 'Run' button to compile. Please check my previous message for more details on Maven. We are only a few steps away from running the basic JMS example. First, we need to configure the 'doLookup' operation. For this, we use a jndi.properties file that goes to a new folder that you should create, src/main/resources. This is important, as the programs may not run without the resources  folder:

java.naming.factory.initial=org.wildfly.naming.client.WildFlyInitialContextFactory
java.naming.provider.url=http-remoting://localhost:8080

jboss.naming.client.ejb.context=false


This sets the class and port that are used to do the lookup for the ConnectionFactory and Destination that both, sender and receiver, must use.

Finally, we need to create the queue that we are using in WildFly, more precisely, 'QueueExample'. Before we do it, we need to create a management user, to create the 'QueueExample' queue in the browser. We also need to set up an application user called 'joao' with password 'br1o+sa*'. For this, you should run add-user.sh command on the bin directory of WildFly:

filipius-portatil: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)

The options are really straightforward, but I detailed them in a previous message, here and won't repeat them here.

We are now in conditions to start with WildFly. Just go to the bin directory of WildFly (if you are not there yet), and do the following on the command line:

./standalone.sh -c standalone-full.xml

the standalone-full.xml configuration launches the ActiveMQ provider, which is not active by default (i.e, when the -c option is not provided).

If everything goes well you should see a large number of lines, including some like these, referring to ActiveMQ Artemis:

12:53:38,900 INFO  [org.apache.activemq.artemis.core.server] (ServerService Thread Pool -- 68) AMQ221007: Server is now live
12:53:38,900 INFO  [org.apache.activemq.artemis.core.server] (ServerService Thread Pool -- 68) AMQ221001: Apache ActiveMQ Artemis Message Broker version 1.5.5.jbossorg-007 [default, nodeID=38071513-93d3-11e7-b595-58b03576e95b] 

It is now time to configure the queue 'ExampleQueue' via a web interface. Just hit 'localhost:9990' in the browser. After a few clicks you should arrive here (look at the 'Queues/Topics' button on the right-top):


Now, we must add the previously mentioned queue and give it a public name (compare this to the lookup in the code):


Once this queue is set, we can now run our sender and receiver to get the 'Hello World, JMS!' output.

Let's do another (typical) exercise and create an asynchronous receiver. The System.in.read() in this code illustrates the possibility of doing something else, like reading from another queue, while waiting for an asynchronous event from 'QueueExample':

package blog.example_JMS;

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.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class AsyncReceiver implements MessageListener {
private ConnectionFactory cf;
private Destination d;
public AsyncReceiver() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/QueueExample");
}

@Override
public void onMessage(Message msg) {
try {
System.out.println(((TextMessage) msg).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}

public void receive() throws IOException {
try (JMSContext cntxt = this.cf.createContext("joao", "br1o+sa*")) {
JMSConsumer cons = cntxt.createConsumer(d);
cons.setMessageListener(this);
System.in.read();
}
}
public static void main(String[] args) throws NamingException, IOException {
new AsyncReceiver().receive();
}


}

1 comment:

  1. Hi, I'm trying to run our sender and receiver (in Eclipse Oxygen, WildFly 11) but it gives me the following error:

    Exception in thread "main" javax.naming.NameNotFoundException: blog.example_JMS -- service jboss.naming.context.java.jboss.exported."blog.example_JMS"
    at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:106)
    at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:207)
    at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:193)
    at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:189)
    at org.wildfly.naming.client.remote.RemoteServerTransport.handleLookup(RemoteServerTransport.java:185)
    at org.wildfly.naming.client.remote.RemoteServerTransport$1.handleMessage(RemoteServerTransport.java:106)
    at org.jboss.remoting3.remote.RemoteConnectionChannel.lambda$handleMessageData$3(RemoteConnectionChannel.java:430)
    at org.jboss.remoting3.EndpointImpl$TrackingExecutor.lambda$execute$0(EndpointImpl.java:926)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)


    Can you help me to understand what I'm doing wrong?

    Thanks

    ReplyDelete