Apache TomEE and Apache Artemis
In a previous post I showed how to use Apache Artemis - the JMS 2 implementation or ActiveMQ "5+1" - with Apache Meecrowave - the very lightweight CDI/JAX-RS container. However if you are used to EE and not yet to build/wire your applications yourself ("à la Spring") then it can look really heavy or complex in term of code. A more natural EE way is to use message driven beans (MDB) which can automatically listen for the messages and then the client side is pretty close since it will reuse JMSContext but will use an externally configured ConfigurationFactory instead of building it from the application.
Let's see how to use TomEE this time as a backbone for Artemis!
What we want to achieve
In term of code this post just wants to be able to deploy automatically a MDB:
@MessageDriven(activationConfig = {
// will be defined later but we want to listen a queue
})
public class Listener implements MessageListener {
@Inject
private Event<Message> msg;
@Override
public void onMessage(final Message message) {
msg.fire(message);
}
}
As you can see to abstract the processing of the message we just fire a CDI event - this is far enough for this post but not a bad practise in real life applications since it will decouple the transport from the processing.
In term of client it will be even simpler:
@Resource(name = "somequeue")
private Queue queue;
@Inject
@JMSConnectionFactory("artemisConnectionFactory")
private JMSContext jmsContext;
// and somewhere
jmsContext.createProducer().send(queue, stringMessage);
Simple right? If you use JMS 1.1 API see how it is smooth compared to that, now old, time. No need of 3 nested try/catch without AutoCloseable.
Deployment setup
Because of the server side we'll need to integrate with Artemis resource adapter.
A resource adapter is just the "user" code (read is as pluggable way) which integrates with the EJB/MDB container.
Concretely the MDB container will give EJB instances (proxies handling the EJB pool if configured) to the resource adapter which will "listen" on the configured interface(s) (JMS Queue or Topic for JMS) and forward the "messages" to the EJB.
Note: if not obvious MDB doesn't mean JMS but any listening API, you can listen for tasks, (S)FTP files etc... See for instance my hazelcast-mdb demo for a hazelcast message oriented implementation.
With TomEE you can deploy resource adapters directly (as .rar) but you can also just deploy them as plain resource. We'll use that solution which avoids to add yet another application in the container and just replaces it by container resources.
What do we need?
- define the resource adapter (MDB-JMS link)
- define a connection factory for our client/JMSContext
- define our queue (named somequeue for the sample)
- define our MDB container linked to our resource adapter
The artemis resource adapter
Our resource adapter will connect to a broker on port 1234 of host localhost. To do that simply define this resource:
<Resource id="artemis" class-name="org.apache.activemq.artemis.ra.ActiveMQResourceAdapter">
ConnectorClassName=org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
ConnectionParameters=host=localhost;port=1234
JndiParams=java.naming.factory.initial=org.apache.openejb.core.OpenEJBInitialContextFactory
</Resource>
- We define the resource based on the implementation of artemis (class-name)
- Connector* properties define how to connect to the broker (we use the tcp/netty implementation and connect on port 1234 of localhost
- JndiParams allows to configure how to lookup destinations (queue for us). Since we define a resource in an unmanaged environment (understand not in tomcat JNDI context) we need to specify to use OpenEJB context.
The artemis connection factory
Defining the connection factory is as easy except the connection url is simpler using an URI syntax there. The trick used there is to build it from its constructor and bind a name to constructor parameter to enable to configure it using resource syntax:
<Resource id="connectionFactory"
class-name="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory"
constructor="uri,username,password"
type="javax.jms.ConnectionFactory">
uri=tcp://localhost:12345
# username = ...
# password = ...
</Resource>
Note it is important there to define the type to let TomEE be able to wire it back when a connection factory is needed.
The Queue
Defining a queue is close to the connection factory except we'll use a static factory method:
<Resource id="somequeue"
class-name="org.apache.activemq.artemis.api.jms.ActiveMQJMSClient"
constructor="name"
factory-name="createQueue"
type="javax.jms.Queue">
name = test
<Resource>
Here we define a queue named somequeue as the resource (@Resource) but test in the artemis system (bindings).
The MDB container
Finally we need to configure our MDB container and make OpenEJB/TomEE use our artemis resource adapter:
<Container id="mdb" type="MESSAGE">
InstanceLimit = -1
ResourceAdapter = artemis
ActivationSpecClass = org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec
</Container>
Here the trick is to not limit the number of instances cause artemis doesn't behave like ActiveMQ (5) which creates the instance then keep it whatever happens on the network but artemis recreates an instance if the network fails at the connection without releasing it. This is a limitation of javax.resource.spi.endpoint.MessageEndpointFactory which makes the InstanceLimit count not that useful. However since then the MDB allows to configure the number of sessions this is not a big deal in practise.
Final configuration: the queue to listen
Finally we need to listen for our queue on our MDB. There are two options there:
- let's artemis auto create the queue from its name (in the binding system, not the container system)
- request artemis to lookup the queue from openejb jndi system
The first one will look like:
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destination", propertyValue = "test")
})
public class Listener implements MessageListener {
@Override
public void onMessage(final Message message) {
// ...
}
}
Where the second one would be:
?@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destination", propertyValue = "somequeue")
@ActivationConfigProperty(propertyName = "useJNDI", propertyValue = "true")
})
public class Listener implements MessageListener {
@Override
public void onMessage(final Message message) {
// ...
}
}
What about JTA
All that runs but without JTA. Artemis uses a "locator" to find the transaction manager and surprisingly it doesn't provide a default implementation. I opened https://issues.apache.org/jira/browse/ARTEMIS-1070 to try to enhance it in next version. What is funny is if the resource adapter would have a setTransactionManager(TransactionManager) then TomEE would automatically integrates with it.
If you want to integrates TomEE and Artemis at JTA level you will need to implement org.apache.activemq.artemis.service.extensions.transactions.TransactionManagerLocator:
import org.apache.activemq.artemis.service.extensions.transactions.TransactionManagerLocator;
import org.apache.openejb.OpenEJB;
import javax.transaction.TransactionManager;
public class OpenEJBTransactionManagerLocator implements TransactionManagerLocator {
@Override
public TransactionManager getTransactionManager() {
return OpenEJB.getTransactionManager();
}
}
Since it is a SPI don't forget to create META-INF/services/org.apache.activemq.artemis.service.extensions.transactions.TransactionManagerLocator with the fully qualified name of OpenEJBTransactionManagerLocator.
Note that an alternative implementation just using a JNDI lookup would also work.
The dependencies to add
To add Artemis you need to add it to TomEE. The most common deployment will be to add them in tomee/lib (I recommand you to use common.loader and not merge them physically in this folder).
The dependencies to add are:
<properties>
<artemis.version>2.0.0</artemis.version>
</properties>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client</artifactId>
<version>${artemis.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-core-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jms_2.0_spec</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-ejb_3.0_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-ra</artifactId>
<version>${artemis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-server</artifactId>
<version>${artemis.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-core-client</artifactId>
<version>${artemis.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-core-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
You can add these dependencies in a pom.xml and run
mvn dependency:copy-dependencies
to get the jar to add copies in target/dependencies.
Conclusion
JCA specification (the one defining resource adapters) allows to integrate very heterogeneous systems quite well even if the API to do that is not that trivial.
In this post we saw how to use the brand new Apache Artemis and therefore the power of JMS 2.0 with Apache TomEE+. It is mainly a matter of adding some libraries and defining some resources (until you need JTA).
Happy JMS!
From the same author:
In the same category: