Author: davsclaus Date: Mon Dec 20 18:48:14 2010 New Revision: 1051239 URL: http://svn.apache.org/viewvc?rev=1051239&view=rev Log: CAMEL-3448: Fixed issue with route scoped onException may pickup an onException from another route. Also fixes so it now prefers route scoped over context scoped.
Added: camel/trunk/camel-core/src/test/java/org/apache/camel/issues/TwoRouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java - copied, changed from r1051125, camel/trunk/camel-core/src/test/java/org/apache/camel/issues/RouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/RouteScopedOnExceptionSameTypeTest.java - copied, changed from r1051125, camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/OnExceptionAfterRouteTest.java Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/processor/ErrorHandlerSupport.java camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/DefaultExceptionPolicyStrategy.java camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/ExceptionPolicyKey.java Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/processor/ErrorHandlerSupport.java URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/processor/ErrorHandlerSupport.java?rev=1051239&r1=1051238&r2=1051239&view=diff ============================================================================== --- camel/trunk/camel-core/src/main/java/org/apache/camel/processor/ErrorHandlerSupport.java (original) +++ camel/trunk/camel-core/src/main/java/org/apache/camel/processor/ErrorHandlerSupport.java Mon Dec 20 18:48:14 2010 @@ -24,6 +24,8 @@ import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.impl.ServiceSupport; import org.apache.camel.model.OnExceptionDefinition; +import org.apache.camel.model.ProcessorDefinitionHelper; +import org.apache.camel.model.RouteDefinition; import org.apache.camel.processor.exceptionpolicy.DefaultExceptionPolicyStrategy; import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyKey; import org.apache.camel.processor.exceptionpolicy.ExceptionPolicyStrategy; @@ -49,7 +51,9 @@ public abstract class ErrorHandlerSuppor List<Class> list = exceptionType.getExceptionClasses(); for (Class clazz : list) { - ExceptionPolicyKey key = new ExceptionPolicyKey(clazz, exceptionType.getOnWhen()); + RouteDefinition route = ProcessorDefinitionHelper.getRoute(exceptionType); + String routeId = route != null ? route.getId() : null; + ExceptionPolicyKey key = new ExceptionPolicyKey(routeId, clazz, exceptionType.getOnWhen()); exceptionPolicies.put(key, exceptionType); } } Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/DefaultExceptionPolicyStrategy.java URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/DefaultExceptionPolicyStrategy.java?rev=1051239&r1=1051238&r2=1051239&view=diff ============================================================================== --- camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/DefaultExceptionPolicyStrategy.java (original) +++ camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/DefaultExceptionPolicyStrategy.java Mon Dec 20 18:48:14 2010 @@ -17,6 +17,7 @@ package org.apache.camel.processor.exceptionpolicy; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -61,17 +62,29 @@ public class DefaultExceptionPolicyStrat Exchange exchange, Throwable exception) { Map<Integer, OnExceptionDefinition> candidates = new TreeMap<Integer, OnExceptionDefinition>(); + Map<ExceptionPolicyKey, OnExceptionDefinition> routeScoped = new LinkedHashMap<ExceptionPolicyKey, OnExceptionDefinition>(); + Map<ExceptionPolicyKey, OnExceptionDefinition> contextScoped = new LinkedHashMap<ExceptionPolicyKey, OnExceptionDefinition>(); + // split policies into route and context scoped + initRouteAndContextScopedExceptionPolicies(exceptionPolicies, routeScoped, contextScoped); + + // at first check route scoped as we prefer them over context scoped // recursive up the tree using the iterator boolean exactMatch = false; Iterator<Throwable> it = createExceptionIterator(exception); while (!exactMatch && it.hasNext()) { // we should stop looking if we have found an exact match - exactMatch = findMatchedExceptionPolicy(exceptionPolicies, exchange, it.next(), candidates); + exactMatch = findMatchedExceptionPolicy(routeScoped, exchange, it.next(), candidates); } - // now go through the candidates and find the best + // fallback to check context scoped (only do this if there was no exact match) + it = createExceptionIterator(exception); + while (!exactMatch && it.hasNext()) { + // we should stop looking if we have found an exact match + exactMatch = findMatchedExceptionPolicy(contextScoped, exchange, it.next(), candidates); + } + // now go through the candidates and find the best if (LOG.isTraceEnabled()) { LOG.trace("Found " + candidates.size() + " candidates"); } @@ -80,11 +93,26 @@ public class DefaultExceptionPolicyStrat // no type found return null; } else { - // return the first in the map as its sorted and + // return the first in the map as its sorted and we checked route scoped first, which we prefer return candidates.values().iterator().next(); } } + private void initRouteAndContextScopedExceptionPolicies(Map<ExceptionPolicyKey, OnExceptionDefinition> exceptionPolicies, + Map<ExceptionPolicyKey, OnExceptionDefinition> routeScoped, + Map<ExceptionPolicyKey, OnExceptionDefinition> contextScoped) { + + // loop through all the entries and split into route and context scoped + Set<Map.Entry<ExceptionPolicyKey, OnExceptionDefinition>> entries = exceptionPolicies.entrySet(); + for (Map.Entry<ExceptionPolicyKey, OnExceptionDefinition> entry : entries) { + if (entry.getKey().getRouteId() != null) { + routeScoped.put(entry.getKey(), entry.getValue()); + } else { + contextScoped.put(entry.getKey(), entry.getValue()); + } + } + } + private boolean findMatchedExceptionPolicy(Map<ExceptionPolicyKey, OnExceptionDefinition> exceptionPolicies, Exchange exchange, Throwable exception, Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/ExceptionPolicyKey.java URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/ExceptionPolicyKey.java?rev=1051239&r1=1051238&r2=1051239&view=diff ============================================================================== --- camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/ExceptionPolicyKey.java (original) +++ camel/trunk/camel-core/src/main/java/org/apache/camel/processor/exceptionpolicy/ExceptionPolicyKey.java Mon Dec 20 18:48:14 2010 @@ -20,16 +20,30 @@ import org.apache.camel.model.WhenDefini /** * Exception policy key is a compound key for storing: - * <b>exception class</b> + <b>when</b> => <b>exception type</b>. + * <b>route id </b> + <b>exception class</b> + <b>when</b> => <b>exception type</b>. * <p/> * This is used by Camel to store the onException types configured that has or has not predicates attached (when). */ public final class ExceptionPolicyKey { + private final String routeId; private final Class exceptionClass; private final WhenDefinition when; + @Deprecated public ExceptionPolicyKey(Class exceptionClass, WhenDefinition when) { + this(null, exceptionClass, when); + } + + /** + * Key for exception clause + * + * @param routeId the route, or use <tt>null</tt> for a global scoped + * @param exceptionClass the exception class + * @param when optional predicate when the exception clause should trigger + */ + public ExceptionPolicyKey(String routeId, Class exceptionClass, WhenDefinition when) { + this.routeId = routeId; this.exceptionClass = exceptionClass; this.when = when; } @@ -42,10 +56,16 @@ public final class ExceptionPolicyKey { return when; } + public String getRouteId() { + return routeId; + } + + @Deprecated public static ExceptionPolicyKey newInstance(Class exceptionClass) { return new ExceptionPolicyKey(exceptionClass, null); } + @Deprecated public static ExceptionPolicyKey newInstance(Class exceptionClass, WhenDefinition when) { return new ExceptionPolicyKey(exceptionClass, when); } @@ -61,7 +81,10 @@ public final class ExceptionPolicyKey { ExceptionPolicyKey that = (ExceptionPolicyKey) o; - if (!exceptionClass.equals(that.exceptionClass)) { + if (exceptionClass != null ? !exceptionClass.equals(that.exceptionClass) : that.exceptionClass != null) { + return false; + } + if (routeId != null ? !routeId.equals(that.routeId) : that.routeId != null) { return false; } if (when != null ? !when.equals(that.when) : that.when != null) { @@ -73,13 +96,14 @@ public final class ExceptionPolicyKey { @Override public int hashCode() { - int result = exceptionClass.hashCode(); + int result = routeId != null ? routeId.hashCode() : 0; + result = 31 * result + (exceptionClass != null ? exceptionClass.hashCode() : 0); result = 31 * result + (when != null ? when.hashCode() : 0); return result; } @Override public String toString() { - return "ExceptionPolicyKey[" + exceptionClass + (when != null ? " " + when : "") + "]"; + return "ExceptionPolicyKey[route: " + (routeId != null ? routeId : "<global>") + ", " + exceptionClass + (when != null ? " " + when : "") + "]"; } } Copied: camel/trunk/camel-core/src/test/java/org/apache/camel/issues/TwoRouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java (from r1051125, camel/trunk/camel-core/src/test/java/org/apache/camel/issues/RouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java) URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/issues/TwoRouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java?p2=camel/trunk/camel-core/src/test/java/org/apache/camel/issues/TwoRouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java&p1=camel/trunk/camel-core/src/test/java/org/apache/camel/issues/RouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java&r1=1051125&r2=1051239&rev=1051239&view=diff ============================================================================== --- camel/trunk/camel-core/src/test/java/org/apache/camel/issues/RouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java (original) +++ camel/trunk/camel-core/src/test/java/org/apache/camel/issues/TwoRouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest.java Mon Dec 20 18:48:14 2010 @@ -33,7 +33,7 @@ import org.apache.camel.model.RouteDefin * * @version $Revision$ */ -public class RouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest extends ContextTestSupport { +public class TwoRouteScopedOnExceptionWithInterceptSendToEndpointIssueWithPredicateTest extends ContextTestSupport { private final AtomicInteger invoked = new AtomicInteger(); @@ -89,6 +89,15 @@ public class RouteScopedOnExceptionWithI .to("mock:exhausted") .end() .to("seda:foo"); + + from("direct:start2") + // no redelivery delay for faster unit tests + .onException(ConnectException.class).maximumRedeliveries(3).redeliveryDelay(0) + .logRetryAttempted(true).retryAttemptedLogLevel(LoggingLevel.FATAL) + // send to mock when we are exhausted + .to("mock:exhausted2") + .end() + .to("seda:foo2"); } }; } Copied: camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/RouteScopedOnExceptionSameTypeTest.java (from r1051125, camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/OnExceptionAfterRouteTest.java) URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/RouteScopedOnExceptionSameTypeTest.java?p2=camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/RouteScopedOnExceptionSameTypeTest.java&p1=camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/OnExceptionAfterRouteTest.java&r1=1051125&r2=1051239&rev=1051239&view=diff ============================================================================== --- camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/OnExceptionAfterRouteTest.java (original) +++ camel/trunk/camel-core/src/test/java/org/apache/camel/processor/onexception/RouteScopedOnExceptionSameTypeTest.java Mon Dec 20 18:48:14 2010 @@ -16,36 +16,285 @@ */ package org.apache.camel.processor.onexception; +import java.io.IOException; + import org.apache.camel.ContextTestSupport; import org.apache.camel.builder.RouteBuilder; /** * @version $Revision$ */ -public class OnExceptionAfterRouteTest extends ContextTestSupport { +public class RouteScopedOnExceptionSameTypeTest extends ContextTestSupport { @Override public boolean isUseRouteBuilder() { return false; } - public void testOnExceptionAfterRoute() throws Exception { - try { - context.addRoutes(new RouteBuilder() { - @Override - public void configure() throws Exception { - from("direct:start") - .throwException(new IllegalArgumentException("Damn")); - - onException(IllegalArgumentException.class) - .handled(true) - .to("mock:damn"); - } - }); - fail("Should have thrown an exception"); - } catch (IllegalArgumentException e) { - assertEquals("onException must be defined before any routes in the RouteBuilder", e.getMessage()); - } + public void testOnExceptionExactType() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + + from("direct:foo") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:foo") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionDifferentType() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + + from("direct:foo") + .onException(IOException.class) + .handled(true) + .to("mock:foo") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionSameTypeRouteLast() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:foo") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:foo") + .end() + .throwException(new IllegalArgumentException("Damn")); + + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionDifferentTypeRouteLast() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:foo") + .onException(IOException.class) + .handled(true) + .to("mock:foo") + .end() + .throwException(new IllegalArgumentException("Damn")); + + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionExactTypeDLC() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + errorHandler(deadLetterChannel("mock:dead")); + + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + + from("direct:foo") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:foo") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + getMockEndpoint("mock:dlc").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testTwoOnExceptionExactType() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .onException(IOException.class) + .handled(true) + .to("mock:io") + .end() + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + + from("direct:foo") + .onException(IOException.class) + .handled(true) + .to("mock:io") + .end() + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:foo") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + getMockEndpoint("mock:io").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionRouteAndGlobalExactType() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + onException(IllegalArgumentException.class) + .handled(true) + .to("mock:foo"); + + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionRouteAndGlobalDifferentType() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + onException(IOException.class) + .handled(true) + .to("mock:foo"); + + from("direct:start") + .onException(IllegalArgumentException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + getMockEndpoint("mock:damn").expectedMessageCount(1); + getMockEndpoint("mock:foo").expectedMessageCount(0); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testOnExceptionRouteAndOnlyGlobalExactType() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + onException(IllegalArgumentException.class) + .handled(true) + .to("mock:foo"); + + from("direct:start") + .onException(IOException.class) + .handled(true) + .to("mock:damn") + .end() + .throwException(new IllegalArgumentException("Damn")); + } + }); + context.start(); + + // this time we pick global scoped as its an exact match, so foo should get the message + getMockEndpoint("mock:damn").expectedMessageCount(0); + getMockEndpoint("mock:foo").expectedMessageCount(1); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); } }