From 3264d8ba82a2a3131f9fb2603635bf26cdd37ffd Mon Sep 17 00:00:00 2001 From: smjain <49463903+allthingssecurity@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:38:37 +0530 Subject: [PATCH 1/2] CAMEL-25721: camel-xml-io, camel-yaml-io, camel-java-io - route dumps keep options set to their default value The model writers leave out an attribute whose value equals its documented default. For several options "not set" means "the value of the parent level", so a route that sets one of them to the default on purpose runs differently once it is dumped (camel transform route, camel validate normalize, camel.main.dumpRoutes) and loaded again: - the binding options of a rest and its verbs (bindingMode, skipBindingOnErrorCode, clientRequestValidation, clientResponseValidation, enableCORS, enableNoContentResponse) take the rest / rest configuration value; skipBindingOnErrorCode is documented false but defaults to true; - the redelivery policy of an onException is a copy of the error handler's policy with only the options that are set overridden; - a resilience4j / fault tolerance configuration is layered over the global one; - a route's shutdownRoute / shutdownRunningTask take the CamelContext value. The writer that each dumper (XML, YAML, Java) creates now writes these options whenever they are set. Other options keep being left out at their default, as the Java DSL sets some defaults itself (simple pretty, json library). The generated writers are not changed. Co-Authored-By: Claude Opus 5.5 --- .../camel/java/LwModelToJavaDumper.java | 70 +++++- .../camel/java/LwModelToJavaDumperTest.java | 27 +++ .../apache/camel/xml/LwModelToXMLDumper.java | 61 +++++ .../camel/yaml/LwModelToYAMLDumper.java | 56 ++++- .../pages/camel-4x-upgrade-guide-4_24.adoc | 9 + .../io/XmlDumpExplicitDefaultValuesTest.java | 181 +++++++++++++++ .../YamlDumpExplicitDefaultValuesTest.java | 209 ++++++++++++++++++ 7 files changed, 606 insertions(+), 7 deletions(-) create mode 100644 dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlDumpExplicitDefaultValuesTest.java create mode 100644 dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlDumpExplicitDefaultValuesTest.java diff --git a/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java b/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java index a31ffe45207eb..75c4eef868162 100644 --- a/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java +++ b/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java @@ -20,12 +20,16 @@ import java.util.Collection; import java.util.List; import java.util.Properties; +import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.NamedNode; import org.apache.camel.builder.EndpointConsumerBuilder; import org.apache.camel.builder.EndpointProducerBuilder; +import org.apache.camel.model.FaultToleranceConfigurationCommon; import org.apache.camel.model.FromDefinition; +import org.apache.camel.model.RedeliveryPolicyDefinition; +import org.apache.camel.model.Resilience4jConfigurationCommon; import org.apache.camel.model.RouteConfigurationDefinition; import org.apache.camel.model.RouteConfigurationsDefinition; import org.apache.camel.model.RouteDefinition; @@ -52,6 +56,17 @@ @JdkService(ModelToJavaDumper.FACTORY) public class LwModelToJavaDumper implements ModelToJavaDumper { + /** + * Options that are written whenever they are set, even with their documented default value: when they are not set, + * they take the value of a parent level (the rest or the rest configuration for a rest or verb, the CamelContext + * for a route), and a dump that leaves them out runs differently once loaded. The same holds for every option of a + * redelivery policy (an onException copies the error handler's policy) and of a circuit breaker configuration (it + * is layered over the global one). + */ + static final Set INHERITED_OPTIONS = Set.of( + "bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", "clientResponseValidation", "enableCORS", + "enableNoContentResponse", "shutdownRoute", "shutdownRunningTask"); + @Override public String dumpModelAsJava(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsJava(context, definition, false, true); @@ -159,19 +174,62 @@ public String dumpModelAsJava( } private static org.apache.camel.java.out.JavaDslModelWriter createWriter(boolean sourceLocation) { - if (!sourceLocation) { - return new org.apache.camel.java.out.JavaDslModelWriter(); - } return new org.apache.camel.java.out.JavaDslModelWriter() { + // the options of a redelivery policy or a circuit breaker configuration that are written as set, see + // INHERITED_OPTIONS + private boolean keepDefault; + @Override protected void doWriteOptionalIdentifiedDefinitionAttributes( StringBuilder sb, org.apache.camel.model.OptionalIdentifiedDefinition def) { - int line = (def instanceof RouteDefinition rd ? rd.getInput() : def).getLineNumber(); - if (line != -1) { - sb.append("\n// sourceLineNumber: ").append(line); + if (sourceLocation) { + int line = (def instanceof RouteDefinition rd ? rd.getInput() : def).getLineNumber(); + if (line != -1) { + sb.append("\n// sourceLineNumber: ").append(line); + } } super.doWriteOptionalIdentifiedDefinitionAttributes(sb, def); } + + @Override + protected void doWriteAttribute(StringBuilder sb, String key, String value, String defaultValue) { + super.doWriteAttribute(sb, key, value, keepDefault || INHERITED_OPTIONS.contains(key) ? null : defaultValue); + } + + @Override + protected void doWriteRedeliveryPolicyDefinition(StringBuilder sb, RedeliveryPolicyDefinition def) { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteRedeliveryPolicyDefinition(sb, def); + } finally { + keepDefault = keep; + } + } + + @Override + protected void doWriteResilience4jConfigurationCommonAttributes( + StringBuilder sb, Resilience4jConfigurationCommon def) { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteResilience4jConfigurationCommonAttributes(sb, def); + } finally { + keepDefault = keep; + } + } + + @Override + protected void doWriteFaultToleranceConfigurationCommonAttributes( + StringBuilder sb, FaultToleranceConfigurationCommon def) { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteFaultToleranceConfigurationCommonAttributes(sb, def); + } finally { + keepDefault = keep; + } + } }; } diff --git a/core/camel-java-io/src/test/java/org/apache/camel/java/LwModelToJavaDumperTest.java b/core/camel-java-io/src/test/java/org/apache/camel/java/LwModelToJavaDumperTest.java index 6e77e7bb614a2..ae4d9a667fa2a 100644 --- a/core/camel-java-io/src/test/java/org/apache/camel/java/LwModelToJavaDumperTest.java +++ b/core/camel-java-io/src/test/java/org/apache/camel/java/LwModelToJavaDumperTest.java @@ -25,6 +25,7 @@ import org.apache.camel.model.RouteConfigurationDefinition; import org.apache.camel.model.RouteTemplateDefinition; import org.apache.camel.model.ThrowExceptionDefinition; +import org.apache.camel.model.rest.RestBindingMode; import org.apache.camel.model.rest.RestDefinition; import org.junit.jupiter.api.Test; @@ -106,4 +107,30 @@ public void configure() { .getExceptionType()).isNull(); } } + + /** + * An option set to its documented default value is written: when it is not set, the option takes the value of a + * parent level (the rest configuration for a verb, the error handler for the redelivery of an onException). + */ + @Test + public void testDumpOptionsSetToTheirDefaultValue() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + rest("/api").get("/raw").bindingMode(RestBindingMode.off).to("direct:raw"); + + from("direct:start").routeId("redelivery") + .onException(IllegalStateException.class).maximumRedeliveries(1).redeliveryDelay(1000).end() + .to("mock:result"); + } + }); + Model model = context.getCamelContextExtension().getContextPlugin(Model.class); + LwModelToJavaDumper dumper = new LwModelToJavaDumper(); + + assertThat(dumper.dumpModelAsJava(context, model.getRestDefinitions().get(0))).contains(".bindingMode(\"off\")"); + assertThat(dumper.dumpModelAsJava(context, context.getRouteDefinition("redelivery"))) + .contains(".redeliveryDelay(\"1000\")"); + } + } } diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index 915e4cceb4a1d..1cfaa61ee9067 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.function.Consumer; import org.apache.camel.CamelContext; @@ -37,8 +38,11 @@ import org.apache.camel.model.BeanFactoryDefinition; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.model.ExpressionNode; +import org.apache.camel.model.FaultToleranceConfigurationCommon; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.OptionalIdentifiedDefinition; +import org.apache.camel.model.RedeliveryPolicyDefinition; +import org.apache.camel.model.Resilience4jConfigurationCommon; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.RouteTemplateDefinition; import org.apache.camel.model.RouteTemplatesDefinition; @@ -65,6 +69,17 @@ @JdkService(ModelToXMLDumper.FACTORY) public class LwModelToXMLDumper implements ModelToXMLDumper { + /** + * Options that are written whenever they are set, even with their documented default value: when they are not set, + * they take the value of a parent level (the rest or the rest configuration for a rest or verb, the CamelContext + * for a route), and a dump that leaves them out runs differently once loaded. The same holds for every option of a + * redelivery policy (an onException copies the error handler's policy) and of a circuit breaker configuration (it + * is layered over the global one). + */ + static final Set INHERITED_OPTIONS = Set.of( + "bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", "clientResponseValidation", "enableCORS", + "enableNoContentResponse", "shutdownRoute", "shutdownRunningTask"); + @Override public String dumpModelAsXml(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsXml(context, definition, false, true, false); @@ -96,6 +111,10 @@ public String dumpModelAsXml( StringWriter buffer = new StringWriter(); ModelWriter writer = new ModelWriter(buffer, BaseWriter.DEFAULT_NAMESPACE) { + // the options of a redelivery policy or a circuit breaker configuration that are written as set, see + // INHERITED_OPTIONS + private boolean keepDefault; + @Override protected void doWriteOptionalIdentifiedDefinitionAttributes(OptionalIdentifiedDefinition def) throws IOException { @@ -168,6 +187,48 @@ protected void doWriteValue(String value) throws IOException { } } + @Override + protected void doWriteAttribute(String attribute, String value, String defaultValue) throws IOException { + super.doWriteAttribute(attribute, value, + keepDefault || INHERITED_OPTIONS.contains(attribute) ? null : defaultValue); + } + + @Override + protected void doWriteRedeliveryPolicyDefinition(String name, RedeliveryPolicyDefinition def) + throws IOException { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteRedeliveryPolicyDefinition(name, def); + } finally { + keepDefault = keep; + } + } + + @Override + protected void doWriteResilience4jConfigurationCommonAttributes(Resilience4jConfigurationCommon def) + throws IOException { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteResilience4jConfigurationCommonAttributes(def); + } finally { + keepDefault = keep; + } + } + + @Override + protected void doWriteFaultToleranceConfigurationCommonAttributes(FaultToleranceConfigurationCommon def) + throws IOException { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteFaultToleranceConfigurationCommonAttributes(def); + } finally { + keepDefault = keep; + } + } + @Override protected void text(String name, String text) throws IOException { if (resolvePlaceholders) { 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 6d6a6441be5e7..b6082ee40026a 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,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -36,12 +37,15 @@ import org.apache.camel.model.BeanFactoryDefinition; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.model.ExpressionNode; +import org.apache.camel.model.FaultToleranceConfigurationCommon; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.InterceptFromDefinition; import org.apache.camel.model.InterceptSendToEndpointDefinition; import org.apache.camel.model.Model; import org.apache.camel.model.OnExceptionDefinition; import org.apache.camel.model.OptionalIdentifiedDefinition; +import org.apache.camel.model.RedeliveryPolicyDefinition; +import org.apache.camel.model.Resilience4jConfigurationCommon; import org.apache.camel.model.RouteConfigurationDefinition; import org.apache.camel.model.RouteConfigurationsDefinition; import org.apache.camel.model.RouteDefinition; @@ -72,6 +76,17 @@ @JdkService(ModelToYAMLDumper.FACTORY) public class LwModelToYAMLDumper implements ModelToYAMLDumper { + /** + * Options that are written whenever they are set, even with their documented default value: when they are not set, + * they take the value of a parent level (the rest or the rest configuration for a rest or verb, the CamelContext + * for a route), and a dump that leaves them out runs differently once loaded. The same holds for every option of a + * redelivery policy (an onException copies the error handler's policy) and of a circuit breaker configuration (it + * is layered over the global one). + */ + static final Set INHERITED_OPTIONS = Set.of( + "bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", "clientResponseValidation", "enableCORS", + "enableNoContentResponse", "shutdownRoute", "shutdownRunningTask"); + @Override public String dumpModelAsYaml(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsYaml(context, definition, false, false, true, false); @@ -126,6 +141,10 @@ public String dumpModelAsYaml( topLevel.removeIf(oe -> isInRouteConfiguration(context, oe)); YamlModelWriter writer = new YamlModelWriter() { + // the options of a redelivery policy or a circuit breaker configuration that are written as set, see + // INHERITED_OPTIONS + private boolean keepDefault; + @Override protected void doWriteOutputs(JsonObject jo, List list, Function writer) { if (list != null && !contextScoped.isEmpty()) { @@ -161,7 +180,42 @@ protected void doWriteAttribute(JsonObject jo, String key, String value, String expandUri(jo, value); return; } - super.doWriteAttribute(jo, key, value, defaultValue); + super.doWriteAttribute(jo, key, value, keepDefault || INHERITED_OPTIONS.contains(key) ? null : defaultValue); + } + + @Override + protected JsonObject doWriteRedeliveryPolicyDefinition(RedeliveryPolicyDefinition def) { + boolean keep = keepDefault; + keepDefault = true; + try { + return super.doWriteRedeliveryPolicyDefinition(def); + } finally { + keepDefault = keep; + } + } + + @Override + protected void doWriteResilience4jConfigurationCommonAttributes( + JsonObject jo, Resilience4jConfigurationCommon def) { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteResilience4jConfigurationCommonAttributes(jo, def); + } finally { + keepDefault = keep; + } + } + + @Override + protected void doWriteFaultToleranceConfigurationCommonAttributes( + JsonObject jo, FaultToleranceConfigurationCommon def) { + boolean keep = keepDefault; + keepDefault = true; + try { + super.doWriteFaultToleranceConfigurationCommonAttributes(jo, def); + } finally { + keepDefault = keep; + } } @Override diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 0ecfc40763c77..2e5255c0ce79e 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -549,6 +549,15 @@ or follows, like Enter does. nothing), and a route that cannot be read, such as invalid YAML, exits with `1` and the error of the parser (it failed with an internal error, `70`, and a stack trace). +=== camel-xml-io, camel-yaml-io, camel-java-io - route dumps keep options set to their default value + +The XML, YAML and Java route dumps (`camel transform route`, `camel validate normalize`, `camel.main.dumpRoutes`, the +developer console) now write these options whenever they are set, also with their documented default value: the +binding options of a rest and its verbs (such as `bindingMode="off"`), the options of a redelivery policy, of a +`resilience4jConfiguration` and of a `faultToleranceConfiguration`, and `shutdownRoute` / `shutdownRunningTask` of a +route. Left out, such an option took the value of the parent level when the dump was loaded (the rest configuration, +the error handler, the global circuit breaker configuration, the CamelContext), so the route behaved differently. + === camel-jbang - camel get bean, get route-controller and top source * `camel get bean --dsl` shows the configuration of each property next to that property: it paired them by position, diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlDumpExplicitDefaultValuesTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlDumpExplicitDefaultValuesTest.java new file mode 100644 index 0000000000000..7e051ac91d8f0 --- /dev/null +++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlDumpExplicitDefaultValuesTest.java @@ -0,0 +1,181 @@ +/* + * 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 java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.component.seda.SedaEndpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.ModelCamelContext; +import org.apache.camel.model.RoutesDefinition; +import org.apache.camel.model.rest.RestsDefinition; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RestConsumerFactory; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.xml.LwModelToXMLDumper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * A route that is dumped as XML and loaded again must behave as the route it was dumped from, also when an option is + * set to its documented default value, as an option that is not set takes the value of a parent level instead. + */ +class XmlDumpExplicitDefaultValuesTest { + + /** A rest consumer that receives from seda:verb-path, so the test can call a rest verb without an HTTP server. */ + public static class SedaRestConsumerFactory implements RestConsumerFactory { + @Override + public org.apache.camel.Consumer createConsumer( + CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate, + String consumes, String produces, RestConfiguration configuration, Map parameters) + throws Exception { + String path = basePath + (uriTemplate != null ? uriTemplate : ""); + SedaEndpoint seda = camelContext.getEndpoint("seda:" + verb + path.replace('/', '-'), SedaEndpoint.class); + return seda.createConsumer(processor); + } + } + + @Test + void verbClientRequestValidationOffUnderRestConfiguration() throws Exception { + String xml = """ + + + + + + + + + + stored + + + + """; + Consumer setup = context -> { + context.getRegistry().bind("seda-rest", new SedaRestConsumerFactory()); + context.getRestConfiguration().setComponent("seda-rest"); + context.getRestConfiguration().setInlineRoutes(false); + // validation for all the rest services, the verb turns it off + context.getRestConfiguration().setClientRequestValidation(true); + }; + Function call = context -> { + Exchange reply = context.createProducerTemplate().request("seda:post-api-notes", e -> { + e.getMessage().setHeader(Exchange.CONTENT_TYPE, "text/plain"); + e.getMessage().setBody("a note"); + }); + return reply.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, "200", String.class) + " " + + reply.getMessage().getBody(String.class); + }; + + assertEquals("200 stored", run(xml, setup, call)); + String dumped = dump(xml, setup); + assertEquals("200 stored", run(dumped, setup, call), dumped); + assertTrue(dumped.contains("clientRequestValidation=\"false\""), dumped); + } + + @Test + void onExceptionRedeliveryDelayUnderErrorHandler() throws Exception { + // the onException redelivers after 1000 ms (the documented default), the error handler after 30 s: an option + // of the onException that is not set takes the value of the error handler + String xml = """ + + + + + + + + + + java.lang.IllegalStateException + + + true + + + handled + + + + + + """; + Consumer setup = context -> context.getShutdownStrategy().setTimeout(1); + Function call = context -> { + try { + return context.createProducerTemplate().asyncRequestBody("direct:start", "x", String.class) + .get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + return "not redelivered within 10 seconds"; + } catch (Exception e) { + return e.toString(); + } + }; + + assertEquals("handled", run(xml, setup, call)); + String dumped = dump(xml, setup); + assertEquals("handled", run(dumped, setup, call), dumped); + } + + private static String run(String xml, Consumer setup, Function call) + throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + setup.accept(context); + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("routes.xml", xml)); + context.start(); + return call.apply(context); + } + } + + /** Loads the routes and rests, and dumps them back as one XML document. */ + private static String dump(String xml, Consumer setup) throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + setup.accept(context); + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("routes.xml", xml)); + ModelCamelContext model = (ModelCamelContext) context; + StringBuilder sb = new StringBuilder(""); + if (!model.getRestDefinitions().isEmpty()) { + RestsDefinition rests = new RestsDefinition(); + rests.getRests().addAll(model.getRestDefinitions()); + sb.append(body(new LwModelToXMLDumper().dumpModelAsXml(context, rests), "rests")); + } + RoutesDefinition routes = new RoutesDefinition(); + model.getRouteDefinitions().stream().filter(r -> !Boolean.TRUE.equals(r.isRest())) + .forEach(routes.getRoutes()::add); + sb.append(body(new LwModelToXMLDumper().dumpModelAsXml(context, routes), "routes")); + return sb.append("").toString(); + } + } + + /** The children of the root element of a dump. */ + private static String body(String dump, String root) { + int start = dump.indexOf('>', dump.indexOf("<" + root)) + 1; + int end = dump.lastIndexOf(""); + return dump.substring(start, end); + } +} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlDumpExplicitDefaultValuesTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlDumpExplicitDefaultValuesTest.java new file mode 100644 index 0000000000000..54fb5f12244db --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/YamlDumpExplicitDefaultValuesTest.java @@ -0,0 +1,209 @@ +/* + * 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 java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.component.seda.SedaEndpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.ModelCamelContext; +import org.apache.camel.model.RoutesDefinition; +import org.apache.camel.model.rest.RestsDefinition; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RestConsumerFactory; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.yaml.LwModelToYAMLDumper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A route that is dumped as YAML (camel validate normalize, camel transform route, the route dump) and loaded again + * must behave as the route it was dumped from, also when an option is set to its documented default value, as an option + * that is not set takes the value of a parent level instead. + */ +class YamlDumpExplicitDefaultValuesTest { + + /** A rest consumer that receives from seda:verb-path, so the test can call a rest verb without an HTTP server. */ + public static class SedaRestConsumerFactory implements RestConsumerFactory { + @Override + public org.apache.camel.Consumer createConsumer( + CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate, + String consumes, String produces, RestConfiguration configuration, Map parameters) + throws Exception { + String path = basePath + (uriTemplate != null ? uriTemplate : ""); + SedaEndpoint seda = camelContext.getEndpoint("seda:" + verb + path.replace('/', '-'), SedaEndpoint.class); + return seda.createConsumer(processor); + } + } + + private static final Consumer JSON_BINDING = context -> { + context.getRegistry().bind("seda-rest", new SedaRestConsumerFactory()); + context.getRestConfiguration().setComponent("seda-rest"); + context.getRestConfiguration().setInlineRoutes(false); + context.getRestConfiguration().setBindingMode("json"); + }; + + @Test + void verbBindingModeOffUnderJsonRestConfiguration() throws Exception { + // the rest configuration binds json, this one verb turns binding off: its reply is not marshalled to json + String yaml = """ + - rest: + path: /api + get: + - path: /raw + bindingMode: "off" + to: direct:raw + - route: + from: + uri: direct:raw + steps: + - setBody: + expression: + simple: + expression: "${bean:type:java.util.Map.of('format', 'raw')}" + """; + Function call + = context -> context.createProducerTemplate().requestBody("seda:get-api-raw", "", String.class); + + assertThat(run(yaml, JSON_BINDING, call)).isEqualTo("{format=raw}"); + String dumped = dump(yaml, JSON_BINDING); + assertThat(run(dumped, JSON_BINDING, call)).as(dumped).isEqualTo("{format=raw}"); + assertThat(dumped).contains("bindingMode: \"off\""); + } + + @Test + void verbSkipBindingOnErrorCodeOff() throws Exception { + // skipBindingOnErrorCode is true when not set, though documented as false: the verb asks for its error + // replies to be bound to json as well + String yaml = """ + - rest: + path: /api + get: + - path: /err + skipBindingOnErrorCode: false + to: direct:err + - route: + from: + uri: direct:err + steps: + - setHeader: + name: CamelHttpResponseCode + expression: + constant: + expression: "400" + - setBody: + expression: + simple: + expression: "${bean:type:java.util.Map.of('error', 'bad request')}" + """; + Function call = context -> { + Exchange reply = context.createProducerTemplate().request("seda:get-api-err", e -> e.getMessage().setBody("")); + return reply.getMessage().getBody(String.class); + }; + + assertThat(run(yaml, JSON_BINDING, call)).isEqualTo("{\"error\":\"bad request\"}"); + String dumped = dump(yaml, JSON_BINDING); + assertThat(run(dumped, JSON_BINDING, call)).as(dumped).isEqualTo("{\"error\":\"bad request\"}"); + } + + @Test + void onExceptionRedeliveryDelayUnderErrorHandler() throws Exception { + // the onException redelivers after 1000 ms (the documented default), the error handler after 30 s: an option + // of the onException that is not set takes the value of the error handler + String yaml = """ + - onException: + exception: + - java.lang.IllegalStateException + redeliveryPolicy: + maximumRedeliveries: 1 + redeliveryDelay: 1000 + handled: + constant: + expression: "true" + steps: + - setBody: + expression: + constant: + expression: handled + - route: + errorHandler: + defaultErrorHandler: + redeliveryPolicy: + maximumRedeliveries: 3 + redeliveryDelay: 30000 + from: + uri: direct:start + steps: + - throwException: + exceptionType: java.lang.IllegalStateException + message: Forced + """; + Consumer setup = context -> context.getShutdownStrategy().setTimeout(1); + Function call = context -> { + try { + return context.createProducerTemplate().asyncRequestBody("direct:start", "x", String.class) + .get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + return "not redelivered within 10 seconds"; + } catch (Exception e) { + return e.toString(); + } + }; + + assertThat(run(yaml, setup, call)).isEqualTo("handled"); + String dumped = dump(yaml, setup); + assertThat(run(dumped, setup, call)).as(dumped).isEqualTo("handled"); + } + + private static String run(String yaml, Consumer setup, Function call) + throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + setup.accept(context); + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("routes.yaml", yaml)); + context.start(); + return call.apply(context); + } + } + + /** Loads the routes and rests, and dumps them back as YAML, as the route dump does. */ + private static String dump(String yaml, Consumer setup) throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + setup.accept(context); + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("routes.yaml", yaml)); + ModelCamelContext model = (ModelCamelContext) context; + StringBuilder sb = new StringBuilder(); + if (!model.getRestDefinitions().isEmpty()) { + RestsDefinition rests = new RestsDefinition(); + rests.getRests().addAll(model.getRestDefinitions()); + sb.append(new LwModelToYAMLDumper().dumpModelAsYaml(context, rests)).append('\n'); + } + RoutesDefinition routes = new RoutesDefinition(); + model.getRouteDefinitions().stream().filter(r -> !Boolean.TRUE.equals(r.isRest())) + .forEach(routes.getRoutes()::add); + return sb.append(new LwModelToYAMLDumper().dumpModelAsYaml(context, routes)).toString(); + } + } +} From 64145639d0f773560f24d9f3847b4e99256eb6af Mon Sep 17 00:00:00 2001 From: smjain <49463903+allthingssecurity@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:22:58 +0530 Subject: [PATCH 2/2] CAMEL-25721: camel-package-maven-plugin - the writer generators keep inherited options, instead of lists in the dumpers Review follow-up: ModelWriterGeneratorMojo passes no default value for the options where "not set" means "the value of a parent level" (every option of RedeliveryPolicyDefinition, Resilience4jConfigurationCommon and FaultToleranceConfigurationCommon; the rest binding options of the rest configuration, rests, verbs and rest bindings; shutdownRoute and shutdownRunningTask of a route), so the generated XML, YAML and Java writers write them whenever they are set. The INHERITED_OPTIONS lists and the keepDefault overrides in the three dumpers are removed, and the three writers are regenerated. New JavaDumpExplicitDefaultValuesTest compiles the Java dump of a route, loads it and runs it (onException redeliveryDelay=1000 under an error handler with 30 s). Co-Authored-By: Claude Opus 5.5 --- core/camel-java-io/pom.xml | 11 ++ .../camel/java/out/JavaDslModelWriter.java | 138 +++++++-------- .../camel/java/LwModelToJavaDumper.java | 70 +------- .../JavaDumpExplicitDefaultValuesTest.java | 161 ++++++++++++++++++ .../org/apache/camel/xml/out/ModelWriter.java | 138 +++++++-------- .../apache/camel/xml/LwModelToXMLDumper.java | 61 ------- .../camel/yaml/out/YamlModelWriter.java | 138 +++++++-------- .../camel/yaml/LwModelToYAMLDumper.java | 56 +----- .../pages/camel-4x-upgrade-guide-4_24.adoc | 9 +- .../packaging/ModelWriterGeneratorMojo.java | 48 ++++++ 10 files changed, 439 insertions(+), 391 deletions(-) create mode 100644 core/camel-java-io/src/test/java/org/apache/camel/java/JavaDumpExplicitDefaultValuesTest.java diff --git a/core/camel-java-io/pom.xml b/core/camel-java-io/pom.xml index e3d6475c55934..cca24faebf145 100644 --- a/core/camel-java-io/pom.xml +++ b/core/camel-java-io/pom.xml @@ -49,6 +49,17 @@ camel-xml-io test + + + org.apache.camel + camel-core-languages + test + + + org.apache.camel + camel-direct + test + org.junit.jupiter junit-jupiter diff --git a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java index 1df84eea2a3be..8ad31bfe061b6 100644 --- a/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java +++ b/core/camel-java-io/src/generated/java/org/apache/camel/java/out/JavaDslModelWriter.java @@ -1958,15 +1958,15 @@ protected void doWriteExpressionSubElementDefinition(StringBuilder sb, Expressio protected void doWriteFaultToleranceConfigurationCommonAttributes(StringBuilder sb, FaultToleranceConfigurationCommon def) { doWriteIdentifiedTypeAttributes(sb, def); doWriteAttribute(sb, "typedGuard", def.getTypedGuard(), null); - doWriteAttribute(sb, "delay", def.getDelay(), "5000"); - doWriteAttribute(sb, "successThreshold", def.getSuccessThreshold(), "1"); - doWriteAttribute(sb, "requestVolumeThreshold", def.getRequestVolumeThreshold(), "20"); - doWriteAttribute(sb, "failureRatio", def.getFailureRatio(), "50"); - doWriteAttribute(sb, "timeoutEnabled", def.getTimeoutEnabled(), "false"); - doWriteAttribute(sb, "timeoutDuration", def.getTimeoutDuration(), "1000"); - doWriteAttribute(sb, "bulkheadEnabled", def.getBulkheadEnabled(), "false"); - doWriteAttribute(sb, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), "10"); - doWriteAttribute(sb, "bulkheadWaitingTaskQueue", def.getBulkheadWaitingTaskQueue(), "10"); + doWriteAttribute(sb, "delay", def.getDelay(), null); + doWriteAttribute(sb, "successThreshold", def.getSuccessThreshold(), null); + doWriteAttribute(sb, "requestVolumeThreshold", def.getRequestVolumeThreshold(), null); + doWriteAttribute(sb, "failureRatio", def.getFailureRatio(), null); + doWriteAttribute(sb, "timeoutEnabled", def.getTimeoutEnabled(), null); + doWriteAttribute(sb, "timeoutDuration", def.getTimeoutDuration(), null); + doWriteAttribute(sb, "bulkheadEnabled", def.getBulkheadEnabled(), null); + doWriteAttribute(sb, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), null); + doWriteAttribute(sb, "bulkheadWaitingTaskQueue", def.getBulkheadWaitingTaskQueue(), null); doWriteAttribute(sb, "threadOffloadExecutorService", def.getThreadOffloadExecutorService(), null); } protected void doWriteFaultToleranceConfigurationCommon(StringBuilder sb, FaultToleranceConfigurationCommon def) { @@ -2307,28 +2307,28 @@ protected void doWriteRecipientListDefinition(StringBuilder sb, RecipientListDef protected void doWriteRedeliveryPolicyDefinition(StringBuilder sb, RedeliveryPolicyDefinition def) { doWriteIdentifiedTypeAttributes(sb, def); doWriteAttribute(sb, "maximumRedeliveries", def.getMaximumRedeliveries(), null); - doWriteAttribute(sb, "redeliveryDelay", def.getRedeliveryDelay(), "1000"); + doWriteAttribute(sb, "redeliveryDelay", def.getRedeliveryDelay(), null); doWriteAttribute(sb, "asyncDelayedRedelivery", def.getAsyncDelayedRedelivery(), null); - doWriteAttribute(sb, "backOffMultiplier", def.getBackOffMultiplier(), "2.0"); + doWriteAttribute(sb, "backOffMultiplier", def.getBackOffMultiplier(), null); doWriteAttribute(sb, "useExponentialBackOff", def.getUseExponentialBackOff(), null); - doWriteAttribute(sb, "collisionAvoidanceFactor", def.getCollisionAvoidanceFactor(), "0.15"); + doWriteAttribute(sb, "collisionAvoidanceFactor", def.getCollisionAvoidanceFactor(), null); doWriteAttribute(sb, "useCollisionAvoidance", def.getUseCollisionAvoidance(), null); - doWriteAttribute(sb, "maximumRedeliveryDelay", def.getMaximumRedeliveryDelay(), "60000"); - doWriteAttribute(sb, "retriesExhaustedLogLevel", def.getRetriesExhaustedLogLevel(), "ERROR"); - doWriteAttribute(sb, "retryAttemptedLogLevel", def.getRetryAttemptedLogLevel(), "DEBUG"); - doWriteAttribute(sb, "retryAttemptedLogInterval", def.getRetryAttemptedLogInterval(), "1"); - doWriteAttribute(sb, "logRetryAttempted", def.getLogRetryAttempted(), "true"); - doWriteAttribute(sb, "logStackTrace", def.getLogStackTrace(), "true"); + doWriteAttribute(sb, "maximumRedeliveryDelay", def.getMaximumRedeliveryDelay(), null); + doWriteAttribute(sb, "retriesExhaustedLogLevel", def.getRetriesExhaustedLogLevel(), null); + doWriteAttribute(sb, "retryAttemptedLogLevel", def.getRetryAttemptedLogLevel(), null); + doWriteAttribute(sb, "retryAttemptedLogInterval", def.getRetryAttemptedLogInterval(), null); + doWriteAttribute(sb, "logRetryAttempted", def.getLogRetryAttempted(), null); + doWriteAttribute(sb, "logStackTrace", def.getLogStackTrace(), null); doWriteAttribute(sb, "logRetryStackTrace", def.getLogRetryStackTrace(), null); doWriteAttribute(sb, "logHandled", def.getLogHandled(), null); - doWriteAttribute(sb, "logNewException", def.getLogNewException(), "true"); + doWriteAttribute(sb, "logNewException", def.getLogNewException(), null); doWriteAttribute(sb, "logContinued", def.getLogContinued(), null); - doWriteAttribute(sb, "logExhausted", def.getLogExhausted(), "true"); + doWriteAttribute(sb, "logExhausted", def.getLogExhausted(), null); doWriteAttribute(sb, "logExhaustedMessageHistory", def.getLogExhaustedMessageHistory(), null); doWriteAttribute(sb, "logExhaustedMessageBody", def.getLogExhaustedMessageBody(), null); doWriteAttribute(sb, "disableRedelivery", def.getDisableRedelivery(), null); doWriteAttribute(sb, "delayPattern", def.getDelayPattern(), null); - doWriteAttribute(sb, "allowRedeliveryWhileStopping", def.getAllowRedeliveryWhileStopping(), "true"); + doWriteAttribute(sb, "allowRedeliveryWhileStopping", def.getAllowRedeliveryWhileStopping(), null); doWriteAttribute(sb, "exchangeFormatterRef", def.getExchangeFormatterRef(), null); } protected void doWriteRemoveHeaderDefinition(StringBuilder sb, RemoveHeaderDefinition def) { @@ -2368,29 +2368,29 @@ protected void doWriteResilience4jConfigurationCommonAttributes(StringBuilder sb doWriteIdentifiedTypeAttributes(sb, def); doWriteAttribute(sb, "circuitBreaker", def.getCircuitBreaker(), null); doWriteAttribute(sb, "config", def.getConfig(), null); - doWriteAttribute(sb, "failureRateThreshold", def.getFailureRateThreshold(), "50"); - doWriteAttribute(sb, "permittedNumberOfCallsInHalfOpenState", def.getPermittedNumberOfCallsInHalfOpenState(), "10"); - doWriteAttribute(sb, "throwExceptionWhenHalfOpenOrOpenState", def.getThrowExceptionWhenHalfOpenOrOpenState(), "false"); - doWriteAttribute(sb, "slidingWindowSize", def.getSlidingWindowSize(), "100"); - doWriteAttribute(sb, "slidingWindowType", def.getSlidingWindowType(), "COUNT_BASED"); - doWriteAttribute(sb, "slidingWindowSynchronizationStrategy", def.getSlidingWindowSynchronizationStrategy(), "SYNCHRONIZED"); - doWriteAttribute(sb, "minimumNumberOfCalls", def.getMinimumNumberOfCalls(), "100"); - doWriteAttribute(sb, "writableStackTraceEnabled", def.getWritableStackTraceEnabled(), "true"); - doWriteAttribute(sb, "waitDurationInOpenState", def.getWaitDurationInOpenState(), "60000"); - doWriteAttribute(sb, "automaticTransitionFromOpenToHalfOpenEnabled", def.getAutomaticTransitionFromOpenToHalfOpenEnabled(), "false"); - doWriteAttribute(sb, "maxWaitDurationInHalfOpenState", def.getMaxWaitDurationInHalfOpenState(), "0"); - doWriteAttribute(sb, "slowCallRateThreshold", def.getSlowCallRateThreshold(), "100"); - doWriteAttribute(sb, "slowCallDurationThreshold", def.getSlowCallDurationThreshold(), "60000"); - doWriteAttribute(sb, "bulkheadEnabled", def.getBulkheadEnabled(), "false"); - doWriteAttribute(sb, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), "25"); - doWriteAttribute(sb, "bulkheadMaxWaitDuration", def.getBulkheadMaxWaitDuration(), "0"); - doWriteAttribute(sb, "bulkheadFairCallHandlingEnabled", def.getBulkheadFairCallHandlingEnabled(), "true"); - doWriteAttribute(sb, "asynchronous", def.getAsynchronous(), "false"); - doWriteAttribute(sb, "timeoutEnabled", def.getTimeoutEnabled(), "false"); + doWriteAttribute(sb, "failureRateThreshold", def.getFailureRateThreshold(), null); + doWriteAttribute(sb, "permittedNumberOfCallsInHalfOpenState", def.getPermittedNumberOfCallsInHalfOpenState(), null); + doWriteAttribute(sb, "throwExceptionWhenHalfOpenOrOpenState", def.getThrowExceptionWhenHalfOpenOrOpenState(), null); + doWriteAttribute(sb, "slidingWindowSize", def.getSlidingWindowSize(), null); + doWriteAttribute(sb, "slidingWindowType", def.getSlidingWindowType(), null); + doWriteAttribute(sb, "slidingWindowSynchronizationStrategy", def.getSlidingWindowSynchronizationStrategy(), null); + doWriteAttribute(sb, "minimumNumberOfCalls", def.getMinimumNumberOfCalls(), null); + doWriteAttribute(sb, "writableStackTraceEnabled", def.getWritableStackTraceEnabled(), null); + doWriteAttribute(sb, "waitDurationInOpenState", def.getWaitDurationInOpenState(), null); + doWriteAttribute(sb, "automaticTransitionFromOpenToHalfOpenEnabled", def.getAutomaticTransitionFromOpenToHalfOpenEnabled(), null); + doWriteAttribute(sb, "maxWaitDurationInHalfOpenState", def.getMaxWaitDurationInHalfOpenState(), null); + doWriteAttribute(sb, "slowCallRateThreshold", def.getSlowCallRateThreshold(), null); + doWriteAttribute(sb, "slowCallDurationThreshold", def.getSlowCallDurationThreshold(), null); + doWriteAttribute(sb, "bulkheadEnabled", def.getBulkheadEnabled(), null); + doWriteAttribute(sb, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), null); + doWriteAttribute(sb, "bulkheadMaxWaitDuration", def.getBulkheadMaxWaitDuration(), null); + doWriteAttribute(sb, "bulkheadFairCallHandlingEnabled", def.getBulkheadFairCallHandlingEnabled(), null); + doWriteAttribute(sb, "asynchronous", def.getAsynchronous(), null); + doWriteAttribute(sb, "timeoutEnabled", def.getTimeoutEnabled(), null); doWriteAttribute(sb, "timeoutExecutorService", def.getTimeoutExecutorService(), null); - doWriteAttribute(sb, "timeoutDuration", def.getTimeoutDuration(), "1000"); - doWriteAttribute(sb, "timeoutCancelRunningFuture", def.getTimeoutCancelRunningFuture(), "true"); - doWriteAttribute(sb, "micrometerEnabled", def.getMicrometerEnabled(), "false"); + doWriteAttribute(sb, "timeoutDuration", def.getTimeoutDuration(), null); + doWriteAttribute(sb, "timeoutCancelRunningFuture", def.getTimeoutCancelRunningFuture(), null); + doWriteAttribute(sb, "micrometerEnabled", def.getMicrometerEnabled(), null); } protected void doWriteResilience4jConfigurationCommonElements(StringBuilder sb, Resilience4jConfigurationCommon def) { doWriteStringList(sb, null, "ignoreException", def.getIgnoreExceptions()); @@ -2457,8 +2457,8 @@ protected void doWriteRouteDefinition(StringBuilder sb, RouteDefinition def) { doWriteAttribute(sb, "delayer", def.getDelayer(), null); doWriteAttribute(sb, "errorHandlerRef", def.getErrorHandlerRef(), null); doWriteAttribute(sb, "routePolicyRef", def.getRoutePolicyRef(), null); - doWriteAttribute(sb, "shutdownRoute", def.getShutdownRoute(), "Default"); - doWriteAttribute(sb, "shutdownRunningTask", def.getShutdownRunningTask(), "CompleteCurrentTaskOnly"); + doWriteAttribute(sb, "shutdownRoute", def.getShutdownRoute(), null); + doWriteAttribute(sb, "shutdownRunningTask", def.getShutdownRunningTask(), null); doWriteAttribute(sb, "precondition", def.getPrecondition(), null); doWriteChildList(sb, "routeProperty", def.getRouteProperties(), this::doWritePropertyDefinition); doWriteChildElement(sb, "errorHandler", def.getErrorHandler(), this::doWriteErrorHandlerDefinition); @@ -3825,14 +3825,14 @@ protected void doWriteRestBindingDefinition(StringBuilder sb, RestBindingDefinit doWriteOptionalIdentifiedDefinitionAttributes(sb, def); doWriteAttribute(sb, "consumes", def.getConsumes(), null); doWriteAttribute(sb, "produces", def.getProduces(), null); - doWriteAttribute(sb, "bindingMode", def.getBindingMode(), "off"); + doWriteAttribute(sb, "bindingMode", def.getBindingMode(), null); doWriteAttribute(sb, "type", def.getType(), null); doWriteAttribute(sb, "outType", def.getOutType(), null); - doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), null); + doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute(sb, "component", def.getComponent(), null); } protected void doWriteRestConfigurationDefinition(StringBuilder sb, RestConfigurationDefinition def) { @@ -3850,13 +3850,13 @@ protected void doWriteRestConfigurationDefinition(StringBuilder sb, RestConfigur doWriteAttribute(sb, "apiContextRouteId", def.getApiContextRouteId(), null); doWriteAttribute(sb, "apiVendorExtension", def.getApiVendorExtension(), "false"); doWriteAttribute(sb, "hostNameResolver", toString(def.getHostNameResolver()), "allLocalIp"); - doWriteAttribute(sb, "bindingMode", toString(def.getBindingMode()), "off"); + doWriteAttribute(sb, "bindingMode", toString(def.getBindingMode()), null); doWriteAttribute(sb, "bindingPackageScan", def.getBindingPackageScan(), null); - doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), null); + doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute(sb, "inlineRoutes", def.getInlineRoutes(), "true"); doWriteAttribute(sb, "jsonDataFormat", def.getJsonDataFormat(), "jackson"); doWriteAttribute(sb, "xmlDataFormat", def.getXmlDataFormat(), "jaxb"); @@ -3874,12 +3874,12 @@ protected void doWriteRestDefinition(StringBuilder sb, RestDefinition def) { doWriteAttribute(sb, "path", def.getPath(), null); doWriteAttribute(sb, "consumes", def.getConsumes(), null); doWriteAttribute(sb, "produces", def.getProduces(), null); - doWriteAttribute(sb, "bindingMode", def.getBindingMode(), "off"); - doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute(sb, "bindingMode", def.getBindingMode(), null); + doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), null); + doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute(sb, "apiDocs", def.getApiDocs(), "true"); doWriteAttribute(sb, "tag", def.getTag(), null); doWriteChildList(sb, "verbs", def.getVerbs(), this::doWriteVerbDefinitionRef); @@ -3922,19 +3922,19 @@ protected void doWriteSecurityDefinition(StringBuilder sb, SecurityDefinition de } protected void doWriteVerbDefinitionAttributes(StringBuilder sb, VerbDefinition def) { doWriteOptionalIdentifiedDefinitionAttributes(sb, def); - doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), "false"); + doWriteAttribute(sb, "enableCORS", def.getEnableCORS(), null); doWriteAttribute(sb, "deprecated", def.getDeprecated(), "false"); doWriteAttribute(sb, "streamCache", def.getStreamCache(), null); doWriteAttribute(sb, "type", def.getType(), null); doWriteAttribute(sb, "outType", def.getOutType(), null); - doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), "false"); + doWriteAttribute(sb, "clientResponseValidation", def.getClientResponseValidation(), null); doWriteAttribute(sb, "path", def.getPath(), null); doWriteAttribute(sb, "routeId", def.getRouteId(), null); - doWriteAttribute(sb, "bindingMode", def.getBindingMode(), "off"); + doWriteAttribute(sb, "bindingMode", def.getBindingMode(), null); doWriteAttribute(sb, "apiDocs", def.getApiDocs(), "true"); - doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); - doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), "false"); + doWriteAttribute(sb, "enableNoContentResponse", def.getEnableNoContentResponse(), null); + doWriteAttribute(sb, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(sb, "clientRequestValidation", def.getClientRequestValidation(), null); doWriteAttribute(sb, "produces", def.getProduces(), null); doWriteAttribute(sb, "disabled", def.getDisabled(), null); doWriteAttribute(sb, "consumes", def.getConsumes(), null); diff --git a/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java b/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java index 75c4eef868162..a31ffe45207eb 100644 --- a/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java +++ b/core/camel-java-io/src/main/java/org/apache/camel/java/LwModelToJavaDumper.java @@ -20,16 +20,12 @@ import java.util.Collection; import java.util.List; import java.util.Properties; -import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.NamedNode; import org.apache.camel.builder.EndpointConsumerBuilder; import org.apache.camel.builder.EndpointProducerBuilder; -import org.apache.camel.model.FaultToleranceConfigurationCommon; import org.apache.camel.model.FromDefinition; -import org.apache.camel.model.RedeliveryPolicyDefinition; -import org.apache.camel.model.Resilience4jConfigurationCommon; import org.apache.camel.model.RouteConfigurationDefinition; import org.apache.camel.model.RouteConfigurationsDefinition; import org.apache.camel.model.RouteDefinition; @@ -56,17 +52,6 @@ @JdkService(ModelToJavaDumper.FACTORY) public class LwModelToJavaDumper implements ModelToJavaDumper { - /** - * Options that are written whenever they are set, even with their documented default value: when they are not set, - * they take the value of a parent level (the rest or the rest configuration for a rest or verb, the CamelContext - * for a route), and a dump that leaves them out runs differently once loaded. The same holds for every option of a - * redelivery policy (an onException copies the error handler's policy) and of a circuit breaker configuration (it - * is layered over the global one). - */ - static final Set INHERITED_OPTIONS = Set.of( - "bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", "clientResponseValidation", "enableCORS", - "enableNoContentResponse", "shutdownRoute", "shutdownRunningTask"); - @Override public String dumpModelAsJava(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsJava(context, definition, false, true); @@ -174,62 +159,19 @@ public String dumpModelAsJava( } private static org.apache.camel.java.out.JavaDslModelWriter createWriter(boolean sourceLocation) { + if (!sourceLocation) { + return new org.apache.camel.java.out.JavaDslModelWriter(); + } return new org.apache.camel.java.out.JavaDslModelWriter() { - // the options of a redelivery policy or a circuit breaker configuration that are written as set, see - // INHERITED_OPTIONS - private boolean keepDefault; - @Override protected void doWriteOptionalIdentifiedDefinitionAttributes( StringBuilder sb, org.apache.camel.model.OptionalIdentifiedDefinition def) { - if (sourceLocation) { - int line = (def instanceof RouteDefinition rd ? rd.getInput() : def).getLineNumber(); - if (line != -1) { - sb.append("\n// sourceLineNumber: ").append(line); - } + int line = (def instanceof RouteDefinition rd ? rd.getInput() : def).getLineNumber(); + if (line != -1) { + sb.append("\n// sourceLineNumber: ").append(line); } super.doWriteOptionalIdentifiedDefinitionAttributes(sb, def); } - - @Override - protected void doWriteAttribute(StringBuilder sb, String key, String value, String defaultValue) { - super.doWriteAttribute(sb, key, value, keepDefault || INHERITED_OPTIONS.contains(key) ? null : defaultValue); - } - - @Override - protected void doWriteRedeliveryPolicyDefinition(StringBuilder sb, RedeliveryPolicyDefinition def) { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteRedeliveryPolicyDefinition(sb, def); - } finally { - keepDefault = keep; - } - } - - @Override - protected void doWriteResilience4jConfigurationCommonAttributes( - StringBuilder sb, Resilience4jConfigurationCommon def) { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteResilience4jConfigurationCommonAttributes(sb, def); - } finally { - keepDefault = keep; - } - } - - @Override - protected void doWriteFaultToleranceConfigurationCommonAttributes( - StringBuilder sb, FaultToleranceConfigurationCommon def) { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteFaultToleranceConfigurationCommonAttributes(sb, def); - } finally { - keepDefault = keep; - } - } }; } diff --git a/core/camel-java-io/src/test/java/org/apache/camel/java/JavaDumpExplicitDefaultValuesTest.java b/core/camel-java-io/src/test/java/org/apache/camel/java/JavaDumpExplicitDefaultValuesTest.java new file mode 100644 index 0000000000000..63bd7ccecf440 --- /dev/null +++ b/core/camel-java-io/src/test/java/org/apache/camel/java/JavaDumpExplicitDefaultValuesTest.java @@ -0,0 +1,161 @@ +/* + * 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.java; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.tools.DiagnosticCollector; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +import org.apache.camel.CamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.errorhandler.DefaultErrorHandlerDefinition; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A route that sets an option to its documented default value, where "not set" means "the value of a parent level", + * runs the same once its Java dump is compiled and loaded again. + */ +public class JavaDumpExplicitDefaultValuesTest { + + private static final String CLASS_NAME = "test.generated.DumpedRoutes"; + + @Test + public void onExceptionRedeliveryDelayUnderErrorHandler() throws Exception { + // the onException redelivers after 1000 ms (the documented default), the error handler after 30 s: an option of + // the onException that is not set takes the value of the error handler + RouteBuilder routes = new RouteBuilder() { + @Override + public void configure() { + from("direct:start").routeId("redelivery") + .onException(IllegalStateException.class).maximumRedeliveries(1).redeliveryDelay(1000) + .handled(true).setBody(constant("handled")).end() + .throwException(IllegalStateException.class, "Forced"); + } + }; + + assertThat(run(routes)).isEqualTo("handled"); + + String dumped; + try (DefaultCamelContext context = createContext()) { + context.addRoutes(routes); + dumped = new LwModelToJavaDumper().dumpModelAsJava(context, context.getRouteDefinition("redelivery")); + } + assertThat(run(load(dumped))).as(dumped).isEqualTo("handled"); + } + + private static DefaultCamelContext createContext() { + DefaultCamelContext context = new DefaultCamelContext(); + context.getCamelContextExtension().setErrorHandlerFactory( + new DefaultErrorHandlerDefinition().maximumRedeliveries(3).redeliveryDelay(30000)); + context.getShutdownStrategy().setTimeout(1); + return context; + } + + private static String run(RoutesBuilder routes) throws Exception { + try (DefaultCamelContext context = createContext()) { + context.addRoutes(routes); + context.start(); + return call(context); + } + } + + private static String call(CamelContext context) { + try { + return context.createProducerTemplate().asyncRequestBody("direct:start", "x", String.class) + .get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + return "not redelivered within 10 seconds"; + } catch (Exception e) { + return e.toString(); + } + } + + /** Compiles the dump in a RouteBuilder and creates an instance of it. */ + private static RoutesBuilder load(String dump) throws Exception { + String source = "package test.generated;\n\n" + + "import org.apache.camel.ExchangePattern;\n" + + "import org.apache.camel.LoggingLevel;\n" + + "import org.apache.camel.Predicate;\n" + + "import org.apache.camel.builder.RouteBuilder;\n\n" + + "public class DumpedRoutes extends RouteBuilder {\n" + + " @Override\n" + + " public void configure() throws Exception {\n" + + dump + "\n" + + " }\n" + + "}\n"; + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + assertThat(compiler).as("a JDK is needed to compile the dump").isNotNull(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + Map classes = new HashMap<>(); + JavaFileManager fileManager + = new ForwardingJavaFileManager<>(compiler.getStandardFileManager(diagnostics, null, null)) { + @Override + public JavaFileObject getJavaFileForOutput( + Location location, String name, JavaFileObject.Kind kind, FileObject sibling) { + return new SimpleJavaFileObject(URI.create("mem:///" + name.replace('.', '/') + kind.extension), kind) { + @Override + public OutputStream openOutputStream() { + return classes.computeIfAbsent(name, n -> new ByteArrayOutputStream()); + } + }; + } + }; + JavaFileObject file = new SimpleJavaFileObject( + URI.create("string:///" + CLASS_NAME.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension), + JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + }; + boolean compiled = compiler.getTask(null, fileManager, diagnostics, + List.of("-proc:none", "-classpath", System.getProperty("java.class.path")), null, List.of(file)).call(); + assertThat(compiled).as(diagnostics.getDiagnostics() + "\n" + source).isTrue(); + + ClassLoader loader = new ClassLoader(JavaDumpExplicitDefaultValuesTest.class.getClassLoader()) { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + ByteArrayOutputStream bytes = classes.get(name); + if (bytes == null) { + throw new ClassNotFoundException(name); + } + byte[] b = bytes.toByteArray(); + return defineClass(name, b, 0, b.length); + } + }; + return (RoutesBuilder) loader.loadClass(CLASS_NAME).getDeclaredConstructor().newInstance(); + } +} diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java index 824c2a5978089..d53218b156d70 100644 --- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java +++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java @@ -1035,15 +1035,15 @@ protected void doWriteExpressionSubElementDefinition(String name, ExpressionSubE protected void doWriteFaultToleranceConfigurationCommonAttributes(FaultToleranceConfigurationCommon def) throws IOException { doWriteIdentifiedTypeAttributes(def); doWriteAttribute("typedGuard", def.getTypedGuard(), null); - doWriteAttribute("delay", def.getDelay(), "5000"); - doWriteAttribute("successThreshold", def.getSuccessThreshold(), "1"); - doWriteAttribute("requestVolumeThreshold", def.getRequestVolumeThreshold(), "20"); - doWriteAttribute("failureRatio", def.getFailureRatio(), "50"); - doWriteAttribute("timeoutEnabled", def.getTimeoutEnabled(), "false"); - doWriteAttribute("timeoutDuration", def.getTimeoutDuration(), "1000"); - doWriteAttribute("bulkheadEnabled", def.getBulkheadEnabled(), "false"); - doWriteAttribute("bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), "10"); - doWriteAttribute("bulkheadWaitingTaskQueue", def.getBulkheadWaitingTaskQueue(), "10"); + doWriteAttribute("delay", def.getDelay(), null); + doWriteAttribute("successThreshold", def.getSuccessThreshold(), null); + doWriteAttribute("requestVolumeThreshold", def.getRequestVolumeThreshold(), null); + doWriteAttribute("failureRatio", def.getFailureRatio(), null); + doWriteAttribute("timeoutEnabled", def.getTimeoutEnabled(), null); + doWriteAttribute("timeoutDuration", def.getTimeoutDuration(), null); + doWriteAttribute("bulkheadEnabled", def.getBulkheadEnabled(), null); + doWriteAttribute("bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), null); + doWriteAttribute("bulkheadWaitingTaskQueue", def.getBulkheadWaitingTaskQueue(), null); doWriteAttribute("threadOffloadExecutorService", def.getThreadOffloadExecutorService(), null); } protected void doWriteFaultToleranceConfigurationCommon(String name, FaultToleranceConfigurationCommon def) throws IOException { @@ -1467,28 +1467,28 @@ protected void doWriteRedeliveryPolicyDefinition(String name, RedeliveryPolicyDe startElement(name); doWriteIdentifiedTypeAttributes(def); doWriteAttribute("maximumRedeliveries", def.getMaximumRedeliveries(), null); - doWriteAttribute("redeliveryDelay", def.getRedeliveryDelay(), "1000"); + doWriteAttribute("redeliveryDelay", def.getRedeliveryDelay(), null); doWriteAttribute("asyncDelayedRedelivery", def.getAsyncDelayedRedelivery(), null); - doWriteAttribute("backOffMultiplier", def.getBackOffMultiplier(), "2.0"); + doWriteAttribute("backOffMultiplier", def.getBackOffMultiplier(), null); doWriteAttribute("useExponentialBackOff", def.getUseExponentialBackOff(), null); - doWriteAttribute("collisionAvoidanceFactor", def.getCollisionAvoidanceFactor(), "0.15"); + doWriteAttribute("collisionAvoidanceFactor", def.getCollisionAvoidanceFactor(), null); doWriteAttribute("useCollisionAvoidance", def.getUseCollisionAvoidance(), null); - doWriteAttribute("maximumRedeliveryDelay", def.getMaximumRedeliveryDelay(), "60000"); - doWriteAttribute("retriesExhaustedLogLevel", def.getRetriesExhaustedLogLevel(), "ERROR"); - doWriteAttribute("retryAttemptedLogLevel", def.getRetryAttemptedLogLevel(), "DEBUG"); - doWriteAttribute("retryAttemptedLogInterval", def.getRetryAttemptedLogInterval(), "1"); - doWriteAttribute("logRetryAttempted", def.getLogRetryAttempted(), "true"); - doWriteAttribute("logStackTrace", def.getLogStackTrace(), "true"); + doWriteAttribute("maximumRedeliveryDelay", def.getMaximumRedeliveryDelay(), null); + doWriteAttribute("retriesExhaustedLogLevel", def.getRetriesExhaustedLogLevel(), null); + doWriteAttribute("retryAttemptedLogLevel", def.getRetryAttemptedLogLevel(), null); + doWriteAttribute("retryAttemptedLogInterval", def.getRetryAttemptedLogInterval(), null); + doWriteAttribute("logRetryAttempted", def.getLogRetryAttempted(), null); + doWriteAttribute("logStackTrace", def.getLogStackTrace(), null); doWriteAttribute("logRetryStackTrace", def.getLogRetryStackTrace(), null); doWriteAttribute("logHandled", def.getLogHandled(), null); - doWriteAttribute("logNewException", def.getLogNewException(), "true"); + doWriteAttribute("logNewException", def.getLogNewException(), null); doWriteAttribute("logContinued", def.getLogContinued(), null); - doWriteAttribute("logExhausted", def.getLogExhausted(), "true"); + doWriteAttribute("logExhausted", def.getLogExhausted(), null); doWriteAttribute("logExhaustedMessageHistory", def.getLogExhaustedMessageHistory(), null); doWriteAttribute("logExhaustedMessageBody", def.getLogExhaustedMessageBody(), null); doWriteAttribute("disableRedelivery", def.getDisableRedelivery(), null); doWriteAttribute("delayPattern", def.getDelayPattern(), null); - doWriteAttribute("allowRedeliveryWhileStopping", def.getAllowRedeliveryWhileStopping(), "true"); + doWriteAttribute("allowRedeliveryWhileStopping", def.getAllowRedeliveryWhileStopping(), null); doWriteAttribute("exchangeFormatterRef", def.getExchangeFormatterRef(), null); endElement(name); } @@ -1541,29 +1541,29 @@ protected void doWriteResilience4jConfigurationCommonAttributes(Resilience4jConf doWriteIdentifiedTypeAttributes(def); doWriteAttribute("circuitBreaker", def.getCircuitBreaker(), null); doWriteAttribute("config", def.getConfig(), null); - doWriteAttribute("failureRateThreshold", def.getFailureRateThreshold(), "50"); - doWriteAttribute("permittedNumberOfCallsInHalfOpenState", def.getPermittedNumberOfCallsInHalfOpenState(), "10"); - doWriteAttribute("throwExceptionWhenHalfOpenOrOpenState", def.getThrowExceptionWhenHalfOpenOrOpenState(), "false"); - doWriteAttribute("slidingWindowSize", def.getSlidingWindowSize(), "100"); - doWriteAttribute("slidingWindowType", def.getSlidingWindowType(), "COUNT_BASED"); - doWriteAttribute("slidingWindowSynchronizationStrategy", def.getSlidingWindowSynchronizationStrategy(), "SYNCHRONIZED"); - doWriteAttribute("minimumNumberOfCalls", def.getMinimumNumberOfCalls(), "100"); - doWriteAttribute("writableStackTraceEnabled", def.getWritableStackTraceEnabled(), "true"); - doWriteAttribute("waitDurationInOpenState", def.getWaitDurationInOpenState(), "60000"); - doWriteAttribute("automaticTransitionFromOpenToHalfOpenEnabled", def.getAutomaticTransitionFromOpenToHalfOpenEnabled(), "false"); - doWriteAttribute("maxWaitDurationInHalfOpenState", def.getMaxWaitDurationInHalfOpenState(), "0"); - doWriteAttribute("slowCallRateThreshold", def.getSlowCallRateThreshold(), "100"); - doWriteAttribute("slowCallDurationThreshold", def.getSlowCallDurationThreshold(), "60000"); - doWriteAttribute("bulkheadEnabled", def.getBulkheadEnabled(), "false"); - doWriteAttribute("bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), "25"); - doWriteAttribute("bulkheadMaxWaitDuration", def.getBulkheadMaxWaitDuration(), "0"); - doWriteAttribute("bulkheadFairCallHandlingEnabled", def.getBulkheadFairCallHandlingEnabled(), "true"); - doWriteAttribute("asynchronous", def.getAsynchronous(), "false"); - doWriteAttribute("timeoutEnabled", def.getTimeoutEnabled(), "false"); + doWriteAttribute("failureRateThreshold", def.getFailureRateThreshold(), null); + doWriteAttribute("permittedNumberOfCallsInHalfOpenState", def.getPermittedNumberOfCallsInHalfOpenState(), null); + doWriteAttribute("throwExceptionWhenHalfOpenOrOpenState", def.getThrowExceptionWhenHalfOpenOrOpenState(), null); + doWriteAttribute("slidingWindowSize", def.getSlidingWindowSize(), null); + doWriteAttribute("slidingWindowType", def.getSlidingWindowType(), null); + doWriteAttribute("slidingWindowSynchronizationStrategy", def.getSlidingWindowSynchronizationStrategy(), null); + doWriteAttribute("minimumNumberOfCalls", def.getMinimumNumberOfCalls(), null); + doWriteAttribute("writableStackTraceEnabled", def.getWritableStackTraceEnabled(), null); + doWriteAttribute("waitDurationInOpenState", def.getWaitDurationInOpenState(), null); + doWriteAttribute("automaticTransitionFromOpenToHalfOpenEnabled", def.getAutomaticTransitionFromOpenToHalfOpenEnabled(), null); + doWriteAttribute("maxWaitDurationInHalfOpenState", def.getMaxWaitDurationInHalfOpenState(), null); + doWriteAttribute("slowCallRateThreshold", def.getSlowCallRateThreshold(), null); + doWriteAttribute("slowCallDurationThreshold", def.getSlowCallDurationThreshold(), null); + doWriteAttribute("bulkheadEnabled", def.getBulkheadEnabled(), null); + doWriteAttribute("bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), null); + doWriteAttribute("bulkheadMaxWaitDuration", def.getBulkheadMaxWaitDuration(), null); + doWriteAttribute("bulkheadFairCallHandlingEnabled", def.getBulkheadFairCallHandlingEnabled(), null); + doWriteAttribute("asynchronous", def.getAsynchronous(), null); + doWriteAttribute("timeoutEnabled", def.getTimeoutEnabled(), null); doWriteAttribute("timeoutExecutorService", def.getTimeoutExecutorService(), null); - doWriteAttribute("timeoutDuration", def.getTimeoutDuration(), "1000"); - doWriteAttribute("timeoutCancelRunningFuture", def.getTimeoutCancelRunningFuture(), "true"); - doWriteAttribute("micrometerEnabled", def.getMicrometerEnabled(), "false"); + doWriteAttribute("timeoutDuration", def.getTimeoutDuration(), null); + doWriteAttribute("timeoutCancelRunningFuture", def.getTimeoutCancelRunningFuture(), null); + doWriteAttribute("micrometerEnabled", def.getMicrometerEnabled(), null); } protected void doWriteResilience4jConfigurationCommonElements(Resilience4jConfigurationCommon def) throws IOException { doWriteList(null, "ignoreException", def.getIgnoreExceptions(), this::doWriteString); @@ -1651,8 +1651,8 @@ protected void doWriteRouteDefinition(String name, RouteDefinition def) throws I doWriteAttribute("delayer", def.getDelayer(), null); doWriteAttribute("errorHandlerRef", def.getErrorHandlerRef(), null); doWriteAttribute("routePolicyRef", def.getRoutePolicyRef(), null); - doWriteAttribute("shutdownRoute", def.getShutdownRoute(), "Default"); - doWriteAttribute("shutdownRunningTask", def.getShutdownRunningTask(), "CompleteCurrentTaskOnly"); + doWriteAttribute("shutdownRoute", def.getShutdownRoute(), null); + doWriteAttribute("shutdownRunningTask", def.getShutdownRunningTask(), null); doWriteAttribute("precondition", def.getPrecondition(), null); doWriteList(null, "routeProperty", def.getRouteProperties(), this::doWritePropertyDefinition); doWriteElement("errorHandler", def.getErrorHandler(), this::doWriteErrorHandlerDefinition); @@ -3357,14 +3357,14 @@ protected void doWriteRestBindingDefinition(String name, RestBindingDefinition d doWriteOptionalIdentifiedDefinitionAttributes(def); doWriteAttribute("consumes", def.getConsumes(), null); doWriteAttribute("produces", def.getProduces(), null); - doWriteAttribute("bindingMode", def.getBindingMode(), "off"); + doWriteAttribute("bindingMode", def.getBindingMode(), null); doWriteAttribute("type", def.getType(), null); doWriteAttribute("outType", def.getOutType(), null); - doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute("enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute("enableCORS", def.getEnableCORS(), null); + doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute("component", def.getComponent(), null); endElement(name); } @@ -3384,13 +3384,13 @@ protected void doWriteRestConfigurationDefinition(String name, RestConfiguration doWriteAttribute("apiContextRouteId", def.getApiContextRouteId(), null); doWriteAttribute("apiVendorExtension", def.getApiVendorExtension(), "false"); doWriteAttribute("hostNameResolver", toString(def.getHostNameResolver()), "allLocalIp"); - doWriteAttribute("bindingMode", toString(def.getBindingMode()), "off"); + doWriteAttribute("bindingMode", toString(def.getBindingMode()), null); doWriteAttribute("bindingPackageScan", def.getBindingPackageScan(), null); - doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute("enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute("enableCORS", def.getEnableCORS(), null); + doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute("inlineRoutes", def.getInlineRoutes(), "true"); doWriteAttribute("jsonDataFormat", def.getJsonDataFormat(), "jackson"); doWriteAttribute("xmlDataFormat", def.getXmlDataFormat(), "jaxb"); @@ -3410,12 +3410,12 @@ protected void doWriteRestDefinition(String name, RestDefinition def) throws IOE doWriteAttribute("path", def.getPath(), null); doWriteAttribute("consumes", def.getConsumes(), null); doWriteAttribute("produces", def.getProduces(), null); - doWriteAttribute("bindingMode", def.getBindingMode(), "off"); - doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute("enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute("bindingMode", def.getBindingMode(), null); + doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute("enableCORS", def.getEnableCORS(), null); + doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute("apiDocs", def.getApiDocs(), "true"); doWriteAttribute("tag", def.getTag(), null); doWriteList(null, null, def.getVerbs(), this::doWriteVerbDefinitionRef); @@ -3467,19 +3467,19 @@ protected void doWriteSecurityDefinition(String name, SecurityDefinition def) th } protected void doWriteVerbDefinitionAttributes(VerbDefinition def) throws IOException { doWriteOptionalIdentifiedDefinitionAttributes(def); - doWriteAttribute("enableCORS", def.getEnableCORS(), "false"); + doWriteAttribute("enableCORS", def.getEnableCORS(), null); doWriteAttribute("deprecated", def.getDeprecated(), "false"); doWriteAttribute("streamCache", def.getStreamCache(), null); doWriteAttribute("type", def.getType(), null); doWriteAttribute("outType", def.getOutType(), null); - doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), "false"); + doWriteAttribute("clientResponseValidation", def.getClientResponseValidation(), null); doWriteAttribute("path", def.getPath(), null); doWriteAttribute("routeId", def.getRouteId(), null); - doWriteAttribute("bindingMode", def.getBindingMode(), "off"); + doWriteAttribute("bindingMode", def.getBindingMode(), null); doWriteAttribute("apiDocs", def.getApiDocs(), "true"); - doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), "false"); - doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), "false"); + doWriteAttribute("enableNoContentResponse", def.getEnableNoContentResponse(), null); + doWriteAttribute("skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute("clientRequestValidation", def.getClientRequestValidation(), null); doWriteAttribute("produces", def.getProduces(), null); doWriteAttribute("disabled", def.getDisabled(), null); doWriteAttribute("consumes", def.getConsumes(), null); diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index 1cfaa61ee9067..915e4cceb4a1d 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.function.Consumer; import org.apache.camel.CamelContext; @@ -38,11 +37,8 @@ import org.apache.camel.model.BeanFactoryDefinition; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.model.ExpressionNode; -import org.apache.camel.model.FaultToleranceConfigurationCommon; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.OptionalIdentifiedDefinition; -import org.apache.camel.model.RedeliveryPolicyDefinition; -import org.apache.camel.model.Resilience4jConfigurationCommon; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.RouteTemplateDefinition; import org.apache.camel.model.RouteTemplatesDefinition; @@ -69,17 +65,6 @@ @JdkService(ModelToXMLDumper.FACTORY) public class LwModelToXMLDumper implements ModelToXMLDumper { - /** - * Options that are written whenever they are set, even with their documented default value: when they are not set, - * they take the value of a parent level (the rest or the rest configuration for a rest or verb, the CamelContext - * for a route), and a dump that leaves them out runs differently once loaded. The same holds for every option of a - * redelivery policy (an onException copies the error handler's policy) and of a circuit breaker configuration (it - * is layered over the global one). - */ - static final Set INHERITED_OPTIONS = Set.of( - "bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", "clientResponseValidation", "enableCORS", - "enableNoContentResponse", "shutdownRoute", "shutdownRunningTask"); - @Override public String dumpModelAsXml(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsXml(context, definition, false, true, false); @@ -111,10 +96,6 @@ public String dumpModelAsXml( StringWriter buffer = new StringWriter(); ModelWriter writer = new ModelWriter(buffer, BaseWriter.DEFAULT_NAMESPACE) { - // the options of a redelivery policy or a circuit breaker configuration that are written as set, see - // INHERITED_OPTIONS - private boolean keepDefault; - @Override protected void doWriteOptionalIdentifiedDefinitionAttributes(OptionalIdentifiedDefinition def) throws IOException { @@ -187,48 +168,6 @@ protected void doWriteValue(String value) throws IOException { } } - @Override - protected void doWriteAttribute(String attribute, String value, String defaultValue) throws IOException { - super.doWriteAttribute(attribute, value, - keepDefault || INHERITED_OPTIONS.contains(attribute) ? null : defaultValue); - } - - @Override - protected void doWriteRedeliveryPolicyDefinition(String name, RedeliveryPolicyDefinition def) - throws IOException { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteRedeliveryPolicyDefinition(name, def); - } finally { - keepDefault = keep; - } - } - - @Override - protected void doWriteResilience4jConfigurationCommonAttributes(Resilience4jConfigurationCommon def) - throws IOException { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteResilience4jConfigurationCommonAttributes(def); - } finally { - keepDefault = keep; - } - } - - @Override - protected void doWriteFaultToleranceConfigurationCommonAttributes(FaultToleranceConfigurationCommon def) - throws IOException { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteFaultToleranceConfigurationCommonAttributes(def); - } finally { - keepDefault = keep; - } - } - @Override protected void text(String name, String text) throws IOException { if (resolvePlaceholders) { 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 7a1096e27a801..0f0e930a66c5d 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 @@ -1029,15 +1029,15 @@ protected JsonObject doWriteExpressionSubElementDefinition(ExpressionSubElementD protected void doWriteFaultToleranceConfigurationCommonAttributes(JsonObject jo, FaultToleranceConfigurationCommon def) { doWriteIdentifiedTypeAttributes(jo, def); doWriteAttribute(jo, "typedGuard", def.getTypedGuard(), null); - doWriteAttribute(jo, "delay", def.getDelay(), "5000"); - doWriteAttribute(jo, "successThreshold", def.getSuccessThreshold(), "1"); - doWriteAttribute(jo, "requestVolumeThreshold", def.getRequestVolumeThreshold(), "20"); - doWriteAttribute(jo, "failureRatio", def.getFailureRatio(), "50"); - doWriteAttribute(jo, "timeoutEnabled", def.getTimeoutEnabled(), "false"); - doWriteAttribute(jo, "timeoutDuration", def.getTimeoutDuration(), "1000"); - doWriteAttribute(jo, "bulkheadEnabled", def.getBulkheadEnabled(), "false"); - doWriteAttribute(jo, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), "10"); - doWriteAttribute(jo, "bulkheadWaitingTaskQueue", def.getBulkheadWaitingTaskQueue(), "10"); + doWriteAttribute(jo, "delay", def.getDelay(), null); + doWriteAttribute(jo, "successThreshold", def.getSuccessThreshold(), null); + doWriteAttribute(jo, "requestVolumeThreshold", def.getRequestVolumeThreshold(), null); + doWriteAttribute(jo, "failureRatio", def.getFailureRatio(), null); + doWriteAttribute(jo, "timeoutEnabled", def.getTimeoutEnabled(), null); + doWriteAttribute(jo, "timeoutDuration", def.getTimeoutDuration(), null); + doWriteAttribute(jo, "bulkheadEnabled", def.getBulkheadEnabled(), null); + doWriteAttribute(jo, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), null); + doWriteAttribute(jo, "bulkheadWaitingTaskQueue", def.getBulkheadWaitingTaskQueue(), null); doWriteAttribute(jo, "threadOffloadExecutorService", def.getThreadOffloadExecutorService(), null); } protected JsonObject doWriteFaultToleranceConfigurationCommon(FaultToleranceConfigurationCommon def) { @@ -1461,28 +1461,28 @@ protected JsonObject doWriteRedeliveryPolicyDefinition(RedeliveryPolicyDefinitio JsonObject jo = new JsonObject(); doWriteIdentifiedTypeAttributes(jo, def); doWriteAttribute(jo, "maximumRedeliveries", def.getMaximumRedeliveries(), null); - doWriteAttribute(jo, "redeliveryDelay", def.getRedeliveryDelay(), "1000"); + doWriteAttribute(jo, "redeliveryDelay", def.getRedeliveryDelay(), null); doWriteAttribute(jo, "asyncDelayedRedelivery", def.getAsyncDelayedRedelivery(), null); - doWriteAttribute(jo, "backOffMultiplier", def.getBackOffMultiplier(), "2.0"); + doWriteAttribute(jo, "backOffMultiplier", def.getBackOffMultiplier(), null); doWriteAttribute(jo, "useExponentialBackOff", def.getUseExponentialBackOff(), null); - doWriteAttribute(jo, "collisionAvoidanceFactor", def.getCollisionAvoidanceFactor(), "0.15"); + doWriteAttribute(jo, "collisionAvoidanceFactor", def.getCollisionAvoidanceFactor(), null); doWriteAttribute(jo, "useCollisionAvoidance", def.getUseCollisionAvoidance(), null); - doWriteAttribute(jo, "maximumRedeliveryDelay", def.getMaximumRedeliveryDelay(), "60000"); - doWriteAttribute(jo, "retriesExhaustedLogLevel", def.getRetriesExhaustedLogLevel(), "ERROR"); - doWriteAttribute(jo, "retryAttemptedLogLevel", def.getRetryAttemptedLogLevel(), "DEBUG"); - doWriteAttribute(jo, "retryAttemptedLogInterval", def.getRetryAttemptedLogInterval(), "1"); - doWriteAttribute(jo, "logRetryAttempted", def.getLogRetryAttempted(), "true"); - doWriteAttribute(jo, "logStackTrace", def.getLogStackTrace(), "true"); + doWriteAttribute(jo, "maximumRedeliveryDelay", def.getMaximumRedeliveryDelay(), null); + doWriteAttribute(jo, "retriesExhaustedLogLevel", def.getRetriesExhaustedLogLevel(), null); + doWriteAttribute(jo, "retryAttemptedLogLevel", def.getRetryAttemptedLogLevel(), null); + doWriteAttribute(jo, "retryAttemptedLogInterval", def.getRetryAttemptedLogInterval(), null); + doWriteAttribute(jo, "logRetryAttempted", def.getLogRetryAttempted(), null); + doWriteAttribute(jo, "logStackTrace", def.getLogStackTrace(), null); doWriteAttribute(jo, "logRetryStackTrace", def.getLogRetryStackTrace(), null); doWriteAttribute(jo, "logHandled", def.getLogHandled(), null); - doWriteAttribute(jo, "logNewException", def.getLogNewException(), "true"); + doWriteAttribute(jo, "logNewException", def.getLogNewException(), null); doWriteAttribute(jo, "logContinued", def.getLogContinued(), null); - doWriteAttribute(jo, "logExhausted", def.getLogExhausted(), "true"); + doWriteAttribute(jo, "logExhausted", def.getLogExhausted(), null); doWriteAttribute(jo, "logExhaustedMessageHistory", def.getLogExhaustedMessageHistory(), null); doWriteAttribute(jo, "logExhaustedMessageBody", def.getLogExhaustedMessageBody(), null); doWriteAttribute(jo, "disableRedelivery", def.getDisableRedelivery(), null); doWriteAttribute(jo, "delayPattern", def.getDelayPattern(), null); - doWriteAttribute(jo, "allowRedeliveryWhileStopping", def.getAllowRedeliveryWhileStopping(), "true"); + doWriteAttribute(jo, "allowRedeliveryWhileStopping", def.getAllowRedeliveryWhileStopping(), null); doWriteAttribute(jo, "exchangeFormatterRef", def.getExchangeFormatterRef(), null); return jo; } @@ -1535,29 +1535,29 @@ protected void doWriteResilience4jConfigurationCommonAttributes(JsonObject jo, R doWriteIdentifiedTypeAttributes(jo, def); doWriteAttribute(jo, "circuitBreaker", def.getCircuitBreaker(), null); doWriteAttribute(jo, "config", def.getConfig(), null); - doWriteAttribute(jo, "failureRateThreshold", def.getFailureRateThreshold(), "50"); - doWriteAttribute(jo, "permittedNumberOfCallsInHalfOpenState", def.getPermittedNumberOfCallsInHalfOpenState(), "10"); - doWriteAttribute(jo, "throwExceptionWhenHalfOpenOrOpenState", def.getThrowExceptionWhenHalfOpenOrOpenState(), "false"); - doWriteAttribute(jo, "slidingWindowSize", def.getSlidingWindowSize(), "100"); - doWriteAttribute(jo, "slidingWindowType", def.getSlidingWindowType(), "COUNT_BASED"); - doWriteAttribute(jo, "slidingWindowSynchronizationStrategy", def.getSlidingWindowSynchronizationStrategy(), "SYNCHRONIZED"); - doWriteAttribute(jo, "minimumNumberOfCalls", def.getMinimumNumberOfCalls(), "100"); - doWriteAttribute(jo, "writableStackTraceEnabled", def.getWritableStackTraceEnabled(), "true"); - doWriteAttribute(jo, "waitDurationInOpenState", def.getWaitDurationInOpenState(), "60000"); - doWriteAttribute(jo, "automaticTransitionFromOpenToHalfOpenEnabled", def.getAutomaticTransitionFromOpenToHalfOpenEnabled(), "false"); - doWriteAttribute(jo, "maxWaitDurationInHalfOpenState", def.getMaxWaitDurationInHalfOpenState(), "0"); - doWriteAttribute(jo, "slowCallRateThreshold", def.getSlowCallRateThreshold(), "100"); - doWriteAttribute(jo, "slowCallDurationThreshold", def.getSlowCallDurationThreshold(), "60000"); - doWriteAttribute(jo, "bulkheadEnabled", def.getBulkheadEnabled(), "false"); - doWriteAttribute(jo, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), "25"); - doWriteAttribute(jo, "bulkheadMaxWaitDuration", def.getBulkheadMaxWaitDuration(), "0"); - doWriteAttribute(jo, "bulkheadFairCallHandlingEnabled", def.getBulkheadFairCallHandlingEnabled(), "true"); - doWriteAttribute(jo, "asynchronous", def.getAsynchronous(), "false"); - doWriteAttribute(jo, "timeoutEnabled", def.getTimeoutEnabled(), "false"); + doWriteAttribute(jo, "failureRateThreshold", def.getFailureRateThreshold(), null); + doWriteAttribute(jo, "permittedNumberOfCallsInHalfOpenState", def.getPermittedNumberOfCallsInHalfOpenState(), null); + doWriteAttribute(jo, "throwExceptionWhenHalfOpenOrOpenState", def.getThrowExceptionWhenHalfOpenOrOpenState(), null); + doWriteAttribute(jo, "slidingWindowSize", def.getSlidingWindowSize(), null); + doWriteAttribute(jo, "slidingWindowType", def.getSlidingWindowType(), null); + doWriteAttribute(jo, "slidingWindowSynchronizationStrategy", def.getSlidingWindowSynchronizationStrategy(), null); + doWriteAttribute(jo, "minimumNumberOfCalls", def.getMinimumNumberOfCalls(), null); + doWriteAttribute(jo, "writableStackTraceEnabled", def.getWritableStackTraceEnabled(), null); + doWriteAttribute(jo, "waitDurationInOpenState", def.getWaitDurationInOpenState(), null); + doWriteAttribute(jo, "automaticTransitionFromOpenToHalfOpenEnabled", def.getAutomaticTransitionFromOpenToHalfOpenEnabled(), null); + doWriteAttribute(jo, "maxWaitDurationInHalfOpenState", def.getMaxWaitDurationInHalfOpenState(), null); + doWriteAttribute(jo, "slowCallRateThreshold", def.getSlowCallRateThreshold(), null); + doWriteAttribute(jo, "slowCallDurationThreshold", def.getSlowCallDurationThreshold(), null); + doWriteAttribute(jo, "bulkheadEnabled", def.getBulkheadEnabled(), null); + doWriteAttribute(jo, "bulkheadMaxConcurrentCalls", def.getBulkheadMaxConcurrentCalls(), null); + doWriteAttribute(jo, "bulkheadMaxWaitDuration", def.getBulkheadMaxWaitDuration(), null); + doWriteAttribute(jo, "bulkheadFairCallHandlingEnabled", def.getBulkheadFairCallHandlingEnabled(), null); + doWriteAttribute(jo, "asynchronous", def.getAsynchronous(), null); + doWriteAttribute(jo, "timeoutEnabled", def.getTimeoutEnabled(), null); doWriteAttribute(jo, "timeoutExecutorService", def.getTimeoutExecutorService(), null); - doWriteAttribute(jo, "timeoutDuration", def.getTimeoutDuration(), "1000"); - doWriteAttribute(jo, "timeoutCancelRunningFuture", def.getTimeoutCancelRunningFuture(), "true"); - doWriteAttribute(jo, "micrometerEnabled", def.getMicrometerEnabled(), "false"); + doWriteAttribute(jo, "timeoutDuration", def.getTimeoutDuration(), null); + doWriteAttribute(jo, "timeoutCancelRunningFuture", def.getTimeoutCancelRunningFuture(), null); + doWriteAttribute(jo, "micrometerEnabled", def.getMicrometerEnabled(), null); } protected void doWriteResilience4jConfigurationCommonElements(JsonObject jo, Resilience4jConfigurationCommon def) { doWriteStringList(jo, null, "ignoreException", def.getIgnoreExceptions()); @@ -1645,8 +1645,8 @@ protected JsonObject doWriteRouteDefinition(RouteDefinition def) { doWriteAttribute(jo, "delayer", def.getDelayer(), null); doWriteAttribute(jo, "errorHandlerRef", def.getErrorHandlerRef(), null); doWriteAttribute(jo, "routePolicyRef", def.getRoutePolicyRef(), null); - doWriteAttribute(jo, "shutdownRoute", def.getShutdownRoute(), "Default"); - doWriteAttribute(jo, "shutdownRunningTask", def.getShutdownRunningTask(), "CompleteCurrentTaskOnly"); + doWriteAttribute(jo, "shutdownRoute", def.getShutdownRoute(), null); + doWriteAttribute(jo, "shutdownRunningTask", def.getShutdownRunningTask(), null); doWriteAttribute(jo, "precondition", def.getPrecondition(), null); doWriteChildList(jo, null, "routeProperty", def.getRouteProperties(), this::doWritePropertyDefinition); doWriteChildElement(jo, "errorHandler", def.getErrorHandler(), this::doWriteErrorHandlerDefinition); @@ -3358,14 +3358,14 @@ protected JsonObject doWriteRestBindingDefinition(RestBindingDefinition def) { doWriteOptionalIdentifiedDefinitionAttributes(jo, def); doWriteAttribute(jo, "consumes", def.getConsumes(), null); doWriteAttribute(jo, "produces", def.getProduces(), null); - doWriteAttribute(jo, "bindingMode", def.getBindingMode(), "off"); + doWriteAttribute(jo, "bindingMode", def.getBindingMode(), null); doWriteAttribute(jo, "type", def.getType(), null); doWriteAttribute(jo, "outType", def.getOutType(), null); - doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), null); + doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute(jo, "component", def.getComponent(), null); return jo; } @@ -3385,13 +3385,13 @@ protected JsonObject doWriteRestConfigurationDefinition(RestConfigurationDefinit doWriteAttribute(jo, "apiContextRouteId", def.getApiContextRouteId(), null); doWriteAttribute(jo, "apiVendorExtension", def.getApiVendorExtension(), "false"); doWriteAttribute(jo, "hostNameResolver", toString(def.getHostNameResolver()), "allLocalIp"); - doWriteAttribute(jo, "bindingMode", toString(def.getBindingMode()), "off"); + doWriteAttribute(jo, "bindingMode", toString(def.getBindingMode()), null); doWriteAttribute(jo, "bindingPackageScan", def.getBindingPackageScan(), null); - doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), null); + doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute(jo, "inlineRoutes", def.getInlineRoutes(), "true"); doWriteAttribute(jo, "jsonDataFormat", def.getJsonDataFormat(), "jackson"); doWriteAttribute(jo, "xmlDataFormat", def.getXmlDataFormat(), "jaxb"); @@ -3411,12 +3411,12 @@ protected JsonObject doWriteRestDefinition(RestDefinition def) { doWriteAttribute(jo, "path", def.getPath(), null); doWriteAttribute(jo, "consumes", def.getConsumes(), null); doWriteAttribute(jo, "produces", def.getProduces(), null); - doWriteAttribute(jo, "bindingMode", def.getBindingMode(), "off"); - doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), "false"); - doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), "false"); - doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), "false"); - doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); + doWriteAttribute(jo, "bindingMode", def.getBindingMode(), null); + doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), null); + doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), null); + doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), null); + doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), null); doWriteAttribute(jo, "apiDocs", def.getApiDocs(), "true"); doWriteAttribute(jo, "tag", def.getTag(), null); doWriteVerbs(jo, def.getVerbs(), this::doWriteVerbDefinition); @@ -3470,19 +3470,19 @@ protected JsonObject doWriteSecurityDefinition(SecurityDefinition def) { } protected void doWriteVerbDefinitionAttributes(JsonObject jo, VerbDefinition def) { doWriteOptionalIdentifiedDefinitionAttributes(jo, def); - doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), "false"); + doWriteAttribute(jo, "enableCORS", def.getEnableCORS(), null); doWriteAttribute(jo, "deprecated", def.getDeprecated(), "false"); doWriteAttribute(jo, "streamCache", def.getStreamCache(), null); doWriteAttribute(jo, "type", def.getType(), null); doWriteAttribute(jo, "outType", def.getOutType(), null); - doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), "false"); + doWriteAttribute(jo, "clientResponseValidation", def.getClientResponseValidation(), null); doWriteAttribute(jo, "path", def.getPath(), null); doWriteAttribute(jo, "routeId", def.getRouteId(), null); - doWriteAttribute(jo, "bindingMode", def.getBindingMode(), "off"); + doWriteAttribute(jo, "bindingMode", def.getBindingMode(), null); doWriteAttribute(jo, "apiDocs", def.getApiDocs(), "true"); - doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), "false"); - doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), "false"); - doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), "false"); + doWriteAttribute(jo, "enableNoContentResponse", def.getEnableNoContentResponse(), null); + doWriteAttribute(jo, "skipBindingOnErrorCode", def.getSkipBindingOnErrorCode(), null); + doWriteAttribute(jo, "clientRequestValidation", def.getClientRequestValidation(), null); doWriteAttribute(jo, "produces", def.getProduces(), null); doWriteAttribute(jo, "disabled", def.getDisabled(), null); doWriteAttribute(jo, "consumes", def.getConsumes(), null); 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 b6082ee40026a..6d6a6441be5e7 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.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -37,15 +36,12 @@ import org.apache.camel.model.BeanFactoryDefinition; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.model.ExpressionNode; -import org.apache.camel.model.FaultToleranceConfigurationCommon; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.InterceptFromDefinition; import org.apache.camel.model.InterceptSendToEndpointDefinition; import org.apache.camel.model.Model; import org.apache.camel.model.OnExceptionDefinition; import org.apache.camel.model.OptionalIdentifiedDefinition; -import org.apache.camel.model.RedeliveryPolicyDefinition; -import org.apache.camel.model.Resilience4jConfigurationCommon; import org.apache.camel.model.RouteConfigurationDefinition; import org.apache.camel.model.RouteConfigurationsDefinition; import org.apache.camel.model.RouteDefinition; @@ -76,17 +72,6 @@ @JdkService(ModelToYAMLDumper.FACTORY) public class LwModelToYAMLDumper implements ModelToYAMLDumper { - /** - * Options that are written whenever they are set, even with their documented default value: when they are not set, - * they take the value of a parent level (the rest or the rest configuration for a rest or verb, the CamelContext - * for a route), and a dump that leaves them out runs differently once loaded. The same holds for every option of a - * redelivery policy (an onException copies the error handler's policy) and of a circuit breaker configuration (it - * is layered over the global one). - */ - static final Set INHERITED_OPTIONS = Set.of( - "bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", "clientResponseValidation", "enableCORS", - "enableNoContentResponse", "shutdownRoute", "shutdownRunningTask"); - @Override public String dumpModelAsYaml(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsYaml(context, definition, false, false, true, false); @@ -141,10 +126,6 @@ public String dumpModelAsYaml( topLevel.removeIf(oe -> isInRouteConfiguration(context, oe)); YamlModelWriter writer = new YamlModelWriter() { - // the options of a redelivery policy or a circuit breaker configuration that are written as set, see - // INHERITED_OPTIONS - private boolean keepDefault; - @Override protected void doWriteOutputs(JsonObject jo, List list, Function writer) { if (list != null && !contextScoped.isEmpty()) { @@ -180,42 +161,7 @@ protected void doWriteAttribute(JsonObject jo, String key, String value, String expandUri(jo, value); return; } - super.doWriteAttribute(jo, key, value, keepDefault || INHERITED_OPTIONS.contains(key) ? null : defaultValue); - } - - @Override - protected JsonObject doWriteRedeliveryPolicyDefinition(RedeliveryPolicyDefinition def) { - boolean keep = keepDefault; - keepDefault = true; - try { - return super.doWriteRedeliveryPolicyDefinition(def); - } finally { - keepDefault = keep; - } - } - - @Override - protected void doWriteResilience4jConfigurationCommonAttributes( - JsonObject jo, Resilience4jConfigurationCommon def) { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteResilience4jConfigurationCommonAttributes(jo, def); - } finally { - keepDefault = keep; - } - } - - @Override - protected void doWriteFaultToleranceConfigurationCommonAttributes( - JsonObject jo, FaultToleranceConfigurationCommon def) { - boolean keep = keepDefault; - keepDefault = true; - try { - super.doWriteFaultToleranceConfigurationCommonAttributes(jo, def); - } finally { - keepDefault = keep; - } + super.doWriteAttribute(jo, key, value, defaultValue); } @Override diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 2e5255c0ce79e..1a7cd68bdff49 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -553,10 +553,11 @@ or follows, like Enter does. The XML, YAML and Java route dumps (`camel transform route`, `camel validate normalize`, `camel.main.dumpRoutes`, the developer console) now write these options whenever they are set, also with their documented default value: the -binding options of a rest and its verbs (such as `bindingMode="off"`), the options of a redelivery policy, of a -`resilience4jConfiguration` and of a `faultToleranceConfiguration`, and `shutdownRoute` / `shutdownRunningTask` of a -route. Left out, such an option took the value of the parent level when the dump was loaded (the rest configuration, -the error handler, the global circuit breaker configuration, the CamelContext), so the route behaved differently. +binding options of the rest configuration, a rest and its verbs (such as `bindingMode="off"`), the options of a +redelivery policy, of a `resilience4jConfiguration` and of a `faultToleranceConfiguration`, and `shutdownRoute` / +`shutdownRunningTask` of a route. Left out, such an option took the value of the parent level when the dump was loaded +(the rest configuration, the error handler, the global circuit breaker configuration, the CamelContext), so the route +behaved differently. === camel-jbang - camel get bean, get route-controller and top source diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelWriterGeneratorMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelWriterGeneratorMojo.java index f375993b12de3..c772156f02899 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelWriterGeneratorMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelWriterGeneratorMojo.java @@ -39,6 +39,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; @@ -493,6 +494,33 @@ private Stream> fieldReferences(Class clazz) { } public static class Property { + + /** + * The model classes of which every option is written whenever it is set, even to its documented default value: + * an onException copies the error handler's redelivery policy and overrides only the options that are set, and + * a circuit breaker configuration is layered over the global one, so "not set" means "the value of the parent + * level", not "the default value", and a dump that leaves such an option out runs differently once loaded. + */ + private static final Set INHERITED_OPTION_CLASSES = Set.of( + "org.apache.camel.model.RedeliveryPolicyDefinition", + "org.apache.camel.model.Resilience4jConfigurationCommon", + "org.apache.camel.model.FaultToleranceConfigurationCommon"); + + /** + * The options (declaring class and attribute name) that are written whenever they are set, for the same reason: + * the rest binding options of a verb take the value of its rest, then of the rest configuration, which in turn + * falls back to the RestConfiguration of the CamelContext; the shutdown options of a route take the value of + * the CamelContext. + */ + private static final Set INHERITED_OPTIONS = Stream.concat( + Stream.of("RestDefinition", "VerbDefinition", "RestBindingDefinition", "RestConfigurationDefinition") + .flatMap(c -> Stream.of("bindingMode", "skipBindingOnErrorCode", "clientRequestValidation", + "clientResponseValidation", "enableCORS", "enableNoContentResponse") + .map(o -> "org.apache.camel.model.rest." + c + "#" + o)), + Stream.of("org.apache.camel.model.RouteDefinition#shutdownRoute", + "org.apache.camel.model.RouteDefinition#shutdownRunningTask")) + .collect(Collectors.toUnmodifiableSet()); + private final Member field; private final Member getter; private final Member setter; @@ -618,7 +646,27 @@ public String getAttributeName() { return an; } + /** + * Whether "not set" means "the value of a parent level" for this option, see {@link #INHERITED_OPTIONS}. + */ + public boolean isInheritedOption() { + Member declaring = field != null ? field : members().findFirst().orElse(null); + if (declaring == null) { + return false; + } + String className = declaring.getDeclaringClass().getName(); + return INHERITED_OPTION_CLASSES.contains(className) || INHERITED_OPTIONS.contains(className + "#" + getName()); + } + + /** + * The default value as a Java literal, which the writers leave out, or the literal {@code null} when the option + * is always written when set (no default value, or an inherited option). + */ public String getDefaultValue() { + if (isInheritedOption()) { + // written whenever it is set, as "not set" means "the value of a parent level" + return "null"; + } String answer = null; Metadata m = getAnnotation(Metadata.class); if (m != null) {