You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2021/04/15 08:53:45 UTC

[camel] 02/02: [CAMEL-16510] YAML Dsl : support for Kamelet EIP

This is an automated email from the ASF dual-hosted git repository.

lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 536d50806833e6f2c22f9b3dc18173a34a4cfc91
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Wed Apr 14 20:04:26 2021 +0200

    [CAMEL-16510] YAML Dsl : support for Kamelet EIP
---
 .../org/apache/camel/model/KameletDefinition.java  |   4 +
 .../camel-yaml-dsl-deserializers/pom.xml           |   1 +
 .../dsl/yaml/deserializers/ModelDeserializers.java |  47 -----
 .../deserializers/ModelDeserializersResolver.java  |   2 -
 .../dsl/yaml/deserializers/CustomResolver.java     |   5 +-
 .../yaml/deserializers/KameletDeserializer.java    | 102 ++++++++++
 dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml          |   5 +
 .../src/generated/resources/camel-yaml-dsl.json    |  33 ++--
 .../org/apache/camel/dsl/yaml/KameletTest.groovy   | 215 +++++++++++++++++++++
 .../camel/dsl/yaml/support/YamlTestSupport.groovy  |  40 +++-
 10 files changed, 386 insertions(+), 68 deletions(-)

diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/KameletDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/KameletDefinition.java
index f4c1625..1f1364a 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/KameletDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/KameletDefinition.java
@@ -37,6 +37,10 @@ public class KameletDefinition extends OutputDefinition<KameletDefinition> {
     public KameletDefinition() {
     }
 
+    public KameletDefinition(String name) {
+        this.name = name;
+    }
+
     @Override
     public String toString() {
         return "Kamelet[" + getOutputs() + "]";
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml
index a3b85bf..8a7dd45 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml
@@ -128,6 +128,7 @@
                                 <bannedDefinition>org.apache.camel.model.language.ExpressionDefinition</bannedDefinition>
                                 <bannedDefinition>org.apache.camel.model.ExpressionSubElementDefinition</bannedDefinition>
                                 <bannedDefinition>org.apache.camel.model.PropertyDefinitions</bannedDefinition>
+                                <bannedDefinition>org.apache.camel.model.KameletDefinition</bannedDefinition>
                             </bannedDefinitions>
                             <additionalDefinitions>
                                 <additionalDefinition>org.apache.camel.model.SagaOptionDefinition</additionalDefinition>
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index f12f10d..df21838 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -35,7 +35,6 @@ import org.apache.camel.model.InputTypeDefinition;
 import org.apache.camel.model.InterceptDefinition;
 import org.apache.camel.model.InterceptFromDefinition;
 import org.apache.camel.model.InterceptSendToEndpointDefinition;
-import org.apache.camel.model.KameletDefinition;
 import org.apache.camel.model.LoadBalanceDefinition;
 import org.apache.camel.model.LoadBalancerDefinition;
 import org.apache.camel.model.LogDefinition;
@@ -6830,52 +6829,6 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
     }
 
     @YamlType(
-            types = org.apache.camel.model.KameletDefinition.class,
-            order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1,
-            nodes = "kamelet",
-            properties = {
-                    @YamlProperty(name = "inherit-error-handler", type = "boolean"),
-                    @YamlProperty(name = "name", type = "string", required = true),
-                    @YamlProperty(name = "steps", type = "array:org.apache.camel.model.ProcessorDefinition")
-            }
-    )
-    public static class KameletDefinitionDeserializer extends YamlDeserializerBase<KameletDefinition> {
-        public KameletDefinitionDeserializer() {
-            super(KameletDefinition.class);
-        }
-
-        @Override
-        protected KameletDefinition newInstance() {
-            return new KameletDefinition();
-        }
-
-        @Override
-        protected boolean setProperty(KameletDefinition target, String propertyKey,
-                String propertyName, Node node) {
-            switch(propertyKey) {
-                case "inherit-error-handler": {
-                    String val = asText(node);
-                    target.setInheritErrorHandler(java.lang.Boolean.valueOf(val));
-                    break;
-                }
-                case "name": {
-                    String val = asText(node);
-                    target.setName(val);
-                    break;
-                }
-                case "steps": {
-                    setSteps(target, node);;
-                    break;
-                }
-                default: {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    @YamlType(
             types = org.apache.camel.model.cloud.KubernetesServiceCallServiceDiscoveryConfiguration.class,
             order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1,
             nodes = "kubernetes-service-discovery",
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java
index 513a1a9..0a1741b 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java
@@ -167,8 +167,6 @@ public final class ModelDeserializersResolver implements YamlDeserializerResolve
             case "org.apache.camel.model.dataformat.JsonDataFormat": return new ModelDeserializers.JsonDataFormatDeserializer();
             case "jsonpath": return new ModelDeserializers.JsonPathExpressionDeserializer();
             case "org.apache.camel.model.language.JsonPathExpression": return new ModelDeserializers.JsonPathExpressionDeserializer();
-            case "kamelet": return new ModelDeserializers.KameletDefinitionDeserializer();
-            case "org.apache.camel.model.KameletDefinition": return new ModelDeserializers.KameletDefinitionDeserializer();
             case "kubernetes-service-discovery": return new ModelDeserializers.KubernetesServiceCallServiceDiscoveryConfigurationDeserializer();
             case "org.apache.camel.model.cloud.KubernetesServiceCallServiceDiscoveryConfiguration": return new ModelDeserializers.KubernetesServiceCallServiceDiscoveryConfigurationDeserializer();
             case "lzf": return new ModelDeserializers.LZFDataFormatDeserializer();
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java
index 16b29bb..448cf74 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java
@@ -54,10 +54,11 @@ public class CustomResolver implements YamlDeserializerResolver {
                 return new BeansDeserializer();
             case "error-handler":
                 return new ErrorHandlerBuilderDeserializer();
-            //case "do-try":
-            //    return new TryDefinitionDeserializer();
             case "org.apache.camel.model.ProcessorDefinition":
                 return new ProcessorDefinitionDeserializer();
+            case "kamelet":
+            case "org.apache.camel.model.KameletDefinition":
+                return new KameletDeserializer();
             default:
                 return null;
         }
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/KameletDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/KameletDeserializer.java
new file mode 100644
index 0000000..6f1c1dd
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/KameletDeserializer.java
@@ -0,0 +1,102 @@
+/*
+ * 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.dsl.yaml.deserializers;
+
+import java.net.URISyntaxException;
+import java.util.Map;
+
+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.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.model.KameletDefinition;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.apache.camel.util.URISupport;
+import org.snakeyaml.engine.v2.nodes.MappingNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+
+@YamlType(
+          inline = true,
+          types = org.apache.camel.model.KameletDefinition.class,
+          order = YamlDeserializerResolver.ORDER_DEFAULT,
+          nodes = "kamelet",
+          properties = {
+                  @YamlProperty(name = "inherit-error-handler", type = "boolean"),
+                  @YamlProperty(name = "name", type = "string", required = true),
+                  @YamlProperty(name = "parameters", type = "object"),
+                  @YamlProperty(name = "steps", type = "array:org.apache.camel.model.ProcessorDefinition")
+          })
+public class KameletDeserializer extends YamlDeserializerBase<KameletDefinition> {
+    public KameletDeserializer() {
+        super(KameletDefinition.class);
+    }
+
+    @Override
+    protected KameletDefinition newInstance() {
+        return new KameletDefinition();
+    }
+
+    @Override
+    protected KameletDefinition newInstance(String value) {
+        return new KameletDefinition(value);
+    }
+
+    @Override
+    protected void setProperties(KameletDefinition target, MappingNode node) {
+        final YamlDeserializationContext dc = getDeserializationContext(node);
+
+        String name = null;
+        Map<String, Object> parameters = null;
+
+        for (NodeTuple tuple : node.getValue()) {
+            final String key = asText(tuple.getKeyNode());
+            final Node val = tuple.getValueNode();
+
+            setDeserializationContext(val, dc);
+
+            switch (key) {
+                case "steps":
+                    setSteps(target, val);
+                    break;
+                case "id":
+                    target.setId(asText(val));
+                    break;
+                case "name":
+                    name = asText(val);
+                    break;
+                case "parameters":
+                    parameters = asScalarMap(tuple.getValueNode());
+                    break;
+                default:
+                    throw new UnsupportedFieldException(node, key);
+            }
+        }
+
+        if (parameters != null) {
+            try {
+                name += "?" + URISupport.createQueryString(parameters, false);
+            } catch (URISyntaxException e) {
+                throw new YamlDeserializationException(e);
+            }
+        }
+
+        target.setName(name);
+    }
+}
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml
index 54169ce..9d850e9 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml
@@ -129,6 +129,11 @@
             <artifactId>camel-seda</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-kamelet</artifactId>
+            <scope>test</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.codehaus.groovy</groupId>
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 5aa0842..1d2caf5 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
@@ -1036,21 +1036,28 @@
         "required" : [ "uri" ]
       },
       "org.apache.camel.model.KameletDefinition" : {
-        "type" : "object",
-        "properties" : {
-          "inherit-error-handler" : {
-            "type" : "boolean"
-          },
-          "name" : {
-            "type" : "string"
-          },
-          "steps" : {
-            "type" : "array",
-            "items" : {
-              "$ref" : "#/items/definitions/org.apache.camel.model.ProcessorDefinition"
+        "oneOf" : [ {
+          "type" : "string"
+        }, {
+          "type" : "object",
+          "properties" : {
+            "inherit-error-handler" : {
+              "type" : "boolean"
+            },
+            "name" : {
+              "type" : "string"
+            },
+            "parameters" : {
+              "type" : "object"
+            },
+            "steps" : {
+              "type" : "array",
+              "items" : {
+                "$ref" : "#/items/definitions/org.apache.camel.model.ProcessorDefinition"
+              }
             }
           }
-        },
+        } ],
         "required" : [ "name" ]
       },
       "org.apache.camel.model.LoadBalanceDefinition" : {
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletTest.groovy
new file mode 100644
index 0000000..b1ff173
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletTest.groovy
@@ -0,0 +1,215 @@
+/*
+ * 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.dsl.yaml
+
+import org.apache.camel.component.mock.MockEndpoint
+import org.apache.camel.dsl.yaml.common.YamlDeserializationMode
+import org.apache.camel.dsl.yaml.support.YamlTestSupport
+import org.apache.camel.processor.aggregate.UseLatestAggregationStrategy
+import org.apache.camel.spi.Resource
+
+class KameletTest extends YamlTestSupport {
+    @Override
+    def doSetup() {
+        context.start()
+    }
+
+    def "kamelet (#resource)"(Tuple2<YamlDeserializationMode, Resource> resource) {
+        setup:
+            addTemplate('setPayload') {
+                from('kamelet:source')
+                    .setBody().simple('${body}: {{payload}}')
+            }
+
+            setFlowMode(resource[0] as YamlDeserializationMode)
+            loadRoutes(resource[1] as Resource)
+
+            withMock('mock:kamelet') {
+                expectedMessageCount 1
+                expectedBodiesReceived 'a: 1'
+            }
+        when:
+            withTemplate {
+                to('direct:start').withBody('a').send()
+            }
+
+        then:
+            MockEndpoint.assertIsSatisfied(context)
+
+        where:
+            resource << [
+                new Tuple2<YamlDeserializationMode, Resource>(
+                    YamlDeserializationMode.CLASSIC,
+                    asResource('inline', '''
+                        - from:
+                            uri: "direct:start"
+                            steps:
+                              - kamelet: "setPayload?payload=1"
+                              - to: "mock:kamelet"
+                        ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                    YamlDeserializationMode.CLASSIC,
+                    asResource('name', '''
+                        - from:
+                            uri: "direct:start"
+                            steps:
+                              - kamelet: 
+                                  name: "setPayload?payload=1"
+                              - to: "mock:kamelet"
+                        ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                        YamlDeserializationMode.CLASSIC,
+                        asResource('name_with_steps', '''
+                        - from:
+                            uri: "direct:start"
+                            steps:
+                              - kamelet: 
+                                  name: "setPayload?payload=1"
+                                  steps:
+                                    - to: "mock:kamelet"
+                        ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                        YamlDeserializationMode.CLASSIC,
+                        asResource('properties', '''
+                            - from:
+                                uri: "direct:start"
+                                steps:
+                                  - kamelet: 
+                                      name: "setPayload"
+                                      parameters:
+                                        payload: 1
+                                  - to: "mock:kamelet"
+                            ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                        YamlDeserializationMode.CLASSIC,
+                        asResource('properties_with_steps', '''
+                            - from:
+                                uri: "direct:start"
+                                steps:
+                                  - kamelet: 
+                                      name: "setPayload"
+                                      parameters:
+                                        payload: 1
+                                      steps:
+                                        - to: "mock:kamelet"
+                            ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                        YamlDeserializationMode.FLOW,
+                        asResource('inline', '''
+                        - from:
+                            uri: "direct:start"
+                            steps:
+                              - kamelet: "setPayload?payload=1"
+                              - to: "mock:kamelet"
+                        ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                        YamlDeserializationMode.FLOW,
+                        asResource('name', '''
+                        - from:
+                            uri: "direct:start"
+                            steps:
+                              - kamelet: 
+                                  name: "setPayload?payload=1"
+                              - to: "mock:kamelet"
+                        ''')),
+                new Tuple2<YamlDeserializationMode, Resource>(
+                        YamlDeserializationMode.FLOW,
+                        asResource('properties', '''
+                            - from:
+                                uri: "direct:start"
+                                steps:
+                                  - kamelet: 
+                                      name: "setPayload"
+                                      parameters:
+                                        payload: 1
+                                  - to: "mock:kamelet"
+                            '''))
+            ]
+    }
+
+    def "kamelet (aggregation)"() {
+        setup:
+            addTemplate('aggregate') {
+                from('kamelet:source')
+                        .aggregate()
+                            .simple('${header.StockSymbol}')
+                            .aggregationStrategy(new UseLatestAggregationStrategy())
+                            .completionSize("{{size}}")
+                            .to("kamelet:sink")
+            }
+
+            loadRoutes '''
+                - from:
+                    uri: "direct:route"
+                    steps:
+                      - kamelet: 
+                          name: aggregate?size=2
+                          steps:
+                            - to: "mock:result"
+            '''
+
+            withMock('mock:result') {
+                expectedBodiesReceived '2', '4'
+            }
+
+        when:
+            withTemplate {
+                to('direct:route').withBody('1').withHeader('StockSymbol', 1).send()
+                to('direct:route').withBody('2').withHeader('StockSymbol', 1).send()
+                to('direct:route').withBody('3').withHeader('StockSymbol', 2).send()
+                to('direct:route').withBody('4').withHeader('StockSymbol', 2).send()
+            }
+        then:
+            MockEndpoint.assertIsSatisfied(context)
+    }
+
+    def "kamelet (aggregation with flow)"() {
+        setup:
+            setFlowMode(YamlDeserializationMode.FLOW)
+
+            addTemplate('aggregate') {
+                from('kamelet:source')
+                        .aggregate()
+                        .simple('${header.StockSymbol}')
+                        .aggregationStrategy(new UseLatestAggregationStrategy())
+                        .completionSize("{{size}}")
+                        .to("kamelet:sink")
+            }
+
+            loadRoutes '''
+                - from:
+                    uri: "direct:route"
+                    steps:
+                      - kamelet: aggregate?size=2
+                      - to: "mock:result"
+            '''
+
+            withMock('mock:result') {
+                expectedBodiesReceived '2', '4'
+            }
+
+        when:
+            withTemplate {
+                to('direct:route').withBody('1').withHeader('StockSymbol', 1).send()
+                to('direct:route').withBody('2').withHeader('StockSymbol', 1).send()
+                to('direct:route').withBody('3').withHeader('StockSymbol', 2).send()
+                to('direct:route').withBody('4').withHeader('StockSymbol', 2).send()
+            }
+        then:
+            MockEndpoint.assertIsSatisfied(context)
+    }
+}
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
index 1aedc0d..bbffe8d 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
@@ -23,16 +23,20 @@ import com.github.fge.jsonschema.main.JsonSchemaFactory
 import groovy.util.logging.Slf4j
 import org.apache.camel.CamelContext
 import org.apache.camel.FluentProducerTemplate
+import org.apache.camel.builder.RouteBuilder
 import org.apache.camel.component.mock.MockEndpoint
 import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader
 import org.apache.camel.dsl.yaml.common.YamlDeserializationMode
 import org.apache.camel.impl.DefaultCamelContext
+import org.apache.camel.model.RouteTemplateDefinition
 import org.apache.camel.spi.HasCamelContext
 import org.apache.camel.spi.Resource
 import org.apache.camel.support.ResourceHelper
 import spock.lang.AutoCleanup
 import spock.lang.Specification
 
+import java.nio.charset.StandardCharsets
+
 @Slf4j
 class YamlTestSupport extends Specification implements HasCamelContext {
     static def MAPPER = new ObjectMapper(new YAMLFactory())
@@ -56,6 +60,17 @@ class YamlTestSupport extends Specification implements HasCamelContext {
         context.routesLoader.loadRoutes(resources)
     }
 
+    def addTemplate(String name, @DelegatesTo(RouteTemplateDefinition) Closure<?> closure) {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            void configure() throws Exception {
+                closure.resolveStrategy = Closure.DELEGATE_FIRST
+                closure.delegate = routeTemplate(name)
+                closure.call()
+            }
+        });
+    }
+
     def loadRoutes(Resource... resources) {
         loadRoutes(resources.toList())
     }
@@ -92,10 +107,27 @@ class YamlTestSupport extends Specification implements HasCamelContext {
     }
 
     static Resource asResource(String location, String content) {
-        return ResourceHelper.fromString(
-                location.endsWith('.yaml') ? location : location + '.yaml',
-                content.stripIndent()
-        )
+        return new Resource() {
+            @Override
+            String getLocation() {
+                return location.endsWith('.yaml') ? location : location + '.yaml'
+            }
+
+            @Override
+            boolean exists() {
+                return false
+            }
+
+            @Override
+            InputStream getInputStream() throws IOException {
+                return new ByteArrayInputStream(content.stripIndent().getBytes(StandardCharsets.UTF_8))
+            }
+
+            @Override
+            String toString() {
+                return location
+            }
+        }
     }
 
     def setFlowMode(YamlDeserializationMode mode) {