Announcement Announcement Module
Collapse
No announcement yet.
Opinions on Request Handler Advice as an error handler Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Opinions on Request Handler Advice as an error handler

    In the past, we've played around with using different tools to handle error in spring int flows, with mixed success.

    - Error Gateways - Work as advertised, errors can route to the errorChannel. The downside is that they are a gateway so will sit a wait for a response. In flows that use outbound channel adapters, this requires some 'hackery' in the flow

    - Inject JMS/AMQ queue - This puts a queue in the middle with an inbound channel adapter that can then route to an error handler. This works pretty well, and isn't horrible when you come down to looking at logical segments in your flows. But it is a pretty verbose way of doing things.

    So, what about creating an ErrorHandlingAdvice class that can be injected into an endpoint that will wrap the error and send it to a configured error channel? I'm thinking something similar to the retry advice but one that doesn't actually do a retry, just wraps the message as an ErrorMessage like an error gateway and passes it down to an error channel.

    I'm just starting to look at Message Handler Advice. Is this a good idea? Or am I trying to make it do something it isn't meant to do? I don't want to spend too much time diving into the mechanics if I'm missing some basic premise of the new Advice feature.

  • #2
    There is already an ExpressionEvaluatingRequestHandlerAdvice; it has two expressions (onSuccessExpression and onFailureExpression).

    http://static.springsource.org/sprin...ression-advice

    You can configure the advice with just the onFailureExpression and failureChannel. The result of the expression evaluation is sent to the failureChannel wrapped in a ExpressionEvaluatingRequestHandlerAdvice$MessageHa ndlingExpressionEvaluatingAdviceException - which has properties "failedMessage", "cause", and "evaluationResult".

    This was really intended to take some action after a failure (such as renaming a file after an ftp transfer failure), but you can use it for the purposes you describe.

    In this case, you would probably want to set trapException to true, to avoid the exception being propagated to the caller.

    Comment


    • #3
      OK... this isn't working. Apparently I have something misconfigured or misunderstood

      Everything is set up as a direct channel. The billingServicesSender just throws a Runtime exception. I'm seeing the error propagate to the channel "errorChannel" instead of the billingServicesErrorChannel. Based on what I read in your "what's new" post, I inferred that an exception thrown by a downstream endpoint would propagate up the route and get caught upstream (since it's all direct channels)

      Code:
        <int:channel name="sendToBillingChannel" />
        <int:service-activator ref="accountBeforeBilling" input-channel="sendToBillingChannel" output-channel="billingServicesChannel">
          <int:request-handler-advice-chain>
            <bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
              <property name="onFailureExpression" value="payload"/>
              <property name="failureChannel" ref="billingServicesErrorChannel" />
              <property name="trapException" value="true" />
            </bean>
          </int:request-handler-advice-chain>
        </int:service-activator>
        
        <int:channel id="billingServicesChannel" />
        <int:service-activator ref="billingServicesSender" input-channel="billingServicesChannel" output-channel="rtCoreRoutingChannel" /> 
      
      
        <int:channel id="billingServicesErrorChannel"/>
        <int:logging-channel-adapter id="billingServicesErrorLogger" logger-name="BILLING_SERVICES_ERROR" channel="billingServicesErrorChannel" level="ERROR" log-full-message="true"/>
      Last edited by mgirard; Jan 18th, 2013, 05:17 PM.

      Comment


      • #4
        I probably need to see your complete configuration and a full DEBUG log.

        I just wrote a test case and all worked as expected for me...

        Code:
        public class AdvisedMessageHandlerTests {
        ...
        	@Test
        	public void testEERHA() {
        		ApplicationContext ctx = new ClassPathXmlApplicationContext("test-context.xml", this.getClass());
        		MessageChannel in = ctx.getBean("in", MessageChannel.class);
        		in.send(new GenericMessage<String>("foo"));
        		PollableChannel failures = ctx.getBean("failures", PollableChannel.class);
        		ErrorMessage failure = (ErrorMessage) failures.receive(1000);
        		assertNotNull(failure);
        		assertEquals("foo", ((MessagingException) failure.getPayload()).getFailedMessage().getPayload());
        		assertEquals("foo", ((MessageHandlingExpressionEvaluatingAdviceException) failure.getPayload()).getEvaluationResult());
        	}
        
        	public static class Foo {
        
        		public String foo(String bar) {
        			throw new RuntimeException("foo");
        		}
        	}
        }
        Code:
        <int:channel id="in" />
        	
        <int:service-activator ref="foo" input-channel="in">
        	<int:request-handler-advice-chain>
        		<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
        			<property name="trapException" value="true" />
        			<property name="failureChannel" ref="failures" />
        			<property name="onFailureExpression" value="payload" />
        		</bean>
        	</int:request-handler-advice-chain>
        </int:service-activator>
        	
        <bean id="foo" class="org.springframework.integration.handler.advice.AdvisedMessageHandlerTests$Foo" />
        
        <int:channel id="failures">
        	<int:queue />
        </int:channel>
        Code:
        2013-01-18 18:37:57,347 [main] DEBUG: org.springframework.integration.channel.DirectChannel - preSend on channel 'in', message: [Payload=foo][Headers={timestamp=1358552277347, id=28bb2009-0492-4883-864f-795775fbab98}]
        2013-01-18 18:37:57,347 [main] DEBUG: org.springframework.integration.handler.ServiceActivatingHandler - ServiceActivator for [org.spri[email protected]581e80] received message: [Payload=foo][Headers={timestamp=1358552277347, id=28bb2009-0492-4883-864f-795775fbab98}]
        2013-01-18 18:37:57,354 [main] DEBUG: org.springframework.integration.channel.QueueChannel - preSend on channel 'failures', message: [Payload=org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice$MessageHandlingExpressionEvaluatingAdviceException: Handler Failed][Headers={timestamp=1358552277354, id=c141dfd0-22b0-4bff-929b-1b70debfaeb6}]
        Last edited by Gary Russell; Jan 18th, 2013, 06:01 PM.

        Comment


        • #5
          Thank you for taking the time,Gary. I really appreciate it. It's a complex flow, so I'll work It down to a simple example of when it doesn't work and post the final routing. As I go along , I'll work off a fork of the samples project
          Last edited by mgirard; Jan 21st, 2013, 01:34 PM.

          Comment


          • #6
            OK. Long post. I think the issue here is that I'm trying to catch a downstream error - I can understand why it might not work, but what I had read in one of the posts about 2.2 features implied that it could work. Note that the exception isn't thrown on the advised endpoint, but on the one downstream. In the below example, I want the error to go to "failureChannel"

            (I apologize if there are class name errors - I modified some package names in the text editor...)

            Here's my (very verbose) config: applicationContext-advice.xml
            Code:
            <beans ...>
              <bean id="noExceptionEndpointBean" class="test.ExceptionThrowerSA">
                <property name="shouldThrowException" value="false" />
              </bean>
              <bean id="yesExceptionEndpointBean" class="test.ExceptionThrowerSA">
                <property name="shouldThrowException" value="true" />
              </bean>
              
              <int:gateway service-interface="test.TestProvisionGateway" error-channel="myErrorChannel" default-request-channel="noExceptionChannel" default-reply-timeout="100" />
              
              <int:channel id="noExceptionChannel" />
              
              <int:service-activator ref="noExceptionEndpointBean"  method="process" input-channel="noExceptionChannel" output-channel="throwExceptionChannel">
                <int:request-handler-advice-chain>
                  <bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
                    <property name="onFailureExpression" value="payload"/>
                    <property name="failureChannel" ref="failureChannel" />
                    <property name="trapException" value="true" />
                  </bean>
                </int:request-handler-advice-chain>
              </int:service-activator>
              
              <int:channel id="throwExceptionChannel" />
              <int:service-activator ref="yesExceptionEndpointBean" method="process" input-channel="throwExceptionChannel" output-channel="completeChannel" /> 
            
            
              <int:channel id="failureChannel"/>
              <int:logging-channel-adapter id="billingServicesErrorLogger" expression="'CAUGHT FAILURE'" logger-name="BILLING_SERVICES_ERROR" channel="failureChannel" level="ERROR"/>
              
              <int:channel id="completeChannel" />
              <int:logging-channel-adapter id="completeEndpoint" expression="'ROUTE COMPLETE'" channel="completeChannel" level="ERROR" />
              
              <int:channel id="myErrorChannel" />
              <int:logging-channel-adapter id="gatewayErrorLogger" expression="'ERROR FROM GATEWAY'" channel="myErrorChannel" level="ERROR" />
            </beans>
            My endpoint class to throw a runtime exception
            Code:
            package test;
            
            public class ExceptionThrowerSA {
              private boolean shouldThrowException = false;
            
              public Object process(Object input) {
                if (shouldThrowException) {
                  throw new RuntimeException("ENPOINT EXCEPTION");
                }
                return input;
              }
            
              public boolean isShouldThrowException() {
                return shouldThrowException;
              }
            
              public void setShouldThrowException(boolean shouldThrowException) {
                this.shouldThrowException = shouldThrowException;
              }
            
            }
            Simple gateway:
            Code:
            package test;
            
            public interface TestProvisionGateway {
              public Object process(Object obj);
            }
            Quick test case.

            Code:
            package test;
            
            import org.junit.Test;
            import org.junit.runner.RunWith;
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.test.context.ContextConfiguration;
            import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
            
            @RunWith(SpringJUnit4ClassRunner.class)
            @ContextConfiguration("/applicationContext-advice.xml")
            public class ExceptionAdviceTest {
              @Autowired
              TestProvisionGateway gateway;
            
              @Test
              public void test() {
                gateway.process("Hello World.");
              }
            
            }
            Output is:
            Code:
            2013-01-21 11:26:42.018 [main] ERROR o.s.i.handler.LoggingHandler - ERROR FROM GATEWAY

            Comment


            • #7
              No, the whole point of the request handler advice chain is to provide a mechanism to advise JUST the individual endpoint and not the entire downstream flow.

              One way to advise a complete flow segment is to use a poller and add an advice-chain to it; in that context the entire flow is advised (however, the standard 'request handler' advice classes are not designed to be used there (and won't work); you'd need to write a custom advice.

              However, it's not clear to me how this would be any different to use an error gateway (which requires less code).

              By the way, I just read your initial post - you don't need any "hackery" if you use a <gateway/> service-interface method that returns void (for cases where no reply is expected).

              Comment


              • #8
                I guess it's just stuck in my head that conceptually a gateway always expects a return.

                Is it just a matter of putting a low reply timeout? Or is there a better way?

                At one point we were doing a synchronous pub-sub with our outbound adapter as order 1 and a 'reply' endpoint as order 2 in order to close the gateway 'loop'.

                Comment


                • #9
                  Just have the gateway service-interface method signature return void; the thread will return immediately with null rather than waiting for a reply.

                  Code:
                  public interface noReplyGateway {
                  
                      void send(Message<?> message);
                  
                  }

                  Comment


                  • #10
                    (BTW- I'm OK with a RTFM response )

                    Here's where I got off....

                    From the post about "What's new in 2.2..."

                    Consider a Spring Integration flow:

                    some-inbound-adapter<-poller->http-gateway1->http-gateway2->jdbc-outbound-adapter

                    If the database connection has a glitch, and we have a retry advice on the poller; the entire flow will be reprocessed; causing both http gateways to be called a second (or more) times.
                    So, I assumed that if you had direct channels and an error happened on the jdbc-outbound-adapter that the exception would propagate back to the poller and get caught by the retry advice defined on the poller.

                    For what I'm doing right now, I'll use the gateway, but I'd like to better understand the retry scenario. I'll be using that in our error handling routines.
                    Last edited by mgirard; Jan 21st, 2013, 02:05 PM.

                    Comment


                    • #11
                      That comment simply says that you could previously add some kind of Advice (including retry) on a <poller/> but there was no way to advise a single component - that's what the new 2.2 feature is (the ability to add an advice to JUST an endpoint). In addition, we happened to supply some "standard" advices that we felt might be useful in such a situation (retry, circuit breaker, expression evaluatiing).

                      We had never provided any 'standard' Advice classes (besides the transaction interceptor) for use on a poller - it was (and still is) there to allow users to roll their own advice, and apply it to the entire flow.

                      If you would like to see a set of supplied advices for that context, open a New Feature JIRA (or even better - contribute them!)

                      Comment


                      • #12
                        Thanks, Gary! Once again you've set me on the proper path. Spring-Int team FTW!

                        Comment

                        Working...
                        X