This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-25410-try-outputs in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5b75ef5986a1c90bf4c5e0cd8c1b4f288fbb52ca Author: Claus Ibsen <[email protected]> AuthorDate: Thu Nov 12 22:16:39 2026 +0100 CAMEL-25410: camel-core-model - doTry keeps doCatch and doFinally in its outputs doCatch and doFinally written as properties of doTry (the YAML DSL) were kept only in the @XmlTransient fields catchClauses / finallyClause, so everything that walks getOutputs() missed them (JaxbModelToXMLDumper, the tooling), and the fields only ever grew: a catch removed from the outputs (advice-with) was still reified. The outputs are now the single source of truth: - setCatchClauses / setFinallyClause fold the clauses into the outputs (doCatch before the doFinally; one doFinally); - getCatchClauses / getFinallyClause / getOutputsWithoutCatches derive them from the outputs on each call; - the copy constructor no longer copies the clauses a second time; - a step added to the doTry itself after a clause (YAML steps below doCatch) goes before the clauses, so the outputs read steps, doCatch, doFinally; - the TryDefinition special case of the XML, YAML and Java DSL writer templates (CAMEL-25367) is removed and the writers regenerated. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../java/org/apache/camel/model/TryDefinition.java | 115 ++++++++++++---- .../camel/model/TryDefinitionClausesTest.java | 121 +++++++++++++++++ .../interceptor/AdviceWithRemoveDoCatchTest.java | 69 ++++++++++ .../JaxbDumpModelAsXmlTryPropertyClausesTest.java | 66 +++++++++ .../apache/camel/java/out/JavaDslModelWriter.java | 19 +-- .../apache/camel/java/out/TryModelWriterTest.java | 27 +++- .../java/org/apache/camel/xml/out/ModelWriter.java | 19 +-- .../apache/camel/xml/out/TryModelWriterTest.java | 27 +++- .../org/apache/camel/yaml/out/YamlModelWriter.java | 18 +-- .../apache/camel/yaml/out/TryModelWriterTest.java | 27 +++- .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 9 ++ .../resources/schema/camelYamlDsl-canonical.json | 47 ++++++- .../resources/schema/camelYamlDsl-model.json | 73 +++++++++- .../generated/resources/schema/camelYamlDsl.json | 100 +++++++++++++- .../camel/dsl/yaml/TryPropertyClausesTest.java | 147 +++++++++++++++++++++ .../resources/velocity/model-java-dsl-writer.vm | 21 --- .../src/main/resources/velocity/model-writer.vm | 21 --- .../main/resources/velocity/model-yaml-writer.vm | 20 --- 18 files changed, 799 insertions(+), 147 deletions(-) diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/TryDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/TryDefinition.java index 41f73f3d593a..f383c334462b 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/TryDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/TryDefinition.java @@ -47,6 +47,9 @@ import org.apache.camel.spi.annotations.DslProperty; eipSteps = @BodyType(Carrier.UNCHANGED)) public class TryDefinition extends OutputDefinition<TryDefinition> { + // the doCatch and doFinally clauses are kept in outputs (the single source of truth, CAMEL-25410): these fields + // are only the YAML DSL properties (doCatch and doFinally written as properties of doTry), and a cache derived + // from outputs by checkInitialized @DslProperty @XmlTransient private List<CatchDefinition> catchClauses; @@ -64,9 +67,9 @@ public class TryDefinition extends OutputDefinition<TryDefinition> { } protected TryDefinition(TryDefinition source) { + // the clauses are in the outputs, which super copies: the cache fields are derived again from the copied outputs + // (copying them too would give the copy two instances of each clause) super(source); - this.catchClauses = ProcessorDefinitionHelper.deepCopyDefinitions(source.catchClauses); - this.finallyClause = source.finallyClause != null ? source.finallyClause.copyDefinition() : null; } @Override @@ -162,34 +165,60 @@ public class TryDefinition extends OutputDefinition<TryDefinition> { // Properties // ------------------------------------------------------------------------- + /** + * Adds the doCatch clauses to the outputs (before the doFinally, if any), where the clauses of a doTry are kept. + * Used by the YAML DSL for doCatch written as a property of doTry. + */ @XmlTransient public void setCatchClauses(List<CatchDefinition> catchClauses) { - this.catchClauses = catchClauses; + if (catchClauses != null) { + for (CatchDefinition catchClause : catchClauses) { + if (!outputs.contains(catchClause)) { + catchClause.setParent(this); + configureChild(catchClause); + outputs.add(indexOfFinally(), catchClause); + } + } + } + initialized = false; } + /** + * The doCatch clauses, in the order of the outputs where they are kept. + */ public List<CatchDefinition> getCatchClauses() { - if (catchClauses == null) { - checkInitialized(); - } + refresh(); return catchClauses; } + /** + * Sets the doFinally clause in the outputs, where the clauses of a doTry are kept, replacing any other. Used by the + * YAML DSL for doFinally written as a property of doTry. + */ @XmlTransient public void setFinallyClause(FinallyDefinition finallyClause) { - this.finallyClause = finallyClause; + outputs.removeIf(o -> o instanceof FinallyDefinition && o != finallyClause); + if (finallyClause != null && !outputs.contains(finallyClause)) { + finallyClause.setParent(this); + configureChild(finallyClause); + outputs.add(finallyClause); + } + initialized = false; } + /** + * The doFinally clause from the outputs, or null if there is none. + */ public FinallyDefinition getFinallyClause() { - if (finallyClause == null) { - checkInitialized(); - } + refresh(); return finallyClause; } + /** + * The outputs that are the steps of the doTry block: all outputs except the doCatch and doFinally clauses. + */ public List<ProcessorDefinition<?>> getOutputsWithoutCatches() { - if (outputsWithoutCatches == null) { - checkInitialized(); - } + refresh(); return outputsWithoutCatches; } @@ -211,6 +240,18 @@ public class TryDefinition extends OutputDefinition<TryDefinition> { // reset end counter as we are adding some outputs endCounter = 0; super.addOutput(output); + // a step added to this doTry itself (not to the doCatch or doFinally the Java DSL is building, which takes it + // as its own output) after a clause, such as YAML steps after doCatch written as a property, goes before the + // clauses: the runtime takes the steps from anywhere in the outputs, and the outputs then read (and are dumped) + // as steps, doCatch and doFinally in this order + if (!(output instanceof CatchDefinition) && !(output instanceof FinallyDefinition) + && !outputs.isEmpty() && outputs.get(outputs.size() - 1) == output) { + int index = indexOfFirstClause(); + if (index < outputs.size() - 1) { + outputs.remove(outputs.size() - 1); + outputs.add(index, output); + } + } } protected ProcessorDefinition<?> onEndDoTry() { @@ -230,28 +271,56 @@ public class TryDefinition extends OutputDefinition<TryDefinition> { } /** - * Checks whether or not this object has been initialized + * Derives the clauses and the steps from the outputs again: the outputs can be changed directly (for example by + * advice-with), so a clause that is no longer in the outputs must not stay in the cache. + */ + private void refresh() { + initialized = false; + checkInitialized(); + } + + private int indexOfFinally() { + for (int i = 0; i < outputs.size(); i++) { + if (outputs.get(i) instanceof FinallyDefinition) { + return i; + } + } + return outputs.size(); + } + + private int indexOfFirstClause() { + for (int i = 0; i < outputs.size(); i++) { + if (outputs.get(i) instanceof CatchDefinition || outputs.get(i) instanceof FinallyDefinition) { + return i; + } + } + return outputs.size(); + } + + /** + * Checks whether or not this object has been initialized, and derives the doCatch and doFinally clauses and the + * steps from the outputs if not. */ protected void checkInitialized() { if (!initialized) { initialized = true; - outputsWithoutCatches = new ArrayList<>(); - if (catchClauses == null) { - catchClauses = new ArrayList<>(); - } + List<ProcessorDefinition<?>> steps = new ArrayList<>(); + List<CatchDefinition> catches = new ArrayList<>(); + FinallyDefinition doFinally = null; int doFinallyCounter = 0; for (ProcessorDefinition<?> output : outputs) { if (output instanceof CatchDefinition catchDefinition) { - if (!catchClauses.contains(output)) { - catchClauses.add(catchDefinition); - } + catches.add(catchDefinition); } else if (output instanceof FinallyDefinition finallyDefinition) { ++doFinallyCounter; - finallyClause = finallyDefinition; + doFinally = finallyDefinition; } else { - outputsWithoutCatches.add(output); + steps.add(output); } } + outputsWithoutCatches = steps; + catchClauses = catches; + finallyClause = doFinally; if (doFinallyCounter > 1) { throw new IllegalArgumentException( "Multiple finally clauses added: " + doFinallyCounter); diff --git a/core/camel-core/src/test/java/org/apache/camel/model/TryDefinitionClausesTest.java b/core/camel-core/src/test/java/org/apache/camel/model/TryDefinitionClausesTest.java new file mode 100644 index 000000000000..81c70981452b --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/model/TryDefinitionClausesTest.java @@ -0,0 +1,121 @@ +/* + * 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.model; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The doCatch and doFinally clauses of a doTry are kept in its outputs, also when they are set as properties (as the + * YAML DSL does), and the clause getters are derived from the outputs (CAMEL-25410). + */ +public class TryDefinitionClausesTest { + + @Test + public void testClausesSetAsPropertiesAreInTheOutputs() { + TryDefinition doTry = new TryDefinition(); + CatchDefinition doCatch = newCatch("mock:catch"); + FinallyDefinition doFinally = newFinally("mock:finally"); + doTry.setFinallyClause(doFinally); + doTry.setCatchClauses(List.of(doCatch)); + // steps written after the clauses (as YAML with doCatch above steps) go before them + doTry.addOutput(new ToDefinition("mock:a")); + doTry.addOutput(new ToDefinition("mock:b")); + + assertThat(doTry.getOutputs()).hasSize(4); + assertThat(((ToDefinition) doTry.getOutputs().get(0)).getUri()).isEqualTo("mock:a"); + assertThat(((ToDefinition) doTry.getOutputs().get(1)).getUri()).isEqualTo("mock:b"); + assertThat(doTry.getOutputs().get(2)).isSameAs(doCatch); + assertThat(doTry.getOutputs().get(3)).isSameAs(doFinally); + + assertThat(doTry.getCatchClauses()).containsExactly(doCatch); + assertThat(doTry.getFinallyClause()).isSameAs(doFinally); + assertThat(doTry.getOutputsWithoutCatches()).hasSize(2); + assertThat(doCatch.getParent()).isSameAs(doTry); + assertThat(doFinally.getParent()).isSameAs(doTry); + } + + @Test + public void testSettingTheClausesAgainDoesNotDuplicateThem() { + TryDefinition doTry = new TryDefinition(); + doTry.addOutput(new ToDefinition("mock:a")); + CatchDefinition doCatch = newCatch("mock:catch"); + doTry.setCatchClauses(List.of(doCatch)); + doTry.setCatchClauses(List.of(doCatch)); + FinallyDefinition first = newFinally("mock:first"); + FinallyDefinition second = newFinally("mock:second"); + doTry.setFinallyClause(first); + // a doTry has one doFinally: setting another replaces it + doTry.setFinallyClause(second); + + assertThat(doTry.getOutputs()).hasSize(3); + assertThat(doTry.getCatchClauses()).containsExactly(doCatch); + assertThat(doTry.getFinallyClause()).isSameAs(second); + assertThat(doTry.getOutputs()).doesNotContain(first); + } + + @Test + public void testClauseRemovedFromTheOutputsIsGone() { + TryDefinition doTry = new TryDefinition(); + doTry.addOutput(new ToDefinition("mock:a")); + CatchDefinition doCatch = newCatch("mock:catch"); + CatchDefinition other = newCatch("mock:other"); + FinallyDefinition doFinally = newFinally("mock:finally"); + doTry.setCatchClauses(List.of(doCatch, other)); + doTry.setFinallyClause(doFinally); + assertThat(doTry.getCatchClauses()).containsExactly(doCatch, other); + + // as advice-with does: the outputs are changed directly + doTry.getOutputs().remove(other); + doTry.getOutputs().remove(doFinally); + + assertThat(doTry.getCatchClauses()).containsExactly(doCatch); + assertThat(doTry.getFinallyClause()).isNull(); + } + + @Test + public void testCopyHasOneInstanceOfEachClause() { + TryDefinition doTry = new TryDefinition(); + doTry.addOutput(new ToDefinition("mock:a")); + doTry.setCatchClauses(List.of(newCatch("mock:catch"))); + doTry.setFinallyClause(newFinally("mock:finally")); + + TryDefinition copy = doTry.copyDefinition(); + + assertThat(copy.getOutputs()).hasSize(3); + assertThat(copy.getCatchClauses()).hasSize(1); + assertThat(copy.getOutputs()).contains(copy.getCatchClauses().get(0), copy.getFinallyClause()); + assertThat(copy.getCatchClauses().get(0)).isNotSameAs(doTry.getCatchClauses().get(0)); + assertThat(copy.getCatchClauses().get(0).getParent()).isSameAs(copy); + } + + private static CatchDefinition newCatch(String uri) { + CatchDefinition answer = new CatchDefinition(); + answer.getExceptions().add("java.lang.Exception"); + answer.addOutput(new ToDefinition(uri)); + return answer; + } + + private static FinallyDefinition newFinally(String uri) { + FinallyDefinition answer = new FinallyDefinition(); + answer.addOutput(new ToDefinition(uri)); + return answer; + } +} diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/interceptor/AdviceWithRemoveDoCatchTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/interceptor/AdviceWithRemoveDoCatchTest.java new file mode 100644 index 000000000000..3fa2e3136d43 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/interceptor/AdviceWithRemoveDoCatchTest.java @@ -0,0 +1,69 @@ +/* + * 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.processor.interceptor; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.AdviceWith; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.CatchDefinition; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * A doCatch removed with advice-with from a route that has been started no longer runs (CAMEL-25410). + */ +public class AdviceWithRemoveDoCatchTest extends ContextTestSupport { + + @Test + public void testRemovedDoCatchDoesNotRun() throws Exception { + // the route has been started, so its doTry has been reified once with the doCatch + getMockEndpoint("mock:catch").expectedMessageCount(1); + getMockEndpoint("mock:finally").expectedMessageCount(1); + template.sendBody("direct:start", "Hello"); + assertMockEndpointsSatisfied(); + + AdviceWith.adviceWith(context, "myRoute", a -> a.weaveByType(CatchDefinition.class).remove()); + + resetMocks(); + getMockEndpoint("mock:catch").expectedMessageCount(0); + getMockEndpoint("mock:finally").expectedMessageCount(1); + CamelExecutionException e + = assertThrows(CamelExecutionException.class, () -> template.sendBody("direct:start", "World")); + assertInstanceOf(IllegalArgumentException.class, e.getCause()); + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").routeId("myRoute") + .doTry() + .throwException(new IllegalArgumentException("Forced")) + .doCatch(IllegalArgumentException.class) + .to("mock:catch") + .doFinally() + .to("mock:finally") + .end(); + } + }; + } +} diff --git a/core/camel-core/src/test/java/org/apache/camel/util/JaxbDumpModelAsXmlTryPropertyClausesTest.java b/core/camel-core/src/test/java/org/apache/camel/util/JaxbDumpModelAsXmlTryPropertyClausesTest.java new file mode 100644 index 000000000000..d64ee70a3acf --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/util/JaxbDumpModelAsXmlTryPropertyClausesTest.java @@ -0,0 +1,66 @@ +/* + * 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.util; + +import java.util.List; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.model.CatchDefinition; +import org.apache.camel.model.FinallyDefinition; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.ToDefinition; +import org.apache.camel.model.TryDefinition; +import org.apache.camel.xml.jaxb.JaxbModelToXMLDumper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The JAXB dumper writes the doCatch and doFinally of a doTry also when they are set as properties, as the YAML DSL + * does (CAMEL-25410). + */ +public class JaxbDumpModelAsXmlTryPropertyClausesTest extends ContextTestSupport { + + @Test + public void testDumpModelAsXml() throws Exception { + CatchDefinition doCatch = new CatchDefinition(); + doCatch.getExceptions().add("java.lang.IllegalStateException"); + doCatch.addOutput(new ToDefinition("mock:catch")); + FinallyDefinition doFinally = new FinallyDefinition(); + doFinally.addOutput(new ToDefinition("mock:finally")); + TryDefinition doTry = new TryDefinition(); + // as YAML with doCatch and doFinally written above the steps + doTry.setCatchClauses(List.of(doCatch)); + doTry.setFinallyClause(doFinally); + doTry.addOutput(new ToDefinition("mock:try")); + + RouteDefinition route = new RouteDefinition(); + route.routeId("myRoute"); + route.from("direct:start"); + route.addOutput(doTry); + + String xml = new JaxbModelToXMLDumper().dumpModelAsXml(context, route); + log.info(xml); + + assertTrue(xml.contains("<doCatch"), xml); + assertTrue(xml.contains("<doFinally"), xml); + assertTrue(xml.indexOf("mock:try") < xml.indexOf("mock:catch"), xml); + assertTrue(xml.indexOf("mock:catch") < xml.indexOf("mock:finally"), xml); + assertEquals(xml.indexOf("mock:catch"), xml.lastIndexOf("mock:catch"), xml); + } +} 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 ca964c452a29..8ce8a9047761 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 @@ -20,7 +20,6 @@ */ package org.apache.camel.java.out; -import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.function.BiConsumer; @@ -2707,23 +2706,7 @@ public class JavaDslModelWriter extends JavaDslModelWriterSupport { } protected void doWriteTryDefinition(StringBuilder sb, TryDefinition def) { doWriteProcessorDefinitionAttributes(sb, def); - List<ProcessorDefinition<?>> outputs = new ArrayList<>(def.getOutputs()); - for (CatchDefinition clause : def.getCatchClauses()) { - if (!outputs.contains(clause)) { - int index = outputs.size(); - for (int i = 0; i < outputs.size(); i++) { - if (outputs.get(i) instanceof FinallyDefinition) { - index = i; - break; - } - } - outputs.add(index, clause); - } - } - if (def.getFinallyClause() != null && !outputs.contains(def.getFinallyClause())) { - outputs.add(def.getFinallyClause()); - } - doWriteOutputs(sb, outputs, this::doWriteProcessorDefinitionRef); + doWriteOutputs(sb, def.getOutputs(), this::doWriteProcessorDefinitionRef); } protected void doWriteUnmarshalDefinition(StringBuilder sb, UnmarshalDefinition def) { doWriteProcessorDefinitionAttributes(sb, def); diff --git a/core/camel-java-io/src/test/java/org/apache/camel/java/out/TryModelWriterTest.java b/core/camel-java-io/src/test/java/org/apache/camel/java/out/TryModelWriterTest.java index aab120acefb9..28b0cabd5fdb 100644 --- a/core/camel-java-io/src/test/java/org/apache/camel/java/out/TryModelWriterTest.java +++ b/core/camel-java-io/src/test/java/org/apache/camel/java/out/TryModelWriterTest.java @@ -83,6 +83,26 @@ class TryModelWriterTest { Assertions.assertEquals(originalOutputs, def.getOutputs()); } + @Test + void clausesSetBeforeTheStepsAreDumpedAfterThem() throws Exception { + // as the YAML DSL does when doCatch and doFinally are written above steps (CAMEL-25410) + TryDefinition def = new TryDefinition(); + CatchDefinition clause = new CatchDefinition(); + clause.getExceptions().add("java.lang.Exception"); + clause.addOutput(new ToDefinition("mock:catch")); + FinallyDefinition finallyClause = new FinallyDefinition(); + finallyClause.addOutput(new ToDefinition("mock:finally")); + def.setCatchClauses(List.of(clause)); + def.setFinallyClause(finallyClause); + def.addOutput(new ToDefinition("mock:try1")); + def.addOutput(new ToDefinition("mock:try2")); + String output = dump(def); + Assertions.assertTrue(output.indexOf("mock:try1") < output.indexOf("mock:try2"), output); + Assertions.assertTrue(output.indexOf("mock:try2") < output.indexOf("mock:catch"), output); + Assertions.assertTrue(output.indexOf("mock:catch") < output.indexOf("mock:finally"), output); + Assertions.assertEquals(output.indexOf("mock:catch"), output.lastIndexOf("mock:catch"), output); + } + @Test void dumpingAfterAddingOutputPreservesNewStepAndMixedClauses() throws Exception { TryDefinition def = new TryDefinition(); @@ -102,7 +122,12 @@ class TryModelWriterTest { Assertions.assertTrue(second.contains("mock:later"), second); Assertions.assertEquals(second.indexOf("mock:catch"), second.lastIndexOf("mock:catch"), second); Assertions.assertEquals(second.indexOf("mock:finally"), second.lastIndexOf("mock:finally"), second); - Assertions.assertEquals(3, def.getOutputs().size()); + // the doCatch set as a property is kept in the outputs too (CAMEL-25410), and a step added later goes before + // the clauses: steps, doCatch, doFinally + Assertions.assertEquals(4, def.getOutputs().size()); + Assertions.assertTrue(second.indexOf("mock:later") < second.indexOf("mock:catch"), second); + Assertions.assertSame(clause, def.getOutputs().get(2)); + Assertions.assertSame(finallyClause, def.getOutputs().get(3)); } private String dump(TryDefinition def) throws Exception { 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 4a1416e28d46..0eb472d59208 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 @@ -22,7 +22,6 @@ package org.apache.camel.xml.out; import java.io.IOException; import java.io.Writer; -import java.util.ArrayList; import java.util.Base64; import java.util.List; import javax.annotation.processing.Generated; @@ -1983,23 +1982,7 @@ public class ModelWriter extends BaseWriter { protected void doWriteTryDefinition(String name, TryDefinition def) throws IOException { startElement(name); doWriteProcessorDefinitionAttributes(def); - List<ProcessorDefinition<?>> outputs = new ArrayList<>(def.getOutputs()); - for (CatchDefinition clause : def.getCatchClauses()) { - if (!outputs.contains(clause)) { - int index = outputs.size(); - for (int i = 0; i < outputs.size(); i++) { - if (outputs.get(i) instanceof FinallyDefinition) { - index = i; - break; - } - } - outputs.add(index, clause); - } - } - if (def.getFinallyClause() != null && !outputs.contains(def.getFinallyClause())) { - outputs.add(def.getFinallyClause()); - } - doWriteList(null, null, outputs, this::doWriteProcessorDefinitionRef); + doWriteList(null, null, def.getOutputs(), this::doWriteProcessorDefinitionRef); endElement(name); } protected void doWriteUnmarshalDefinition(String name, UnmarshalDefinition def) throws IOException { diff --git a/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/TryModelWriterTest.java b/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/TryModelWriterTest.java index e00df9728617..0390efbf069b 100644 --- a/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/TryModelWriterTest.java +++ b/core/camel-xml-io/src/test/java/org/apache/camel/xml/out/TryModelWriterTest.java @@ -84,6 +84,26 @@ class TryModelWriterTest { Assertions.assertEquals(originalOutputs, def.getOutputs()); } + @Test + void clausesSetBeforeTheStepsAreDumpedAfterThem() throws Exception { + // as the YAML DSL does when doCatch and doFinally are written above steps (CAMEL-25410) + TryDefinition def = new TryDefinition(); + CatchDefinition clause = new CatchDefinition(); + clause.getExceptions().add("java.lang.Exception"); + clause.addOutput(new ToDefinition("mock:catch")); + FinallyDefinition finallyClause = new FinallyDefinition(); + finallyClause.addOutput(new ToDefinition("mock:finally")); + def.setCatchClauses(List.of(clause)); + def.setFinallyClause(finallyClause); + def.addOutput(new ToDefinition("mock:try1")); + def.addOutput(new ToDefinition("mock:try2")); + String output = dump(def); + Assertions.assertTrue(output.indexOf("mock:try1") < output.indexOf("mock:try2"), output); + Assertions.assertTrue(output.indexOf("mock:try2") < output.indexOf("mock:catch"), output); + Assertions.assertTrue(output.indexOf("mock:catch") < output.indexOf("mock:finally"), output); + Assertions.assertEquals(output.indexOf("mock:catch"), output.lastIndexOf("mock:catch"), output); + } + @Test void dumpingAfterAddingOutputPreservesNewStepAndMixedClauses() throws Exception { TryDefinition def = new TryDefinition(); @@ -103,7 +123,12 @@ class TryModelWriterTest { Assertions.assertTrue(second.contains("mock:later"), second); Assertions.assertEquals(second.indexOf("mock:catch"), second.lastIndexOf("mock:catch"), second); Assertions.assertEquals(second.indexOf("mock:finally"), second.lastIndexOf("mock:finally"), second); - Assertions.assertEquals(3, def.getOutputs().size()); + // the doCatch set as a property is kept in the outputs too (CAMEL-25410), and a step added later goes before + // the clauses: steps, doCatch, doFinally + Assertions.assertEquals(4, def.getOutputs().size()); + Assertions.assertTrue(second.indexOf("mock:later") < second.indexOf("mock:catch"), second); + Assertions.assertSame(clause, def.getOutputs().get(2)); + Assertions.assertSame(finallyClause, def.getOutputs().get(3)); } private String dump(TryDefinition def) throws Exception { 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 730eb07f5578..fab0593590ec 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 @@ -1982,23 +1982,7 @@ public class YamlModelWriter extends YamlModelWriterSupport { protected JsonObject doWriteTryDefinition(TryDefinition def) { JsonObject jo = new JsonObject(); doWriteProcessorDefinitionAttributes(jo, def); - List<ProcessorDefinition<?>> outputs = new ArrayList<>(def.getOutputs()); - for (CatchDefinition clause : def.getCatchClauses()) { - if (!outputs.contains(clause)) { - int index = outputs.size(); - for (int i = 0; i < outputs.size(); i++) { - if (outputs.get(i) instanceof FinallyDefinition) { - index = i; - break; - } - } - outputs.add(index, clause); - } - } - if (def.getFinallyClause() != null && !outputs.contains(def.getFinallyClause())) { - outputs.add(def.getFinallyClause()); - } - doWriteOutputs(jo, outputs, this::doWriteProcessorDefinitionRef); + doWriteOutputs(jo, def.getOutputs(), this::doWriteProcessorDefinitionRef); return jo; } protected JsonObject doWriteUnmarshalDefinition(UnmarshalDefinition def) { diff --git a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/TryModelWriterTest.java b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/TryModelWriterTest.java index dff4a6e3ad73..cb1b710d44c8 100644 --- a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/TryModelWriterTest.java +++ b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/out/TryModelWriterTest.java @@ -83,6 +83,26 @@ class TryModelWriterTest { Assertions.assertEquals(originalOutputs, def.getOutputs()); } + @Test + void clausesSetBeforeTheStepsAreDumpedAfterThem() throws Exception { + // as the YAML DSL does when doCatch and doFinally are written above steps (CAMEL-25410) + TryDefinition def = new TryDefinition(); + CatchDefinition clause = new CatchDefinition(); + clause.getExceptions().add("java.lang.Exception"); + clause.addOutput(new ToDefinition("mock:catch")); + FinallyDefinition finallyClause = new FinallyDefinition(); + finallyClause.addOutput(new ToDefinition("mock:finally")); + def.setCatchClauses(List.of(clause)); + def.setFinallyClause(finallyClause); + def.addOutput(new ToDefinition("mock:try1")); + def.addOutput(new ToDefinition("mock:try2")); + String output = dump(def); + Assertions.assertTrue(output.indexOf("mock:try1") < output.indexOf("mock:try2"), output); + Assertions.assertTrue(output.indexOf("mock:try2") < output.indexOf("mock:catch"), output); + Assertions.assertTrue(output.indexOf("mock:catch") < output.indexOf("mock:finally"), output); + Assertions.assertEquals(output.indexOf("mock:catch"), output.lastIndexOf("mock:catch"), output); + } + @Test void dumpingAfterAddingOutputPreservesNewStepAndMixedClauses() throws Exception { TryDefinition def = new TryDefinition(); @@ -102,7 +122,12 @@ class TryModelWriterTest { Assertions.assertTrue(second.contains("mock:later"), second); Assertions.assertEquals(second.indexOf("mock:catch"), second.lastIndexOf("mock:catch"), second); Assertions.assertEquals(second.indexOf("mock:finally"), second.lastIndexOf("mock:finally"), second); - Assertions.assertEquals(3, def.getOutputs().size()); + // the doCatch set as a property is kept in the outputs too (CAMEL-25410), and a step added later goes before + // the clauses: steps, doCatch, doFinally + Assertions.assertEquals(4, def.getOutputs().size()); + Assertions.assertTrue(second.indexOf("mock:later") < second.indexOf("mock:catch"), second); + Assertions.assertSame(clause, def.getOutputs().get(2)); + Assertions.assertSame(finallyClause, def.getOutputs().get(3)); } private String dump(TryDefinition def) throws Exception { 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 2b76f4e30d41..840ce3deb4cc 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 @@ -36,6 +36,15 @@ Starting in 4.24: * The Cache EIP stores keys directly without a namespace prefix, preserving documented cache invalidation recipes (`cache.delete(key)` and `cache.clear()`). Note that because cache keys are un-prefixed, calling `clear()` on a `KeyValueRepository` shared with the Cache EIP will clear all entries in that repository, including state-store entries. It is recommended to use a dedicated repository for the Cache EIP when co-located with state-store. * `KeyValueAggregationRepository` safely verifies entry types before unmarshalling, ignoring unexpected types gracefully instead of failing with a class cast exception. +=== camel-core-model - doTry keeps doCatch and doFinally in its outputs + +`doCatch` and `doFinally` written as properties of `doTry` in the YAML DSL are now kept in the outputs of the +`TryDefinition`, as the clauses of the Java and XML DSLs always were, after the steps whatever the order of the YAML +keys. Code that walks `getOutputs()` (route dumps, tooling) now sees them, and `getCatchClauses()`, +`getFinallyClause()` and `getOutputsWithoutCatches()` are derived from the outputs on each call: a clause removed from +the outputs, for example with advice-with, is no longer used, and changing the list returned by `getCatchClauses()` +no longer changes the doTry. Use `setCatchClauses` and `setFinallyClause`, or the outputs, instead. + === camel-ehcache - shared CacheManager and changed defaults The following user-visible changes take effect in 4.24: diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json index 8fdfd8138bf0..1b949f581e07 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-canonical.json @@ -1409,6 +1409,9 @@ "mvel" : { "$ref" : "#/items/definitions/org.apache.camel.model.language.MvelExpression" }, + "ognl" : { + "$ref" : "#/items/definitions/org.apache.camel.model.language.OgnlExpression" + }, "python" : { "$ref" : "#/items/definitions/org.apache.camel.model.language.PythonExpression" }, @@ -2020,7 +2023,7 @@ "logLanguage" : { "type" : "string", "title" : "Log Language", - "description" : "The language to use for evaluating the log message, such as simple or groovy." + "description" : "The language to use for evaluating the log message, such as simple, groovy, or ognl." }, "logName" : { "type" : "string", @@ -8011,7 +8014,7 @@ "namingStrategy" : { "type" : "string", "title" : "Naming Strategy", - "description" : "If set then Jackson will use the defined Property Naming Strategy. Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE" + "description" : "If set then Jackson will use the the defined Property Naming Strategy.Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE" }, "objectMapper" : { "type" : "string", @@ -9859,6 +9862,9 @@ "mvel" : { "$ref" : "#/items/definitions/org.apache.camel.model.language.MvelExpression" }, + "ognl" : { + "$ref" : "#/items/definitions/org.apache.camel.model.language.OgnlExpression" + }, "python" : { "$ref" : "#/items/definitions/org.apache.camel.model.language.PythonExpression" }, @@ -10370,6 +10376,43 @@ }, "required" : [ "expression" ] }, + "org.apache.camel.model.language.OgnlExpression" : { + "title" : "OGNL", + "description" : "Evaluates an OGNL expression (Apache Commons OGNL)", + "deprecated" : true, + "type" : "object", + "additionalProperties" : false, + "properties" : { + "expression" : { + "type" : "string", + "title" : "Expression", + "description" : "The expression value in your chosen language syntax." + }, + "id" : { + "type" : "string", + "title" : "Id", + "description" : "The id of this node." + }, + "resolveResource" : { + "type" : "boolean", + "title" : "Resolve Resource", + "description" : "Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate.", + "default" : false + }, + "resultType" : { + "type" : "string", + "title" : "Result Type", + "description" : "The class of the result type (type from output)." + }, + "trim" : { + "type" : "boolean", + "title" : "Trim", + "description" : "Whether to trim the source code to remove leading and trailing whitespaces and line breaks.", + "default" : true + } + }, + "required" : [ "expression" ] + }, "org.apache.camel.model.language.Python3Expression" : { "title" : "Python 3", "description" : "Evaluates a Python 3 expression", diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json index 15f79fc9ca70..b9df804b5c90 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json @@ -697,6 +697,14 @@ "title" : "MVEL", "description" : "Evaluates a MVEL template", "label" : "language,java" + }, { + "name" : "ognl", + "type" : "object", + "ref" : "ognl", + "title" : "OGNL", + "description" : "Evaluates an OGNL expression (Apache Commons OGNL)", + "label" : "language,java", + "deprecated" : true }, { "name" : "python", "type" : "object", @@ -2679,6 +2687,10 @@ "name" : "mvel", "type" : "object", "ref" : "mvel" + }, { + "name" : "ognl", + "type" : "object", + "ref" : "ognl" }, { "name" : "python", "type" : "object", @@ -3676,7 +3688,7 @@ }, { "name" : "logLanguage", "type" : "string", - "description" : "The language to use for evaluating the log message, such as simple or groovy.", + "description" : "The language to use for evaluating the log message, such as simple, groovy, or ognl.", "title" : "Log Language", "displayName" : "Log Language", "kind" : "attribute", @@ -12743,7 +12755,7 @@ }, { "name" : "namingStrategy", "type" : "string", - "description" : "If set then Jackson will use the defined Property Naming Strategy. Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE", + "description" : "If set then Jackson will use the the defined Property Naming Strategy.Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE", "title" : "Naming Strategy", "displayName" : "Naming Strategy", "kind" : "attribute", @@ -15785,6 +15797,63 @@ "label" : "advanced" } ] }, + "ognl" : { + "title" : "OGNL", + "description" : "Evaluates an OGNL expression (Apache Commons OGNL)", + "deprecated" : true, + "label" : "language,java", + "children" : [ { + "name" : "id", + "type" : "string", + "description" : "The id of this node.", + "title" : "Id", + "displayName" : "Id", + "kind" : "attribute", + "index" : 0, + "group" : "common" + }, { + "name" : "expression", + "type" : "string", + "description" : "The expression value in your chosen language syntax.", + "title" : "Expression", + "required" : true, + "displayName" : "Expression", + "kind" : "value", + "index" : 1, + "group" : "common" + }, { + "name" : "resultType", + "type" : "string", + "description" : "The class of the result type (type from output).", + "title" : "Result Type", + "displayName" : "Result Type", + "kind" : "attribute", + "index" : 2, + "group" : "common" + }, { + "name" : "trim", + "type" : "boolean", + "description" : "Whether to trim the source code to remove leading and trailing whitespaces and line breaks.", + "title" : "Trim", + "default" : "true", + "displayName" : "Trim", + "kind" : "attribute", + "index" : 3, + "group" : "advanced", + "label" : "advanced" + }, { + "name" : "resolveResource", + "type" : "boolean", + "description" : "Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate.", + "title" : "Resolve Resource", + "default" : "false", + "displayName" : "Resolve Resource", + "kind" : "attribute", + "index" : 4, + "group" : "advanced", + "label" : "advanced" + } ] + }, "python3" : { "title" : "Python 3", "description" : "Evaluates a Python 3 expression", diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json index 841e584a241c..ccf8ac935579 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json @@ -829,6 +829,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -1330,6 +1331,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -1422,6 +1424,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -1557,6 +1560,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -1699,6 +1703,8 @@ "required" : [ "method" ] }, { "required" : [ "mvel" ] + }, { + "required" : [ "ognl" ] }, { "required" : [ "python" ] }, { @@ -1827,6 +1833,14 @@ "$ref" : "#/items/definitions/org.apache.camel.model.language.MvelExpression" } } + }, { + "type" : "object", + "required" : [ "ognl" ], + "properties" : { + "ognl" : { + "$ref" : "#/items/definitions/org.apache.camel.model.language.OgnlExpression" + } + } }, { "type" : "object", "required" : [ "python" ], @@ -1932,6 +1946,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -2089,6 +2104,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -2295,6 +2311,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -2671,7 +2688,7 @@ "logLanguage" : { "type" : "string", "title" : "Log Language", - "description" : "The language to use for evaluating the log message, such as simple or groovy." + "description" : "The language to use for evaluating the log message, such as simple, groovy, or ognl." }, "logName" : { "type" : "string", @@ -2794,6 +2811,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -3667,6 +3685,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -4129,6 +4148,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -4237,6 +4257,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -4399,6 +4420,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -4862,6 +4884,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -5544,6 +5567,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -5736,6 +5760,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -5806,6 +5831,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -5921,6 +5947,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -6034,6 +6061,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -6110,6 +6138,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -6202,6 +6231,8 @@ "required" : [ "method" ] }, { "required" : [ "mvel" ] + }, { + "required" : [ "ognl" ] }, { "required" : [ "python" ] }, { @@ -6279,6 +6310,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -6460,6 +6492,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -6938,6 +6971,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -7376,6 +7410,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -7997,6 +8032,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -8090,6 +8126,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -10130,7 +10167,7 @@ "namingStrategy" : { "type" : "string", "title" : "Naming Strategy", - "description" : "If set then Jackson will use the defined Property Naming Strategy. Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE" + "description" : "If set then Jackson will use the the defined Property Naming Strategy.Possible values are: LOWER_CAMEL_CASE, LOWER_DOT_CASE, LOWER_CASE, KEBAB_CASE, SNAKE_CASE and UPPER_CAMEL_CASE" }, "objectMapper" : { "type" : "string", @@ -12084,6 +12121,14 @@ "$ref" : "#/items/definitions/org.apache.camel.model.language.MvelExpression" } } + }, { + "type" : "object", + "required" : [ "ognl" ], + "properties" : { + "ognl" : { + "$ref" : "#/items/definitions/org.apache.camel.model.language.OgnlExpression" + } + } }, { "type" : "object", "required" : [ "python" ], @@ -12189,6 +12234,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, @@ -12718,6 +12764,47 @@ } ], "required" : [ "expression" ] }, + "org.apache.camel.model.language.OgnlExpression" : { + "title" : "OGNL", + "description" : "Evaluates an OGNL expression (Apache Commons OGNL)", + "deprecated" : true, + "oneOf" : [ { + "type" : "string" + }, { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "expression" : { + "type" : "string", + "title" : "Expression", + "description" : "The expression value in your chosen language syntax." + }, + "id" : { + "type" : "string", + "title" : "Id", + "description" : "The id of this node." + }, + "resolveResource" : { + "type" : "boolean", + "title" : "Resolve Resource", + "description" : "Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate.", + "default" : false + }, + "resultType" : { + "type" : "string", + "title" : "Result Type", + "description" : "The class of the result type (type from output)." + }, + "trim" : { + "type" : "boolean", + "title" : "Trim", + "description" : "Whether to trim the source code to remove leading and trailing whitespaces and line breaks.", + "default" : true + } + } + } ], + "required" : [ "expression" ] + }, "org.apache.camel.model.language.Python3Expression" : { "title" : "Python 3", "description" : "Evaluates a Python 3 expression", @@ -16681,6 +16768,14 @@ "$ref" : "#/items/definitions/org.apache.camel.model.language.MvelExpression" } } + }, { + "type" : "object", + "required" : [ "ognl" ], + "properties" : { + "ognl" : { + "$ref" : "#/items/definitions/org.apache.camel.model.language.OgnlExpression" + } + } }, { "type" : "object", "required" : [ "python" ], @@ -16786,6 +16881,7 @@ "language" : { }, "method" : { }, "mvel" : { }, + "ognl" : { }, "python" : { }, "python3" : { }, "quickjs" : { }, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/TryPropertyClausesTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/TryPropertyClausesTest.java new file mode 100644 index 000000000000..e9d9896d0986 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/TryPropertyClausesTest.java @@ -0,0 +1,147 @@ +/* + * 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.concurrent.TimeUnit; + +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.dsl.yaml.support.YamlTestSupport; +import org.apache.camel.model.CatchDefinition; +import org.apache.camel.model.FinallyDefinition; +import org.apache.camel.model.ToDefinition; +import org.apache.camel.model.TryDefinition; +import org.apache.camel.yaml.LwModelToYAMLDumper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * doCatch and doFinally written as properties of doTry are kept in its outputs, after the steps whatever the YAML key + * order (CAMEL-25410). + */ +class TryPropertyClausesTest extends YamlTestSupport { + + private static final String CLAUSES_ABOVE_STEPS = """ + - route: + id: clauses + from: + uri: direct:start + steps: + - doTry: + doCatch: + - exception: + - java.lang.IllegalStateException + steps: + - to: mock:catch + doFinally: + steps: + - to: mock:finally + steps: + - to: mock:try + - throwException: + exceptionType: java.lang.IllegalStateException + message: Forced + """; + + @Test + void clausesAboveStepsAreAfterTheStepsInTheOutputs() throws Exception { + loadRoutes(CLAUSES_ABOVE_STEPS); + + TryDefinition doTry = (TryDefinition) context.getRouteDefinition("clauses").getOutputs().get(0); + assertThat(doTry.getOutputs()).hasSize(4); + assertThat(doTry.getOutputs().get(0)).isInstanceOf(ToDefinition.class); + assertThat(doTry.getOutputs().get(2)).isInstanceOf(CatchDefinition.class); + assertThat(doTry.getOutputs().get(3)).isInstanceOf(FinallyDefinition.class); + assertThat(doTry.getCatchClauses()).containsExactly((CatchDefinition) doTry.getOutputs().get(2)); + assertThat(doTry.getFinallyClause()).isSameAs(doTry.getOutputs().get(3)); + + assertRuns(); + } + + @Test + void clausesAboveStepsSurviveDumpAndReload() throws Exception { + loadRoutes(CLAUSES_ABOVE_STEPS); + var route = context.getRouteDefinition("clauses"); + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + assertThat(yaml).contains("doCatch:", "doFinally:"); + assertThat(yaml.indexOf("mock:try")).isLessThan(yaml.indexOf("mock:catch")); + assertThat(yaml.indexOf("mock:catch")).isLessThan(yaml.indexOf("mock:finally")); + + context.removeRouteDefinition(route); + loadRoutes(yaml); + + assertRuns(); + } + + @Test + void templatedRoutesRunEachClauseOnce() throws Exception { + loadRoutes(""" + - routeTemplate: + id: myTemplate + from: + uri: "direct:{{name}}" + steps: + - doTry: + steps: + - throwException: + exceptionType: java.lang.IllegalStateException + message: Forced + doCatch: + - exception: + - java.lang.IllegalStateException + steps: + - to: "mock:catch-{{name}}" + doFinally: + steps: + - to: "mock:finally-{{name}}" + - templatedRoute: + routeId: one + routeTemplateRef: myTemplate + parameters: + - name: name + value: one + - templatedRoute: + routeId: two + routeTemplateRef: myTemplate + parameters: + - name: name + value: two + """); + + for (String name : new String[] { "one", "two" }) { + withMock("mock:catch-" + name, mock -> mock.expectedMessageCount(1)); + withMock("mock:finally-" + name, mock -> mock.expectedMessageCount(1)); + } + context.start(); + + withTemplate(t -> t.to("direct:one").withBody("Hello").send()); + withTemplate(t -> t.to("direct:two").withBody("World").send()); + + MockEndpoint.assertIsSatisfied(context); + TryDefinition doTry = (TryDefinition) context.getRouteDefinition("one").getOutputs().get(0); + assertThat(doTry.getCatchClauses()).hasSize(1); + } + + private void assertRuns() throws Exception { + withMock("mock:try", mock -> mock.expectedMessageCount(1)); + withMock("mock:catch", mock -> mock.expectedMessageCount(1)); + withMock("mock:finally", mock -> mock.expectedMessageCount(1)); + context.start(); + withTemplate(t -> t.to("direct:start").withBody("Hello").send()); + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-java-dsl-writer.vm b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-java-dsl-writer.vm index ce06ff42ae16..12dc6184d1c6 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-java-dsl-writer.vm +++ b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-java-dsl-writer.vm @@ -37,7 +37,6 @@ */ package ${package}; -import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.function.BiConsumer; @@ -151,27 +150,7 @@ public class JavaDslModelWriter extends JavaDslModelWriterSupport { #set( $foo = $elementRefs.add($root) ) #if( $list ) #if( $member.name == "outputs" ) - #if( $clazz.simpleName == "TryDefinition" ) - List<ProcessorDefinition<?>> outputs = new ArrayList<>(def.getOutputs()); - for (CatchDefinition clause : def.getCatchClauses()) { - if (!outputs.contains(clause)) { - int index = outputs.size(); - for (int i = 0; i < outputs.size(); i++) { - if (outputs.get(i) instanceof FinallyDefinition) { - index = i; - break; - } - } - outputs.add(index, clause); - } - } - if (def.getFinallyClause() != null && !outputs.contains(def.getFinallyClause())) { - outputs.add(def.getFinallyClause()); - } - doWriteOutputs(sb, outputs, this::doWrite${root.simpleName}Ref); - #else doWriteOutputs(sb, def.${member.getter}(), this::doWrite${root.simpleName}Ref); - #end #else #set( $refName = $member.xmlElementRef.name() ) #if( $refName && $refName != $default && $refName != "" ) diff --git a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm index 8fb03247d608..4b15a7fce29a 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm +++ b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-writer.vm @@ -39,7 +39,6 @@ package ${package}; import java.io.IOException; import java.io.Writer; -import java.util.ArrayList; import java.util.Base64; import java.util.List; import javax.annotation.processing.Generated; @@ -145,27 +144,7 @@ public class ModelWriter extends BaseWriter { #elseif( $member.xmlElementRef ) #set( $foo = $elementRefs.add($root) ) #if( $list ) - #if( $clazz.simpleName == "TryDefinition" && $member.name == "outputs" ) - List<ProcessorDefinition<?>> outputs = new ArrayList<>(def.getOutputs()); - for (CatchDefinition clause : def.getCatchClauses()) { - if (!outputs.contains(clause)) { - int index = outputs.size(); - for (int i = 0; i < outputs.size(); i++) { - if (outputs.get(i) instanceof FinallyDefinition) { - index = i; - break; - } - } - outputs.add(index, clause); - } - } - if (def.getFinallyClause() != null && !outputs.contains(def.getFinallyClause())) { - outputs.add(def.getFinallyClause()); - } - doWriteList(null, null, outputs, this::doWrite${root.simpleName}Ref); - #else doWriteList(null, null, def.${member.getter}(), this::doWrite${root.simpleName}Ref); - #end #else doWriteElement(null, def.${member.getter}(), this::doWrite${root.simpleName}Ref); #end diff --git a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm index 8f294d11b62f..127b2be78341 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm +++ b/tooling/maven/camel-package-maven-plugin/src/main/resources/velocity/model-yaml-writer.vm @@ -148,27 +148,7 @@ public class YamlModelWriter extends YamlModelWriterSupport { #if( $list ) #set( $yamlKey = $yamlKeys.get("${clazz.simpleName}.${member.name}") ) #if( $member.name == "outputs" ) - #if( $clazz.simpleName == "TryDefinition" ) - List<ProcessorDefinition<?>> outputs = new ArrayList<>(def.getOutputs()); - for (CatchDefinition clause : def.getCatchClauses()) { - if (!outputs.contains(clause)) { - int index = outputs.size(); - for (int i = 0; i < outputs.size(); i++) { - if (outputs.get(i) instanceof FinallyDefinition) { - index = i; - break; - } - } - outputs.add(index, clause); - } - } - if (def.getFinallyClause() != null && !outputs.contains(def.getFinallyClause())) { - outputs.add(def.getFinallyClause()); - } - doWriteOutputs(jo, outputs, this::doWrite${root.simpleName}Ref); - #else doWriteOutputs(jo, def.${member.getter}(), this::doWrite${root.simpleName}Ref); - #end #elseif( $clazz.simpleName == "RestDefinition" && $member.name == "verbs" ) doWriteVerbs(jo, def.${member.getter}(), this::doWrite${root.simpleName}); #elseif( $yamlKey )
