This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-25255 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 57075694ad066fd7e13cb8bc0344feefcbee050a Author: Claus Ibsen <[email protected]> AuthorDate: Fri Oct 2 08:50:48 2026 +0200 CAMEL-25255: camel-yaml-io - rests, route templates and bean properties are written as the YAML DSL loads them Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/yaml/out/YamlModelWriter.java | 16 +- .../org/apache/camel/yaml/LwModelToYAMLDumper.java | 73 ++----- .../camel/yaml/out/YamlModelWriterSupport.java | 32 +++ .../apache/camel/yaml/out/YamlBeansDumpTest.java | 74 +++++++ .../src/test/resources/yaml-beans-with-route.yaml | 4 +- .../src/test/resources/yaml-beans.yaml | 7 +- .../src/test/resources/yaml-rest.yaml | 3 +- .../src/test/resources/yaml-route-template.yaml | 2 +- .../src/test/resources/blueprint-out.yaml | 2 +- .../validator/YamlRestTemplateRoundTripTest.java | 221 +++++++++++++++++++++ .../main/resources/velocity/model-yaml-writer.vm | 19 +- 11 files changed, 376 insertions(+), 77 deletions(-) diff --git a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java index 55f7b50c48ca..3aeb60ef3950 100644 --- a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java +++ b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/YamlModelWriter.java @@ -870,8 +870,8 @@ public class YamlModelWriter extends YamlModelWriterSupport { doWriteAttribute(jo, "builderMethod", def.getBuilderMethod(), "build"); doWriteAttribute(jo, "scriptLanguage", def.getScriptLanguage(), null); doWriteAttribute(jo, "scriptPropertyPlaceholders", def.getScriptPropertyPlaceholders(), "true"); - doWriteChildElement(jo, "constructors", new BeanConstructorsAdapter().marshal(def.getConstructors()), this::doWriteBeanConstructorsDefinition); - doWriteChildElement(jo, "properties", new BeanPropertiesAdapter().marshal(def.getProperties()), this::doWriteBeanPropertiesDefinition); + doWriteBeanMap(jo, "constructors", def.getConstructors()); + doWriteBeanMap(jo, "properties", def.getProperties()); if (def.getScript() != null) { jo.put("script", def.getScript()); } @@ -1667,8 +1667,8 @@ public class YamlModelWriter extends YamlModelWriterSupport { protected JsonObject doWriteRouteTemplateDefinition(RouteTemplateDefinition def) { JsonObject jo = new JsonObject(); doWriteOptionalIdentifiedDefinitionAttributes(jo, def); - doWriteChildList(jo, null, "templateParameter", def.getTemplateParameters(), this::doWriteRouteTemplateParameterDefinition); - doWriteChildList(jo, null, "templateBean", def.getTemplateBeans(), this::doWriteBeanFactoryDefinition); + doWriteChildList(jo, null, "parameters", def.getTemplateParameters(), this::doWriteRouteTemplateParameterDefinition); + doWriteChildList(jo, null, "beans", def.getTemplateBeans(), this::doWriteBeanFactoryDefinition); doWriteChildElement(jo, "route", def.getRoute(), this::doWriteRouteDefinition); return jo; } @@ -2061,7 +2061,7 @@ public class YamlModelWriter extends YamlModelWriterSupport { } protected JsonObject doWriteValueDefinition(ValueDefinition def) { JsonObject jo = new JsonObject(); - doWriteValue(jo, def.getValue()); + doWriteAttribute(jo, "value", def.getValue(), null); return jo; } protected JsonObject doWriteWhenDefinition(WhenDefinition def) { @@ -3411,7 +3411,7 @@ public class YamlModelWriter extends YamlModelWriterSupport { doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); doWriteAttribute(jo, "apiDocs", def.getApiDocs(), "true"); doWriteAttribute(jo, "tag", def.getTag(), null); - doWriteChildList(jo, "verbs", "verbs", def.getVerbs(), this::doWriteVerbDefinition); + doWriteVerbs(jo, def.getVerbs(), this::doWriteVerbDefinition); doWriteChildElement(jo, "openApi", def.getOpenApi(), this::doWriteOpenApiDefinition); doWriteChildElement(jo, "securityDefinitions", def.getSecurityDefinitions(), this::doWriteRestSecuritiesDefinition); doWriteChildList(jo, null, "securityRequirements", def.getSecurityRequirements(), this::doWriteSecurityDefinition); @@ -3480,9 +3480,9 @@ public class YamlModelWriter extends YamlModelWriterSupport { doWriteAttribute(jo, "consumes", def.getConsumes(), null); } protected void doWriteVerbDefinitionElements(JsonObject jo, VerbDefinition def) { - doWriteChildList(jo, "params", "params", def.getParams(), this::doWriteParamDefinition); + doWriteChildList(jo, null, "param", def.getParams(), this::doWriteParamDefinition); doWriteChildList(jo, "security", "security", def.getSecurity(), this::doWriteSecurityDefinition); - doWriteChildList(jo, "responseMsgs", "responseMsgs", def.getResponseMsgs(), this::doWriteResponseMessageDefinition); + doWriteChildList(jo, null, "responseMessage", def.getResponseMsgs(), this::doWriteResponseMessageDefinition); doWriteChildElement(jo, "to", def.getTo(), this::doWriteToDefinition); } protected JsonObject doWriteVerbDefinition(VerbDefinition def) { diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java index c5db9efafe79..22510f4548b7 100644 --- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java +++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java @@ -24,7 +24,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.apache.camel.CamelContext; @@ -407,63 +406,25 @@ public class LwModelToYAMLDumper implements ModelToYAMLDumper { if (beans.isEmpty()) { return; } - buffer.write("- beans:\n"); + // written by the model writer, so nested properties come out as nested maps, as the yaml dsl loads them + var writer = new YamlModelWriter() { + @Override + public JsonObject doWriteBeanFactoryDefinition(BeanFactoryDefinition<?> def) { + return super.doWriteBeanFactoryDefinition(def); + } + }; + writer.setCamelContext(camelContext); + JsonArray list = new JsonArray(); for (BeanFactoryDefinition<?> b : beans) { - doWriteBeanFactoryDefinition(b); - } - } - - private void doWriteBeanFactoryDefinition(BeanFactoryDefinition<?> b) { - String type = b.getType(); - if (type.startsWith("#class:")) { - type = type.substring(7); - } - buffer.write(String.format(" - name: %s%n", b.getName())); - buffer.write(String.format(" type: \"%s\"%n", type)); - if (b.getFactoryBean() != null) { - buffer.write(String.format(" factoryBean: \"%s\"%n", b.getFactoryBean())); - } - if (b.getFactoryMethod() != null) { - buffer.write(String.format(" factoryMethod: \"%s\"%n", b.getFactoryMethod())); - } - if (b.getBuilderClass() != null) { - buffer.write(String.format(" builderClass: \"%s\"%n", b.getBuilderClass())); - } - if (b.getBuilderMethod() != null) { - buffer.write(String.format(" builderMethod: \"%s\"%n", b.getBuilderMethod())); - } - if (b.getInitMethod() != null) { - buffer.write(String.format(" initMethod: \"%s\"%n", b.getInitMethod())); - } - if (b.getDestroyMethod() != null) { - buffer.write(String.format(" destroyMethod: \"%s\"%n", b.getDestroyMethod())); - } - if (b.getScriptLanguage() != null) { - buffer.write(String.format(" scriptLanguage: \"%s\"%n", b.getScriptLanguage())); - } - if (b.getScript() != null) { - buffer.write(String.format(" script: \"%s\"%n", b.getScript())); - } - if (b.getConstructors() != null && !b.getConstructors().isEmpty()) { - buffer.write(String.format(" constructors:%n")); - final AtomicInteger counter = new AtomicInteger(); - b.getConstructors().forEach((key, value) -> { - if (key == null) { - key = counter.getAndIncrement(); - } - buffer.write(String.format(" %d: \"%s\"%n", key, value)); - }); - } - if (b.getProperties() != null && !b.getProperties().isEmpty()) { - buffer.write(String.format(" properties:%n")); - b.getProperties().forEach((key, value) -> { - if (value instanceof String) { - buffer.write(String.format(" %s: \"%s\"%n", key, value)); - } else { - buffer.write(String.format(" %s: %s%n", key, value)); - } - }); + JsonObject bean = writer.doWriteBeanFactoryDefinition(b); + if (bean.get("type") instanceof String type && type.startsWith("#class:")) { + bean.put("type", type.substring(7)); + } + list.add(bean); } + JsonObject root = new JsonObject(); + root.put("beans", list); + buffer.write(writer.printAsYaml(List.of(root))); } } diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/YamlModelWriterSupport.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/YamlModelWriterSupport.java index 4fe49a127205..6af0454bb719 100644 --- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/YamlModelWriterSupport.java +++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/out/YamlModelWriterSupport.java @@ -25,6 +25,7 @@ import java.util.function.Function; import org.apache.camel.CamelContext; import org.apache.camel.catalog.RuntimeCamelCatalog; +import org.apache.camel.model.rest.VerbDefinition; import org.apache.camel.util.URISupport; import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; @@ -143,6 +144,37 @@ public abstract class YamlModelWriterSupport { } } + /** + * The verbs of a rest as the YAML DSL has them: a list per kind (get, post, ...), not one list of all verbs. + */ + protected <T extends VerbDefinition> void doWriteVerbs(JsonObject jo, List<T> verbs, Function<T, JsonObject> writer) { + if (verbs != null) { + for (T verb : verbs) { + JsonObject child = writer.apply(verb); + if (child != null) { + JsonArray arr = (JsonArray) jo.computeIfAbsent(verb.asVerb(), k -> new JsonArray()); + arr.add(child); + } + } + } + } + + /** + * The properties or constructor arguments of a bean as the YAML DSL has them: a map, with a nested map for nested + * properties. + */ + protected void doWriteBeanMap(JsonObject jo, String key, Map<?, ?> map) { + if (map != null && !map.isEmpty()) { + jo.put(key, beanMap(map)); + } + } + + private static JsonObject beanMap(Map<?, ?> map) { + JsonObject answer = new JsonObject(); + map.forEach((k, v) -> answer.put(String.valueOf(k), v instanceof Map<?, ?> m ? beanMap(m) : v)); + return answer; + } + @SuppressWarnings("unchecked") protected void doWriteStringList(JsonObject jo, String wrapperKey, String itemKey, List<String> list) { if (list != null && !list.isEmpty()) { diff --git a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/YamlBeansDumpTest.java b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/YamlBeansDumpTest.java new file mode 100644 index 000000000000..28e23450a3e4 --- /dev/null +++ b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/YamlBeansDumpTest.java @@ -0,0 +1,74 @@ +/* + * 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.yaml.out; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.camel.model.BeanFactoryDefinition; +import org.apache.camel.model.app.BeansDefinition; +import org.apache.camel.xml.in.ModelParser; +import org.apache.camel.yaml.LwModelToYAMLDumper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The beans dumped as YAML have their properties as the YAML DSL loads them: nested properties as a nested map + * (CAMEL-25255). + */ +class YamlBeansDumpTest { + + @Test + void nestedPropertiesAreANestedMap() throws Exception { + BeansDefinition beans; + try (InputStream in = Files.newInputStream(Paths.get("../camel-xml-io/src/test/resources/beansWithProperties.xml"))) { + beans = new ModelParser(in, "http://camel.apache.org/schema/xml-io").parseBeansDefinition().orElseThrow(); + } + String yaml = new LwModelToYAMLDumper().dumpBeansAsYaml(null, new ArrayList<>(beans.getBeans())); + assertThat(yaml).isEqualTo(""" + - beans: + - name: b1 + type: org.apache.camel.xml.in.ModelParserTest.MyBean + properties: + p1: v1 + p2: v2 + nested: + p1: v1a + p2: v2a + - name: b2 + type: org.apache.camel.xml.in.ModelParserTest.MyBean + properties: + p1: v1 + p2: v2 + nested.p1: v1a + nested.p2: v2a + """); + } + + @Test + void theClassPrefixOfTheTypeIsLeftOut() throws Exception { + BeanFactoryDefinition<?> bean = new BeanFactoryDefinition<>(); + bean.setName("greeter"); + bean.setType("#class:com.foo.Greeter"); + String yaml = new LwModelToYAMLDumper().dumpBeansAsYaml(null, List.of(bean)); + assertThat(yaml).contains("type: com.foo.Greeter").doesNotContain("#class"); + } +} diff --git a/core/camel-yaml-io/src/test/resources/yaml-beans-with-route.yaml b/core/camel-yaml-io/src/test/resources/yaml-beans-with-route.yaml index 64db76b553c7..02359c8af424 100644 --- a/core/camel-yaml-io/src/test/resources/yaml-beans-with-route.yaml +++ b/core/camel-yaml-io/src/test/resources/yaml-beans-with-route.yaml @@ -20,9 +20,7 @@ - name: myService type: com.example.MyService properties: - property: - - key: url - value: http://localhost:8080 + url: http://localhost:8080 route: - customId: true id: myRoute diff --git a/core/camel-yaml-io/src/test/resources/yaml-beans.yaml b/core/camel-yaml-io/src/test/resources/yaml-beans.yaml index 3e50198f4fdc..841c9322fc89 100644 --- a/core/camel-yaml-io/src/test/resources/yaml-beans.yaml +++ b/core/camel-yaml-io/src/test/resources/yaml-beans.yaml @@ -19,11 +19,8 @@ name: myBean type: com.example.MyBean properties: - property: - - key: greeting - value: Hello - - key: count - value: 5 + greeting: Hello + count: "5" - beanFactory: name: myFactory type: com.example.MyFactory diff --git a/core/camel-yaml-io/src/test/resources/yaml-rest.yaml b/core/camel-yaml-io/src/test/resources/yaml-rest.yaml index fee9da12aaaa..9ee93c87a537 100644 --- a/core/camel-yaml-io/src/test/resources/yaml-rest.yaml +++ b/core/camel-yaml-io/src/test/resources/yaml-rest.yaml @@ -17,10 +17,11 @@ - rest: path: /api - verbs: + get: - path: /hello to: uri: direct:hello + post: - path: /bye consumes: application/json to: diff --git a/core/camel-yaml-io/src/test/resources/yaml-route-template.yaml b/core/camel-yaml-io/src/test/resources/yaml-route-template.yaml index a1ad10bd8c38..544d3a05f61a 100644 --- a/core/camel-yaml-io/src/test/resources/yaml-route-template.yaml +++ b/core/camel-yaml-io/src/test/resources/yaml-route-template.yaml @@ -18,7 +18,7 @@ - routeTemplate: customId: true id: myTemplate - templateParameter: + parameters: - description: the foo parameter name: foo - description: the bar parameter diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/blueprint-out.yaml b/dsl/camel-jbang/camel-jbang-core/src/test/resources/blueprint-out.yaml index 754bcf2ae5e9..e304ac8d51cc 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/blueprint-out.yaml +++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/blueprint-out.yaml @@ -17,7 +17,7 @@ - beans: - name: userService - type: "org.apache.camel.example.rest.UserService" + type: org.apache.camel.example.rest.UserService - route: from: uri: direct diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlRestTemplateRoundTripTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlRestTemplateRoundTripTest.java new file mode 100644 index 000000000000..e6e5792cf56e --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlRestTemplateRoundTripTest.java @@ -0,0 +1,221 @@ +/* + * 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.validator; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import com.networknt.schema.Error; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.ModelCamelContext; +import org.apache.camel.model.RouteTemplatesDefinition; +import org.apache.camel.model.rest.RestsDefinition; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.xml.LwModelToXMLDumper; +import org.apache.camel.xml.in.ModelParser; +import org.apache.camel.yaml.LwModelToYAMLDumper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * Rests and route templates dumped as YAML are loaded back by the YAML DSL as they were (CAMEL-25255): the YAML writer + * writes the verbs of a rest by their kind (get, post, ...), and the parameters and beans of a route template as the + * YAML DSL names them. + */ +class YamlRestTemplateRoundTripTest { + + private static final String NS = "http://camel.apache.org/schema/xml-io"; + private static final Path CORPUS = Path.of("../../../core/camel-xml-io/src/test/resources"); + + private static final String REST = """ + <rests xmlns="http://camel.apache.org/schema/xml-io"> + <rest id="api" path="/api" tag="orders" consumes="application/json" bindingMode="json"> + <securityDefinitions> + <apiKey key="key" name="X-Key" inHeader="true"/> + <oauth2 key="oauth" flow="implicit" authorizationUrl="https://auth/authorize"> + <scopes key="read" value="Read orders"/> + </oauth2> + </securityDefinitions> + <securityRequirements key="key"/> + <get path="/orders/{id}" id="getOrder" outType="com.foo.Order" routeId="get-order" + description="Gets an order"> + <param name="id" type="path" description="The order id" dataType="integer"> + <examples key="one" value="1"/> + </param> + <param name="verbose" type="query" required="false" defaultValue="false"> + <allowableValues> + <value>true</value> + <value>false</value> + </allowableValues> + </param> + <responseMessage code="404" message="Not found"> + <header name="X-Reason" dataType="string"/> + <examples key="json" value="{}"/> + </responseMessage> + <security key="oauth" scopes="read"/> + <to uri="direct:getOrder"/> + </get> + <get path="/orders"> + <to uri="direct:listOrders"/> + </get> + <post path="/orders" type="com.foo.Order"> + <to uri="direct:createOrder"/> + </post> + <put path="/orders/{id}"> + <to uri="direct:updateOrder"/> + </put> + <delete path="/orders/{id}"> + <to uri="direct:deleteOrder"/> + </delete> + <head path="/orders"> + <to uri="direct:headOrders"/> + </head> + <patch path="/orders/{id}"> + <to uri="direct:patchOrder"/> + </patch> + </rest> + <rest path="/petstore"> + <openApi specification="petstore.json" missingOperation="mock"/> + </rest> + </rests> + """; + + private static final String TEMPLATE = """ + <routeTemplates xmlns="http://camel.apache.org/schema/xml-io"> + <routeTemplate id="myTemplate"> + <templateParameter name="foo" description="The input"/> + <templateParameter name="bar" defaultValue="out" required="false"/> + <templateBean name="greeter" type="com.foo.Greeter"> + <properties> + <property key="greeting" value="Hello"/> + </properties> + </templateBean> + <route> + <from uri="direct:{{foo}}"/> + <to uri="mock:{{bar}}"/> + </route> + </routeTemplate> + </routeTemplates> + """; + + @Test + void restWithAllItsParts() throws Exception { + assertThat(restsRoundTrip(REST)).isNull(); + } + + @Test + void routeTemplateWithParametersAndBeans() throws Exception { + assertThat(templatesRoundTrip(TEMPLATE)).isNull(); + } + + @ParameterizedTest + @ValueSource(strings = { "barRest.xml", "simpleRest.xml", "restAllowedValues.xml" }) + void restsOfTheCorpus(String file) throws Exception { + assumeTrue(Files.isDirectory(CORPUS), "the core test routes are in the source tree"); + assertThat(restsRoundTrip(Files.readString(CORPUS.resolve(file)))).isNull(); + } + + @Test + void routeTemplatesOfTheCorpus() throws Exception { + assumeTrue(Files.isDirectory(CORPUS), "the core test routes are in the source tree"); + assertThat(templatesRoundTrip(Files.readString(CORPUS.resolve("barTemplate.xml")))).isNull(); + } + + private static InputStream in(String xml) { + return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Null when the rests read back as they were, else what went wrong. Both sides are added to a context the same way, + * so what loading adds (such as the params of a path) is on both. + */ + private static String restsRoundTrip(String xml) throws Exception { + RestsDefinition rests = new ModelParser(in(xml), NS).parseRestsDefinition().orElseThrow(); + String yaml; + String before; + try (DefaultCamelContext context = new DefaultCamelContext()) { + yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, rests); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + getRestCollection().setRests(rests.getRests()); + } + }); + before = dumpRests(context); + } + return readBack(yaml, before, YamlRestTemplateRoundTripTest::dumpRests); + } + + /** Null when the route templates read back as they were, else what went wrong. */ + private static String templatesRoundTrip(String xml) throws Exception { + RouteTemplatesDefinition templates = new ModelParser(in(xml), NS).parseRouteTemplatesDefinition().orElseThrow(); + String yaml; + String before; + try (DefaultCamelContext context = new DefaultCamelContext()) { + yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, templates); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + getRouteTemplateCollection().setRouteTemplates(templates.getRouteTemplates()); + } + }); + before = dumpTemplates(context); + } + return readBack(yaml, before, YamlRestTemplateRoundTripTest::dumpTemplates); + } + + private static String dumpRests(DefaultCamelContext context) throws Exception { + RestsDefinition rests = new RestsDefinition(); + rests.setRests(context.getCamelContextExtension().getContextPlugin(ModelCamelContext.class).getRestDefinitions()); + return new LwModelToXMLDumper().dumpModelAsXml(context, rests); + } + + private static String dumpTemplates(DefaultCamelContext context) throws Exception { + RouteTemplatesDefinition templates = new RouteTemplatesDefinition(); + templates.setRouteTemplates(context.getCamelContextExtension().getContextPlugin(ModelCamelContext.class) + .getRouteTemplateDefinitions()); + return new LwModelToXMLDumper().dumpModelAsXml(context, templates); + } + + private interface Dump { + String dump(DefaultCamelContext context) throws Exception; + } + + private static String readBack(String yaml, String before, Dump dump) throws Exception { + List<Error> errors = new YamlValidator().validate(yaml); + if (!errors.isEmpty()) { + return yaml + "\n--- does not validate: " + errors; + } + try (DefaultCamelContext context = new DefaultCamelContext()) { + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("rest.yaml", yaml)); + String after = dump.dump(context); + return after.equals(before) ? null : yaml + "\n--- was ---\n" + before + "\n--- read back as ---\n" + after; + } catch (RuntimeException e) { + return yaml + "\n--- does not load: " + e; + } + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm index 711796bc892a..c9153ee6f1a6 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm +++ b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm @@ -15,6 +15,8 @@ ## limitations under the License. ## --------------------------------------------------------------------------- #set( $default = "#[[##default]]#" ) +## the YAML DSL names some lists other than the XML DSL does (keyed by class.member) +#set( $yamlKeys = { "VerbDefinition.params": "param", "VerbDefinition.responseMsgs": "responseMessage", "RouteTemplateDefinition.templateParameters": "parameters", "RouteTemplateDefinition.templateBeans": "beans" } ) /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -141,8 +143,13 @@ public class YamlModelWriter extends YamlModelWriterSupport { #elseif( $member.xmlElementRef ) #set( $foo = $elementRefs.add($root) ) #if( $list ) + #set( $yamlKey = $yamlKeys.get("${clazz.simpleName}.${member.name}") ) #if( $member.name == "outputs" ) doWriteOutputs(jo, def.${member.getter}(), this::doWrite${root.simpleName}Ref); + #elseif( $clazz.simpleName == "RestDefinition" && $member.name == "verbs" ) + doWriteVerbs(jo, def.${member.getter}(), this::doWrite${root.simpleName}); + #elseif( $yamlKey ) + doWriteChildList(jo, null, "${yamlKey}", def.${member.getter}(), this::doWrite${root.simpleName}); #else #set( $refName = $member.xmlElementRef.name() ) #if( $refName && $refName != $default && $refName != "" ) @@ -186,6 +193,10 @@ public class YamlModelWriter extends YamlModelWriterSupport { #if( $n == $default ) #set( $n = ${member.name} ) #end + #set( $yamlKey = $yamlKeys.get("${clazz.simpleName}.${member.name}") ) + #if( $yamlKey ) + #set( $n = $yamlKey ) + #end #if( $list ) #set( $w = false ) #if( $member.xmlElementWrapper ) @@ -198,7 +209,9 @@ public class YamlModelWriter extends YamlModelWriterSupport { #end #else #set( $adapter = $member.xmlJavaTypeAdapter ) - #if( $adapter ) + #if( $adapter && $clazz.simpleName == "BeanFactoryDefinition" ) + doWriteBeanMap(jo, "${n}", def.${member.getter}()); + #elseif( $adapter ) #set( $cl = $adapter.value() ) #set( $actualType = false ) #foreach( $m in $cl.declaredMethods ) @@ -268,7 +281,9 @@ public class YamlModelWriter extends YamlModelWriterSupport { #writeYamlAttrs #end #set( $value = $mojo.getValues($mojo.getClassAndSuper($clazz)).findFirst() ) - #if( $value.isPresent() ) + #if( $value.isPresent() && $name == "ValueDefinition" ) + doWriteAttribute(jo, "value", def.${value.get().getter}(), null); + #elseif( $value.isPresent() ) doWriteValue(jo, def.${value.get().getter}()); #end #if( $hasDerived && !$thisElements.isEmpty() )
