You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/03/19 14:15:24 UTC

[camel] 03/03: CAMEL-17815: camel-jbang - In modeline mode then eager discover routes and pre-load spec/* from camel-k integration yaml files to allow properties to be used during bootstrap.

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 6832fc27c6648d7757255ee0fd75cd889997d20e
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Mar 19 13:59:13 2022 +0100

    CAMEL-17815: camel-jbang - In modeline mode then eager discover routes and pre-load spec/* from camel-k integration yaml files to allow properties to be used during bootstrap.
---
 .../org/apache/camel/spi/RoutesBuilderLoader.java  | 12 +++
 .../java/org/apache/camel/spi/RoutesLoader.java    |  4 +
 .../camel/impl/engine/DefaultRoutesLoader.java     | 38 ++++++---
 .../org/apache/camel/main/RoutesConfigurer.java    |  8 ++
 .../camel/dsl/yaml/YamlRoutesBuilderLoader.java    | 99 +++++++++++++++++-----
 .../dsl/yaml/YamlRoutesBuilderLoaderSupport.java   |  6 +-
 6 files changed, 132 insertions(+), 35 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/RoutesBuilderLoader.java b/core/camel-api/src/main/java/org/apache/camel/spi/RoutesBuilderLoader.java
index f55da10..7492e03 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RoutesBuilderLoader.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RoutesBuilderLoader.java
@@ -49,4 +49,16 @@ public interface RoutesBuilderLoader extends StaticService, CamelContextAware {
      * @return          a {@link RoutesBuilder}
      */
     RoutesBuilder loadRoutesBuilder(Resource resource) throws Exception;
+
+    /**
+     * Pre-parses the {@link RoutesBuilder} from {@link Resource}.
+     *
+     * This is used during bootstrap, to eager detect configurations from route DSL resources which makes it possible to
+     * specify configurations that affect the bootstrap, such as by camel-jbang and camel-yaml-dsl.
+     *
+     * @param resource the resource to be pre parsed.
+     */
+    default void preParseRoute(Resource resource) throws Exception {
+        // noop
+    }
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/RoutesLoader.java b/core/camel-api/src/main/java/org/apache/camel/spi/RoutesLoader.java
index 4ac5c35..e00720e 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RoutesLoader.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RoutesLoader.java
@@ -115,4 +115,8 @@ public interface RoutesLoader extends CamelContextAware {
      * @return           a collection {@link RoutesBuilder}
      */
     Collection<RoutesBuilder> findRoutesBuilders(Collection<Resource> resources) throws Exception;
+
+    default void preParseRoute(Resource resource) throws Exception {
+        // noop
+    }
 }
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
index 1d134e3..0f4737f 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
@@ -87,24 +87,14 @@ public class DefaultRoutesLoader extends ServiceSupport implements RoutesLoader,
         List<RoutesBuilder> answer = new ArrayList<>(resources.size());
 
         for (Resource resource : resources) {
-            // the loader to use is derived from the file extension
-            final String extension = FileUtil.onlyExt(resource.getLocation(), false);
-
-            if (ObjectHelper.isEmpty(extension)) {
-                throw new IllegalArgumentException(
-                        "Unable to determine file extension for resource: " + resource.getLocation());
-            }
-
-            RoutesBuilderLoader loader = getRoutesLoader(extension);
-            if (loader == null) {
-                throw new IllegalArgumentException(
-                        "Cannot find RoutesBuilderLoader in classpath supporting file extension: " + extension);
-            }
+            RoutesBuilderLoader loader = resolveRoutesBuilderLoader(resource);
 
             if (camelContext.isModeline()) {
                 ModelineFactory factory = camelContext.adapt(ExtendedCamelContext.class).getModelineFactory();
                 // gather resources for modeline
                 factory.parseModeline(resource);
+                // pre-parse before loading
+                loader.preParseRoute(resource);
             }
 
             RoutesBuilder builder = loader.loadRoutesBuilder(resource);
@@ -116,6 +106,11 @@ public class DefaultRoutesLoader extends ServiceSupport implements RoutesLoader,
         return answer;
     }
 
