Page 1 of 3 123 LastLast
Results 1 to 10 of 23

Thread: n00b needs help with error handling

  1. #1
    Join Date
    Aug 2009
    Posts
    22

    Default n00b needs help with error handling

    Hi!
    I've been trying out Spring Integration a bit, so far with unexpected amounts of success, but when it comes to handling exceptions I'm stumped.

    I have a JMS queue which takes requests, the service does stuff and the result is posted to another queue, defined in the request's JMSRepyTo header. This works fine, but if an exception occurs I'd like some error message to be sent on the reply queue and that I've had no luck achieving.

    This is the configuration I have at the moment:
    Code:
        <!-- Channels -->
        
        <si:channel id="inputChannel"/>
        <si:chain input-channel="inputChannel" output-channel="outputChannel">
            <si:header-enricher error-channel="errorChannel"/>
            
            <si:filter ref="messageRouter" method="reroute"/>
            <si:transformer ref="payloadExtractor" method="extract"/>
            <si:transformer ref="OXMapper" method="unmarshall"/>
            <si:service-activator ref="messageEndpoint" method="doStuff"/>
            <si:transformer ref="OXMapper" method="marshall"/>
            <si:transformer ref="payloadExtractor" method="soapify"/>
        </si:chain>
        <si-jms:message-driven-channel-adapter channel="inputChannel" connection-factory="connectionFactory"/>
        
        <si:channel id="outputChannel"/>
        <si-jms:outbound-channel-adapter channel="outputChannel" jms-template="jmsTemplate" />
        
        <si:channel id="errorChannel"/>
        <si:chain input-channel="errorChannel" output-channel="outputChannel">
            <si:transformer ref="errorHandler" method="handleMessage"/>
        </si:chain>
    
        <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="defaultDestinationName" value="DEFAULTOUTPUTQUEUE"/>
        </bean>
    
        <!-- Classes -->
    
        <!-- changes jmsTemplate's defaultDestinationName to queue in message's JMSReplyTo header  -->
        <bean id="messageRouter" class="test.MessageRouter">
            <property name="jmsTemplate" ref="jmsTemplate"/>
        </bean>
        
        <!-- extracts/wraps payload from/in SOAP message -->
        <bean id="payloadExtractor" class="test.PayloadTransformer">
            <property name="jmsTemplate" ref="jmsTemplate"/>
        </bean>
        
        <!-- unmarshalls/marshalls objects from/to XML -->
        <bean id="OXMapper" class="test.OXMapper">
            <property name="jmsTemplate" ref="jmsTemplate"/>
            <property name="mapper" ref="testMapper"/>
        </bean>
        
        <!-- does stuff -->
        <bean id="messageEndpoint" class="test.MessageEndpoint">
            <property name="jmsTemplate" ref="jmsTemplate"/>
            <property name="localityService" ref="localityService"/>
        </bean>
        
        <!-- is supposed to handle error messages -->
        <bean id="errorHandler" class="test.ErrorHandler">
            <property name="jmsTemplate" ref="jmsTemplate"/>
        </bean>
    and this is the ErrorHandler:
    Code:
    public class ErrorHandler extends MessageSender {
      
      @Transformer
      public String handleMessage(Message errorMessage) {
        System.out.println("Here!");
        return "Something happened!";
      }
    }
    When I throw an exception in the input chain nothing is printed and no reply is sent to any queue.
    Please help me! What am I doing wrong?

  2. #2
    Join Date
    Oct 2005
    Location
    Boston, MA
    Posts
    2,853

    Default

    Okay. The 'error-channel' would only take effect if the Exception-throwing message handler were invoked within the scope of an asynchronous task (one submitted to a TaskExecutor or resulting from a poller). In your case, it's actually the thread that receives the JMS Message in the DefaultMessageListenerContainer that is invoking the entire chain. The default handing of Exceptions in that container is to log only unless it's a JMSException (then you can provide a JMS ExceptionListener to deal with those).

    There are a few options, but first let me know if what I stated above is clear.

  3. #3
    Join Date
    Aug 2009
    Posts
    22

    Default

    Yeah, I think so.

  4. #4
    Join Date
    Aug 2009
    Posts
    22

    Default

    Mark? Don't forget about me!

  5. #5
    Join Date
    Oct 2005
    Location
    Boston, MA
    Posts
    2,853

    Default

    Well, based on what I was saying, your error channel is not taking effect. The error-channel only comes into play if there is an asynchronous boundary - if the task is submitted to a TaskExecutor along the way (e.g. when a channel with a 'queue' is used). However, that would also break the transactional boundary that starts with the JMS message being received.

    Where does your Exception occur. Is there a way to catch it explicitly?

    Are you seeing the Exception simply logged from the DefaultMessageListenerContainer?

  6. #6
    Join Date
    Aug 2009
    Posts
    22

    Default

    Exceptions will most likely occur somewhere in the doStuff method. As far as catching them, I'm acctually doing that right now and printing the stack trace.

    I guess I could just let doStuff return an error message in a String instead of the intended value if an exception was caught, but I'd just like to know if there was a "proper" way to do it.

  7. #7
    Join Date
    Oct 2005
    Location
    Boston, MA
    Posts
    2,853

    Default

    The reason that the error-channel is only used in an asynchronous dispatch is that the standard behavior for synchronous dispatch is to simply throw the Exception to the caller. In other words, if you invoke channel.send(m) directly and that pipeline throws an Exception without any TaskExecutors to cause a new Thread to take over, then any runtime Exceptions would be thrown right there at the send(m) call.

    Now, that is great in most cases, but there is one problem with this in a JMS Message-driven scenario. The DefaultMessageListenerContainer is the "caller" in this case, and this is the code that handles any RuntimeException (in the AbstractMessageListenerContainer base class):
    Code:
    protected void handleListenerException(Throwable ex) {
    	if (ex instanceof MessageRejectedWhileStoppingException) {
    		// Internal exception - has been handled before.
    		return;
    	}
    	if (ex instanceof JMSException) {
    		invokeExceptionListener((JMSException) ex);
    	}
    	if (isActive()) {
    		// Regular case: failed while active.
    		// Log at error level.
    		logger.warn("Execution of JMS message listener failed", ex);
    	}
    	else {
    		// Rare case: listener thread failed after container shutdown.
    		// Log at debug level, to avoid spamming the shutdown log.
    		logger.debug("Listener exception after container shutdown", ex);
    	}
    }
    As you can see, only logging occurs for RuntimeExceptions (only JMSExceptions are "handled"). The reason for this is that the listener container assumes any MessageListener should handle its own Exceptions. The JMSExceptions signify something at the "infrastructure" level, so those can be handled by the framework. However, there is no clear one-size-fits-all approach for handling an Exception that occurs while executing business logic within the scope of the handler.

    So, one thing you could do is subclass DefaultMessageListenerContainer and override that method. Then you can provide that in your "container" reference in the message-driven-channel-adapter. That probably does not sound ideal, but it would at least keep your generic Exception handling code out of the business logic.

    I'm sure you are thinking that we should provide some out-of-the-box support for this situation in Spring Integration, and I actually agree. There are a few things we could do. First, we could catch any Exception (re-throwing only JMSExceptions) thrown within the scope of our MessageListenter, and we could check for an "error-channel" header on the Message. Second, we could provide a boolean for "sendErrorMessagesOnReplyChannel". Third, we could provide a separate configurable reply-to for errors. I am also open to other suggestions.

    If you think something along these lines seems right, then please open a JIRA issue, and we can address this within Spring Integration during the 2.0 roadmap.

    Thanks,
    Mark

  8. #8
    Join Date
    Aug 2009
    Posts
    22

    Default

    Will try your suggestion of overriding the handler method.
    Thanks a lot!

  9. #9
    Join Date
    Aug 2009
    Posts
    22

    Default

    I subclassed DefaultMessageListenerContainer and used it as the container in channel adapter like you suggested. However, now when I try to deploy I get stuck in a loop which every second logs this message:

    Code:
    DEBUG Consumer [org.jboss.resource.adapter.jms.JmsMessageConsumer@9a5d54] of session [org.jboss.resource.adapter.jms.JmsSession@1602bbc] did not receive a message at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:347)
    Any idea what's going on?

  10. #10
    Join Date
    Aug 2009
    Posts
    22

    Default

    Um.. nevermind. I guess it's supposed to do that.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •