This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch yaml-dsl-bean-eager in repository https://gitbox.apache.org/repos/asf/camel.git
commit a58d6a7f8b8c139bae8f73ffc1f7ea4e14b79822 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Tue Sep 19 21:18:20 2023 +0200 CAMEL-19846: camel-yaml-dsl - Eager load beans via preParse like camel-xml-io-dsl do, to ensure beans for camel context configuration are able to be auto-configured during bootstrap. Fix RegistryBeanDefinition to have name/type as required. --- .../apache/camel/catalog/schemas/camel-spring.xsd | 4 +- .../camel/model/app/RegistryBeanDefinition.java | 4 +- .../dsl/xml/io/MessageHistoryFactoryTest.java | 50 +++++++++++++++++ .../camel/dsl/xml/io/messageHistoryFactory.xml | 33 ++++++++++++ .../dsl/yaml/deserializers/ModelDeserializers.java | 4 +- .../dsl/yaml/deserializers/BeansDeserializer.java | 19 ++++++- .../dsl/yaml/deserializers/CustomResolver.java | 8 ++- .../generated/resources/schema/camelYamlDsl.json | 3 +- .../camel/dsl/yaml/YamlRoutesBuilderLoader.java | 62 ++++++++++++++++++++-- .../dsl/yaml/YamlRoutesBuilderLoaderSupport.java | 6 ++- .../dsl/yaml/MessageHistoryFactoryTest.groovy | 49 +++++++++++++++++ .../org/apache/camel/dsl/yaml/TryTest.groovy | 2 +- 12 files changed, 229 insertions(+), 15 deletions(-) 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 4572bd2431d..8882c7ddb24 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 @@ -13600,8 +13600,8 @@ org.apache.camel.builder.RouteBuilder. <xs:sequence> <xs:element minOccurs="0" name="properties" type="tns:beanPropertiesDefinition"/> </xs:sequence> - <xs:attribute name="name" type="xs:string"/> - <xs:attribute name="type" type="xs:string"/> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="type" type="xs:string" use="required"/> </xs:complexType> <xs:complexType name="restConfigurationDefinition"> <xs:sequence> diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java index fc24ba49670..524428a4e3e 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/app/RegistryBeanDefinition.java @@ -42,9 +42,9 @@ public class RegistryBeanDefinition implements ResourceAware { @XmlTransient private Resource resource; - @XmlAttribute + @XmlAttribute(required = true) private String name; - @XmlAttribute + @XmlAttribute(required = true) private String type; @XmlElement(name = "properties") @XmlJavaTypeAdapter(BeanPropertiesAdapter.class) diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/MessageHistoryFactoryTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/MessageHistoryFactoryTest.java new file mode 100644 index 00000000000..421b41cb29d --- /dev/null +++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/MessageHistoryFactoryTest.java @@ -0,0 +1,50 @@ +/* + * 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.xml.io; + +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.main.DefaultConfigurationConfigurer; +import org.apache.camel.spi.Resource; +import org.apache.camel.support.PluginHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MessageHistoryFactoryTest { + + @Test + public void testMessageHistoryFactoryTest() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.start(); + + assertFalse(context.getMessageHistoryFactory().isCopyMessage()); + + // load route from XML and add them to the existing camel context + Resource resource = PluginHelper.getResourceLoader(context).resolveResource( + "/org/apache/camel/dsl/xml/io/messageHistoryFactory.xml"); + + PluginHelper.getRoutesLoader(context).loadRoutes(resource); + // auto-configure context + DefaultConfigurationConfigurer.afterConfigure(context); + + // the loaded route has a bean that change the factory to copy enabled + assertTrue(context.getMessageHistoryFactory().isCopyMessage()); + } + } + +} diff --git a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/messageHistoryFactory.xml b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/messageHistoryFactory.xml new file mode 100644 index 00000000000..48a0e10795d --- /dev/null +++ b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/messageHistoryFactory.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<camel xmlns="http://camel.apache.org/schema/spring"> + + <bean name="myMessageHistoryFactory" type="org.apache.camel.impl.engine.DefaultMessageHistoryFactory"> + <properties> + <property key="copyMessage" value="true"/> + </properties> + </bean> + + <route messageHistory="true"> + <from uri="timer:yaml?period=5000"/> + <to uri="log:test"/> + </route> + +</camel> \ No newline at end of file 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 95828c4db3a..0655b5bdd82 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 @@ -12274,9 +12274,9 @@ public final class ModelDeserializers extends YamlDeserializerSupport { types = org.apache.camel.model.app.RegistryBeanDefinition.class, order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, properties = { - @YamlProperty(name = "name", type = "string"), + @YamlProperty(name = "name", type = "string", required = true), @YamlProperty(name = "properties", type = "object"), - @YamlProperty(name = "type", type = "string") + @YamlProperty(name = "type", type = "string", required = true) } ) public static class RegistryBeanDefinitionDeserializer extends YamlDeserializerBase<RegistryBeanDefinition> { diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java index 13c0e91a76a..7ac54a605ec 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java @@ -17,7 +17,9 @@ package org.apache.camel.dsl.yaml.deserializers; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.dsl.yaml.common.YamlDeserializationContext; @@ -44,6 +46,9 @@ import org.snakeyaml.engine.v2.nodes.SequenceNode; type = "array:org.apache.camel.model.app.RegistryBeanDefinition") }) public class BeansDeserializer extends YamlDeserializerSupport implements ConstructNode { + + private final Set<String> beanCache = new HashSet<>(); + @Override public Object construct(Node node) { final BeansCustomizer answer = new BeansCustomizer(); @@ -64,12 +69,24 @@ public class BeansDeserializer extends YamlDeserializerSupport implements Constr bean.setType("#class:" + bean.getType()); } - answer.addBean(bean); + // due to yaml-dsl is pre parsing beans which gets created eager + // and then later beans can be parsed again such as from Camel K Integration CRD files + // we need to avoid double creating beans and therefore has a cache to check for duplicates + String key = bean.getName() + ":" + bean.getType(); + boolean duplicate = beanCache.contains(key); + if (!duplicate) { + answer.addBean(bean); + beanCache.add(key); + } } return answer; } + public void clearCache() { + beanCache.clear(); + } + public Object newInstance(RegistryBeanDefinition bean, CamelContext context) throws Exception { final Object target = PropertyBindingSupport.resolveBean(context, bean.getType()); 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 6c4855aa07f..52b6fdec608 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 @@ -25,6 +25,12 @@ public class CustomResolver implements YamlDeserializerResolver { return YamlDeserializerResolver.ORDER_DEFAULT; } + private final BeansDeserializer beansDeserializer; + + public CustomResolver(BeansDeserializer beansDeserializer) { + this.beansDeserializer = beansDeserializer; + } + @Override public ConstructNode resolve(String id) { switch (id) { @@ -71,7 +77,7 @@ public class CustomResolver implements YamlDeserializerResolver { // Misc // case "beans": - return new BeansDeserializer(); + return beansDeserializer; case "error-handler": case "errorHandler": return new ErrorHandlerBuilderDeserializer(); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json index 01393f4e7f0..b1022f4f969 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json @@ -8007,7 +8007,8 @@ "type" : { "type" : "string" } - } + }, + "required" : [ "name", "type" ] }, "org.apache.camel.model.cloud.BlacklistServiceCallServiceFilterConfiguration" : { "title" : "Blacklist Service Filter", 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 a3239d737ae..29c5de899bc 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 @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; @@ -106,6 +107,8 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport { private static final String STRIMZI_VERSION = "kafka.strimzi.io/v1"; private static final String KNATIVE_VERSION = "messaging.knative.dev/v1"; + private final Map<String, Boolean> preparseDone = new ConcurrentHashMap<>(); + public YamlRoutesBuilderLoader() { super(EXTENSION); } @@ -153,6 +156,14 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport { doConfigure(target); } } + + // knowing this is the last time an XML may have been parsed, we can clear the cache + // (route may get reloaded later) + Resource resource = ctx.getResource(); + if (resource != null) { + preparseDone.remove(resource.getLocation()); + } + beansDeserializer.clearCache(); } private boolean doConfigure(Object item) throws Exception { @@ -264,10 +275,19 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport { idx = node.getStartMark().get().getIndex(); } if (idx == -1 || !indexes.contains(idx)) { - Object item = ctx.mandatoryResolve(node).construct(node); - boolean accepted = doConfiguration(item); - if (accepted && idx != -1) { - indexes.add(idx); + if (node.getNodeType() == NodeType.MAPPING) { + MappingNode mn = asMappingNode(node); + for (NodeTuple nt : mn.getValue()) { + String key = asText(nt.getKeyNode()); + // only accept route-configuration + if ("route-configuration".equals(key) || "routeConfiguration".equals(key)) { + Object item = ctx.mandatoryResolve(node).construct(node); + boolean accepted = doConfiguration(item); + if (accepted && idx != -1) { + indexes.add(idx); + } + } + } } } } @@ -311,6 +331,32 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport { } } + // only detect beans during pre-parsing + if (preParse && Objects.equals(root.getNodeType(), NodeType.SEQUENCE)) { + final List<Object> list = new ArrayList<>(); + + final SequenceNode sn = asSequenceNode(root); + for (Node node : sn.getValue()) { + if (Objects.equals(node.getNodeType(), NodeType.MAPPING)) { + MappingNode mn = asMappingNode(node); + for (NodeTuple nt : mn.getValue()) { + String key = asText(nt.getKeyNode()); + if ("beans".equals(key)) { + // inlined beans + Node beans = nt.getValueNode(); + setDeserializationContext(beans, ctx); + Object output = beansDeserializer.construct(beans); + if (output != null) { + list.add(output); + } + } + } + } + } + if (!list.isEmpty()) { + target = list; + } + } return target; } @@ -842,6 +888,12 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport { @Override public void preParseRoute(Resource resource) throws Exception { + // preparsing is done at early stage, so we have a chance to load additional beans and populate + // Camel registry + if (preparseDone.getOrDefault(resource.getLocation(), false)) { + return; + } + LOG.trace("Pre-parsing: {}", resource.getLocation()); if (!resource.exists()) { @@ -863,6 +915,8 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport { ctx.close(); } } + + preparseDone.put(resource.getLocation(), true); } private Object preParseNode(final YamlDeserializationContext ctx, final Node root) { 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 bb7998c615d..bfd11e5212f 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 @@ -26,6 +26,7 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.dsl.support.RouteBuilderLoaderSupport; import org.apache.camel.dsl.yaml.common.YamlDeserializationContext; import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException; +import org.apache.camel.dsl.yaml.deserializers.BeansDeserializer; import org.apache.camel.dsl.yaml.deserializers.CustomResolver; import org.apache.camel.dsl.yaml.deserializers.ModelDeserializersResolver; import org.apache.camel.spi.Resource; @@ -43,6 +44,9 @@ import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText; public abstract class YamlRoutesBuilderLoaderSupport extends RouteBuilderLoaderSupport { + // need to use shared bean deserializer + final BeansDeserializer beansDeserializer = new BeansDeserializer(); + public YamlRoutesBuilderLoaderSupport(String extension) { super(extension); } @@ -52,7 +56,7 @@ public abstract class YamlRoutesBuilderLoaderSupport extends RouteBuilderLoaderS ctx.setResource(resource); ctx.setCamelContext(getCamelContext()); - ctx.addResolvers(new CustomResolver()); + ctx.addResolvers(new CustomResolver(beansDeserializer)); ctx.addResolvers(new ModelDeserializersResolver()); return ctx; } diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/MessageHistoryFactoryTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/MessageHistoryFactoryTest.groovy new file mode 100644 index 00000000000..64003042273 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/MessageHistoryFactoryTest.groovy @@ -0,0 +1,49 @@ +/* + * 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.dsl.yaml.support.YamlTestSupport +import org.apache.camel.main.DefaultConfigurationConfigurer + +class MessageHistoryFactoryTest extends YamlTestSupport { + + def "messageHistoryFactory bean"() { + setup: + loadRoutes ''' + - beans: + - name: messageHistoryFactory + type: org.apache.camel.impl.engine.DefaultMessageHistoryFactory + properties: + copyMessage: true + - route: + messageHistory: true + from: + uri: "timer:yaml" + parameters: + period: "3000" + steps: + - to: + uri: "log:test" + ''' + when: + DefaultConfigurationConfigurer.afterConfigure(context) + context.start() + then: + context.getMessageHistoryFactory().copyMessage == true + } + +} \ No newline at end of file diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/TryTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/TryTest.groovy index 73e13e02ba1..552c2bd3674 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/TryTest.groovy +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/TryTest.groovy @@ -216,7 +216,7 @@ class TryTest extends YamlTestSupport { } } - def "Error: kebab-case: do-catch"() { + def "Error: kebab-case: do-catch2"() { when: var route = ''' - from: