MDB to listen queue and put message into another queue - websphere

I'm new to J2EE - MDB but I'm trying to create a Message Driven Bean (MDB) that simply listens to a queue (read the messages), then process that message and push it to a different queue. I have found several working examples on Google to achieve these two tasks in a separate fashion, but I've been having issues trying to do them both on the same MDB.
This is the code for my MDB
#MessageDriven(mappedName = "jms/propuestasQ")
public class ObtenerNumPolizaBean implements MessageListener {
#Resource(name="jms/polizasQCF")
private QueueConnectionFactory connectionFactory;
private Connection connection;
#Resource(name = "jms/polizasQ")
private Destination targetQueue;
#PostConstruct
private void initJMS() {
try {
connection = connectionFactory.createConnection();
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
#PreDestroy
private void closeJMS() {
try {
connection.close();
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
/**
* #see MessageListener#onMessage(Message)
*/
#Override
public void onMessage(Message message) {
//validate the received message type
if (message instanceof FolioEntity) {
try {
//generate Web Service proxy
GenerarFoliosImplService serviceGenerarFolios = new GenerarFoliosImplService();
GenerarFoliosImplDelegate delGenerarFolios = serviceGenerarFolios.getGenerarFoliosImplPort();
//call the method with the object
FolioEntity responseFolio = delGenerarFolios.generarFolios((FolioEntity)message);
System.out.println("Bean generated the following FolioNumber: " + responseFolio.getNumeroFolio());
//put the message on the next queue
putMessage(responseFolio);
}
catch (JMSException e) {
throw new RuntimeException(e);
}
}
else {
throw new IllegalArgumentException("Message must be of type FolioEntity");
}
}
private void putMessage(final FolioEntity folio) throws JMSException {
final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
final MessageProducer producer = session.createProducer(targetQueue);
final ObjectMessage objectMessage = session.createObjectMessage();
producer.send(objectMessage);
session.close();
}
Here is the content of my ejb-jar.xml file
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
<display-name>MDBRenovarPolizaEJB </display-name>
<enterprise-beans >
<message-driven>
<ejb-name>ObtenerNumPolizaBean</ejb-name>
<message-destination-ref>
<description />
<message-destination-ref-name>
jms/polizasQ
</message-destination-ref-name>
<message-destination-type>
javax.jms.Queue
</message-destination-type>
<message-destination-usage>
ConsumesProduces
</message-destination-usage>
<message-destination-link>
jms/polizasQ
</message-destination-link>
</message-destination-ref>
<message-destination-ref>
<description />
<message-destination-ref-name>
jms/polizasQCF
</message-destination-ref-name>
<message-destination-type>
javax.jms.QueueConnectionFactory
</message-destination-type>
<message-destination-usage>
ConsumesProduces
</message-destination-usage>
<message-destination-link>
jms/polizasQCF
</message-destination-link>
</message-destination-ref>
The issue I'm having is that I can't set the "Message Driven Bean listener bindings" on WAS Console 8.5.5, when I try to set the activation specification I'm getting the error:
MDBRenovarPolizaModelEJB.jar\META-INF\ejb-jar_merged.xml (The system cannot find the file specified.)
I don't know what this exception means. I've always set the "Activation specification" this way to listen to a particular queue, so I have no idea what is this file: "ejb-jar_merged.xml".
Any clue? Thanks in advance.
Or if anyone has a working example to achieve this with step by step to make it work under WebSphere that would be useful.

I just solved similar problem (WAS 8.5.5, but MDB, EJB, servlet - all stuffed into one war module)
It is clearly a bug in WAS. This is workaround:
Ensure Run server with resources on Server
Publish to repeat an error (but this time with resources on server)
Find directory where WAS expects "ejb-jar_merged.xml":
Locate WAS SystemErr.log
There are messages about missing "ejb-jar_merged.xml".
You are looking for directory name of missing file.
Goto to the found directory.
(something like .IBM/WebSphere/AppServer/profiles/AppSrv01/wstemp/0/workspace/... ....deployments /.....-INF/
copy ejb-jar.xml ejb-jar_merged.xml
In case of missing web_merged.xml just copy web.xml into web_merged.xml.
The problem will not appear again at application updates, it has to be reapplied sometimes after app remove/install.

Related

IBM MQ provider for JMS : How to automatically roll back messages?

Working versions in the app
IBM AllClient version : 'com.ibm.mq:com.ibm.mq.allclient:9.1.1.0'
org.springframework:spring-jms : 4.3.9.RELEASE
javax.jms:javax.jms-api : 2.0.1
My requirement is that in case of the failure of a message processing due to say, consumer not being available (eg. DB is unavailable), the message remains in the queue or put back on the queue (if that is even possible). This is because the order of the messages is important, messages have to be consumed in the same order that they are received. The Java app is single-threaded.
I have tried the following
#Override
public void onMessage(Message message)
{
try{
if(message instanceOf Textmessage)
{
}
:
:
throw new Exception("Test");// Just to test the retry
}
catch(Exception ex)
{
try
{
int temp = message.getIntProperty("JMSXDeliveryCount");
throw new RuntimeException("Redlivery attempted ");
// At this point, I am expecting JMS to put the message back into the queue.
// But it is actually put into the Bakout queue.
}
catch(JMSException ef)
{
String temp = ef.getMessage();
}
}
}
I have set this in my spring.xml for the jmsContainer bean.
<property name="sessionTransacted" value="true" />
What is wrong with the code above ?
And if putting the message back in the queue is not practical, how can one browse the message, process it and, if successful, pull the message (so it is consumed and no longer on the queue) ? Is this scenario supported in IBM provider for JMS?
The IBM MQ Local queue has BOTHRESH(1).
To preserve message ordering, one approach might be to stop the message listener temporarily as part of your rollback strategy. Looking at the Spring Boot doc for DefaultMessageListenerContainer there is a stop(Runnable callback) method. I've experimented with using this in a rollback as follows.
To ensure my Listener is single threaded, on my DefaultJmsListenerContainerFactory I set containerFactory.setConcurrency("1").
In my Listener, I set an id
#JmsListener(destination = "DEV.QUEUE.2", containerFactory = "listenerTwoFactory", concurrency="1", id="listenerTwo")
And retrieve the DefaultMessageListenerContainer instance.
JmsListenerEndpointRegistry reg = context.getBean(JmsListenerEndpointRegistry.class);
DefaultMessageListenerContainer mlc = (DefaultMessageListenerContainer) reg.getListenerContainer("listenerTwo");
For testing, I check JMSXDeliveryCount and throw an exception to rollback.
retryCount = Integer.parseInt(msg.getStringProperty("JMSXDeliveryCount"));
if (retryCount < 5) {
throw new Exception("Rollback test "+retryCount);
}
In the Listener's catch processing, I call stop(Runnable callback) on the DefaultMessageListenerContainer instance and pass in a new class ContainerTimedRestart as defined below.
//catch processing here and decide to rollback
mlc.stop(new ContainerTimedRestart(mlc,delay));
System.out.println("#### "+getClass().getName()+" Unable to process message.");
throw new Exception();
ContainerTimedRestart extends Runnable and DefaultMessageListenerContainer is responsible for invoking the run() method when the stop call completes.
public class ContainerTimedRestart implements Runnable {
//Container instance to restart.
private DefaultMessageListenerContainer theMlc;
//Default delay before restart in mills.
private long theDelay = 5000L;
//Basic constructor for testing.
public ContainerTimedRestart(DefaultMessageListenerContainer mlc, long delay) {
theMlc = mlc;
theDelay = delay;
}
public void run(){
//Validate container instance.
try {
System.out.println("#### "+getClass().getName()+"Waiting for "+theDelay+" millis.");
Thread.sleep(theDelay);
System.out.println("#### "+getClass().getName()+"Restarting container.");
theMlc.start();
System.out.println("#### "+getClass().getName()+"Container started!");
} catch (InterruptedException ie) {
ie.printStackTrace();
//Further checks and ensure container is in correct state.
//Report errors.
}
}
I loaded my queue with three messages with payloads "a", "b", and "c" respectively and started the listener.
Checking DEV.QUEUE.2 on my queue manager I see IPPROCS(1) confirming only one application handle has the queue open. The messages are processed in order after each is rolled five times and with a 5 second delay between rollback attempts.
IBM MQ classes for JMS has poison message handling built in. This handling is based on the QLOCAL setting BOTHRESH, this stands for Backout Threshold. Each IBM MQ message has a "header" called the MQMD (MQ Message Descriptor). One of the fields in the MQMD is BackoutCount. The default value of BackoutCount on a new message is 0. Each time a message rolled back to the queue this count is incremented by 1. A rollback can be either from a specific call to rollback(), or due to the application being disconnected from MQ before commit() is called (due to a network issue for example or the application crashing).
Poison message handling is disabled if you set BOTHRESH(0).
If BOTHRESH is >= 1, then poison message handling is enabled and when IBM MQ classes for JMS reads a message from a queue it will check if the BackoutCount is >= to the BOTHRESH. If the message is eligible for poison message handling then it will be moved to the queue specified in the BOQNAME attribute, if this attribute is empty or the application does not have access to PUT to this queue for some reason, it will instead attempt to put the message to the queue specified in the queue managers DEADQ attribute, if it can't put to either of these locations it will be rolled back to the queue.
You can find more detailed information on IBM MQ classes for JMS poison message handling in the IBM MQ v9.1 Knowledge Center page Developing applications>Developing JMS and Java applications>Using IBM MQ classes for JMS>Writing IBM MQ classes for JMS applications>Handling poison messages in IBM MQ classes for JMS
In Spring JMS you can define your own container. One container is created for one Jms Destination. We should run a single-threaded JMS listener to maintain the message ordering, to make this work set the concurrency to 1.
We can design our container to return null once it encounters errors, post-failure all receive calls should return null so that no messages are polled from the destination till the destination is active once again. We can maintain an active state using a timestamp, that could be simple milliseconds. A sample JMS config should be sufficient to add backoff. You can add small sleep instead of continuously returning null from receiveMessage method, for example, sleep for 10 seconds before making the next call, this will save some CPU resources.
#Configuration
#EnableJms
public class JmsConfig {
#Bean
public JmsListenerContainerFactory<?> jmsContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() {
#Override
protected DefaultMessageListenerContainer createContainerInstance() {
return new DefaultMessageListenerContainer() {
private long deactivatedTill = 0;
#Override
protected Message receiveMessage(MessageConsumer consumer) throws JMSException {
if (deactivatedTill < System.currentTimeMillis()) {
return receiveFromConsumer(consumer, getReceiveTimeout());
}
logger.info("Disabled due to failure :(");
return null;
}
#Override
protected void doInvokeListener(MessageListener listener, Message message)
throws JMSException {
try {
super.doInvokeListener(listener, message);
} catch (Exception e) {
handleException(message);
throw e;
}
}
private long getDelay(int retryCount) {
if (retryCount <= 1) {
return 20;
}
return (long) (20 * Math.pow(2, retryCount));
}
private void handleException(Message msg) throws JMSException {
if (msg.propertyExists("JMSXDeliveryCount")) {
int retryCount = msg.getIntProperty("JMSXDeliveryCount");
deactivatedTill = System.currentTimeMillis() + getDelay(retryCount);
}
}
#Override
protected void doInvokeListener(SessionAwareMessageListener listener, Session session,
Message message)
throws JMSException {
try {
super.doInvokeListener(listener, session, message);
} catch (Exception e) {
handleException(message);
throw e;
}
}
};
}
};
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
return factory;
}
}

Replay/synchronous messages memory not released for messages sent by producers to queue in SpringBoot JMS with ActiveMQ

1. Context:
A two-modules/microservice application developed with SpringBoot 2.3.0 and ActiveMQ.
Also we use ActiveMQ 5.15.13 server/broker.
Broker is defined in both modules with application properties.
Also broker connection pool is defined in both modules as well with application properties and added in both modules the pooled-jms artifact dependency (with maven):
spring.activemq.broker-url=xxx
spring.activemq.user=xxx
spring.activemq.password=xx
spring.activemq.non-blocking-redelivery=true
spring.activemq.pool.enabled=true
spring.activemq.pool.time-between-expiration-check=5s
spring.activemq.pool.max-connections=10
spring.activemq.pool.max-sessions-per-connection=10
spring.activemq.pool.idle-timeout=60s
Other configurations for JMS I done are:
spring.jms.listener.acknowledge-mode=auto
spring.jms.listener.auto-startup=true
spring.jms.listener.concurrency=5
spring.jms.listener.max-concurrency=10
spring.jms.pub-sub-domain=false
spring.jms.template.priority=100
spring.jms.template.qos-enabled=true
spring.jms.template.delivery-mode=persistent
In module 1 the JmsTemplate is used to send synchronous messages (or we can name replay-messages as well). I've opted out for a proper queue instead of a temporary queue as I understand that if there are lots of messages sent than a temporary queue is not recommended to be used for replays - so that's what I did.
2. Code samples:
MODULE 1:
#Value("${app.request-video.jms.queue.name}")
private String requestVideoQueueNameAppProperty;
#Bean
public Queue requestVideoJmsQueue() {
logger.info("Initializing requestVideoJmsQueue using application property value for " +
"app.request-video.jms.queue.name=" + requestVideoQueueNameAppProperty);
return new ActiveMQQueue(requestVideoQueueNameAppProperty);
}
#Value("${app.request-video-replay.jms.queue.name}")
private String requestVideoReplayQueueNameAppProperty;
#Bean
public Queue requestVideoReplayJmsQueue() {
logger.info("Initializing requestVideoReplayJmsQueue using application property value for " +
"app.request-video-replay.jms.queue.name=" + requestVideoReplayQueueNameAppProperty);
return new ActiveMQQueue(requestVideoReplayQueueNameAppProperty);
}
#Autowired
private JmsTemplate jmsTemplate;
public Message callSendAndReceive(TextJMSMessageDTO messageDTO, Destination jmsDestination, Destination jmsReplay) {
return jmsTemplate.sendAndReceive(jmsDestination, jmsSession -> {
try {
TextMessage textMessage = jmsSession.createTextMessage();
textMessage.setText(messageDTO.getText());
textMessage.setJMSReplyTo(jmsReplay);
textMessage.setJMSCorrelationID(UUID.randomUUID().toString());
textMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
return textMessage;
} catch (IOException e) {
logger.error("Error sending JMS message to destination: " + jmsDestination, e);
throw new JMSException("Error sending JMS message to destination: " + jmsDestination);
}
});
}
MODULE 2:
#JmsListener(destination = "${app.backend-get-request-video.jms.queue.name}")
public void onBackendGetRequestsVideoMessage(TextMessage message, Session session) throws JMSException, IOException {
logger.info("Get requests video file message consumed!");
try {
Object replayObject = handleReplayAction(message);
JMSMessageDTO messageDTO = messageDTOFactory.getJMSMessageDTO(replayObject);
Message replayMessage = messageFactory.getJMSMessage(messageDTO, session);
BytesMessage replayBytesMessage = jmsSession.createBytesMessage();
fillByteMessageFromMediaDTO(replayBytesMessage, mediaMessageDTO);
replayBytesMessage.setJMSCorrelationID(message.getJMSCorrelationID());
final MessageProducer producer = session.createProducer(message.getJMSReplyTo());
producer.send(replayBytesMessage);
JmsUtils.closeMessageProducer(producer);
} catch (JMSException | IOException e) {
logger.error("onBackendGetRequestsVideoMessage()JMSException: " + e.getMessage(), e);
throw e;
}
}
private void fillByteMessageFromMediaDTO(BytesMessage bytesMessage, MediaJMSMessageDTO mediaMessageDTO)
throws IOException, JMSException {
String filePath = fileStorageConfiguration.getMediaFilePath(mediaMessageDTO);
FileInputStream fileInputStream = null;
try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
byte[] byteBuffer = new byte[1024];
int bytes_read = 0;
while ((bytes_read = fileInputStream.read(byteBuffer)) != -1) {
bytesMessage.writeBytes(byteBuffer, 0, bytes_read);
}
} catch (JMSException e) {
logger.error("Can not write data in JMS ByteMessage from file: " + fileName, e);
} catch (FileNotFoundException e) {
logger.error("Can not open stream to file: " + fileName, e);
} catch (IOException e) {
logger.error("Can not read data from file: " + fileName, e);
}
}
3. The problem:
As I send many messages and receive many corresponding replays through producer/comsumer/JmsTamplate both application modules 1 and 2 are fast-filling the heap memory allocated until an out-of-memory error is thrown, but the memory leak appears only when using synchronous messages with replay as shown above.
I've debugged my code and all instances (session, producers, consumers, jmsTamplate, etc) are pooled and have instances of the right classes from pooled-jms library; so pool should - apparently - work properly.
I've made a heap dump of the second module and looks like producers messages (ActiveMQBytesMessage) are still in memory even long time after have been successfully consumed by the right consumer.
I have asynchronous messages sent as well in my modules and seams that those messages producer-consumer works well; the problem is present only for the synch/replay messages producer-consumer.
Sample heap dump files - taken after full night of application inactivity - as following:
module 1
module_1_dump
module 2
module_2_dump
activemq broker/server
activemq_dump
Anyone have any idea what I'm doing wrong?!

Using a Connection Name List in a JMS Load Balanced Environment

Our JMS infrastructure is load balanced. As a result of this, I am attempting to use a connectionNameList when configuring the connection factory. The idea here is that any JMS message that arrives on either of the primary or secondary queue manager will get picked up and processed. However, it only appears that messages are being picked up by the primary.
Here is my listener annotation:
#JmsListener(destination = "${request-queue}", containerFactory = "DefaultJmsListenerContainerFactory")
public void onMessage(Message msg) {
System.out.println(msg.toString());
}
Here is the JMS listener container factory:
#Bean(name = "DefaultJmsListenerContainerFactory")
public DefaultJmsListenerContainerFactory createJmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(buildConnectionFactory());
factory.setConcurrency(numberOfListeners);
factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
factory.setSessionTransacted(false);
factory.setErrorHandler(queueErrorHandler);
factory.setBackOff(getBackOffStrategy());
return factory;
}
And here is the connection factory:
#Bean(name = "MQConnectionFactory")
public ConnectionFactory buildConnectionFactory() {
try {
MQConnectionFactory mqcf = new MQConnectionFactory();
mqcf.setConnectionNameList(mq1.daluga.com(2171),mq2.daluga.com(2171));
mqcf.setChannel(channel);
mqcf.setTransportType(WMQConstants.WMQ_CM_CLIENT);
return mqcf;
} catch (Exception e) {
throw new RuntimeException(message, e);
}
}
I suspect something in my configuration is just not right. Is there anything obvious that folks see that might cause messages not to be picked up from the secondary queue manager?
Thanks!

Correct usage of JMS-Topic communication

I want to use JMS (Topic) in my JavaEE 6 project. I have one class which acts as a publisher and subscriber of a topic at once. The following code shows the most important parts of the class.
public class MessageHandler implements MessageListener {
private static TopicConnectionFactory factory;
private static Topic topic;
private TopicSubscriber subscriber;
private TopicPublisher publisher;
public MessageHandler() throws NamingException, JMSException {
if (factory == null) {
Context context = new InitialContext();
factory = (TopicConnectionFactory) new InitialContext()
.lookup("jms/myfactory");
topic = (Topic) context.lookup("jms/mytopic");
}
TopicConnection connection = factory.createTopicConnection();
connection.start();
TopicSession session = connection
.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
subscriber = session.createSubscriber(topic);
}
#Override
public void onMessage(Message message) {
try {
ObjectMessage msg = (ObjectMessage) message;
Object someO= msg.getObject();
System.out.println(this + " receives "+someO);
} catch (JMSException e) {
e.printStackTrace();
}
}
public void sendMessage(Object someO) {
try {
ObjectMessage msg = session.createObjectMessage();
msg.setObject(someO);
publisher = session.createPublisher(topic);
publisher.publish(msg);
publisher.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
My question is, if this is a good way to design such a class. My idea was to share one connection and session for both subscribing and publishing. But I'm scared that this could lead to some overhead or blocking because I'm not closing the connection, session, subscriber and publisher until the object is not needed anymore. All examples I found online directly close everything after a message was sent or received...
Thanks in advance!
Why do you want the class to be subscriber and publisher at once?
Whenever using a messaging system, you may well act as both, but why would you do it for the same topic, you surely don't want to receive your own messages?
So, the purpose of a topic, is to be used among several parts within an application or among several applications - one is placing a message into the topic and others receive the message they subscribed for.
And that also explains what you saw in the examples - the message processing is a one time thing, thus the connection can be closed afterwards.
By the way, since you ask this question within the "java-ee 6" area - can't you use a message driven bean, annotate your topic configuration and let the application server do the infrastructure part for you?

jms sending message on any server

I want to write generic code for sending message on any jms server. so for that i thought if i have jndi.properties file then we can place server configuration in this file and we can access this file through the code but i am able to do this only for 'ActiveMQ Server'. Now i am facing problems to send the message on any other server like glassfish server or jboss server. can somebody help me to do this task.
Here is my code :
public class Producer
{
public Producer() throws JMSException, NamingException,IOException
{
InputStream is = getClass().getResourceAsStream("my.jndi.properties");
Properties jndiParamaters = new Properties();
jndiParamaters.load(is);
Context jndi = new InitialContext(jndiParamaters);
ConnectionFactory conFactory = (ConnectionFactory) jndi.lookup("connectionFactory");
Connection connection;
connection = conFactory.createConnection();
try
{
connection.start();
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Destination destination = (Destination) jndi.lookup("Myqueue");
MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage("Hello World!");
producer.send(message);
System.out.println("Sent message '" + message.getText() + "'");
}
finally
{
connection.close();
}
}
public static void main(String[] args) throws JMSException
{
try
{
BasicConfigurator.configure();
new Producer();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
thanks
Have you tried using the Spring JMS Template? http://static.springsource.org/spring/docs/2.0.x/reference/jms.html
It provides an abstraction layer to JMS and could probably help you when your implementation changes.

Resources