Transaction Support for Springs onMessage handler - java

So I have some boilerplate code that consumes messages from a topic:
public void onMessage(Message message )
{
try
{
// try my conversion
}
catch(MyConversionException e)
{
//catch conversion error but still consume off topic
}
//Any other error i.e. runtime errors will not cause the message to be consumed from topic. So it can be retried
}
I wish to be able to try to convert the message into another object. If this causes an error I will catch it with my own exception handling and write it to an error queue.
My question is, how do I set up Springs messageListenerContainer bean to be Transactional and only consume if this has taken place successfully???
[EDIT] Here is the bean so far:
<!-- MESSAGE LISTENER CONTAINER -->
<bean id="ListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="messageListener" ref="MessageListener" />
<property name="connectionFactory" ref="Tcf" />
<property name="destinationResolver" ref="JmsDestinationResolver" />
<property name="receiveTimeout" value="${jms-timeout}" />
<property name="destinationName" value="${jms-topic}" />
<property name="concurrency" value="1" />
<property name="pubSubDomain" value="true" />
<property name="subscriptionDurable" value="${jms-durable-flag}"/>
<property name="durableSubscriptionName" value="${jms-durable-name}" />
<property name="clientId" value="${jms-client-id}"/>
</bean>

Is not recommended to do so, but you can call TransactionStatus.setRollbackOnly().
Also you may want to consider to be consistent with the transaction model, and if you want to rollback, then do it through an exception... In that case... You need to throw a RuntimeException so that Spring rollbacks the message, if you catch the exception and only logs it, Spring has no clue that something went wrong...

Related

Spring JMS Message doesn't retry or goes to Backout Queue on Error

I'm using Spring JMS and MQ to Send and Receive Messages. While reading messages, I want to make sure that on any Error, the messages will be re-delivered at least 3 times and then later send them to the Backout Queue. The Main Queue has Backout Threshold of 3.
I am using the Transacted set to True and sessionAcknowledgeMode to CLIENT_ACKNOWLEDGE (value is 2).
And in the Message Listener, i'm also doing message.acknowledge();
However, it's not working. Am I missing anything?
<jee:jndi-lookup id="QConnectionFactory" jndi-name="jndi/QCF"/>
<jee:jndi-lookup id="MainQ" jndi-name="jndi/MainQ"/>
<jee:jndi-lookup id="MainQBO" jndi-name="jndi/MainQBO"/>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref local="QConnectionFactory"/>
</property>
<property name="sessionAcknowledgeMode" value="2" />
<property name="sessionTransacted" value="true" />
</bean>
<bean id="msgHandler" class="myservice.MyMessageHandler">
<property name="jmsTemplate" ref="jmsTemplate"/>
<property name="MainQ" ref="MainQ"/>
<property name="MainQBO" ref="MainQBO"/>
</bean>
<bean id="messageListener" class="myservice.MyMessageListener" />
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="QConnectionFactory"/>
<property name="destination" ref="MainQ"/>
<property name="messageListener" ref="messageListener" />
<property name="sessionTransacted" value="true"/>
<property name="sessionAcknowledgeMode" value="2"/>
</bean>
Listener Class ...
public void onMessage(Message message) {
try{
... some code ...
message.acknowledge();
}catch(Exception E){
logger.erro(e);
}
}
"Not working" is never enough information.
That said
}catch(Exception E){
logger.erro(e);
}
You are catching and eating the exception; the listener needs to throw an exception to requeue the message.
I was able to fix the problem using the following approach. I hope it will help others.
Configuration
<bean id="messageListener" class="messageListenerClass" />
<bean id="fixedBackOff" class="org.springframework.util.backoff.FixedBackOff">
<constructor-arg index="0" value="30000" />
<constructor-arg index="1" value="3" />
</bean>
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactoryBean" />
<property name="destination" ref="destinationQueue" />
<property name="messageListener" ref="messageListener" />
<property name="sessionTransacted" value="true" />
<property name="backOff" ref="fixedBackOff" />
</bean>
Listener Class
#Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
String msg = ((TextMessage) message).getText();
........
........
} catch (JMSException e) {
logger.error("JMSException occured while reading the JMSMessage : {}", e);
throw new RuntimeException();
} catch (SomeException e) {
logger.error("SomeException ... : {}", e);
throw new RuntimeException();
} catch (SomeException e) {
logger.error("SomeException ... : {}", e);
throw new RuntimeException();
}
} else {
logger.error("Message must be of type TextMessage");
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
This may be a late answer to the OP question, but in case you have management access to the IBM MQ server (the queue manager) the application connects to, it is possible to essentially delegate all the message retry to MQ own algorithms, called "message backout policy" for poison messages i.e. messages that cannot be properly handled by the consuming application for any reason.
Refer to https://www.ibm.com/docs/en/ibm-mq/9.1?topic=applications-handling-poison-messages-in-mq-classes-jms for details.
Note that MQ will happily handle the backout counting (i.e. how many times the same message has been re-delivered), backout threshold (how many failures in a row equal to a message being "poisonous"), and the backout queue (a soft dead-letter queue where poison messages will be routed to once they exceed the backout threshold) configuration for you, BUT this mechanism provides no convenient means for a) delaying the message re-delivery, and b) selective message re-delivery (which messages to attempt again, and which ones should be rejected right away). At least in IBM MQ Series 9 as the time of writing this. If this is something required by your business flow, the application has to implement it.
P.S. And do not forget about setting the sessionTransacted property of JMS message listener container to true so it will properly backout messages on exceptions raised by application!

