From 83c24fee3ef8ebeb4282421f8194d88061c68c2f Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Tue, 26 May 2020 19:59:26 +0200 Subject: [PATCH] yaml-loader: add support for OnException --- .../k/loader/yaml/parser/FromStepParser.java | 2 +- .../yaml/parser/OnExceptionStepParser.java | 170 ++++++++++++++++++ .../k/loader/yaml/parser/RestStepParser.java | 2 +- .../k/loader/yaml/spi/StartStepParser.java | 1 - .../camel/k/loader/yaml/spi/StepParser.java | 17 +- .../camel/k/loader/yaml/YamlSourceLoader.java | 42 +---- .../camel/k/loader/yaml/TestSupport.groovy | 17 +- .../loader/yaml/parser/OnExceptionTest.groovy | 121 +++++++++++++ .../yaml/parser/KnativeStepParser.java | 2 +- 9 files changed, 322 insertions(+), 52 deletions(-) create mode 100644 camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/OnExceptionStepParser.java create mode 100644 camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/parser/OnExceptionTest.groovy diff --git a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/FromStepParser.java b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/FromStepParser.java index 22f846fde..47e95fc75 100644 --- a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/FromStepParser.java +++ b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/FromStepParser.java @@ -37,7 +37,7 @@ public class FromStepParser implements StartStepParser { public ProcessorDefinition toStartProcessor(Context context) { final FromStepDefinition definition = context.node(FromStepDefinition.class); final String uri = definition.getEndpointUri(); - final RouteDefinition route = new RouteDefinition().from(uri); + final RouteDefinition route = context.builder().from(uri); // as this is a start converter, steps are mandatory StepParserSupport.notNull(definition.steps, "steps"); diff --git a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/OnExceptionStepParser.java b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/OnExceptionStepParser.java new file mode 100644 index 000000000..748b5bd36 --- /dev/null +++ b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/OnExceptionStepParser.java @@ -0,0 +1,170 @@ +/* + * 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.k.loader.yaml.parser; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonAlias; +import org.apache.camel.k.annotation.yaml.YAMLNodeDefinition; +import org.apache.camel.k.annotation.yaml.YAMLStepParser; +import org.apache.camel.k.loader.yaml.model.Step; +import org.apache.camel.k.loader.yaml.spi.ProcessorStepParser; +import org.apache.camel.k.loader.yaml.spi.StartStepParser; +import org.apache.camel.k.loader.yaml.spi.StepParserSupport; +import org.apache.camel.model.ExpressionSubElementDefinition; +import org.apache.camel.model.OnExceptionDefinition; +import org.apache.camel.model.ProcessorDefinition; +import org.apache.camel.model.RedeliveryPolicyDefinition; +import org.apache.camel.model.WhenDefinition; +import org.apache.camel.model.language.ExpressionDefinition; +import org.apache.camel.reifier.OnExceptionReifier; +import org.apache.camel.spi.ClassResolver; +import org.apache.camel.util.ObjectHelper; + +import static org.apache.camel.util.ObjectHelper.ifNotEmpty; + +@YAMLStepParser("on-exception") +public class OnExceptionStepParser implements StartStepParser, ProcessorStepParser { + @Override + public ProcessorDefinition toStartProcessor(Context context) { + final ClassResolver resolver = context.getCamelContext().getClassResolver(); + final Definition definition = context.node(Definition.class); + + if (ObjectHelper.isEmpty(definition.exceptions)) { + definition.exceptions = List.of(Exception.class.getName()); + } + + OnExceptionDefinition onException = null; + + // + // don0t know if it is correct but builder.onException(A, B, C) seems to + // do the same, so it loops over the given exception classes and creates + // a OnExceptionDefinition for each exception. + // + for (String className: definition.exceptions) { + onException = context.builder().onException(resolver.resolveClass(className, Throwable.class)); + onException.setRouteScoped(false); + + mapToOnException(context, definition, onException); + } + + return StepParserSupport.convertSteps( + context, + onException, + definition.steps); + } + + @Override + public ProcessorDefinition toProcessor(Context context) { + final Definition definition = context.node(Definition.class); + final OnExceptionDefinition onException = new OnExceptionDefinition(); + + if (ObjectHelper.isEmpty(definition.exceptions)) { + definition.exceptions = List.of(Exception.class.getName()); + } + + onException.setExceptions(definition.exceptions); + onException.setRouteScoped(true); + + mapToOnException(context, definition, onException); + + return StepParserSupport.convertSteps( + context, + onException, + definition.steps); + } + + private static void mapToOnException(Context context, Definition definition, OnExceptionDefinition onException) { + ifNotEmpty(definition.retryWhile, onException::setRetryWhile); + ifNotEmpty(definition.handled, onException::setHandled); + ifNotEmpty(definition.continued, onException::setContinued); + ifNotEmpty(definition.continued, onException::setContinued); + ifNotEmpty(definition.redeliveryPolicyType, onException::setRedeliveryPolicyType); + ifNotEmpty(definition.redeliveryPolicyRef, onException::setRedeliveryPolicyRef); + ifNotEmpty(definition.onRedeliveryRef, onException::setOnRedeliveryRef); + ifNotEmpty(definition.onExceptionOccurredRef, onException::setOnExceptionOccurredRef); + ifNotEmpty(definition.useOriginalMessage, val -> onException.setUseOriginalMessage(Boolean.toString(val))); + ifNotEmpty(definition.useOriginalBody, val -> onException.setUseOriginalBody(Boolean.toString(val))); + + if (definition.onWhen != null) { + StepParserSupport.notNull(definition.onWhen.steps, "onWhen.steps"); + + // + // This is needed as when processing the start step, the same definition + // is applied to multiple OnExceptionDefinition + // + WhenDefinition whenDefinition = new WhenDefinition(); + whenDefinition.setExpression(definition.onWhen.getExpression()); + + StepParserSupport.convertSteps( + context, + whenDefinition, + definition.onWhen.steps + ); + + onException.setOnWhen(whenDefinition); + } + } + + @YAMLNodeDefinition(reifiers = OnExceptionReifier.class) + public static final class Definition { + public List steps; + + @JsonAlias("exceptions") + public List exceptions; + + @JsonAlias({"when", "on-when"}) + public When onWhen; + @JsonAlias("retry-while") + public ExpressionElement retryWhile; + @JsonAlias("handled") + public ExpressionElement handled; + @JsonAlias("continued") + public ExpressionElement continued; + + @JsonAlias("redelivery-policy") + public RedeliveryPolicyDefinition redeliveryPolicyType; + @JsonAlias("redelivery-policy-ref") + public String redeliveryPolicyRef; + + @JsonAlias("on-redelivery-ref") + public String onRedeliveryRef; + @JsonAlias("on-exception-occurred-ref") + public String onExceptionOccurredRef; + @JsonAlias("use-original-message") + public boolean useOriginalMessage; + @JsonAlias("use-original-body") + public boolean useOriginalBody; + + public static final class When extends WhenDefinition implements HasExpression { + public List steps; + } + + public static final class ExpressionElement extends ExpressionSubElementDefinition implements HasExpression { + @Override + public void setExpression(ExpressionDefinition expressionDefinition) { + super.setExpressionType(expressionDefinition); + } + + @Override + public ExpressionDefinition getExpression() { + return super.getExpressionType(); + } + } + } +} + diff --git a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/RestStepParser.java b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/RestStepParser.java index 99cb057a0..936f5567a 100644 --- a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/RestStepParser.java +++ b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/parser/RestStepParser.java @@ -41,7 +41,7 @@ public ProcessorDefinition toStartProcessor(Context context) { StepParserSupport.notNull(definition.verb, "verb"); StepParserSupport.notNull(definition.steps, "steps"); - RestDefinition rest = new RestDefinition().verb(definition.verb, definition.uri); + RestDefinition rest = context.builder().rest().verb(definition.verb, definition.uri); ObjectHelper.ifNotEmpty(definition.apiDocs, rest::apiDocs); ObjectHelper.ifNotEmpty(definition.enableCORS, rest::enableCORS); diff --git a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StartStepParser.java b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StartStepParser.java index f80bcdbb5..10426a218 100644 --- a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StartStepParser.java +++ b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StartStepParser.java @@ -22,7 +22,6 @@ public interface StartStepParser extends StepParser { /** * @param context - * @return */ ProcessorDefinition toStartProcessor(Context context); diff --git a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StepParser.java b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StepParser.java index 2fab5f246..42099ee2d 100644 --- a/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StepParser.java +++ b/camel-k-loader-yaml/camel-k-loader-yaml-common/src/main/java/org/apache/camel/k/loader/yaml/spi/StepParser.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.CamelContext; import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.builder.RouteBuilder; import org.apache.camel.spi.HasCamelContext; import org.apache.camel.util.ObjectHelper; @@ -35,12 +36,12 @@ public interface StepParser { */ class Context implements HasCamelContext { private final ObjectMapper mapper; - private final CamelContext camelContext; + private final RouteBuilder builder; private final JsonNode node; private final Resolver resolver; - public Context(CamelContext camelContext, ObjectMapper mapper, JsonNode node, Resolver resolver) { - this.camelContext = camelContext; + public Context(RouteBuilder builder, ObjectMapper mapper, JsonNode node, Resolver resolver) { + this.builder = builder; this.mapper = mapper; this.node = node; this.resolver = ObjectHelper.notNull(resolver, "resolver"); @@ -48,7 +49,11 @@ public Context(CamelContext camelContext, ObjectMapper mapper, JsonNode node, Re @Override public CamelContext getCamelContext() { - return camelContext; + return builder.getContext(); + } + + public RouteBuilder builder() { + return builder; } public JsonNode node() { @@ -75,7 +80,7 @@ public T node(Class type) { } public T lookup(Class type, String stepId) { - StepParser parser = resolver.resolve(camelContext, stepId); + StepParser parser = resolver.resolve(builder.getContext(), stepId); if (type.isInstance(parser)) { return type.cast(parser); } @@ -85,7 +90,7 @@ public T lookup(Class type, String stepId) { public static Context of(Context context, JsonNode step) { return new Context( - context.camelContext, + context.builder, context.mapper, step, context.resolver diff --git a/camel-k-loader-yaml/camel-k-loader-yaml/src/main/java/org/apache/camel/k/loader/yaml/YamlSourceLoader.java b/camel-k-loader-yaml/camel-k-loader-yaml/src/main/java/org/apache/camel/k/loader/yaml/YamlSourceLoader.java index 1c1b4cd06..a300fe10a 100644 --- a/camel-k-loader-yaml/camel-k-loader-yaml/src/main/java/org/apache/camel/k/loader/yaml/YamlSourceLoader.java +++ b/camel-k-loader-yaml/camel-k-loader-yaml/src/main/java/org/apache/camel/k/loader/yaml/YamlSourceLoader.java @@ -19,7 +19,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -31,7 +30,6 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.k.Runtime; import org.apache.camel.k.Source; @@ -40,11 +38,6 @@ import org.apache.camel.k.loader.yaml.model.Step; import org.apache.camel.k.loader.yaml.spi.StartStepParser; import org.apache.camel.k.loader.yaml.spi.StepParser; -import org.apache.camel.model.ProcessorDefinition; -import org.apache.camel.model.RouteDefinition; -import org.apache.camel.model.RoutesDefinition; -import org.apache.camel.model.rest.RestDefinition; -import org.apache.camel.model.rest.RestsDefinition; @Loader("yaml") public class YamlSourceLoader implements SourceLoader { @@ -94,41 +87,12 @@ final RouteBuilder builder(InputStream is) { @Override public void configure() throws Exception { final StepParser.Resolver resolver = StepParser.Resolver.caching(new YamlStepResolver()); - final CamelContext camelContext = getContext(); - final List routes = new ArrayList<>(); - final List rests = new ArrayList<>(); try (is) { for (Step step : mapper.readValue(is, Step[].class)) { - final StepParser.Context context = new StepParser.Context(camelContext, mapper, step.node, resolver); - final ProcessorDefinition root = StartStepParser.invoke(context, step.id); - - if (root == null) { - throw new IllegalStateException("No route definition"); - } - if (!(root instanceof RouteDefinition)) { - throw new IllegalStateException("Root definition should be of type RouteDefinition"); - } - - RouteDefinition r = (RouteDefinition) root; - if (r.getRestDefinition() == null) { - routes.add(r); - } else { - rests.add(r.getRestDefinition()); - } - } - - if (!routes.isEmpty()) { - RoutesDefinition definition = new RoutesDefinition(); - definition.setRoutes(routes); - - setRouteCollection(definition); - } - if (!rests.isEmpty()) { - RestsDefinition definition = new RestsDefinition(); - definition.setRests(rests); - - setRestCollection(definition); + StartStepParser.invoke( + new StepParser.Context(this, mapper, step.node, resolver), + step.id); } } } diff --git a/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/TestSupport.groovy b/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/TestSupport.groovy index 597d4296c..7a0f17a60 100644 --- a/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/TestSupport.groovy +++ b/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/TestSupport.groovy @@ -19,6 +19,7 @@ package org.apache.camel.k.loader.yaml import com.fasterxml.jackson.databind.JsonNode import groovy.util.logging.Slf4j import org.apache.camel.CamelContext +import org.apache.camel.builder.RouteBuilder import org.apache.camel.component.mock.MockEndpoint import org.apache.camel.impl.DefaultCamelContext import org.apache.camel.k.loader.yaml.spi.ProcessorStepParser @@ -36,13 +37,23 @@ class TestSupport extends Specification { static StepParser.Context stepContext(String content) { def node = MAPPER.readTree(content.stripMargin()) - def cctx = new DefaultCamelContext() + def builder = new RouteBuilder(new DefaultCamelContext()) { + @Override + void configure() throws Exception { + } + } - return new StepParser.Context(cctx, MAPPER, node, RESOLVER) + return new StepParser.Context(builder, MAPPER, node, RESOLVER) } static StepParser.Context stepContext(JsonNode content) { - return new StepParser.Context(new DefaultCamelContext(), MAPPER, content, RESOLVER) + def builder = new RouteBuilder(new DefaultCamelContext()) { + @Override + void configure() throws Exception { + } + } + + return new StepParser.Context(builder, MAPPER, content, RESOLVER) } static CamelContext startContext(String content) { diff --git a/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/parser/OnExceptionTest.groovy b/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/parser/OnExceptionTest.groovy new file mode 100644 index 000000000..d1be1a1fe --- /dev/null +++ b/camel-k-loader-yaml/camel-k-loader-yaml/src/test/groovy/org/apache/camel/k/loader/yaml/parser/OnExceptionTest.groovy @@ -0,0 +1,121 @@ +/* + * 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.k.loader.yaml.parser + +import org.apache.camel.k.loader.yaml.TestSupport +import org.apache.camel.model.OnExceptionDefinition +import org.apache.camel.model.ToDefinition +import org.apache.camel.model.language.ConstantExpression + +class OnExceptionTest extends TestSupport { + def "definition (route)"() { + given: + def stepContext = stepContext(''' + exceptions: + - java.lang.Exception + - java.io.IOException + when: + constant: "when" + steps: + - to: 'log:when' + retry-while: + constant: "while" + handled: + constant: "handled" + continued: + constant: "continued" + + ''') + when: + def processor = new OnExceptionStepParser().toProcessor(stepContext) + then: + with(processor, OnExceptionDefinition) { + exceptions.contains('java.lang.Exception') + exceptions.contains('java.io.IOException') + + with(onWhen) { + outputs.size() == 1 + + with(outputs[0], ToDefinition) { + endpointUri == 'log:when' + } + with(expression, ConstantExpression) { + expression == 'when' + } + } + with(retryWhile.expressionType, ConstantExpression) { + expression == 'while' + } + with(handled.expressionType, ConstantExpression) { + expression == 'handled' + } + with(continued.expressionType, ConstantExpression) { + expression == 'continued' + } + } + } + + def "definition (global)"() { + given: + def stepContext = stepContext(''' + exceptions: + - java.lang.Exception + - java.io.IOException + when: + constant: "when" + steps: + - to: 'log:when' + retry-while: + constant: "while" + handled: + constant: "handled" + continued: + constant: "continued" + ''') + when: + def processor = new OnExceptionStepParser().toStartProcessor(stepContext) + then: + stepContext.builder().routeCollection.onExceptions.size() == 2 + stepContext.builder().routeCollection.onExceptions[0].exceptions.contains("java.lang.Exception") + stepContext.builder().routeCollection.onExceptions[1].exceptions.contains("java.io.IOException") + + with(processor, OnExceptionDefinition) { + exceptions.size() == 1 + exceptions.contains('java.io.IOException') + + with(onWhen) { + outputs.size() == 1 + + with(outputs[0], ToDefinition) { + endpointUri == 'log:when' + } + with(expression, ConstantExpression) { + expression == 'when' + } + } + with(retryWhile.expressionType, ConstantExpression) { + expression == 'while' + } + with(handled.expressionType, ConstantExpression) { + expression == 'handled' + } + with(continued.expressionType, ConstantExpression) { + expression == 'continued' + } + } + } +} diff --git a/camel-k-runtime-knative/src/main/java/org/apache/camel/k/knative/yaml/parser/KnativeStepParser.java b/camel-k-runtime-knative/src/main/java/org/apache/camel/k/knative/yaml/parser/KnativeStepParser.java index 48c07f8ac..b1d4c750b 100644 --- a/camel-k-runtime-knative/src/main/java/org/apache/camel/k/knative/yaml/parser/KnativeStepParser.java +++ b/camel-k-runtime-knative/src/main/java/org/apache/camel/k/knative/yaml/parser/KnativeStepParser.java @@ -37,7 +37,7 @@ public class KnativeStepParser implements ProcessorStepParser, StartStepParser { public ProcessorDefinition toStartProcessor(Context context) { final Definition definition = context.node(Definition.class); final String uri = definition.getEndpointUri(); - final RouteDefinition route = new RouteDefinition().from(uri); + final RouteDefinition route = context.builder().from(uri); // steps are mandatory ObjectHelper.notNull(definition.steps, "from steps");