This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit ab118f72431ecd124e4ead85b5d4e0f44b16e0d7 Author: Luigi De Masi <[email protected]> AuthorDate: Tue Sep 29 15:58:12 2026 +0200 CAMEL-24988: Represent Switch fallback as an endpoint element Use an otherwise child with a required uri in XML and an otherwise URI object in YAML. Preserve the Java fluent API and literal matching rules, and regenerate DSL metadata, schemas, and documentation samples. Co-authored-by: Codex <[email protected]> Signed-off-by: Luigi De Masi <[email protected]> --- .../org/apache/camel/catalog/docs/switch-eip.adoc | 18 +++++--- .../org/apache/camel/catalog/models.properties | 1 + .../org/apache/camel/catalog/models/switch.json | 2 +- .../camel/catalog/models/switchOtherwise.json | 17 +++++++ .../apache/camel/catalog/schemas/camel-spring.xsd | 35 +++++++++++--- .../apache/camel/catalog/schemas/camel-xml-io.xsd | 35 +++++++++++--- .../camel/catalog/schemas/camelYamlDsl-model.json | 24 +++++++++- .../org/apache/camel/spring/SwitchJaxbTest.java | 1 + .../main/docs/modules/eips/pages/switch-eip.adoc | 18 +++++--- .../META-INF/org/apache/camel/model/switch.json | 2 +- .../org/apache/camel/model/switchOtherwise.json | 17 +++++++ .../services/org/apache/camel/model.properties | 1 + .../resources/org/apache/camel/model/jaxb.index | 1 + .../org/apache/camel/model/SwitchDefinition.java | 23 ++++++---- .../camel/model/SwitchOtherwiseDefinition.java | 53 ++++++++++++++++++++++ .../org/apache/camel/reifier/SwitchReifier.java | 2 +- .../apache/camel/processor/SwitchEndpointTest.java | 8 +++- .../org/apache/camel/processor/SwitchTest.java | 6 ++- .../camel/processor/SwitchValidationTest.java | 11 +++++ .../apache/camel/java/out/JavaDslModelWriter.java | 12 ++++- .../camel/java/out/JavaDslModelWriterSupport.java | 2 +- .../camel/java/out/JavaDslModelWriterTest.java | 2 + .../src/test/resources/switchLiteral.xml | 6 ++- .../camel/management/mbean/ManagedSwitch.java | 2 +- .../apache/camel/management/ManagedSwitchTest.java | 1 + .../java/org/apache/camel/xml/in/ModelParser.java | 12 +++-- .../java/org/apache/camel/xml/out/ModelWriter.java | 10 +++- .../org/apache/camel/yaml/out/YamlModelWriter.java | 10 +++- .../dsl/jbang/core/commands/ai/eip-samples.json | 6 +-- .../org/apache/camel/dsl/xml/io/XmlSwitchTest.java | 3 +- .../dsl/yaml/deserializers/ModelDeserializers.java | 51 ++++++++++++++++++++- .../deserializers/ModelDeserializersResolver.java | 2 + .../camel/dsl/yaml/validator/SwitchSchemaTest.java | 50 ++++++++++++++++++++ .../resources/schema/camelYamlDsl-canonical.json | 21 ++++++++- .../resources/schema/camelYamlDsl-model.json | 24 +++++++++- .../generated/resources/schema/camelYamlDsl.json | 21 ++++++++- .../java/org/apache/camel/dsl/yaml/SwitchTest.java | 25 +++++++++- .../apache/camel/dsl/yaml/SwitchYamlDumpTest.java | 5 ++ 38 files changed, 473 insertions(+), 67 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc index 610202d2c2c0..1f20dde1adbb 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc @@ -55,10 +55,11 @@ XML:: ---- <route xmlns="http://camel.apache.org/schema/spring"> <from uri="direct:tickets"/> - <switch otherwise="direct:review"> + <switch> <selector><header>department</header></selector> <case value="billing" uri="direct:billing"/> <case value="technical" uri="direct:technical"/> + <otherwise uri="direct:review"/> </switch> </route> ---- @@ -79,7 +80,8 @@ YAML:: uri: direct:billing - value: technical uri: direct:technical - otherwise: direct:review + otherwise: + uri: direct:review ---- ==== @@ -151,7 +153,7 @@ XML:: ---- <route xmlns="http://camel.apache.org/schema/spring"> <from uri="direct:tickets"/> - <switch otherwise="direct:review"> + <switch> <selector><header>decision</header></selector> <keys>department</keys> <keys>urgent</keys> @@ -167,6 +169,7 @@ XML:: <values name="department" value="technical"/> <values name="urgent" type="boolean" value="true"/> </case> + <otherwise uri="direct:review"/> </switch> </route> ---- @@ -191,7 +194,8 @@ YAML:: uri: direct:billing - values: [department: technical, urgent: true] uri: direct:incident - otherwise: direct:review + otherwise: + uri: direct:review ---- ==== @@ -284,7 +288,7 @@ XML:: ---- <route xmlns="http://camel.apache.org/schema/spring"> <from uri="direct:tickets"/> - <switch otherwise="direct:review"> + <switch> <selector><language language="semantic">refs:department,urgent</language></selector> <keys>department</keys> <keys>urgent</keys> @@ -300,6 +304,7 @@ XML:: <values name="department" value="technical"/> <values name="urgent" type="boolean" value="true"/> </case> + <otherwise uri="direct:review"/> </switch> </route> ---- @@ -335,7 +340,8 @@ YAML:: uri: direct:billing - values: [department: technical, urgent: true] uri: direct:incident - otherwise: direct:review + otherwise: + uri: direct:review ---- ==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models.properties index 70faa2f60b2a..0e50bde32a49 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models.properties @@ -198,6 +198,7 @@ streamConfig swiftMt swiftMx switch +switchOtherwise switchValue syslog tarFile diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json index c557fd3550a3..d4158f770eff 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json @@ -20,6 +20,6 @@ "selector": { "index": 4, "kind": "expression", "displayName": "Selector", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.ExpressionSubElementDefinition", "oneOf": [ "constant", "datasonnet", "exchangeProperty", "groovy", "header", "hl7terser", "jactl", "java", "jq", "js", "jsonpath", "language", "method", "mvel", "ognl", "python", "python3", "quickjs", "ref", "simple", "spel", "tokenize", "variable", "wasm", "xpath", "xquery", "xtokenize" [...] "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List<java.lang.String>", "deprecated": false, "autowired": false, "secret": false, "description": "Exact map field names forming a composite key. Additional result fields are ignored." }, "case": { "index": 6, "kind": "element", "displayName": "Case", "group": "common", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.SwitchCaseDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Literal cases. Duplicate combinations are rejected at startup." }, - "otherwise": { "index": 7, "kind": "attribute", "displayName": "Otherwise", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues." } + "otherwise": { "index": 7, "kind": "element", "displayName": "Otherwise", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.SwitchOtherwiseDefinition", "oneOf": [ "otherwise" ], "deprecated": false, "autowired": false, "secret": false, "description": "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchOtherwise.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchOtherwise.json new file mode 100644 index 000000000000..ef34bee1569a --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchOtherwise.json @@ -0,0 +1,17 @@ +{ + "model": { + "kind": "model", + "name": "switchOtherwise", + "title": "Switch Otherwise", + "description": "The fixed endpoint used when no switch case matches", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.SwitchOtherwiseDefinition", + "abstract": false, + "input": false, + "output": false + }, + "properties": { + "uri": { "index": 0, "kind": "attribute", "displayName": "Uri", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The fixed destination URI. Supports property placeholders, but not Simple expressions." } + } +} diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd index 2477aa7cfd2b..54d85d44a379 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd @@ -2018,6 +2018,15 @@ Encode and decode SWIFT MX messages <xs:documentation xml:lang="en"> <![CDATA[ Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values +]]> + </xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="switchOtherwise" type="tns:switchOtherwiseDefinition"> + <xs:annotation> + <xs:documentation xml:lang="en"> +<![CDATA[ +The fixed endpoint used when no switch case matches ]]> </xs:documentation> </xs:annotation> @@ -13999,19 +14008,31 @@ Exact map field names forming a composite key. Additional result fields are igno </xs:annotation> </xs:element> <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:case"/> - </xs:sequence> - <xs:attribute name="otherwise" type="xs:string"> - <xs:annotation> - <xs:documentation xml:lang="en"> + <xs:element minOccurs="0" name="otherwise" type="tns:switchOtherwiseDefinition"> + <xs:annotation> + <xs:documentation xml:lang="en"> <![CDATA[ Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues. ]]> - </xs:documentation> - </xs:annotation> - </xs:attribute> + </xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> + <xs:complexType name="switchOtherwiseDefinition"> + <xs:sequence/> + <xs:attribute name="uri" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> +<![CDATA[ +The fixed destination URI. Supports property placeholders, but not Simple expressions. +]]> + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> <xs:complexType final="extension restriction" name="caseBuilder"> <xs:sequence/> </xs:complexType> diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd index 4b76d226029d..4902984793f5 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd @@ -1847,6 +1847,15 @@ Encode and decode SWIFT MX messages <xs:documentation xml:lang="en"> <![CDATA[ Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values +]]> + </xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="switchOtherwise" type="tns:switchOtherwiseDefinition"> + <xs:annotation> + <xs:documentation xml:lang="en"> +<![CDATA[ +The fixed endpoint used when no switch case matches ]]> </xs:documentation> </xs:annotation> @@ -13142,19 +13151,31 @@ Exact map field names forming a composite key. Additional result fields are igno </xs:annotation> </xs:element> <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:case"/> - </xs:sequence> - <xs:attribute name="otherwise" type="xs:string"> - <xs:annotation> - <xs:documentation xml:lang="en"> + <xs:element minOccurs="0" name="otherwise" type="tns:switchOtherwiseDefinition"> + <xs:annotation> + <xs:documentation xml:lang="en"> <![CDATA[ Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues. ]]> - </xs:documentation> - </xs:annotation> - </xs:attribute> + </xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> + <xs:complexType name="switchOtherwiseDefinition"> + <xs:sequence/> + <xs:attribute name="uri" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation xml:lang="en"> +<![CDATA[ +The fixed destination URI. Supports property placeholders, but not Simple expressions. +]]> + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> <xs:complexType final="extension restriction" name="caseBuilder"> <xs:sequence/> </xs:complexType> diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json index 50e78118351f..d505bf5061b6 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camelYamlDsl-model.json @@ -8446,15 +8446,35 @@ "group" : "common" }, { "name" : "otherwise", - "type" : "string", + "type" : "object", + "ref" : "switchOtherwise", "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", "title" : "Otherwise", "displayName" : "Otherwise", - "kind" : "attribute", + "kind" : "element", "index" : 7, "group" : "common" } ] }, + "switchOtherwise" : { + "title" : "Switch Otherwise", + "description" : "The fixed endpoint used when no switch case matches", + "label" : "configuration", + "children" : [ { + "name" : "uri", + "type" : "string", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions.", + "title" : "Uri", + "required" : true, + "displayName" : "Uri", + "kind" : "attribute", + "index" : 0, + "group" : "common" + }, { + "name" : "parameters", + "type" : "object" + } ] + }, "switchValue" : { "title" : "Switch Value", "description" : "A named literal in a composite switch case", diff --git a/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/SwitchJaxbTest.java b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/SwitchJaxbTest.java index 5c3eec5f775e..b52ff8f335f6 100644 --- a/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/SwitchJaxbTest.java +++ b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/SwitchJaxbTest.java @@ -75,6 +75,7 @@ class SwitchJaxbTest { assertEquals("urn:tickets", xpath.getNamespaces().get("t")); assertEquals("billing", choice.getCases().get(0).getValue()); assertEquals("mock:billing", choice.getCases().get(0).getUri()); + assertEquals("mock:other", choice.getOtherwise().getUri()); } } } diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc index 610202d2c2c0..1f20dde1adbb 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc @@ -55,10 +55,11 @@ XML:: ---- <route xmlns="http://camel.apache.org/schema/spring"> <from uri="direct:tickets"/> - <switch otherwise="direct:review"> + <switch> <selector><header>department</header></selector> <case value="billing" uri="direct:billing"/> <case value="technical" uri="direct:technical"/> + <otherwise uri="direct:review"/> </switch> </route> ---- @@ -79,7 +80,8 @@ YAML:: uri: direct:billing - value: technical uri: direct:technical - otherwise: direct:review + otherwise: + uri: direct:review ---- ==== @@ -151,7 +153,7 @@ XML:: ---- <route xmlns="http://camel.apache.org/schema/spring"> <from uri="direct:tickets"/> - <switch otherwise="direct:review"> + <switch> <selector><header>decision</header></selector> <keys>department</keys> <keys>urgent</keys> @@ -167,6 +169,7 @@ XML:: <values name="department" value="technical"/> <values name="urgent" type="boolean" value="true"/> </case> + <otherwise uri="direct:review"/> </switch> </route> ---- @@ -191,7 +194,8 @@ YAML:: uri: direct:billing - values: [department: technical, urgent: true] uri: direct:incident - otherwise: direct:review + otherwise: + uri: direct:review ---- ==== @@ -284,7 +288,7 @@ XML:: ---- <route xmlns="http://camel.apache.org/schema/spring"> <from uri="direct:tickets"/> - <switch otherwise="direct:review"> + <switch> <selector><language language="semantic">refs:department,urgent</language></selector> <keys>department</keys> <keys>urgent</keys> @@ -300,6 +304,7 @@ XML:: <values name="department" value="technical"/> <values name="urgent" type="boolean" value="true"/> </case> + <otherwise uri="direct:review"/> </switch> </route> ---- @@ -335,7 +340,8 @@ YAML:: uri: direct:billing - values: [department: technical, urgent: true] uri: direct:incident - otherwise: direct:review + otherwise: + uri: direct:review ---- ==== diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json index c557fd3550a3..d4158f770eff 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json @@ -20,6 +20,6 @@ "selector": { "index": 4, "kind": "expression", "displayName": "Selector", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.ExpressionSubElementDefinition", "oneOf": [ "constant", "datasonnet", "exchangeProperty", "groovy", "header", "hl7terser", "jactl", "java", "jq", "js", "jsonpath", "language", "method", "mvel", "ognl", "python", "python3", "quickjs", "ref", "simple", "spel", "tokenize", "variable", "wasm", "xpath", "xquery", "xtokenize" [...] "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List<java.lang.String>", "deprecated": false, "autowired": false, "secret": false, "description": "Exact map field names forming a composite key. Additional result fields are ignored." }, "case": { "index": 6, "kind": "element", "displayName": "Case", "group": "common", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.SwitchCaseDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Literal cases. Duplicate combinations are rejected at startup." }, - "otherwise": { "index": 7, "kind": "attribute", "displayName": "Otherwise", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues." } + "otherwise": { "index": 7, "kind": "element", "displayName": "Otherwise", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.SwitchOtherwiseDefinition", "oneOf": [ "otherwise" ], "deprecated": false, "autowired": false, "secret": false, "description": "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues." } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchOtherwise.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchOtherwise.json new file mode 100644 index 000000000000..ef34bee1569a --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchOtherwise.json @@ -0,0 +1,17 @@ +{ + "model": { + "kind": "model", + "name": "switchOtherwise", + "title": "Switch Otherwise", + "description": "The fixed endpoint used when no switch case matches", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.SwitchOtherwiseDefinition", + "abstract": false, + "input": false, + "output": false + }, + "properties": { + "uri": { "index": 0, "kind": "attribute", "displayName": "Uri", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The fixed destination URI. Supports property placeholders, but not Simple expressions." } + } +} diff --git a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties index bfdfc89a0ebf..b68bb04e5d7f 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties +++ b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties @@ -199,6 +199,7 @@ streamConfig swiftMt swiftMx switch +switchOtherwise switchValue syslog tarFile diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index b/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index index 13090d116b52..88d41e0cfb88 100644 --- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index +++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index @@ -99,6 +99,7 @@ StepDefinition StopDefinition SwitchCaseDefinition SwitchDefinition +SwitchOtherwiseDefinition SwitchValueDefinition TemplatedRouteDefinition TemplatedRouteParameterDefinition diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java index 32284c8d8f15..badf6610b656 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java @@ -23,7 +23,6 @@ import java.util.Map; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlTransient; @@ -40,7 +39,7 @@ import org.apache.camel.spi.Metadata; description = "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values") @XmlRootElement(name = "switch") @XmlAccessorType(XmlAccessType.FIELD) -@XmlType(propOrder = { "selector", "keys", "cases" }) +@XmlType(propOrder = { "selector", "keys", "cases", "otherwise" }) public class SwitchDefinition extends NoOutputDefinition<SwitchDefinition> { @XmlElement(required = true) @Metadata(required = true, @@ -52,9 +51,9 @@ public class SwitchDefinition extends NoOutputDefinition<SwitchDefinition> { @XmlElement(name = "case") @Metadata(description = "Literal cases. Duplicate combinations are rejected at startup.") private List<SwitchCaseDefinition> cases = new ArrayList<>(); - @XmlAttribute + @XmlElement @Metadata(description = "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.") - private String otherwise; + private SwitchOtherwiseDefinition otherwise; @XmlTransient private ToDefinition otherwiseDefinition; @@ -69,7 +68,7 @@ public class SwitchDefinition extends NoOutputDefinition<SwitchDefinition> { super(source); this.selector = source.selector != null ? source.selector.copyDefinition() : null; this.keys = new ArrayList<>(source.keys); - this.otherwise = source.otherwise; + this.otherwise = source.otherwise != null ? source.otherwise.copyDefinition() : null; for (SwitchCaseDefinition c : source.cases) { SwitchCaseDefinition copy = c.copyDefinition(); copy.setParent(this); @@ -116,11 +115,11 @@ public class SwitchDefinition extends NoOutputDefinition<SwitchDefinition> { this.cases = cases; } - public String getOtherwise() { + public SwitchOtherwiseDefinition getOtherwise() { return otherwise; } - public void setOtherwise(String otherwise) { + public void setOtherwise(SwitchOtherwiseDefinition otherwise) { this.otherwise = otherwise; this.otherwiseDefinition = null; } @@ -132,9 +131,10 @@ public class SwitchDefinition extends NoOutputDefinition<SwitchDefinition> { return null; } if (otherwiseDefinition == null) { - otherwiseDefinition = new ToDefinition(otherwise); + otherwiseDefinition = new ToDefinition(); otherwiseDefinition.setParent(this); } + otherwiseDefinition.setUri(otherwise.getUri()); if (getId() != null) { if (hasCustomIdAssigned()) { otherwiseDefinition.setId(getId() + "-otherwise"); @@ -188,7 +188,12 @@ public class SwitchDefinition extends NoOutputDefinition<SwitchDefinition> { /** Set the fixed fallback destination. */ public SwitchDefinition otherwise(String uri) { - setOtherwise(uri); + SwitchOtherwiseDefinition fallback = null; + if (uri != null) { + fallback = new SwitchOtherwiseDefinition(); + fallback.setUri(uri); + } + setOtherwise(fallback); return this; } diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchOtherwiseDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchOtherwiseDefinition.java new file mode 100644 index 000000000000..0c5e79d282bd --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchOtherwiseDefinition.java @@ -0,0 +1,53 @@ +/* + * 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 jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlRootElement; + +import org.apache.camel.spi.Metadata; + +/** The fixed fallback destination of a switch. */ +@Metadata(label = "configuration", description = "The fixed endpoint used when no switch case matches") +@XmlRootElement(name = "switchOtherwise") +@XmlAccessorType(XmlAccessType.FIELD) +public class SwitchOtherwiseDefinition implements EndpointRequiredDefinition { + @XmlAttribute(required = true) + @Metadata(description = "The fixed destination URI. Supports property placeholders, but not Simple expressions.") + private String uri; + + public SwitchOtherwiseDefinition copyDefinition() { + SwitchOtherwiseDefinition copy = new SwitchOtherwiseDefinition(); + copy.uri = uri; + return copy; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + @Override + public String getEndpointUri() { + return uri; + } +} diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java index d156792a8482..b96ebd1152c3 100644 --- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java +++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java @@ -80,7 +80,7 @@ public class SwitchReifier extends ProcessorReifier<SwitchDefinition> { if (new HashSet<>(caseKeys).size() != caseKeys.size()) { throw new IllegalArgumentException("Duplicate switch case after literal normalization"); } - String otherwiseUri = definition.getOtherwise() == null ? null : staticUri(definition.getOtherwise()); + String otherwiseUri = definition.getOtherwise() == null ? null : staticUri(definition.getOtherwise().getUri()); Expression selector = createExpression(definition.getSelector().getExpressionType()); NodeIdFactory ids = camelContext.getCamelContextExtension().getContextPlugin(NodeIdFactory.class); Map<Object, Processor> cases = new LinkedHashMap<>(); diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java index fc3aa5b880a0..eaf5952a4da7 100644 --- a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java @@ -30,6 +30,7 @@ import org.apache.camel.support.ExpressionAdapter; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class SwitchEndpointTest extends ContextTestSupport { @@ -40,10 +41,15 @@ class SwitchEndpointTest extends ContextTestSupport { @Test void routeEndpointDiscoveryIncludesCaseAndFallback() { RouteDefinition route = new RouteDefinition("direct:discovery"); - route.doSwitch(new HeaderExpression("decision")).doCase("match", "mock:destination").otherwise("mock:fallback"); + var sw = route.doSwitch(new HeaderExpression("decision")).doCase("match", "mock:destination") + .otherwise("mock:fallback"); var uris = RouteDefinitionHelper.gatherAllStaticEndpointUris(context, route, false, true); assertTrue(uris.contains("mock://destination"), uris.toString()); assertTrue(uris.contains("mock://fallback"), uris.toString()); + sw.getOtherwise().setUri("mock:updated"); + uris = RouteDefinitionHelper.gatherAllStaticEndpointUris(context, route, false, true); + assertTrue(uris.contains("mock://updated"), uris.toString()); + assertFalse(uris.contains("mock://fallback"), uris.toString()); } @Test diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java index ac089634b840..de30c94bb77f 100644 --- a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java @@ -43,9 +43,10 @@ class SwitchTest extends ContextTestSupport { getMockEndpoint("mock:billing").expectedMessageCount(2); getMockEndpoint("mock:empty").expectedMessageCount(1); getMockEndpoint("mock:literal").expectedMessageCount(1); + getMockEndpoint("mock:otherwiseLiteral").expectedMessageCount(1); getMockEndpoint("mock:review").expectedMessageCount(3); - getMockEndpoint("mock:after").expectedMessageCount(7); - for (String value : new String[] { "billing", "BILLING", "", "billing*", "billing123", "other" }) { + getMockEndpoint("mock:after").expectedMessageCount(8); + for (String value : new String[] { "billing", "BILLING", "", "billing*", "billing123", "other", "OTHERWISE" }) { template.sendBodyAndHeader("direct:scalar", "message", "department", value); } template.sendBody("direct:scalar", "message"); @@ -153,6 +154,7 @@ class SwitchTest extends ContextTestSupport { public void configure() { from("direct:scalar").doSwitch().header("department") .doCase("billing", "mock:billing").doCase("", "mock:empty").doCase("billing*", "mock:literal") + .doCase("otherwise", "mock:otherwiseLiteral") .otherwise("mock:review").end().to("mock:after"); from("direct:composite").doSwitch(header("decision")).keys("department", "urgent") .doCase().value("department", "billing").value("urgent", true).id("urgentCase").to("mock:urgent") diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java index 5b1271eeb56f..078175d64d39 100644 --- a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java @@ -22,6 +22,7 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.model.ExpressionSubElementDefinition; import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.SwitchOtherwiseDefinition; import org.apache.camel.model.SwitchValueDefinition; import org.apache.camel.model.language.HeaderExpression; import org.junit.jupiter.api.Test; @@ -46,6 +47,16 @@ class SwitchValidationTest { rejects(s -> s.doCase("billing", null), "nonblank uri"); } + @Test + void rejectsMissingFallbackUri() { + rejects(s -> s.setOtherwise(new SwitchOtherwiseDefinition()), "nonblank uri"); + } + + @Test + void rejectsDynamicFallbackUri() { + rejects(s -> s.otherwise("mock:${header.target}"), "must be static"); + } + @Test void rejectsDynamicUri() { rejects(s -> s.doCase("billing", "mock:${header.target}"), "must be static"); 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 360a82b59925..1d5aefcd29a1 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 @@ -676,6 +676,13 @@ public class JavaDslModelWriter extends JavaDslModelWriterSupport { writeSwitch(sb, def); return sb.toString(); } + public String writeSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) { + resetState(); + StringBuilder sb = new StringBuilder(); + beginStep(sb, "switchOtherwise", def); + doWriteSwitchOtherwiseDefinition(sb, def); + return sb.toString(); + } public String writeSwitchValueDefinition(SwitchValueDefinition def) { resetState(); StringBuilder sb = new StringBuilder(); @@ -2604,10 +2611,13 @@ public class JavaDslModelWriter extends JavaDslModelWriterSupport { } protected void doWriteSwitchDefinition(StringBuilder sb, SwitchDefinition def) { doWriteProcessorDefinitionAttributes(sb, def); - doWriteAttribute(sb, "otherwise", def.getOtherwise(), null); doWriteChildElement(sb, "selector", def.getSelector(), this::doWriteExpressionSubElementDefinition); doWriteStringList(sb, null, "keys", def.getKeys()); doWriteChildList(sb, "case", def.getCases(), this::doWriteSwitchCaseDefinition); + doWriteChildElement(sb, "otherwise", def.getOtherwise(), this::doWriteSwitchOtherwiseDefinition); + } + protected void doWriteSwitchOtherwiseDefinition(StringBuilder sb, SwitchOtherwiseDefinition def) { + doWriteAttribute(sb, "uri", def.getUri(), null); } protected void doWriteSwitchValueDefinition(StringBuilder sb, SwitchValueDefinition def) { doWriteAttribute(sb, "name", def.getName(), null); diff --git a/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java b/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java index f163c5e50026..bd41f7532499 100644 --- a/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java +++ b/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java @@ -129,7 +129,7 @@ public abstract class JavaDslModelWriterSupport { sb.append(".to(").append(quote(c.getUri())).append(")"); } if (definition.getOtherwise() != null) { - sb.append(NL).append(indent()).append(".otherwise(").append(quote(definition.getOtherwise())).append(")"); + sb.append(NL).append(indent()).append(".otherwise(").append(quote(definition.getOtherwise().getUri())).append(")"); } sb.append(NL).append(indent()).append(".end()"); } diff --git a/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslModelWriterTest.java b/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslModelWriterTest.java index f7abd5e1d43c..c75129c61077 100644 --- a/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslModelWriterTest.java +++ b/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslModelWriterTest.java @@ -52,6 +52,7 @@ public class JavaDslModelWriterTest { SwitchDefinition sw = new SwitchDefinition(new HeaderExpression("decision")); sw.id("dispatch").disabled(true).description("Table dispatch").note("dispatch note"); sw.doCase("billing").id("billingCase").description("Billing destination").note("case note").to("mock:billing"); + sw.otherwise("mock:review"); JavaDslModelWriter writer = new JavaDslModelWriter(); String java = writer.writeSwitchDefinition(sw); Assertions.assertTrue(java.contains(".doSwitch(header(\"decision\"))"), java); @@ -60,6 +61,7 @@ public class JavaDslModelWriterTest { Assertions.assertTrue(java.contains(".description(\"Table dispatch\")"), java); Assertions.assertTrue(java.contains(".id(\"billingCase\")"), java); Assertions.assertTrue(java.contains(".note(\"case note\")"), java); + Assertions.assertTrue(java.contains(".otherwise(\"mock:review\")"), java); sw.setCustomId(false); sw.getCases().get(0).setCustomId(false); java = writer.writeSwitchDefinition(sw); diff --git a/core/camel-java-io/src/test/resources/switchLiteral.xml b/core/camel-java-io/src/test/resources/switchLiteral.xml index da1c216ada3e..eaeaf5aea17c 100644 --- a/core/camel-java-io/src/test/resources/switchLiteral.xml +++ b/core/camel-java-io/src/test/resources/switchLiteral.xml @@ -20,11 +20,12 @@ <routes xmlns="http://camel.apache.org/schema/xml-io"> <route id="switchExport"> <from uri="direct:start"/> - <switch id="scalar" disabled="false" description="Scalar dispatch" note="dispatch note" otherwise="mock:other"> + <switch id="scalar" disabled="false" description="Scalar dispatch" note="dispatch note"> <selector><header>department</header></selector> <case id="billing" description="Billing destination" note="case note" value="billing" uri="mock:billing"/> + <otherwise uri="mock:other"/> </switch> - <switch id="composite" otherwise="mock:review"> + <switch id="composite"> <selector><header>decision</header></selector> <keys>department</keys><keys>urgent</keys><keys>score</keys> <case id="urgent" uri="mock:urgent"> @@ -32,6 +33,7 @@ <values name="urgent" type="boolean" value="true"/> <values name="score" type="number" value="2.0"/> </case> + <otherwise uri="mock:review"/> </switch> <to uri="mock:after"/> </route> diff --git a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedSwitch.java b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedSwitch.java index ad11eb423ec4..e2cfd746e6c1 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedSwitch.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedSwitch.java @@ -72,7 +72,7 @@ public class ManagedSwitch extends ManagedProcessor implements ManagedSwitchMBea c.getUri(), getProcessor().getMatchedCount(i)); } if (getDefinition().getOtherwise() != null) { - addRow(table, getDefinition().getId() + "-otherwise", "otherwise", getDefinition().getOtherwise(), + addRow(table, getDefinition().getId() + "-otherwise", "otherwise", getDefinition().getOtherwise().getUri(), getUnmatchedCount()); } return table; diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java index e41f21a22c5a..689556d51231 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java @@ -47,6 +47,7 @@ class ManagedSwitchTest extends ManagementTestSupport { assertEquals("mock:urgent", first.get("uri")); assertEquals(2L, first.get("matches")); assertEquals(1L, table.get(new Object[] { 1 }).get("matches")); + assertEquals("mock:review", table.get(new Object[] { 1 }).get("uri")); assertTrue(getMBeanServer().isRegistered(getCamelObjectName(TYPE_PROCESSOR, "p-urgentCase")), getMBeanServer().queryNames(new ObjectName("org.apache.camel:type=processors,*"), null).toString()); assertTrue(getMBeanServer().isRegistered(getCamelObjectName(TYPE_PROCESSOR, "p-dispatch-otherwise"))); diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java index 8675447cb92f..5cfe23f033d0 100644 --- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java +++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java @@ -1200,16 +1200,20 @@ public class ModelParser extends BaseParser { }, noElementHandler(), noValueHandler()); } protected SwitchDefinition doParseSwitchDefinition() throws IOException, XmlPullParserException { - return doParse(new SwitchDefinition(), (def, key, val) -> switch (key) { - case "otherwise": def.setOtherwise(val); yield true; - default: yield processorDefinitionAttributeHandler().accept(def, key, val); - }, (def, key) -> switch (key) { + return doParse(new SwitchDefinition(), processorDefinitionAttributeHandler(), (def, key) -> switch (key) { case "case": doAdd(doParseSwitchCaseDefinition(), def.getCases(), def::setCases); yield true; case "keys": doAdd(doParseText(), def.getKeys(), def::setKeys); yield true; + case "otherwise": def.setOtherwise(doParseSwitchOtherwiseDefinition()); yield true; case "selector": def.setSelector(doParseExpressionSubElementDefinition()); yield true; default: yield optionalIdentifiedDefinitionElementHandler().accept(def, key); }, noValueHandler()); } + protected SwitchOtherwiseDefinition doParseSwitchOtherwiseDefinition() throws IOException, XmlPullParserException { + return doParse(new SwitchOtherwiseDefinition(), (def, key, val) -> switch (key) { + case "uri": def.setUri(sanitizeUri(val)); yield true; + default: yield false; + }, noElementHandler(), noValueHandler()); + } protected TemplatedRouteDefinition doParseTemplatedRouteDefinition() throws IOException, XmlPullParserException { return doParse(new TemplatedRouteDefinition(), (def, key, val) -> switch (key) { case "group": def.setGroup(val); yield true; 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 65530d7a495b..5d77377031dd 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 @@ -326,6 +326,9 @@ public class ModelWriter extends BaseWriter { public void writeSwitchDefinition(SwitchDefinition def) throws IOException { doWriteSwitchDefinition("switch", def); } + public void writeSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) throws IOException { + doWriteSwitchOtherwiseDefinition("switchOtherwise", def); + } public void writeSwitchValueDefinition(SwitchValueDefinition def) throws IOException { doWriteSwitchValueDefinition("switchValue", def); } @@ -1840,10 +1843,15 @@ public class ModelWriter extends BaseWriter { protected void doWriteSwitchDefinition(String name, SwitchDefinition def) throws IOException { startElement(name); doWriteProcessorDefinitionAttributes(def); - doWriteAttribute("otherwise", def.getOtherwise(), null); doWriteElement("selector", def.getSelector(), this::doWriteExpressionSubElementDefinition); doWriteList(null, "keys", def.getKeys(), this::doWriteString); doWriteList(null, "case", def.getCases(), this::doWriteSwitchCaseDefinition); + doWriteElement("otherwise", def.getOtherwise(), this::doWriteSwitchOtherwiseDefinition); + endElement(name); + } + protected void doWriteSwitchOtherwiseDefinition(String name, SwitchOtherwiseDefinition def) throws IOException { + startElement(name); + doWriteAttribute("uri", def.getUri(), null); endElement(name); } protected void doWriteSwitchValueDefinition(String name, SwitchValueDefinition def) throws IOException { 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 02633f4f7c06..3b2a3eac565f 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 @@ -319,6 +319,9 @@ public class YamlModelWriter extends YamlModelWriterSupport { public JsonObject writeSwitchDefinition(SwitchDefinition def) { return wrapNode("switch", doWriteSwitchDefinition(def)); } + public JsonObject writeSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) { + return wrapNode("switchOtherwise", doWriteSwitchOtherwiseDefinition(def)); + } public JsonObject writeSwitchValueDefinition(SwitchValueDefinition def) { return wrapNode("switchValue", doWriteSwitchValueDefinition(def)); } @@ -1839,10 +1842,15 @@ public class YamlModelWriter extends YamlModelWriterSupport { protected JsonObject doWriteSwitchDefinition(SwitchDefinition def) { JsonObject jo = new JsonObject(); doWriteProcessorDefinitionAttributes(jo, def); - doWriteAttribute(jo, "otherwise", def.getOtherwise(), null); doWriteChildElement(jo, "selector", def.getSelector(), this::doWriteExpressionSubElementDefinition); doWriteStringList(jo, null, "keys", def.getKeys()); doWriteChildList(jo, null, "case", def.getCases(), this::doWriteSwitchCaseDefinition); + doWriteChildElement(jo, "otherwise", def.getOtherwise(), this::doWriteSwitchOtherwiseDefinition); + return jo; + } + protected JsonObject doWriteSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) { + JsonObject jo = new JsonObject(); + doWriteAttribute(jo, "uri", def.getUri(), null); return jo; } protected JsonObject doWriteSwitchValueDefinition(SwitchValueDefinition def) { diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json index d330b5fbc7b7..0d9fd35b2073 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json @@ -1406,15 +1406,15 @@ "switch": [ { "source": "switch-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n header:\n expression: department\n case:\n - value: billing\n uri: direct:billing\n - value: technical\n uri: direct:technical\n otherwise: direct:review\n" + "yaml": "- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n header:\n expression: department\n case:\n - value: billing\n uri: direct:billing\n - value: technical\n uri: direct:technical\n otherwise:\n uri: direct:review\n" }, { "source": "switch-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n header:\n expression: decision\n keys: [department, urgent]\n case:\n - id: urgentBilling\n values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n [...] + "yaml": "- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n header:\n expression: decision\n keys: [department, urgent]\n case:\n - id: urgentBilling\n values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n [...] }, { "source": "switch-eip.adoc", - "yaml": "- semantic:\n question:\n department:\n type: choice\n instructions: Which department should handle this message?\n criteria:\n billing: Invoices, payments and refunds\n technical: Bugs, outages and technical problems\n urgent:\n type: boolean\n instructions: Does this request need immediate attention?\n- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n [...] + "yaml": "- semantic:\n question:\n department:\n type: choice\n instructions: Which department should handle this message?\n criteria:\n billing: Invoices, payments and refunds\n technical: Bugs, outages and technical problems\n urgent:\n type: boolean\n instructions: Does this request need immediate attention?\n- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n [...] }, { "source": "switch-eip.adoc", diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSwitchTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSwitchTest.java index bdc1bdb34020..5a2f494fe652 100644 --- a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSwitchTest.java +++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSwitchTest.java @@ -63,13 +63,14 @@ class XmlSwitchTest { <routes xmlns="http://camel.apache.org/schema/spring"> <route id="selector"> <from uri="direct:start"/> - <switch otherwise="direct:other"> + <switch> <selector><header>decision</header></selector> <keys>department</keys><keys>urgent</keys> <case uri="direct:matched"> <values name="urgent" type="boolean" value="true"/> <values name="department" value="billing"/> </case> + <otherwise uri="direct:other"/> </switch> </route> <route><from uri="direct:matched"/><setBody><constant>matched</constant></setBody></route> diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java index ea2ecd39a2e1..2633e7186105 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java @@ -95,6 +95,7 @@ import org.apache.camel.model.StepDefinition; import org.apache.camel.model.StopDefinition; import org.apache.camel.model.SwitchCaseDefinition; import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.SwitchOtherwiseDefinition; import org.apache.camel.model.TemplatedRouteParameterDefinition; import org.apache.camel.model.ThreadPoolProfileDefinition; import org.apache.camel.model.ThreadsDefinition; @@ -18126,7 +18127,7 @@ public final class ModelDeserializers extends YamlDeserializerSupport { @YamlProperty(name = "id", type = "string", description = "The id of this node", displayName = "Id"), @YamlProperty(name = "keys", type = "array:string", description = "Exact map field names forming a composite key. Additional result fields are ignored.", displayName = "Keys"), @YamlProperty(name = "note", type = "string", description = "The note for this node", displayName = "Note"), - @YamlProperty(name = "otherwise", type = "string", description = "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", displayName = "Otherwise"), + @YamlProperty(name = "otherwise", type = "object:org.apache.camel.model.SwitchOtherwiseDefinition", description = "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", displayName = "Otherwise"), @YamlProperty(name = "selector", type = "object:org.apache.camel.model.ExpressionSubElementDefinition", required = true, description = "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", displayName = "Selector") } ) @@ -18161,7 +18162,7 @@ public final class ModelDeserializers extends YamlDeserializerSupport { break; } case "otherwise": { - String val = asText(node); + org.apache.camel.model.SwitchOtherwiseDefinition val = asType(node, org.apache.camel.model.SwitchOtherwiseDefinition.class); target.setOtherwise(val); break; } @@ -18193,6 +18194,52 @@ public final class ModelDeserializers extends YamlDeserializerSupport { } } + @YamlType( + nodes = "switchOtherwise", + types = org.apache.camel.model.SwitchOtherwiseDefinition.class, + order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, + displayName = "Switch Otherwise", + description = "The fixed endpoint used when no switch case matches", + deprecated = false, + properties = { + @YamlProperty(name = "parameters", type = "object"), + @YamlProperty(name = "uri", type = "string", required = true, description = "The fixed destination URI. Supports property placeholders, but not Simple expressions.", displayName = "Uri") + } + ) + public static class SwitchOtherwiseDefinitionDeserializer extends YamlDeserializerEndpointAwareBase<SwitchOtherwiseDefinition> { + public SwitchOtherwiseDefinitionDeserializer() { + super(SwitchOtherwiseDefinition.class); + } + + @Override + protected SwitchOtherwiseDefinition newInstance() { + return new SwitchOtherwiseDefinition(); + } + + @Override + protected void setEndpointUri(CamelContext camelContext, Node node, + SwitchOtherwiseDefinition target, Map<String, Object> parameters) { + target.setUri(org.apache.camel.dsl.yaml.common.YamlSupport.createEndpointUri(camelContext, node, target.getUri(), parameters)); + } + + @Override + protected boolean setProperty(SwitchOtherwiseDefinition target, String propertyKey, + String propertyName, Node node) { + propertyKey = org.apache.camel.util.StringHelper.dashToCamelCase(propertyKey); + switch(propertyKey) { + case "uri": { + String val = asText(node); + target.setUri(val); + break; + } + default: { + return false; + } + } + return true; + } + } + @YamlType( nodes = "syslog", types = org.apache.camel.model.dataformat.SyslogDataFormat.class, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java index 097b74b24f35..853a854e8332 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java @@ -407,6 +407,8 @@ public final class ModelDeserializersResolver implements YamlDeserializerResolve case "org.apache.camel.model.SwitchCaseDefinition": return new ModelDeserializers.SwitchCaseDefinitionDeserializer(); case "switch": return new ModelDeserializers.SwitchDefinitionDeserializer(); case "org.apache.camel.model.SwitchDefinition": return new ModelDeserializers.SwitchDefinitionDeserializer(); + case "switchOtherwise": return new ModelDeserializers.SwitchOtherwiseDefinitionDeserializer(); + case "org.apache.camel.model.SwitchOtherwiseDefinition": return new ModelDeserializers.SwitchOtherwiseDefinitionDeserializer(); case "syslog": return new ModelDeserializers.SyslogDataFormatDeserializer(); case "org.apache.camel.model.dataformat.SyslogDataFormat": return new ModelDeserializers.SyslogDataFormatDeserializer(); case "tarFile": return new ModelDeserializers.TarFileDataFormatDeserializer(); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/SwitchSchemaTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/SwitchSchemaTest.java new file mode 100644 index 000000000000..641d883aeb4e --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/SwitchSchemaTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.yaml.validator; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class SwitchSchemaTest { + @ParameterizedTest + @ValueSource(booleans = { false, true }) + void fallbackIsAnEndpointObject(boolean canonical) throws Exception { + YamlValidator validator = new YamlValidator(canonical); + String route = """ + - route: + from: + uri: direct:start + steps: + - switch: + selector: + header: + expression: department + case: + - value: billing + uri: direct:billing + otherwise: + uri: direct:review + """; + assertThat(validator.validate(route)).isEmpty(); + for (String fallback : new String[] { "direct:review", "{}", "{steps: [{to: {uri: direct:review}}]}" }) { + String invalid = route.replace("otherwise:\n uri: direct:review", "otherwise: " + fallback); + assertThat(validator.validate(invalid)).as("fallback: %s", fallback).isNotEmpty(); + } + } +} 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 d23a4def3fe5..470ac159bf01 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 @@ -5027,9 +5027,9 @@ "description" : "The note for this node" }, "otherwise" : { - "type" : "string", "title" : "Otherwise", - "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues." + "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchOtherwiseDefinition" }, "selector" : { "title" : "Selector", @@ -5039,6 +5039,23 @@ }, "required" : [ "selector" ] }, + "org.apache.camel.model.SwitchOtherwiseDefinition" : { + "title" : "Switch Otherwise", + "description" : "The fixed endpoint used when no switch case matches", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "parameters" : { + "type" : "object" + }, + "uri" : { + "type" : "string", + "title" : "Uri", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions." + } + }, + "required" : [ "uri" ] + }, "org.apache.camel.model.SwitchValueDefinition" : { "type" : "object", "minProperties" : 1, 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 50e78118351f..d505bf5061b6 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 @@ -8446,15 +8446,35 @@ "group" : "common" }, { "name" : "otherwise", - "type" : "string", + "type" : "object", + "ref" : "switchOtherwise", "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", "title" : "Otherwise", "displayName" : "Otherwise", - "kind" : "attribute", + "kind" : "element", "index" : 7, "group" : "common" } ] }, + "switchOtherwise" : { + "title" : "Switch Otherwise", + "description" : "The fixed endpoint used when no switch case matches", + "label" : "configuration", + "children" : [ { + "name" : "uri", + "type" : "string", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions.", + "title" : "Uri", + "required" : true, + "displayName" : "Uri", + "kind" : "attribute", + "index" : 0, + "group" : "common" + }, { + "name" : "parameters", + "type" : "object" + } ] + }, "switchValue" : { "title" : "Switch Value", "description" : "A named literal in a composite switch case", 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 e677a187f48d..d0ae29c4ab00 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 @@ -6680,9 +6680,9 @@ "description" : "The note for this node" }, "otherwise" : { - "type" : "string", "title" : "Otherwise", - "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues." + "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchOtherwiseDefinition" }, "selector" : { "title" : "Selector", @@ -6692,6 +6692,23 @@ }, "required" : [ "selector" ] }, + "org.apache.camel.model.SwitchOtherwiseDefinition" : { + "title" : "Switch Otherwise", + "description" : "The fixed endpoint used when no switch case matches", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "parameters" : { + "type" : "object" + }, + "uri" : { + "type" : "string", + "title" : "Uri", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions." + } + }, + "required" : [ "uri" ] + }, "org.apache.camel.model.SwitchValueDefinition" : { "type" : "object", "minProperties" : 1, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchTest.java index 589267245058..cced5921f312 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchTest.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchTest.java @@ -52,7 +52,8 @@ class SwitchTest extends YamlTestSupport { name: urgent - values: [urgent: false, department: billing] uri: direct:billing - otherwise: direct:review + otherwise: + uri: direct:review - route: from: uri: direct:urgent @@ -75,6 +76,7 @@ class SwitchTest extends YamlTestSupport { SwitchDefinition sw = (SwitchDefinition) context.getRouteDefinition("decision").getOutputs().get(0); assertThat(sw.getCases().get(0).getUri()).isEqualTo("direct:urgent"); assertThat(sw.getCases().get(0).getValues().get(1).asLiteral()).isEqualTo(true); + assertThat(sw.getOtherwise().getUri()).isEqualTo("direct:review"); try (var restored = new DefaultCamelContext()) { for (var route : context.getRouteDefinitions()) { String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); @@ -143,6 +145,27 @@ class SwitchTest extends YamlTestSupport { } } + @Test + void fallbackRequiresAnEndpointObject() { + for (String fallback : new String[] { "direct:review", "{steps: [{to: {uri: direct:review}}]}" }) { + String yaml = """ + - route: + from: + uri: direct:start + steps: + - switch: + selector: + header: + expression: department + case: + - value: billing + uri: direct:billing + otherwise: %s + """.formatted(fallback); + assertThatThrownBy(() -> loadRoutes(yaml)).isInstanceOf(Exception.class); + } + } + @Test void toDStillAcceptsParametersAndRoundTrips() throws Exception { loadRoutes(""" diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchYamlDumpTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchYamlDumpTest.java index 5300b0724b86..0ad730d9f2ca 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchYamlDumpTest.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchYamlDumpTest.java @@ -53,5 +53,10 @@ class SwitchYamlDumpTest extends YamlTestSupport { assertThat(xpath.getNamespaces()).containsEntry("t", "urn:tickets"); assertThat(restored.getCases().get(0).getValue()).isEqualTo("billing"); assertThat(restored.getCases().get(0).getUri()).isEqualTo("mock:billing"); + assertThat(restored.getOtherwise().getUri()).isEqualTo("mock:other"); + SwitchDefinition copy = restored.copyDefinition(); + copy.getOtherwise().setUri("mock:copy"); + assertThat(restored.getOtherwise().getUri()).isEqualTo("mock:other"); + assertThat(copy.getOtherwise().getUri()).isEqualTo("mock:copy"); } }