SimpleMessageListenerContainer method should stop processing after timeout

I have JVM1 sending requests to JVM2 synchronously via MQ. This is achieved using JmsInvokerServiceExporter ,SimpleMessageListenerContainer and JmsInvokerProxyFactoryBean. The listener method in JVM2 is written such that should any exception arise during processing, it will fabricate a default response indicating that something went wrong. If everything goes fine, it will send successful response.
Now what I am looking for is if the listener method is taking more time than a predefined period, an exception be thrown so that the listener can stop further procesing and send the default response back to JVM1. Basically JVM2 listener should not process beyond time X sec and should be able to send default response back.
I can make the JVM1 time out after X secs but then I won't get the default response that I am expecting from JVM2. Can someone please let me know if such a thing can be achieved or not? The code snippet for setting up the communication between jvm1 and 2 is given below.
Expose a pojo Calculator.java service for remoting over JMS using JMSInvokerServiceExporter
<bean id="calculatorService" class="com.spring.remoting.service.impl.SalaryCalculator" />
<bean id="remoteCalculatorService" class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
<property name="service" ref="calculatorService">
<property name="serviceInterface" value="com.spring.remoting.service.Calculator" />
</bean>
Listener and Invoker
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="myQueue"/>
<property name="concurrentConsumers" value="3"/>
<property name="messageListener" ref="remoteCalculatorService"/>
</bean>
<bean id="remoteCalculatorService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
<property name="serviceInterface" value="com.spring.remoting.service.Calculator" />
<property name="connectionFactory" ref="connectionFactory"/>
<property name="queue" ref="myQueue"/>
</bean>

org.springframework.jdbc.cannotgetjdbcconnectionexception Rmi class loader disabled

Rather new to Java coming from a c# background.
What I am trying to achieve is just to expose a method to jConsole via jmx and rim.
When I run my service and open jConsole I can see the method there and all looks good, now the problem comes in when i try to run this method via the console. The error I get is "Problem invoking helloWorld : java.rmi.UnmarshalException: Error unmarshaling return; nested exception is: java.lang.ClassNotFoundException: org.springframework.jdbc.CannotGetJdbcConnectionException (no security manager: RMI class loader disabled)".
The method im trying to expose is
#ManagedOperation
public int helloWorld() throws Exception {
return jdbcTemplate.queryForInt(sql);
}
Here is my applicationContext file
<!-- this bean must not be lazily initialized if the exporting is to happen -->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="assembler" ref="assembler" />
<property name="namingStrategy" ref="namingStrategy" />
<property name="autodetect" value="true" />
</bean>
<bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />
<!-- will create management interface using annotation metadata -->
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource" />
</bean>
<!-- will pick up the ObjectName from the annotation -->
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource" />
</bean>
<context:component-scan base-package="com.bulks" />
<!-- enable annotations like #Autowired, which you also most likely need -->
<context:annotation-config/>
<bean class="com.bulksms.resources.HelloWorldResource"/>
<!-- setup basic datasource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/apu"></property>
<property name="username" value="root"></property>
<property name="password" value="password"></property>
</bean>
<!-- jdbcTemplate bean -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
What am i missing so my method can be executed from the console?
-----SOLUTION------
So after hacking away at this for long i tried to put the sql part in its own method and then just call the method in the helloWorld method.... Low and behold!!!! SUCCESS!!!
public int helloWorld() throws Exception {
setValueFromQuery();
return value;
}
private void setValueFromQuery() {
this.value = jdbcTemplate.queryForInt(sql);
}
You exception is a nested exception, so its happened on on your application,
java.lang.ClassNotFoundException: org.springframework.jdbc.CannotGetJdbcConnectionException
So its say that there is a missing class, which could be the jdbc, make sure that you have it in your class path.
so if you have it, the check the connection criteria to your DB.