+    @Override
+    public void preParseRoute(Resource resource) throws Exception {
+        resolveRoutesBuilderLoader(resource).preParseRoute(resource);
+    }
+
     /**
      * Looks up a {@link RoutesBuilderLoader} in the registry or fallback to a factory finder mechanism if none found.
      *
@@ -178,4 +173,21 @@ public class DefaultRoutesLoader extends ServiceSupport implements RoutesLoader,
         return answer;
     }
 
+    protected RoutesBuilderLoader resolveRoutesBuilderLoader(Resource resource) throws Exception {
+        // the loader to use is derived from the file extension
+        final String extension = FileUtil.onlyExt(resource.getLocation(), false);
+
+        if (ObjectHelper.isEmpty(extension)) {
+            throw new IllegalArgumentException(
+                    "Unable to determine file extension for resource: " + resource.getLocation());
+        }
+
+        RoutesBuilderLoader loader = getRoutesLoader(extension);
+        if (loader == null) {
+            throw new IllegalArgumentException(
+                    "Cannot find RoutesBuilderLoader in classpath supporting file extension: " + extension);
+        }
+        return loader;
+    }
+
 }
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
index e680485..e05a0af 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
@@ -30,6 +30,7 @@ import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.spi.CamelBeanPostProcessor;
 import org.apache.camel.spi.ModelineFactory;
 import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.RoutesLoader;
 import org.apache.camel.support.OrderedComparator;
 import org.apache.camel.util.StopWatch;
 import org.apache.camel.util.TimeUtils;
@@ -282,6 +283,13 @@ public class RoutesConfigurer {
             LOG.debug("Parsing modeline: {}", resource);
             factory.parseModeline(resource);
         }
+        // the resource may also have additional configurations which we need to detect via pre-parsing
+        for (Resource resource : resources) {
+            LOG.debug("Pre-parsing: {}", resource);
+            RoutesLoader loader = camelContext.adapt(ExtendedCamelContext.class).getRoutesLoader();
+            loader.preParseRoute(resource);
+        }
+
     }
 
 }
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
index f43e373..3d6d141 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.dsl.yaml;
 
+import java.io.FileNotFoundException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -59,11 +61,18 @@ import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.support.PropertyBindingSupport;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.URISupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.snakeyaml.engine.v2.api.YamlUnicodeReader;
+import org.snakeyaml.engine.v2.composer.Composer;
 import org.snakeyaml.engine.v2.nodes.MappingNode;
 import org.snakeyaml.engine.v2.nodes.Node;
 import org.snakeyaml.engine.v2.nodes.NodeTuple;
 import org.snakeyaml.engine.v2.nodes.NodeType;
 import org.snakeyaml.engine.v2.nodes.SequenceNode;
+import org.snakeyaml.engine.v2.parser.Parser;
+import org.snakeyaml.engine.v2.parser.ParserImpl;
+import org.snakeyaml.engine.v2.scanner.StreamReader;
 
 import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMap;
 import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMappingNode;
@@ -79,6 +88,8 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
 
     public static final String EXTENSION = "yaml";
 
+    private static final Logger LOG = LoggerFactory.getLogger(YamlRoutesBuilderLoader.class);
+
     // API versions for Camel-K Integration and Kamelet Binding
     // we are lenient so lets just assume we can work with any of the v1 even if they evolve
     private static final String INTEGRATION_VERSION = "camel.apache.org/v1";
@@ -104,7 +115,7 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
                 ctx.setResource(resource);
                 setDeserializationContext(root, ctx);
 
-                Object target = preConfigureNode(root, ctx);
+                Object target = preConfigureNode(root, ctx, false);
                 if (target == null) {
                     return;
                 }
@@ -196,7 +207,7 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
                 ctx.setResource(resource);
                 setDeserializationContext(root, ctx);
 
-                Object target = preConfigureNode(root, ctx);
+                Object target = preConfigureNode(root, ctx, false);
                 if (target == null) {
                     return;
                 }
@@ -236,7 +247,7 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
         };
     }
 
-    private Object preConfigureNode(Node root, YamlDeserializationContext ctx) throws Exception {
+    private Object preConfigureNode(Node root, YamlDeserializationContext ctx, boolean preParse) throws Exception {
         Object target = root;
 
         // check if the yaml is a camel-k yaml with embedded binding/routes (called flow(s))
@@ -249,8 +260,9 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
             boolean binding = anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(BINDING_VERSION)) &&
                     anyTupleMatches(mn.getValue(), "kind", "KameletBinding");
             if (integration) {
-                target = preConfigureIntegration(root, ctx, target);
-            } else if (binding) {
+                target = preConfigureIntegration(root, ctx, target, preParse);
+            } else if (binding && !preParse) {
+                // kamelet binding does not take part in pre-parse phase
                 target = preConfigureKameletBinding(root, ctx, target);
             }
         }
@@ -261,7 +273,9 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
     /**
      * Camel K Integration file
      */
