As required by the J2EE v1.4 specification, application components (servlets, JSP pages and enterprise beans) can use JMS for Java messaging. Furthermore, applications can use Message-driven Beans for asynchronous EJB method invocation, as specified by the EJB 2.1 specification.
Starting with the JOnAS 3.1 version, JOnAS supports the Java Message Service Specification 1.1. Previously in JMS 1.0.2, client programming for Point-to-point and Pub/Sub domains was achieved using similar, but separate, class hierarchies. Now, JMS 1.1 offers a domain-independent approach to programming the client application. Thus, the programming model is simpler and it is now possible to engage queues and topics in the same transaction.
Enterprise Bean providers can use JMS Connection Factory resources via resource references, and JMS Destination resources (JMS Queues and JMS Topics) via resource environment references. Thus, they are able to provide JMS code, inside an EJB method or web component method, for sending or synchronously receiving messages to/from a JMS Queue or Topic.
The EJB container and the Web container can allow for JMS operations within a global transaction, which may include other resources such as databases.
JOnAS integrates a third party JMS implementation (JORAM) which is the default JMS service, and for which a J2EE1.4-compliant Resource Adapter archive file is also provided. Other JMS providers, such as SwiftMQ and WebSphere MQ, may easily be integrated.
Starting with release 4.1, a JMS provider can be integrated within JOnAS by deploying a corresponding resource adapter. This is the preferred method as the JMS service will eventually become deprecated in later JOnAS releases. Also, this method allows deployment of 2.1 MDBs (not possible with the JMS service).
For performing JMS operations, JMS-administered objects will be used by the application components, such as connection factories and destinations. Refer to the JMS Administration section for an explanation of how to create those objects.
Additionally, the SwiftMQ product and IBM's WebSphere MQ have been used with JOnAS.
Refer to the JOnAS example jms
as a supplement to this
present reading. This example jms is described here.
javax.jms.ConnectionFactory
,
javax.jms.QueueConnectionFactory
and
javax.jms.TopicConnectionFactory
) are connection factories
for JMS connection objects.javax.mail.Session
or
javax.mail.internet.MimePartDataSource
) are connection
factories for Java Mail connection objects.Note that starting with JMS 1.1, it is recommended that only the
javax.jms.ConnectionFactory
be used (rather than
javax.jms.QueueConnectionFactory
or
javax.jms.TopicConnectionFactory
). However, the new
implementation is fully backwards compatible and existing applications will
work as is.
The standard deployment descriptor should contain the following
resource-ref
element:
<resource-ref> <res-ref-name>jms/conFact</res-ref-name> <res-type>javax.jms.ConnectionFactory</res-type> <res-auth>Container</res-auth> </resource-ref>This means that the programmer will have access to a
ConnectionFactory
object using the JNDI name
java:comp/env/jms/conFact. The source code for obtaining the factory
object is the following:
ConnectionFactory qcf = (ConnectionFactory) ctx.lookup("java:comp/env/jms/conFact");The mapping to the actual JNDI name of the connection factory (as assigned by the JMS provider administration tool), CF in the example, is defined in the JOnAS-specific deployment descriptor with the following element:
<jonas-resource> <res-ref-name>jms/conFact</res-ref-name> <jndi-name>CF</jndi-name> </jonas-resource>
<resource-env-ref> <resource-env-ref-name>jms/stockQueue</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue<resource-env-ref-type> </resource-env-ref>The application component's source code should contain:
Queue q = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");the mapping to the actual JNDI name (e.g. "myQueue") being defined in the JOnAS-specific deployment descriptor in the following way:
<jonas-resource-env> <resource-env-ref-name>jms/stockQueue</resource-env-ref-name> <jndi-name>myQueue<jndi-name> </jonas-resource-env>
void sendMyMessage() { ConnectionFactory cf = (ConnectionFactory) ctx.lookup("java:comp/env/jms/conFact"); Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue"); Connection conn = cf.createConnection(); Session sess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); MessageProducer mp = sess.createProducer((Destination)queue); ObjectMessage msg = sess.createObjectMessage(); msg.setObject("Hello"); sender.send(msg); sess.close(); conn.close(); }It is also possible for an application component to synchronously receive a message. An EJB method performing synchronous message reception on a queue is illustrated in the following:
public String recMsg() { ConnectionFactory cf = (ConnectionFactory) ctx.lookup("java:comp/env/jms/conFact"); Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue"); Connection conn = cf.createConnection(); Session sess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); MessageConsumer mc = sess.createConsumer((Destination)queue); conn.start(); ObjectMessage msg = (ObjectMessage) mc.receive(); String msgtxt = (String) msg.getObject(); sess.close(); conn.close(); return msgtxt; }A method that performs JMS operations should always contain the session create and close statements, as follows:
public void doSomethingWithJMS (...) { ... session = connection.createSession(...); ... // JMS operations session.close(); }The contained JMS operations will be a part of the transaction, if there is one, when the JOnAS server executes the method.
Be sure to never send and receive a particular message in the same transaction, since the JMS sending operations are actually performed at commit time only.
The previous examples illustrate point-to-point messaging. However,
application components can also be developed using the publish/subscribe JMS
API, i.e. using the Topic
instead of the Queue
destination type. This offers the capability of broadcasting a message to
several message consumers at the same time. The following example illustrates
a typical method for publishing a message on a JMS topic and demonstrates how
interfaces have been simplified since JMS 1.1.
public void sendMsg(java.lang.String s) { ConnectionFactory cf = (ConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); Topic topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener"); Connection conn = cf.createConnection(); Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); MessageConsumer mc = session.createConsumer((Destination)topic); ObjectMessage message = session.createObjectMessage(); message.setObject(s); mc.send(message); session.close(); conn.close(); }
public class EjbCompBean implements SessionBean { ... QueueConnectionFactory qcf = null; Queue queue = null; public void ejbCreate() { .... ictx = new InitialContext(); qcf = (QueueConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); queue = (Queue) ictx.lookup("java:comp/env/jms/queue1"); } public void doSomethingWithJMS (...) { ... Connection conn = qcf.createConnection(); Session session = conn.createSession(...); ... // JMS operations session.close(); conn.close(); } ... }To keep the connection open during the life of a bean instance, the programming style shown in the following example is preferred, since it avoids many connection opening and closing operations:
public class EjbCompBean implements SessionBean { ... ConnectionFactory qcf = null; Queue queue = null; Connection conn = null; public void ejbCreate() { .... ictx = new InitialContext(); cf = (ConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); queue = (Queue) ictx.lookup("queue1"); conn = cf.createConnection(); } public void doSomethingWithJMS (...) { ... Session session = conn.createSession(...); ... // JMS operations session.close(); } public void ejbRemove() { conn.close(); } ... }Be aware that maintaining JMS objects in the bean state is not always possible, depending on the type of bean.
Note that, due to a known problem with the Sun JDK 1.3 on Linux, the close of the connection can block. The problem is fixed with JDK 1.4.
public class EjbCompBean implements SessionBean { ... public void doSomethingWithJMS (...) { ... Connection conn = cf.createConnection(); Session session = conn.createSession(...); ut = ejbContext.getUserTransaction(); ut.begin(); ... // JMS operations ut.commit(); session.close(); conn.close(); } ... }To have the session operations involved in the transaction, the session creation and close should be inside the transaction boundaries, and the connection creation and close operations can either be both outside the transaction boundaries or both inside the transaction boundaries, as follows:
public class EjbCompBean implements SessionBean { ... public void doSomethingWithJMS (...) { ... Connection conn = qcf.createConnection(); ut = ejbContext.getUserTransaction(); ut.begin(); Session session = conn.createSession(...); ... // JMS operations session.close(); ut.commit(); conn.close(); } ... }or
public class EjbCompBean implements SessionBean { ... public void doSomethingWithJMS (...) { ... ut = ejbContext.getUserTransaction(); ut.begin(); Connection conn = cf.createConnection(); Session session = conn.createSession(...); ... // JMS operations session.close(); conn.close(); ut.commit(); } ... }Programming EJB components with bean-managed transactions can result in complex code. Using container-managed transactions can help avoid problems such as those previously described.
As provided, the default JMS service and JORAM adapter configurations automatically create six connection factories and two destination objects.
The six connection factories automatically created are described in the following table:
JNDI name | JMS type | Usage |
CF | ConnectionFactory | To be used by an application component to create a Connection. |
QCF | QueueConnectionFactory | To be used by an application component to create a QueueConnection. |
TCF | TopicConnectionFactory | To be used by an application component to create a TopicConnection. |
JCF | ConnectionFactory | To be used by any other Java component (for instance a client) to create a Connection. |
JQCF | QueueConnectionFactory | To be used by any other Java component (for instance a client) to create a QueueConnection. |
JTCF | TopicConnectionFactory | To be used by any other Java component (for instance a client) to create a TopicConnection. |
The CF, QCF and TCF connection factories are managed connection
factories. The application components should use only managed connection
factories to allow JOnAS to manage the JMS resources created via these
connection factories (the JMS sessions).
In contrast, JCF, JQCF and JTCF are non-managed connection factories.
They are used by Java components implementing a JMS client behavior, but
running outside the application server.
The two destinations automatically created are described in the following table:
JNDI name | JMS type | Usage |
sampleQueue | Queue | Can be equally used by an EJB component or a Java component. |
sampleTopic | Topic | Can be equally used by an EJB component or a Java component. |
jonas.services security,jtm,dbm,jms,ejb
JOnAS will not create additional connection factories when using the default configuration. However, JOnAS can create requested destination objects at server launching time, if specified in the jonas.properties file. To do this, specify the JNDI names of the Topic and Queue destination objects to be created in a jonas.service.jms.topics and jonas.service.jms.queues property respectively, as follows:
jonas.service.jms.topics t1,t2 // JOnAS server creates 2 topic destinations (t1,t2) jonas.service.jms.queues myQueue // JOnAS server creates 1 queue destination (myQueue)It is recommended that programmers use resource references and resource environment references to access the connection factories and destination objects created by JOnAS, as already presented in the "Writing JMS operations within an application component" section.
Starting with JOnAS release 4.1, it is recommended that a JMS resource adapter be deployed instead of using the jms service. Refer to the JMS Resource Adapters configuration guide for an explanation.
jonas start
The Message-Oriented Middleware (the JMS provider implementation) is automatically started (or at least accessed) and the JMS-administered objects that will be used by the Enterprise Beans are automatically created and registered in JNDI.
Then, the EJB can be deployed as usual with:
jonas admin -a XX.jar
For launching the MOM, consider the following possibilities:
$JONAS_ROOT/conf
directory).
jonas.service.jms.collocated trueIn this case, the MOM will be launched automatically at server launching time (command jonas start).
For other MOMs, use the proprietary command.
In this case, the JOnAS property jonas.service.jms.collocated must
be set to false in the jonas.properties file.
jonas.service.jms.collocated false
jonas.service.jms.collocated false jonas.service.jms.url joram://host2:16010
Note: the MOM may be directly launched by the proprietary
command. The command for JORAM is:
java -DTransaction=fr.dyade.aaa.util.NullTransaction
fr.dyade.aaa.agent.AgentServer 0 ./s0
This command corresponds to the default options used by the JmsServer command.
The server is not persistent when launched with this command. If persistence is required, the -DTransaction=fr.dyade.aaa.util.NullTransaction option should be replaced with the -DTransaction=fr.dyade.aaa.util.ATransaction option.
To change other MOM configurations (distribution, multi-servers, ...), refer to the JORAM documentation on http://joram.objectweb.org.
Starting with JOnAS release 4.1, a JMS server can be accessed through a resource adapter which may be deployed.
For deploying such a resource adapter, place the corresponding archive
file (*.rar
) in the JOnAS's rars/autoload
directory, or declare it at the end of the jonas.properties
file, or deploy it manually through the jonasAdmin tool.
Configuring and deploying such adapters is explained in the Configuring JMS Resource Adapters section.
public class EjbCompBean implements SessionBean { ... ConnectionFactory cf = null; Topic topic = null; public void ejbCreate() { .... ictx = new InitialContext(); cf = (ConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener"); } ... }This code has been intentionally cleared from all the elements in which it is not necessary for understanding the JMS logic aspects of the example, e.g. exception management.
The JMS-administered objects ConnectionFactory and Topic
have been made available to the bean by a resource reference in the
first example, and by a resource environment reference in the second
example.
The standard deployment descriptor should contain the following element:
<resource-ref> <res-ref-name>jms/conFactSender</res-ref-name> <res-type>javax.jms.ConnectionFactory</res-type> <res-auth>Container</res-auth> </resource-ref>
<resource-env-ref> <resource-env-ref-name>jms/topiclistener</resource-env-ref-name> <resource-env-ref-type>javax.jms.Topic</resource-env-ref-type> </resource-env-ref>The JOnAS-specific deployment descriptor should contain the following element:
<jonas-resource> <res-ref-name>jms/conFactSender</res-ref-name> <jndi-name>TCF</jndi-name> </jonas-resource>
<jonas-resource-env> <resource-env-ref-name>jms/topiclistener</resource-env-ref-name> <jndi-name>sampleTopic</jndi-name> </jonas-resource-env>Note that the EjbComp SessionBean will use the administered objects automatically created by JOnAS in the default JMS configuration.
Because the administered objects are now accessible, it is possible to perform JMS operations within a method. The following occurs in the sendMsg method:
public class EjbCompBean implements SessionBean { ... public void sendMsg(java.lang.String s) { // create Connection, Session and MessageProducer Connection conn = null; Session session = null; MessageProducer mp = null; try { conn = cf.createConnection(); session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); mp = session.createProducer((Destination)topic); } catch (Exception e) {e.printStackTrace();} // send the message to the topic try { ObjectMessage message; message = session.createObjectMessage(); message.setObject(s); mp.send(message); session.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); } } ... }This method sends a message containing its String argument.
public class EjbCompClient { ... public static void main(String[] arg) { ... utx = (UserTransaction) initialContext.lookup("javax.transaction.UserTransaction"); ... home1 = (EjbCompHome) initialContext.lookup("EjbCompHome"); home2 = (AccountHome) initialContext.lookup("AccountImplHome"); ... EjbComp aJmsBean = home1.create(); Account aDataBean = null; ... utx.begin(); aJmsBean.sendMsg("Hello commit"); // sending a JMS message aDataBean = home2.create(222, "JMS Sample OK", 0); utx.commit(); utx.begin(); aJmsBean.sendMsg("Hello rollback"); // sending a JMS message aDataBean = home2.create(223, "JMS Sample KO", 0); utx.rollback(); ... } }The result of this client execution will be that:
public class MsgReceptor { static Context ictx = null; static ConnectionFactory cf = null; static Topic topic = null; public static void main(String[] arg) { ictx = new InitialContext(); cf = (ConnectionFactory) ictx.lookup("JCF"); topic = (Topic) ictx.lookup("sampleTopic"); ... Connection conn = cf.createConnection(); Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer mc = session.createConsumer((Destination)topic); MyListenerSimple listener = new MyListenerSimple(); mc.setMessageListener(listener); conn.start(); System.in.read(); // waiting for messages session.close(); conn.close(); ... } }
public MyListenerSimple implements javax.jms.MessageListener { MyListenerSimple() {} public void onMessage(javax.jms.Message msg) { try { if(msg==null) System.out.println("Message: message null "); else { if(msg instanceof ObjectMessage) { String m = (String) ((ObjectMessage)msg).getObject(); System.out.println ("JMS client: received message ======> " + m); } else if(msg instanceof TextMessage) { String m = ((TextMessage)msg).getText(); System.out.println ("JMS client: received message ======> " + m); } }catch(Exception exc) { System.out.println("Exception caught :" + exc); exc.printStackTrace(); } } }