Details about JMS should be found in the Java Message Service Specification 1.0.2.
In the JOnAS JMS integration, JOnAS makes use of a third party JMS implementation: currently the Joram opensource is integrated and delivered with JOnAS, the SwiftMQ product may also be used, other JMS provider implementations may easily be integrated. The JMS administered objects used by the EJB components, such as the connection factories and the destinations, may be created previously to the EJB execution, by the way of the proprietary JMS implementation administration facilities. However, JOnAS provides "wrappers" upon such JMS administration APIs, allowing simple administration operations to be achieved automatically by the EJB server itself. This avoids the deployer to cope with proprietary administration APIs or tools. See JMS Administration.
You may of course decide to use another JMS implementation. Currently the SwiftMQ product has been tested.
<resource-ref> <res-ref-name>jms/conFact</res-ref-name> <res-type>javax.jms.QueueConnectionFactory</res-type> <res-auth>Container</res-auth> </resource-ref>This means that the bean programmer will have access to a QueueConnectionFactory object using the JNDI name java:comp/env/jms/conFact. To obtain the factory object the EJB should contain the code :
QueueConnectionFactory qcf = (QueueConnectionFactory) 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), QCF in the example, will be done in the JOnAS specific deployment descriptor with the following element:
<jonas-resource> <res-ref-name>jms/conFact</res-ref-name> <jndi-name>QCF</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>And the EJB code should contain
Queue q = (Queue) ctx.lookup("java:comp/env/jms/stockQueue");the mapping to the actual JNDI name (e.g. "myQueue") being done 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() { QueueConnectionFactory qcf = (QueueConnectionFactory) ctx.lookup("java:comp/env/jms/conFact"); Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue"); QueueConnection qc = qcf.createQueueConnection(); QueueSession qs = qc.createQueueSession(true, Session.AUTO_ACKNOWLEDGE); QueueSender sender = qs.createSender(queue); ObjectMessage msg = qs.createObjectMessage(); msg.setObject("Hello"); sender.send(msg); qs.close(); qc.close(); }It is also possible for an EJB component to synchronously receive a message (asynchronous message reception is not possible in session or entity beans, it should be done through message-driven beans). An EJB method performing synchronous message reception on a queue is illustrated below:
public String recMsg() { QueueConnectionFactory qcf = (QueueConnectionFactory) ctx.lookup("java:comp/env/jms/conFact"); Queue queue = (Queue) ctx.lookup("java:comp/env/jms/stockQueue"); QueueConnection qc = qcf.createQueueConnection(); QueueSession qs = qc.createQueueSession(true, Session.AUTO_ACKNOWLEDGE); QueueReceiver qr = qs.createReceiver(queue); qc.start(); ObjectMessage msg = (ObjectMessage) qr.receive(); String msgtxt = (String) msg.getObject(); qs.close(); qc.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.create<Queue|Topic>Session(...); ... // JMS operations session.close(); }Of course, the contained JMS operations will be part of the transaction, if there is one, when the EJB server executes the method.
Be careful to never send and receive a given message in the same transaction, since the JMS sending operations are actually performed at commit time only !
The examples above illustrate point to point messaging, you may of course develop EJB components using the publish/subscribe JMS API, i.e. use the Topic instead of the Queue destination type. This allows you to broadcast a message to several message consumers at a time. The example below illustrates a typical EJB method for publishing a message on a JMS topic.
public void sendMsg(java.lang.String s) { TopicConnectionFactory tcf = (TopicConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); Topic topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener"); TopicConnection tc = tcf.createTopicConnection(); TopicSession session = tc.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); TopicPublisher tp = session.createPublisher(topic); ObjectMessage message = session.createObjectMessage(); message.setObject(s); tp.publish(message); session.close(); tc.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 (...) { ... QueueConnection qc = qcf.createQueueConnection(); QueueSession session = qc.createQueueSession(...); ... // JMS operations session.close(); qc.close(); } ... }If you would like to keep the connection opened during the life of your bean instance , the following programming style could be preferred, which avoids many connection opening and closing operations:
public class EjbCompBean implements SessionBean { ... QueueConnectionFactory qcf = null; Queue queue = null; QueueConnection qc = null; public void ejbCreate() { .... ictx = new InitialContext(); qcf = (QueueConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); queue = (Queue) ictx.lookup("queue1"); qc = qcf.createQueueConnection(); } public void doSomethingWithJMS (...) { ... QueueSession session = qc.createQueueSession(...); ... // JMS operations session.close(); } public void ejbRemove() { qc.close(); } ... }Be careful that maintaining JMS objects in the bean state, is not always possible depending on the type of the bean:
public class EjbCompBean implements SessionBean { ... public void doSomethingWithJMS (...) { ... QueueConnection qc = qcf.createQueueConnection(); QueueSession session = qc.createQueueSession(...); ut = ejbContext.getUserTransaction(); ut.begin(); ... // JMS operations ut.commit(); session.close(); qc.close(); } ... }For having the session operations involved in the transaction, you should have the session creation and close inside the transaction boundaries, and may choose to have the connection creation and close operations either both outside the transaction boundaries or both inside the transaction boundaries, as follows:
public class EjbCompBean implements SessionBean { ... public void doSomethingWithJMS (...) { ... QueueConnection qc = qcf.createQueueConnection(); ut = ejbContext.getUserTransaction(); ut.begin(); QueueSession session = qc.createQueueSession(...); ... // JMS operations session.close(); ut.commit(); qc.close(); } ... }or
public class EjbCompBean implements SessionBean { ... public void doSomethingWithJMS (...) { ... ut = ejbContext.getUserTransaction(); ut.begin(); QueueConnection qc = qcf.createQueueConnection(); QueueSession session = qc.createQueueSession(...); ... // JMS operations session.close(); qc.close(); ut.commit(); } ... }Programming EJB components with bean-managed transactions may induce complex code, that is why we recommend to use container-managed transactions, which will avoid you to face such problems as described above.
jonas.services security,jtm,dbm,jms,ejbThe four connection factories automatically created are described in the table below:
JNDI name | JMS type | Usage |
QCF | QueueConnectionFactory | To be used by an EJB component to create a QueueConnection. |
TCF | TopicConnectionFactory | To be used by an EJB component to create a TopicConnection. |
JQCF | QueueConnectionFactory | To be used by a Java component (an EJB client for instance) to create a QueueConnection. |
JTCF | TopicConnectionFactory | To be used by a Java component (an EJB client for instance) to create a TopicConnection. |
The QCF and TCF connection factories are managed connection factories.
The EJB components should use only managed connection factories in order
to allow JOnAS to manage the JMS resources created via these connection
factories (the JMS sessions).
On the contrary, JQCF and JTCF are non-managed connection factories.
They are to be used by Java component implementing a JMS client behavior,
but running outside the application server.
The two destination objects automatically created are described in the
table below:
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. |
You cannot ask JOnAS to create additional connection factories when using the default configuration. However, you may ask JOnAS to create the destination objects you want at the EJB server launching time, by specifying it in the jonas.properties file. You have to specify the JNDI names of the Topic (resp. Queue) destination objects to be created in a jonas.service.jms.topics (resp. jonas.service.jms.queues) property 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 to EJB programmers to use resource references and resource environment references to access the connection factories and destination objects created by JOnAS, as already presented in Writing JMS operations within an enterprise bean section.
EJBServerNote that when using the default configuration, the first three steps are automatically executed when launching the EJB server (the registry and the MOM are running in the server's JVM and the default administered objects are automatically created as presented in the JOnAS simple JMS administration section).
For launching the MOM, three possibilities may be considered:
jonas.service.jms.collocated trueIn that case, the MOM will be launched automatically at EJB server launching time (command EJBServer).
For other MOMs, you may use the proprietary command.
In that 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.
In the case of Joram, the command is:
This corresponds to the default options used by the JmsServer
command.
The JMS messages are not persistent when launching the MOM with this command. If your messages need to be persistent, you should replace the -DTransaction=NullTransaction option by the -DTransaction=ATransaction option. See the Joram documentation for more details about this command.
public class EjbCompBean implements SessionBean { ... TopicConnectionFactory qcf = null; topic queue = null; public void ejbCreate() { .... ictx = new InitialContext(); qcf = (TopicConnectionFactory) ictx.lookup("java:comp/env/jms/conFactSender"); topic = (Topic) ictx.lookup("java:comp/env/jms/topiclistener"); } ... }This code has been voluntary cleared from all the elements which are not necessary for understanding the JMS logic aspects of the example, e.g. the exception management ...
The JMS administered objects TopicConnectionFactory and Topic
have been made available to the bean by a resource reference for
the first one, and by a resource environment reference for the second
one.
The standard deployment descriptor should contain the following element:
<resource-ref> <res-ref-name>jms/conFactSender</res-ref-name> <res-type>javax.jms.TopicConnectionFactory</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.
The administered objects being accessible it is now possible to perform within a method some JMS operations. This is what is done in the sendMsg method:
public class EjbCompBean implements SessionBean { ... public void sendMsg(java.lang.String s) { // create TopicConnection, TopicSession and TopicSender TopicConnection tc = null; TopicSession session = null; TopicPublisher tp = null; try { tc = tcf.createTopicConnection(); session = tc.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); tp = session.createPublisher(topic); } catch (Exception e) {e.printStackTrace();} // send the message to the topic try { ObjectMessage message; message = session.createObjectMessage(); message.setObject(s); tp.publish(message); session.close(); tc.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 TopicConnectionFactory tcf = null; static Topic topic = null; public static void main(String[] arg) { ictx = new InitialContext(); tcf = (TopicConnectionFactory) ictx.lookup("JTCF"); topic = (Topic) ictx.lookup("sampleTopic"); ... TopicConnection tc = tcf.createTopicConnection(); TopicSession session = tc.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); TopicSubscriber ts = session.createSubscriber(topic); MyListenerSimple listener = new MyListenerSimple(); ts.setMessageListener(listener); tc.start(); System.in.read(); // waiting for messages session.close(); qc.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(); } } }
jonas.services security,jtm,dbm,jms,ejbThe JMS Server is collocated to the JOnAS server and a topic named sampleTopic is created when launching the JOnAS server with the EJBServer command.