How to use a Tibco JMS module defined in weblogic and post messages to it using Spring

I am fairly new to spring...
I have a change whereby we need to add a message on a Tibco queue. The queue is defined in weblogic under JMS Modules as a Foreign Server (setup with a Connection Factory and Destination).
I would like to post message from my java app to the queue by making use of SPRING.
How should the wiring look in my spring applicationContext.xml file?
And how can I use it from the code?
I had a look and do not find a proper tutorial that indicate this.
Can someone point me in a direction please.
Thanks a lot
Use the following Spring config:
<bean id="jmsDestination" class="com.tibco.tibjms.TibjmsQueue">
<constructor-arg value="queue.sample" />
</bean>
<bean id="jmsConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
<property name="targetConnectionFactory" ref="tibcoConnectionFactory"/>
<property name="username" value="admin"/>
<property name="password" value=""/>
</bean>
<bean id="tibcoConnectionFactory" class="com.tibco.tibjms.TibjmsConnectionFactory">
<property name="serverUrl" value="tcp://hostname:7222"/>
<property name="userName" value="admin"/>
<property name="userPassword" value=""/>
</bean>
<bean id="jmsProducerTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<property name="defaultDestination" ref="jmsDestination"/>
</bean>
Then in the code, publish a message like this:
jmsProducerTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(dataString);
}
});
This will publish directly to the Tibco queue, to use the JNDI of your WebLogic see this post: Configuration of tibco jms with Spring

MDP JMS Transaction rolls back then reprocesses message in an endless loop

If I enable transaction management on my DefaultMessageListenerContainer by specifying sessionTransacted=true or transactionManager=jmsTransactionManager, whenever an exception occurs in the MDP, the transaction is rolled back and the message is placed back on the queue. That then causes the message to be processed again, the transaction to roll back again over and over again so that it creates an endless loop.
I guess my question is ... what am I missing here? Why would you want the message to go back on the queue if it just means it will be processed over and over again?
<!-- jms connection factory -->
<bean name="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:ConnectionFactory" />
</bean>
<!-- jms transaction manager -->
<bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="jmsConnectionFactory" />
</bean>
<!-- Destination for Inbound_Email_Q -->
<bean name="inboundEmailDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="queue/inbound_Email_Queue" />
</bean>
<!-- JmsTemplate for Inbound_Email_Q -->
<bean name="jmsInboundEmailTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="defaultDestination" ref="inboundEmailDestination" />
<property name="messageConverter" ref="xmlMessageConverter" />
</bean>
<!-- jms asynchronous listener -->
<bean id="emailMessageServiceMdp" class="org.site.wso.core.jms.EmailMessageServiceMdp" />
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<!-- <property name="transactionManager" ref="jmsTransactionManager" /> -->
<!-- <property name="sessionTransacted" value="true"/> -->
<property name="destination" ref="inboundEmailDestination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
<!-- jms message listener adapter -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="org.site.wso.core.jms.EmailMessageServiceMdp"/>
</constructor-arg>
<property name="messageConverter" ref="xmlMessageConverter"/>
</bean>
And here is my MDP:
public class EmailMessageServiceMdp implements MessageDelegate {
public void handleMessage(Object object) {
EmailMessageRequestVO requestVO = (EmailMessageRequestVO) object;
try {
//Service call that throw exception
} catch (Exception e) {
throw new ApplicationException(e);
}
}
}
The message redelivery is simply the default behaviour for your [configured] JMS implementation. It's up for endless debate what the relative usefulness of this is, but it would seem that rather than discard a message with some potentially unrecoverable data, some sort of retry is a sensible and conservative approach. For example, in your case, you appear to be converting a JMS message to an email message and dispatching to an SMTP server. If the SMTP gateway is down, you might want to hold onto the JMS messages and reprocess them when the gateway comes back up.
In general I would say your options for handling a message that failed processing are (depending on the JMS implementation):
Ditch the message.
Write the message to an error queue.
Redeliver the message after a delay of n seconds.
Redeliver the message n times and then write the message to an error queue.
Redeliver the message after a delay of n seconds x times and then write the message to an error queue.
If you prefer #1, then simply suppress the exception, commit the transaction and wave good bye to the message. For the rest, the JMS configuration (or the destination specific configuration) should handle those.
Additionally, if you want something more specific, you can interrogate the message's getJMSRedelivered() and/or the implementation specific message header property that indicates how many times the message has been redelivered (supported by most JMS implementations, but not standard) and dispose of the message accordingly.

Categories