-    private Object preConfigureIntegration(Node root, YamlDeserializationContext ctx, Object target) {
+    private Object preConfigureIntegration(Node root, YamlDeserializationContext ctx, Object target, boolean preParse) {
+        // when in pre-parse phase then we only want to gather spec/dependencies,spec/configuration,spec/traits
+
         List<Object> answer = new ArrayList<>();
 
         // if there are dependencies then include them first
@@ -270,6 +284,7 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
             var dep = preConfigureDependencies(deps);
             answer.add(dep);
         }
+
         // if there are configurations then include them early
         Node configuration = nodeAt(root, "/spec/configuration");
         if (configuration != null) {
@@ -288,20 +303,24 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
             var list = preConfigureTraitEnvironment(configuration);
             answer.addAll(list);
         }
-        // if there are sources then include them before routes
-        Node sources = nodeAt(root, "/spec/sources");
-        if (sources != null) {
-            var list = preConfigureSources(sources);
-            answer.addAll(list);
-        }
-        // add routes last
-        Node routes = nodeAt(root, "/spec/flows");
-        if (routes == null) {
-            routes = nodeAt(root, "/spec/flow");
-        }
-        if (routes != null) {
-            answer.add(routes);
+
+        if (!preParse) {
+            // if there are sources then include them before routes
+            Node sources = nodeAt(root, "/spec/sources");
+            if (sources != null) {
+                var list = preConfigureSources(sources);
+                answer.addAll(list);
+            }
+            // add routes last
+            Node routes = nodeAt(root, "/spec/flows");
+            if (routes == null) {
+                routes = nodeAt(root, "/spec/flow");
+            }
+            if (routes != null) {
+                answer.add(routes);
+            }
         }
+
         return answer;
     }
 
@@ -624,4 +643,46 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
         }
     }
 
+    @Override
+    public void preParseRoute(Resource resource) throws Exception {
+        LOG.trace("Pre-parsing: {}", resource.getLocation());
+
+        if (!resource.exists()) {
+            throw new FileNotFoundException("Resource not found: " + resource.getLocation());
+        }
+
+        try (InputStream is = resource.getInputStream()) {
+            final StreamReader reader = new StreamReader(settings, new YamlUnicodeReader(is));
+            final Parser parser = new ParserImpl(settings, reader);
+            final Composer composer = new Composer(settings, parser);
+
+            composer.getSingleNode()
+                    .map(node -> preParseNode(node, resource));
+        }
+    }
+
+    private Object preParseNode(Node root, Resource resource) {
+        LOG.trace("Pre-parsing node: {}", root);
+
+        YamlDeserializationContext ctx = getDeserializationContext();
+        ctx.setResource(resource);
+        setDeserializationContext(root, ctx);
+
+        try {
+            Object target = preConfigureNode(root, ctx, true);
+            Iterator<?> it = ObjectHelper.createIterator(target);
+            while (it.hasNext()) {
+                target = it.next();
+                if (target instanceof CamelContextCustomizer) {
+                    CamelContextCustomizer customizer = (CamelContextCustomizer) target;
+                    customizer.configure(getCamelContext());
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeCamelException("Error pre-parsing resource: " + resource.getLocation(), e);
+        }
+
+        return null;
+    }
+
 }
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
index 6102197..04429d2 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
@@ -50,9 +50,9 @@ import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText;
 public abstract class YamlRoutesBuilderLoaderSupport extends RouteBuilderLoaderSupport {
     public static final String DESERIALIZATION_MODE = "CamelYamlDslDeserializationMode";
 
-    private LoadSettings settings;
-    private YamlDeserializationContext deserializationContext;
-    private YamlDeserializationMode deserializationMode;
+    LoadSettings settings;
+    YamlDeserializationContext deserializationContext;
+    YamlDeserializationMode deserializationMode;
 
     public YamlRoutesBuilderLoaderSupport(String extension) {
         super(extension);