This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 81c32ebe7af5624d0f29722297363a5498b65fb3 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Apr 13 16:42:52 2022 +0200 CAMEL-16834: error handler in DSL model --- .../camel/catalog/models/routeConfiguration.json | 1 + .../apache/camel/catalog/schemas/camel-spring.xsd | 1 + .../org/apache/camel/model/routeConfiguration.json | 1 + .../camel/model/RouteConfigurationDefinition.java | 40 +++++- .../apache/camel/model/RouteDefinitionHelper.java | 24 +++- .../org/apache/camel/model/RoutesDefinition.java | 24 +++- .../core/xml/AbstractCamelContextFactoryBean.java | 11 +- .../model/RoutesConfigurationErrorHandlerTest.java | 160 +++++++++++++++++++++ .../java/org/apache/camel/xml/in/ModelParser.java | 1 + .../modules/ROOT/pages/route-configuration.adoc | 27 ++++ .../RouteConfigurationDefinitionDeserializer.java | 7 + .../src/generated/resources/camel-yaml-dsl.json | 3 + .../src/generated/resources/camelYamlDsl.json | 3 + .../camel/dsl/yaml/RouteConfigurationTest.groovy | 42 ++++++ 14 files changed, 334 insertions(+), 11 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json index a1b45b6bb7c..5e560cdfaad 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json @@ -12,6 +12,7 @@ "output": false }, "properties": { + "errorHandler": { "kind": "element", "displayName": "Error Handler", "required": false, "type": "object", "javaType": "org.apache.camel.model.ErrorHandlerDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the error handler to use, for routes that has not already been configured with an error handler." }, "intercept": { "kind": "element", "displayName": "Intercept", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts every processing step." }, "interceptFrom": { "kind": "element", "displayName": "Intercept From", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptFromDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts incoming messages on the given endpoint." }, "interceptSendToEndpoint": { "kind": "element", "displayName": "Intercept Send To Endpoint", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptSendToEndpointDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Applies a route for an interceptor if an exchange is send to the given endpoint" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd index 72e6d5fa578..0c731250a98 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd @@ -10535,6 +10535,7 @@ Reference to the route templates in the xml dsl. <xs:complexContent> <xs:extension base="tns:optionalIdentifiedDefinition"> <xs:sequence> + <xs:element minOccurs="0" ref="tns:errorHandler"/> <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:intercept"/> <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:interceptFrom"/> <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:interceptSendToEndpoint"/> diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json index a1b45b6bb7c..5e560cdfaad 100644 --- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json +++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json @@ -12,6 +12,7 @@ "output": false }, "properties": { + "errorHandler": { "kind": "element", "displayName": "Error Handler", "required": false, "type": "object", "javaType": "org.apache.camel.model.ErrorHandlerDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the error handler to use, for routes that has not already been configured with an error handler." }, "intercept": { "kind": "element", "displayName": "Intercept", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts every processing step." }, "interceptFrom": { "kind": "element", "displayName": "Intercept From", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptFromDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts incoming messages on the given endpoint." }, "interceptSendToEndpoint": { "kind": "element", "displayName": "Intercept Send To Endpoint", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptSendToEndpointDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Applies a route for an interceptor if an exchange is send to the given endpoint" }, diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java index 1a7b65f59a1..0d509cef27d 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java @@ -26,6 +26,8 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import org.apache.camel.ErrorHandlerFactory; +import org.apache.camel.model.errorhandler.ErrorHandlerRefDefinition; import org.apache.camel.spi.Metadata; /** @@ -37,8 +39,8 @@ import org.apache.camel.spi.Metadata; public class RouteConfigurationDefinition extends OptionalIdentifiedDefinition<RouteConfigurationDefinition> implements PreconditionContainer { - // TODO: Model for ErrorHandler (requires to move error handler model from spring-xml, blueprint to core) - + @XmlElement + private ErrorHandlerDefinition errorHandler; @XmlElement(name = "intercept") private List<InterceptDefinition> intercepts = new ArrayList<>(); @XmlElement(name = "interceptFrom") @@ -71,6 +73,14 @@ public class RouteConfigurationDefinition extends OptionalIdentifiedDefinition<R return "RoutesConfiguration " + getId(); } + public ErrorHandlerDefinition getErrorHandler() { + return errorHandler; + } + + public void setErrorHandler(ErrorHandlerDefinition errorHandler) { + this.errorHandler = errorHandler; + } + public List<OnExceptionDefinition> getOnExceptions() { return onExceptions; } @@ -132,6 +142,32 @@ public class RouteConfigurationDefinition extends OptionalIdentifiedDefinition<R // Fluent API // ------------------------------------------------------------------------- + /** + * Sets the error handler to use, for routes that has not already been configured with an error handler. + * + * @param ref reference to existing error handler + * @return the builder + */ + public RouteConfigurationDefinition errorHandler(String ref) { + ErrorHandlerDefinition def = new ErrorHandlerDefinition(); + def.setErrorHandlerType(new ErrorHandlerRefDefinition(ref)); + setErrorHandler(def); + return this; + } + + /** + * Sets the error handler to use, for routes that has not already been configured with an error handler. + * + * @param errorHandler the error handler + * @return the builder + */ + public RouteConfigurationDefinition errorHandler(ErrorHandlerFactory errorHandler) { + ErrorHandlerDefinition def = new ErrorHandlerDefinition(); + def.setErrorHandlerType(errorHandler); + setErrorHandler(def); + return this; + } + /** * Sets the predicate of the precondition in simple language to evaluate in order to determine if this route * configuration should be included or not. diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java index 98b9d1923f5..e1d072d2a29 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java @@ -307,7 +307,7 @@ public final class RouteDefinitionHelper { * @param route the route */ public static void prepareRoute(CamelContext context, RouteDefinition route) { - prepareRoute(context, route, null, null, null, null, null); + prepareRoute(context, route, null, null, null, null, null, null); } /** @@ -317,6 +317,7 @@ public final class RouteDefinitionHelper { * * @param context the camel context * @param route the route + * @param errorHandler optional error handler * @param onExceptions optional list of onExceptions * @param intercepts optional list of interceptors * @param interceptFromDefinitions optional list of interceptFroms @@ -324,13 +325,16 @@ public final class RouteDefinitionHelper { * @param onCompletions optional list onCompletions */ public static void prepareRoute( - CamelContext context, RouteDefinition route, List<OnExceptionDefinition> onExceptions, + CamelContext context, RouteDefinition route, + ErrorHandlerDefinition errorHandler, + List<OnExceptionDefinition> onExceptions, List<InterceptDefinition> intercepts, List<InterceptFromDefinition> interceptFromDefinitions, List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions, List<OnCompletionDefinition> onCompletions) { - prepareRouteImp(context, route, onExceptions, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions, + prepareRouteImp(context, route, errorHandler, onExceptions, intercepts, interceptFromDefinitions, + interceptSendToEndpointDefinitions, onCompletions); } @@ -341,6 +345,7 @@ public final class RouteDefinitionHelper { * * @param context the camel context * @param route the route + * @param errorHandler optional error handler * @param onExceptions optional list of onExceptions * @param intercepts optional list of interceptors * @param interceptFromDefinitions optional list of interceptFroms @@ -348,7 +353,9 @@ public final class RouteDefinitionHelper { * @param onCompletions optional list onCompletions */ private static void prepareRouteImp( - CamelContext context, RouteDefinition route, List<OnExceptionDefinition> onExceptions, + CamelContext context, RouteDefinition route, + ErrorHandlerDefinition errorHandler, + List<OnExceptionDefinition> onExceptions, List<InterceptDefinition> intercepts, List<InterceptFromDefinition> interceptFromDefinitions, List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions, @@ -370,7 +377,7 @@ public final class RouteDefinitionHelper { RouteDefinitionHelper.prepareRouteForInit(route, abstracts, lower); // parent and error handler builder should be initialized first - initParentAndErrorHandlerBuilder(context, route, abstracts, onExceptions); + initParentAndErrorHandlerBuilder(context, route, errorHandler, abstracts, onExceptions); // validate top-level violations validateTopLevel(route.getOutputs()); // then interceptors @@ -440,10 +447,13 @@ public final class RouteDefinitionHelper { } private static void initParentAndErrorHandlerBuilder( - CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> abstracts, + CamelContext context, RouteDefinition route, ErrorHandlerDefinition errorHandler, + List<ProcessorDefinition<?>> abstracts, List<OnExceptionDefinition> onExceptions) { - if (context != null) { + if (errorHandler != null) { + route.setErrorHandlerFactoryIfNull(errorHandler.getErrorHandlerType()); + } else if (context != null) { // let the route inherit the error handler builder from camel // context if none already set diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java index f53235dc458..7c582c65bfb 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java @@ -18,6 +18,7 @@ package org.apache.camel.model; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -241,6 +242,7 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit route.setResource(resource); // merge global and route scoped together + final AtomicReference<ErrorHandlerDefinition> gcErrorHandler = new AtomicReference<>(); List<OnExceptionDefinition> oe = new ArrayList<>(onExceptions); List<InterceptDefinition> icp = new ArrayList<>(intercepts); List<InterceptFromDefinition> ifrom = new ArrayList<>(interceptFroms); @@ -250,6 +252,7 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit List<RouteConfigurationDefinition> globalConfigurations = getCamelContext().adapt(ModelCamelContext.class).getRouteConfigurationDefinitions(); if (globalConfigurations != null) { + // if there are multiple ids configured then we should apply in that same order String[] ids = route.getRouteConfigurationId() != null ? route.getRouteConfigurationId().split(",") : new String[] { "*" }; @@ -266,6 +269,12 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit } }) .forEach(g -> { + // there can only be one global error handler, so override previous, meaning + // that we will pick the last in the sort (take precedence) + if (g.getErrorHandler() != null) { + gcErrorHandler.set(g.getErrorHandler()); + } + String aid = g.getId() == null ? "<default>" : g.getId(); // remember the id that was used on the route route.addAppliedRouteConfigurationId(aid); @@ -276,11 +285,24 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit oc.addAll(g.getOnCompletions()); }); } + + // set error handler before prepare + if (errorHandlerFactory == null && gcErrorHandler.get() != null) { + ErrorHandlerDefinition ehd = gcErrorHandler.get(); + route.setErrorHandlerFactoryIfNull(ehd.getErrorHandlerType()); + } } } + // if the route does not already have an error handler set then use route configured error handler + // if one was configured + ErrorHandlerDefinition ehd = null; + if (errorHandlerFactory == null && gcErrorHandler.get() != null) { + ehd = gcErrorHandler.get(); + } + // must prepare the route before we can add it to the routes list - RouteDefinitionHelper.prepareRoute(getCamelContext(), route, oe, icp, ifrom, ito, oc); + RouteDefinitionHelper.prepareRoute(getCamelContext(), route, ehd, oe, icp, ifrom, ito, oc); if (LOG.isDebugEnabled() && route.getAppliedRouteConfigurationIds() != null) { LOG.debug("Route: {} is using route configurations ids: {}", route.getId(), diff --git a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java index adbd809e0c9..b9238c769fa 100644 --- a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java +++ b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java @@ -25,6 +25,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -57,6 +58,7 @@ import org.apache.camel.impl.engine.DefaultManagementStrategy; import org.apache.camel.impl.engine.TransformerKey; import org.apache.camel.impl.engine.ValidatorKey; import org.apache.camel.model.ContextScanDefinition; +import org.apache.camel.model.ErrorHandlerDefinition; import org.apache.camel.model.FaultToleranceConfigurationDefinition; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.GlobalOptionsDefinition; @@ -590,6 +592,7 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex route.resetPrepare(); // merge global and route scoped together + AtomicReference<ErrorHandlerDefinition> errorHandler = new AtomicReference<>(); List<OnExceptionDefinition> oe = new ArrayList<>(getOnExceptions()); List<InterceptDefinition> icp = new ArrayList<>(getIntercepts()); List<InterceptFromDefinition> ifrom = new ArrayList<>(getInterceptFroms()); @@ -615,6 +618,12 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex } }) .forEach(g -> { + // there can only be one global error handler, so override previous, meaning + // that we will pick the last in the sort (take precedence) + if (g.getErrorHandler() != null) { + errorHandler.set(g.getErrorHandler()); + } + String aid = g.getId() == null ? "<default>" : g.getId(); // remember the id that was used on the route route.addAppliedRouteConfigurationId(aid); @@ -629,7 +638,7 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex } // must prepare the route before we can add it to the routes list - RouteDefinitionHelper.prepareRoute(getContext(), route, oe, icp, ifrom, ito, oc); + RouteDefinitionHelper.prepareRoute(getContext(), route, errorHandler.get(), oe, icp, ifrom, ito, oc); if (LOG.isDebugEnabled() && route.getAppliedRouteConfigurationIds() != null) { LOG.debug("Route: {} is using route configurations ids: {}", route.getId(), diff --git a/core/camel-core/src/test/java/org/apache/camel/model/RoutesConfigurationErrorHandlerTest.java b/core/camel-core/src/test/java/org/apache/camel/model/RoutesConfigurationErrorHandlerTest.java new file mode 100644 index 00000000000..0dcc4ec67b7 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/model/RoutesConfigurationErrorHandlerTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.model; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.builder.RouteConfigurationBuilder; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Fail.fail; + +public class RoutesConfigurationErrorHandlerTest extends ContextTestSupport { + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Test + public void testGlobal() throws Exception { + context.addRoutes(new RouteConfigurationBuilder() { + @Override + public void configuration() throws Exception { + // global routes configuration + routeConfiguration().errorHandler(deadLetterChannel("mock:error")); + + } + }); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .throwException(new IllegalArgumentException("Foo")); + + from("direct:start2") + .throwException(new IllegalArgumentException("Foo2")); + } + }); + context.start(); + + getMockEndpoint("mock:error").expectedBodiesReceived("Hello World", "Bye World"); + + template.sendBody("direct:start", "Hello World"); + template.sendBody("direct:start2", "Bye World"); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testLocalOverride() throws Exception { + context.addRoutes(new RouteConfigurationBuilder() { + @Override + public void configuration() throws Exception { + // global routes configuration + routeConfiguration().errorHandler(deadLetterChannel("mock:error")); + + } + }); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .throwException(new IllegalArgumentException("Foo")); + + from("direct:start2") + .errorHandler(deadLetterChannel("mock:error2")) + .throwException(new IllegalArgumentException("Foo2")); + } + }); + context.start(); + + getMockEndpoint("mock:error").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:error2").expectedBodiesReceived("Bye World"); + + template.sendBody("direct:start", "Hello World"); + template.sendBody("direct:start2", "Bye World"); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testLocalConfiguration() throws Exception { + context.addRoutes(new RouteConfigurationBuilder() { + @Override + public void configuration() throws Exception { + routeConfiguration("mylocal").errorHandler(deadLetterChannel("mock:error")); + + } + }); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .throwException(new IllegalArgumentException("Foo")); + + from("direct:start2").routeConfigurationId("mylocal") + .throwException(new IllegalArgumentException("Foo2")); + } + }); + context.start(); + + getMockEndpoint("mock:error").expectedBodiesReceived("Bye World"); + + try { + template.sendBody("direct:start", "Hello World"); + fail("Should throw exception"); + } catch (Exception e) { + // expected + } + template.sendBody("direct:start2", "Bye World"); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testGlobalAndLocal() throws Exception { + context.addRoutes(new RouteConfigurationBuilder() { + @Override + public void configuration() throws Exception { + routeConfiguration().errorHandler(deadLetterChannel("mock:error")); + routeConfiguration("mylocal").errorHandler(deadLetterChannel("mock:error2")); + + } + }); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .throwException(new IllegalArgumentException("Foo")); + + from("direct:start2").routeConfigurationId("mylocal") + .throwException(new IllegalArgumentException("Foo2")); + } + }); + context.start(); + + getMockEndpoint("mock:error").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:error2").expectedBodiesReceived("Bye World"); + + template.sendBody("direct:start", "Hello World"); + template.sendBody("direct:start2", "Bye World"); + + assertMockEndpointsSatisfied(); + } + +} diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java index 78c3d65df2a..fe6144cae44 100644 --- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java +++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java @@ -978,6 +978,7 @@ public class ModelParser extends BaseParser { return optionalIdentifiedDefinitionAttributeHandler().accept(def, key, val); }, (def, key) -> { switch (key) { + case "errorHandler": def.setErrorHandler(doParseErrorHandlerDefinition()); break; case "interceptFrom": doAdd(doParseInterceptFromDefinition(), def.getInterceptFroms(), def::setInterceptFroms); break; case "interceptSendToEndpoint": doAdd(doParseInterceptSendToEndpointDefinition(), def.getInterceptSendTos(), def::setInterceptSendTos); break; case "intercept": doAdd(doParseInterceptDefinition(), def.getIntercepts(), def::setIntercepts); break; diff --git a/docs/user-manual/modules/ROOT/pages/route-configuration.adoc b/docs/user-manual/modules/ROOT/pages/route-configuration.adoc index 2215ffb67f6..4a71d7c07c1 100644 --- a/docs/user-manual/modules/ROOT/pages/route-configuration.adoc +++ b/docs/user-manual/modules/ROOT/pages/route-configuration.adoc @@ -12,6 +12,7 @@ The route configuration is supported by all DSL's, so useable by: Java, XML, Gro In the route configuration you can setup common strategies for: +- xref:error-handler.adoc[Error Handler] - xref:exception-clause.adoc[OnException] - xref:oncompletion.adoc[OnCompletion] - xref:components:eips:intercept.adoc[Intercept] @@ -176,6 +177,32 @@ then fail and rollback. If you add more routes, then those routes can also be assigned the _retryError_ configuration if they should also retry in case of error. +=== Route Configuration with Error Handler + +Each route configuration can also have a specific error handler configured, as shown below: + +[source,java] +---- +public class MyJavaErrorHandler extends RouteConfigurationBuilder { + + @Override + public void configuration() throws Exception { + routeConfiguration() + .errorHandler(deadLetterChannel("mock:dead")); + + routeConfiguration("retryError") + .onException(Exception.class).maximumRedeliveries(5); + } +} +---- + +In the example above, the _nameless_ configuration has an error handler with a dead letter queue. +And the route configuration with id _retryError_ does not, and instead it will attempt +to retry the failing message up till 5 times before giving up (exhausted). Because this +route configuration does not have any error handler assigned, then Camel will use the default error handler. + +IMPORTANT: Routes that have a local error handler defined, will always use this error handler, +instead of the error handler from route configurations. A route can only have 1 error handler. == Route Configuration in XML diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java index 5b99d26a339..1123731de60 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java @@ -20,6 +20,7 @@ import org.apache.camel.dsl.yaml.common.YamlDeserializationContext; import org.apache.camel.dsl.yaml.common.YamlDeserializerBase; import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver; import org.apache.camel.dsl.yaml.common.exception.UnsupportedFieldException; +import org.apache.camel.model.ErrorHandlerDefinition; import org.apache.camel.model.InterceptDefinition; import org.apache.camel.model.InterceptFromDefinition; import org.apache.camel.model.InterceptSendToEndpointDefinition; @@ -41,6 +42,7 @@ import org.snakeyaml.engine.v2.nodes.SequenceNode; properties = { @YamlProperty(name = "id", type = "string"), @YamlProperty(name = "precondition", type = "string"), + @YamlProperty(name = "error-handler", type = "object:org.apache.camel.model.ErrorHandlerDefinition.class"), @YamlProperty(name = "intercept", type = "array:org.apache.camel.model.InterceptDefinition"), @YamlProperty(name = "intercept-from", type = "array:org.apache.camel.model.InterceptFromDefinition"), @YamlProperty(name = "intercept-send-to-endpoint", @@ -79,6 +81,11 @@ public class RouteConfigurationDefinitionDeserializer extends YamlDeserializerBa case "precondition": target.setPrecondition(asText(val)); break; + case "error-handler": + setDeserializationContext(val, dc); + ErrorHandlerDefinition ehd = asType(val, ErrorHandlerDefinition.class); + target.setErrorHandler(ehd); + break; case "on-exception": setDeserializationContext(val, dc); OnExceptionDefinition oed = asType(val, OnExceptionDefinition.class); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json index 24e65a97bef..260ec1996de 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json @@ -2324,6 +2324,9 @@ }, { "type" : "object", "properties" : { + "error-handler" : { + "$ref" : "#/items/definitions/org.apache.camel.model.ErrorHandlerDefinition.class" + }, "id" : { "type" : "string" }, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json index ac348b5ee5f..62c0864d508 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json @@ -2228,6 +2228,9 @@ }, { "type" : "object", "properties" : { + "errorHandler" : { + "$ref" : "#/items/definitions/org.apache.camel.model.ErrorHandlerDefinition.class" + }, "id" : { "type" : "string" }, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy index 04ff381cb6e..bba80b12c80 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy @@ -223,4 +223,46 @@ class RouteConfigurationTest extends YamlTestSupport { Assertions.assertTrue(out2.isFailed()) } + def "route-configuration-error-handler"() { + setup: + // global configurations + loadRoutes """ + - beans: + - name: myFailingProcessor + type: ${MyFailingProcessor.name} + - route-configuration: + - error-handler: + dead-letter-channel: + dead-letter-uri: "mock:on-error" + """ + // routes + loadRoutes """ + - from: + uri: "direct:start" + steps: + - process: + ref: "myFailingProcessor" + - from: + uri: "direct:start2" + steps: + - process: + ref: "myFailingProcessor" + """ + + withMock('mock:on-error') { + expectedBodiesReceived 'hello', 'hello2' + } + + when: + context.start() + + withTemplate { + to('direct:start').withBody('hello').send() + to('direct:start2').withBody('hello2').send() + } + then: + MockEndpoint.assertIsSatisfied(context) + } + + }
