In our current application using JBoss EAP 6.2, we have many batch jobs triggered by remote EJB invocations. In order to centralize all notification logic for these jobs we are deciding to route all calls through an MDB by passing a serialized message. The intended flow is as below:
Batch job client sends message to a remote queue
MDB listens on this remote queue, process message and invokes EJB
DLQ is configured to process notifications when all retries are
exhausted
Notification should also be sent on each retry. To avoid too many
notifications, retry interval is sufficiently high
To handle the last point, I tried creating a Reply queue by setting it in the JMSReplyTo header. To simulate above flow, I have created the below MDB implementations...
Main MDB:
#MessageDriven(name = "MiddleManMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/test"),
#ActivationConfigProperty(propertyName = "connectorClassName", propertyValue = "org.hornetq.core.remoting.impl.netty.NettyConnectorFactory"),
#ActivationConfigProperty(propertyName = "connectionParameters", propertyValue = "host=localhost;port=5445"),
#ActivationConfigProperty(propertyName = "user", propertyValue = "queueuser"),
#ActivationConfigProperty(propertyName = "password", propertyValue = "queuepassword")
})
public class MiddleManMDB implements MessageListener {
private static final Logger LOGGER = LoggerFactory.getLogger(MiddleManMDB.class);
#Resource(name = "java:/JmsXA")
private ConnectionFactory connectionFactory;
/*
* (non-Javadoc)
* #see javax.jms.MessageListener#onMessage(javax.jms.Message)
*/
#Override
public void onMessage(Message message)
{
try {
if (message instanceof TextMessage) {
LOGGER.info("Received text message --> {}", ((TextMessage)message).getText());
}
throw new JMSException("thrown exception");
}
catch (Exception e) {
sendToReplyQueue(e.getMessage(), message);
LOGGER.info("Throwing exception to simulate retry...");
throw new RuntimeException(e);
}
}
private void sendToReplyQueue(String errorMessage, Message message)
{
Context context = null;
Connection conn = null;
LOGGER.info("Sending exception details to reply queue...");
try {
context = new InitialContext();
conn = connectionFactory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination jmsReplyTo = message.getJMSReplyTo();
MessageProducer replyProducer = session.createProducer(jmsReplyTo);
replyProducer.send(jmsReplyTo, session.createTextMessage(errorMessage));
}
catch (NamingException | JMSException e) {
e.printStackTrace();
}
finally {
// close connection and context
}
}
}
Reply MDB:
#MessageDriven(name = "ReplyMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/reply")
})
public class ReplyMDB implements MessageListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ReplyMDB.class);
#Override
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
LOGGER.info("Received reply message --> " + ((TextMessage)message).getText());
}
}
catch (JMSException e) {
LOGGER.error("Error in reply queue...", e);
}
}
}
** Dead Letter MDB:**
#MessageDriven(name = "DeadLetterMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/dead")
})
public class DeadLetterMDB implements MessageListener {
private static final Logger LOGGER = LoggerFactory.getLogger(DeadLetterMDB.class);
#Override
public void onMessage(Message message) {
try {
LOGGER.info("Message has arrived in dead letter queue");
LOGGER.info("Current delivery count - {}", message.getIntProperty("JMSXDeliveryCount"));
if (message instanceof TextMessage) {
LOGGER.info("Received text message --> {}", ((TextMessage)message).getText());
}
}
catch (JMSException e) {
e.printStackTrace();
}
}
}
** Client:**
public static void main(String[] args) {
Connection connection = null;
Context context = null;
try {
// create context and connection factory
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = (Destination) context.lookup("jms/queue/test");
Destination replyDest = (Destination) context.lookup("jms/queue/reply");
MessageProducer producer = session.createProducer(destination);
connection.start();
TextMessage message = session.createTextMessage("Hello World");
message.setJMSReplyTo(replyDest);
producer.send(message);
}
catch (NamingException | JMSException e) {
e.printStackTrace();
}
finally {
// close context and connection
}
}
** Relevant entries in standalone-full.xml:**
<address-settings>
<address-setting match="jms.queue.testQueue">
<dead-letter-address>jms.queue.DLQ</dead-letter-address>
<expiry-address>jms.queue.ExpiryQueue</expiry-address>
<redelivery-delay>1000</redelivery-delay>
<max-delivery-attempts>3</max-delivery-attempts>
<max-size-bytes>10485760</max-size-bytes>
<address-full-policy>BLOCK</address-full-policy>
<message-counter-history-day-limit>10</message-counter-history-day-limit>
</address-setting>
<address-setting match="jms.queue.replyQueue">
<redelivery-delay>1000</redelivery-delay>
<max-delivery-attempts>3</max-delivery-attempts>
<max-size-bytes>10485760</max-size-bytes>
<address-full-policy>BLOCK</address-full-policy>
<message-counter-history-day-limit>10</message-counter-history-day-limit>
</address-setting>
<address-setting match="jms.queue.DLQ">
<redelivery-delay>1000</redelivery-delay>
<max-delivery-attempts>3</max-delivery-attempts>
<max-size-bytes>10485760</max-size-bytes>
<address-full-policy>BLOCK</address-full-policy>
<message-counter-history-day-limit>10</message-counter-history-day-limit>
</address-setting>
</address-settings>
<jms-destinations>
<jms-queue name="testQueue">
<entry name="queue/test"/>
<entry name="java:jboss/exported/jms/queue/test"/>
</jms-queue>
<jms-queue name="replyQueue">
<entry name="queue/reply"/>
<entry name="java:jboss/exported/jms/queue/reply"/>
</jms-queue>
<jms-queue name="DLQ">
<entry name="queue/dead"/>
<entry name="java:jboss/exported/jms/queue/dead"/>
</jms-queue>
<jms-topic name="testTopic">
<entry name="topic/test"/>
<entry name="java:jboss/exported/jms/topic/test"/>
</jms-topic>
</jms-destinations>
Now with the above flow in the MDBs, the message is never received in the reply queue. All three queues are deployed on the same server.
I am guessing the reason is the below line:
sendToReplyQueue(e.getMessage(), message);
LOGGER.info("Throwing exception to simulate retry...");
throw new RuntimeException(e);
Since the send is asynchronous and I am throwing an RTE (to trigger retry), the message is somehow never sent. Is there a way to resolve this problem ?
I am guessing the reason is the below line......
You can try with commenting RTE. Also add some more logger to trace. check if reply destination set properly or not.
message.setJMSReplyTo(replydestination);
LOGGER.info("Reply to: " + message.getJMSReplyTo());
or message sent to replay queue or not
replyProducer.send(jmsReplyTo, session.createTextMessage(errorMessage));
LOGGER.info("exception details sent to reply queue...");
Related
I have a question about Message Driven Beans (MDB). Is there a way to generate these only at runtime? I want to provide a way in my backend to start or stop receiving messages. It should be controlled via a configuration entry in the database. When starting WildFly, it should also first check whether the MDBs can be started.
Is it Java EE compliant to do without an MDB and create the listeners manually?
I am currently using the following code
#MessageDriven(name = "MyMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "maxSession", propertyValue = "2"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "java:/jms/queue/Test"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")})
public class JmsConsumer implements MessageListener {
#Override
public void onMessage(final Message msg) {
if (msg instanceof TextMessage) {
try {
final String text = ((TextMessage) msg).getText();
System.out.println("message: " + text + " (" + msg.getJMSRedelivered() + ")");
} catch (final JMSException e) {
e.printStackTrace();
}
}
}
}
Would this code also conform to Java EE?
#Singleton
#LocalBean
public class QueueWorkerManager {
private InitialContext initialContext = null;
private QueueConnectionFactory queueConnectionFactory = null;
private Queue queue = null;
private QueueConnection queueConnection = null;
private QueueSession queueSession = null;
private MessageConsumer consumer = null;
#PostConstruct
public void init() {
try {
this.initialContext = new InitialContext();
this.queueConnectionFactory = (QueueConnectionFactory) initialContext
.lookup("java:/ConnectionFactory");
this.queue = (Queue) initialContext.lookup(MyQueueSender.WORKER_QUEUE);
this.queueConnection = queueConnectionFactory.createQueueConnection();
this.queueSession = queueConnection.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
this.consumer = queueSession.createConsumer(this.queue);
this.consumer.setMessageListener(new ConsumerMessageListener());
this.queueConnection.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
#PreDestroy
public void destroy() {
this.stopConsumer(this.consumer;
if(this.consumer != null) {
try {
this.consumer.close();
} catch (JMSException e) {
}
this.consumer = null;
}
if(this.queueSession != null) {
try {
this.queueSession.close();
} catch (JMSException e) {
}
this.queueSession = null;
}
if(this.queueConnection != null) {
try {
this.queueConnection.close();
} catch (JMSException e) {
}
this.queueConnection = null;
}
}
}
public class ConsumerMessageListener implements MessageListener {
#Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("message: " + textMessage.getText() + " (" + msg.getJMSRedelivered() + ")");
message.acknowledge();
} catch (JMSException | InterruptedException e) {
e.printStackTrace();
}
}
}
I would suggest you to
add a #DeliveryActive(false/true) annotation to your MDB in order to boot it in the desired initial state
Use JMX or JBoss CLI to programatically change the value of the active attribute of your MDB
Everything is explained here:
https://docs.wildfly.org/16/Developer_Guide.html#Message_Driven_Beans_Controlled_Delivery
Good luck
I want to publish message for which I am writing a JMS application which will publish messages to Tibco EMS queues. There are two queues one is for normal logging and another for exception logging. Now how to send message to two different queues in JMS. Can anyone help me with this as it is very critical?
The very basic JMS API code to send message to queue is shown below. You need to adjust the connection factory as well queue name as per your environment. Also need to adjust initial context settings.
void sendMessage() {
Connection con = null;
try {
// Get the initial context
Hashtable<String, String> hTable = new Hashtable<String, String>();
hTable.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
hTable.put(Context.PROVIDER_URL, "t3://localhost:7001");
Context ctx = new InitialContext(hTable);
// Create ConnectionFactory
ConnectionFactory cf = (ConnectionFactory) ctx.lookup("JMS-JNDI-ConFactory");
// Create connection
con = cf.createConnection();
// Create Non transacted Session with auto ack
Session session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Create the destination
Queue queue = (Queue) ctx.lookup("JMS-JNDI-Queue");
// Create MessageProducer for the destination
MessageProducer producer = session.createProducer(queue);
// Create empty Message with header and properties only
TextMessage message = session.createTextMessage();
// set the message body
message.setText("Message-1");
// Send the message
producer.send(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
try {
con.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
This is an old topic, but maybe it can help.
To send a message to a JMS queue actually need the followings:
Context context = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
Queue queue;
And this is how it works:
context = getContext(host, port, user, password);
queueConnection = getConnectionFactory(context, connectionFactoryJndi);
queueSession = getQueueSession(queueConnection);
queue = getQueue(context, queueJndi);
// send a text message
queueConnection.start();
String message = "hello";
sendMessageToQueue(verbose, message, queueSession, queue);
queueConnection.stop();
To obtain the context you need to connect to the server:
private Context getContext(String host, int port, String user, String password) throws NamingException {
String url = String.format("%s://%s:%d", protocol, host, port);
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_PRINCIPAL, user);
env.put(Context.SECURITY_CREDENTIALS, password);
return new InitialContext(env);
}
Get the connection factory:
private QueueConnection getConnectionFactory(Context context, String jndiName)
throws NamingException, JMSException {
QueueConnectionFactory connectionFactory = (QueueConnectionFactory) context.lookup(jndiName);
return connectionFactory.createQueueConnection();
}
Open a queue session:
private QueueSession getQueueSession(QueueConnection queueConnection) throws JMSException {
return queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
}
Get the queue:
private Queue getQueue(Context context, String jndiName) throws NamingException {
return (Queue) context.lookup(jndiName);
}
And finally, send your message to the queue:
private static void sendMessageToQueue(boolean verbose,
String message,
QueueSession queueSession,
Queue queue) throws JMSException {
TextMessage textMessage = queueSession.createTextMessage(message);
try (QueueSender queueSender = queueSession.createSender(queue)) {
queueSender.send(textMessage);
}
}
These code snippets come from here: https://github.com/zappee/jms-message-sender
This is a JMS sender command-line tool, you can use this project as an example.
Hope that it helps.
Hi i've added this WebListener class to my webproject
#WebListener
public class SelfSend implements ServletContextListener {
private MessageProducer producer;
private Connection sendconnection;
private Connection receiveconnection;
private Session sendsession;
private Session receivesession;
private MessageConsumer receiver;
#Override
public void contextInitialized(ServletContextEvent arg0) {
try {
InitialContext initCtx = new InitialContext();
ConnectionFactory connectionFactory = (ConnectionFactory) initCtx.lookup("java:comp/env/jms/ConnectionFactory");
sendconnection = connectionFactory.createConnection();
receiveconnection = connectionFactory.createConnection();
sendsession = sendconnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
receivesession = receiveconnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = sendsession.createProducer((Destination) initCtx.lookup("java:comp/env/jms/queue/MyQueue"));
receiver = receivesession.createConsumer((Destination) initCtx.lookup("java:comp/env/jms/queue/MyQueue"));
receiver.setMessageListener(new MessageListener() {
#Override
public void onMessage(Message message) {
System.out.println("MESSAGE RECEIVED");
}
});
TextMessage testMessage = sendsession.createTextMessage();
testMessage.setStringProperty("from", "ki");
producer.send(testMessage);
System.out.println("MESSAGE SENT");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Override
public void contextDestroyed(ServletContextEvent arg0) {
}
}
But the message is never received.
When i put the reciver in a #WebServlet like this
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
InitialContext initCtx = new InitialContext();
ConnectionFactory connectionFactory = (ConnectionFactory) initCtx.lookup("java:comp/env/jms/ConnectionFactory");
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
receiver = session.createConsumer((Destination) initCtx.lookup("java:comp/env/jms/queue/MyQueue"));
receiver.setMessageListener(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
mud = new MongoUserdata();
}
i recive the message, when i put it in both i receive only every second message with the Servlet-Receiver, the other messasge seems to be lost.
Can anyone explain theis odd behaviour to me?
In your first example class you don't appear to be starting the receiver connection which would mean it will not dispatch any messages that are received. It will however hold onto incoming messages in the consumer prefetch buffer leading to the every other message receive that you are experiencing.
I am new to jms. The goal is to process messages concurrently from a queue in an asynchronous listener's onMessage method by attaching a listener instance to multiple consumer's with each consumer using its own session and running in a separate thread, that way the messages are passed on to the different consumers for concurrent processing.
1) Is it possible to process messages concurrently from a single queue by creating multiple consumers ?
2) I came up with the below code, but would like to get your thoughts on whether the below code looks correct for what I want to accomplish.
public class QueueConsumer implements Runnable, MessageListener {
public static void main(String[] args) {
QueueConsumer consumer1 = new QueueConsumer();
QueueConsumer consumer2 = new QueueConsumer();
try {
consumer1.init("oms", "US.Q.CHECKOUT-ORDER.1.0.JSON");
consumer2.init("oms","US.Q.CHECKOUT-ORDER.1.0.JSON");
} catch (JMSException ex) {
ex.printStackTrace();
System.exit(-1);
}
Thread newThread1 = new Thread(consumer1);
Thread newThread2 = new Thread(consumer1);
newThread1.start();
newThread2.start();
}
private static String connectionFactoryName = null;
private static String queueName = null;
private static ConnectionFactory qcf = null;
private static Connection queueConnection = null;
private Session ses = null;
private Destination queue = null;
private MessageConsumer msgConsumer = null;
public static final Logger logger = LoggerFactory
.getLogger(QueueConsumer.class);
public QueueConsumer() {
super();
}
public void onMessage(Message msg) {
if (msg instanceof TextMessage) {
try {
//process message
} catch (JMSException ex) {
ex.printStackTrace();
}
}
}
public void run() {
try {
queueConnection.start();
} catch (JMSException e) {
e.printStackTrace();
System.exit(-1);
}
while (!Thread.currentThread().isInterrupted()) {
synchronized (this) {
try {
wait();
} catch (InterruptedException ex) {
break;
}
}
}
}
public void init(String factoryName, String queue2) throws JMSException {
try {
qcf = new JMSConnectionFactory(factoryName);
queueConnection = qcf.createConnection();
ses = queueConnection.createSession(false,
Session.CLIENT_ACKNOWLEDGE);
queue = ses.createQueue(queue2);
logger.info("Subscribing to destination: " + queue2);
msgConsumer = ses.createConsumer(queue);
msgConsumer.setMessageListener(this);
System.out.println("Listening on queue " + queue2);
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
}
private static void setConnectionFactoryName(String name) {
connectionFactoryName = name;
}
private static String getQueueName() {
return queueName;
}
private static void setQueueName(String name) {
queueName = name;
}
}
Yes absolutely
I only took a brief look and I noticed that you pass the wrong consumer to your second thread:
Thread newThread2 = new Thread(consumer1); // has to pass consumer2
beside of this, some variables such as ConnectionFactory are static and initialized multiple times/overriden. You only need one connection that could create multiple sessions and/or consumers.
Related to the code example you provided, It is not recommanded by Oracle to create low-level threads on a deployed application. Example for Weblogic :
Using Threads in WebLogic Server
Instead in the applicationcontext.xml, where you have made the bean of mail container, you can add concurrent consumer property which would be a better approach.
<bean id="jmsMailContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="concurrentConsumers">
<value>100</value>
</property>
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="mailDestination"/>
<property name="messageListener" ref="jmsMailConsumer"/>
I have activemq5.3.2 running and I wanted to subscribe existing advisory topics using my java program. while, `jndi` lookup I am getting following error:
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:657)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:259)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:296)
at javax.naming.InitialContext.lookup(InitialContext.java:363)
at jmsclient.Consumer.<init>(Consumer.java:38)
at jmsclient.Consumer.main(Consumer.java:74)
Exception occurred: javax.jms.InvalidDestinationException: Don't understand null destinations
Please suggest where the problem is, or how could I use my topic name to look for?
package jmsclient;
import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer implements MessageListener {
private static int ackMode;
private static String clientTopicName;
private boolean transacted = false;
//private MessageConsumer messageConsumer;
static {
clientTopicName = "ActiveMQ.Advisory.Consumer.Queue.example.A";
ackMode = Session.AUTO_ACKNOWLEDGE;
}
#SuppressWarnings("null")
public Consumer()
{// TODO Auto-generated method stub
TextMessage message = null;
Context jndiContext;
//TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://usaxwas012ccxra.ccmp.ibm.lab:61616");
try{
Topic myTopic = null;
try { jndiContext = new InitialContext();
myTopic = (Topic) jndiContext.lookup(clientTopicName);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
topicConnection = connectionFactory.createTopicConnection();
topicConnection.start();
topicSession = topicConnection.createTopicSession(transacted, ackMode);
TopicSubscriber topicSubscriber = topicSession.createSubscriber(myTopic);
Message m = topicSubscriber.receive(1000);
if (m != null) {
if (m instanceof TextMessage) {
message = (TextMessage) m;
System.out.println("Reading message: " + message.getText());
}
}
} //try ends
catch (JMSException e) {
System.out.println("Exception occurred: " + e.toString());
} finally {
if (topicConnection != null) {
try {
topicConnection.close();
} catch (JMSException e) {}
}}}
public void onMessage(Message arg0) {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
new Consumer();
}
}