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 <claus.ib...@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);