From d2b0d1978a6f2014d6f5d226fc28d8346109d30e Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Mon, 28 Sep 2026 20:02:13 +0200 Subject: [PATCH 1/7] CAMEL-24988: Add Switch EIP with scalar and composite literal matching Evaluate a Camel expression once and select one static endpoint using a literal lookup table. Support named, typed composite keys, including semantic batch results, without coupling core routing to a language. Add Java, XML and YAML DSL support, model round trips, case instrumentation, startup validation, documentation and generated metadata. Co-authored-by: Codex Signed-off-by: Luigi De Masi --- .../org/apache/camel/catalog/docs.properties | 1 + .../camel/catalog/docs/semantic-language.adoc | 1 + .../apache/camel/catalog/docs/switch-eip.adoc | 208 ++++++++++++++ .../apache/camel/catalog/models.properties | 3 + .../camel/catalog/models/a2aSubTask.json | 2 +- .../camel/catalog/models/aggregate.json | 2 +- .../apache/camel/catalog/models/cache.json | 2 +- .../org/apache/camel/catalog/models/case.json | 22 ++ .../camel/catalog/models/circuitBreaker.json | 2 +- .../apache/camel/catalog/models/doCatch.json | 2 +- .../camel/catalog/models/doFinally.json | 2 +- .../apache/camel/catalog/models/doTry.json | 2 +- .../apache/camel/catalog/models/filter.json | 2 +- .../catalog/models/idempotentConsumer.json | 2 +- .../camel/catalog/models/intercept.json | 2 +- .../camel/catalog/models/interceptFrom.json | 2 +- .../models/interceptSendToEndpoint.json | 2 +- .../apache/camel/catalog/models/kamelet.json | 2 +- .../camel/catalog/models/loadBalance.json | 2 +- .../org/apache/camel/catalog/models/loop.json | 2 +- .../camel/catalog/models/multicast.json | 2 +- .../camel/catalog/models/onCompletion.json | 2 +- .../camel/catalog/models/onException.json | 2 +- .../camel/catalog/models/onFallback.json | 2 +- .../camel/catalog/models/otherwise.json | 2 +- .../apache/camel/catalog/models/pipeline.json | 2 +- .../camel/catalog/models/resequence.json | 2 +- .../apache/camel/catalog/models/route.json | 2 +- .../org/apache/camel/catalog/models/saga.json | 2 +- .../apache/camel/catalog/models/split.json | 2 +- .../org/apache/camel/catalog/models/step.json | 2 +- .../apache/camel/catalog/models/switch.json | 25 ++ .../camel/catalog/models/switchValue.json | 19 ++ .../org/apache/camel/catalog/models/when.json | 2 +- .../camel/catalog/schemas/camel-spring.xsd | 159 +++++++++++ .../camel/catalog/schemas/camel-xml-io.xsd | 159 +++++++++++ .../catalog/schemas/camelYamlDsl-model.json | 138 ++++++++++ .../src/main/docs/semantic-language.adoc | 1 + .../camel/semantic/SemanticEipTest.java | 14 + .../apache/camel/spring/SwitchJaxbTest.java | 81 ++++++ .../modules/eips/examples/json/switch.json | 1 + .../src/main/docs/modules/eips/nav.adoc | 1 + .../docs/modules/eips/pages/switch-eip.adoc | 208 ++++++++++++++ .../org/apache/camel/model/a2aSubTask.json | 2 +- .../org/apache/camel/model/aggregate.json | 2 +- .../org/apache/camel/model/cache.json | 2 +- .../META-INF/org/apache/camel/model/case.json | 22 ++ .../apache/camel/model/circuitBreaker.json | 2 +- .../org/apache/camel/model/doCatch.json | 2 +- .../org/apache/camel/model/doFinally.json | 2 +- .../org/apache/camel/model/doTry.json | 2 +- .../org/apache/camel/model/filter.json | 2 +- .../camel/model/idempotentConsumer.json | 2 +- .../org/apache/camel/model/intercept.json | 2 +- .../org/apache/camel/model/interceptFrom.json | 2 +- .../camel/model/interceptSendToEndpoint.json | 2 +- .../org/apache/camel/model/kamelet.json | 2 +- .../org/apache/camel/model/loadBalance.json | 2 +- .../META-INF/org/apache/camel/model/loop.json | 2 +- .../org/apache/camel/model/multicast.json | 2 +- .../org/apache/camel/model/onCompletion.json | 2 +- .../org/apache/camel/model/onException.json | 2 +- .../org/apache/camel/model/onFallback.json | 2 +- .../org/apache/camel/model/otherwise.json | 2 +- .../org/apache/camel/model/pipeline.json | 2 +- .../org/apache/camel/model/resequence.json | 2 +- .../org/apache/camel/model/route.json | 2 +- .../META-INF/org/apache/camel/model/saga.json | 2 +- .../org/apache/camel/model/split.json | 2 +- .../META-INF/org/apache/camel/model/step.json | 2 +- .../org/apache/camel/model/switch.json | 25 ++ .../org/apache/camel/model/switchValue.json | 19 ++ .../META-INF/org/apache/camel/model/when.json | 2 +- .../org/apache/camel/model.properties | 3 + .../org/apache/camel/model/jaxb.index | 3 + .../camel/model/ProcessorDefinition.java | 25 ++ .../camel/model/SwitchCaseDefinition.java | 143 ++++++++++ .../apache/camel/model/SwitchDefinition.java | 255 ++++++++++++++++++ .../camel/model/SwitchValueDefinition.java | 115 ++++++++ .../camel/processor/SwitchProcessor.java | 232 ++++++++++++++++ .../camel/reifier/ProcessorReifier.java | 3 + .../apache/camel/reifier/SwitchReifier.java | 127 +++++++++ .../camel/processor/SwitchEndpointTest.java | 103 +++++++ .../apache/camel/processor/SwitchTest.java | 196 ++++++++++++++ .../camel/processor/SwitchValidationTest.java | 139 ++++++++++ .../camel/java/out/JavaDslModelWriter.java | 45 ++++ .../java/out/JavaDslModelWriterSupport.java | 52 ++++ .../java/out/JavaDslModelWriterTest.java | 20 ++ .../src/test/resources/switchLiteral.xml | 38 +++ .../management/mbean/CamelOpenMBeanTypes.java | 13 + .../management/mbean/ManagedSwitchMBean.java | 31 +++ .../DefaultManagementObjectStrategy.java | 4 + .../JmxManagementLifecycleStrategy.java | 11 + .../camel/management/mbean/ManagedSwitch.java | 90 +++++++ .../camel/management/ManagedSwitchTest.java | 73 +++++ .../org/apache/camel/xml/in/ModelParser.java | 30 +++ .../org/apache/camel/xml/out/ModelWriter.java | 36 +++ .../apache/camel/xml/LwModelToXMLDumper.java | 18 ++ .../org/apache/camel/xml/jaxb/JaxbHelper.java | 30 +++ .../camel/yaml/out/YamlModelWriter.java | 41 +++ .../camel/yaml/LwModelToYAMLDumper.java | 18 ++ .../jbang/core/commands/ai/eip-samples.json | 14 + .../camel/dsl/xml/io/XmlSwitchTest.java | 94 +++++++ .../camel-yaml-dsl-deserializers/pom.xml | 1 + .../deserializers/ModelDeserializers.java | 159 +++++++++++ .../ModelDeserializersResolver.java | 4 + .../yaml/deserializers/CustomResolver.java | 2 + .../SwitchValueDefinitionDeserializer.java | 63 +++++ .../yaml/GenerateYamlDeserializersMojo.java | 6 +- .../dsl/yaml/GenerateYamlSchemaMojo.java | 10 + .../dsl/yaml/GenerateYamlSupportMojo.java | 6 +- dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml | 5 + .../schema/camelYamlDsl-canonical.json | 112 ++++++++ .../resources/schema/camelYamlDsl-model.json | 138 ++++++++++ .../resources/schema/camelYamlDsl.json | 112 ++++++++ .../org/apache/camel/dsl/yaml/SwitchTest.java | 168 ++++++++++++ .../camel/dsl/yaml/SwitchYamlDumpTest.java | 57 ++++ .../velocity/model-java-dsl-writer.vm | 6 +- .../resources/velocity/model-yaml-writer.vm | 8 + 119 files changed, 4011 insertions(+), 63 deletions(-) create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json create mode 100644 components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/SwitchJaxbTest.java create mode 120000 core/camel-core-engine/src/main/docs/modules/eips/examples/json/switch.json create mode 100644 core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc create mode 100644 core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json create mode 100644 core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json create mode 100644 core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json create mode 100644 core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java create mode 100644 core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java create mode 100644 core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java create mode 100644 core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java create mode 100644 core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java create mode 100644 core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java create mode 100644 core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java create mode 100644 core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java create mode 100644 core/camel-java-io/src/test/resources/switchLiteral.xml create mode 100644 core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedSwitchMBean.java create mode 100644 core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedSwitch.java create mode 100644 core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java create mode 100644 dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSwitchTest.java create mode 100644 dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java create mode 100644 dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchTest.java create mode 100644 dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchYamlDumpTest.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties index f619507ee2de7..7d7f9a408f118 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties @@ -626,6 +626,7 @@ stripe-component stub-component swiftMt-dataformat swiftMx-dataformat +switch-eip syslog-dataformat tahu-edge-component tahu-host-component diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc index f8ec5e5b7e3d0..a58c8402ca306 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc @@ -223,6 +223,7 @@ Use `language("semantic", "ref:name")` wherever an expression or boolean predica [cols="1,3"] |=== |EIP / integration point |Usage +|Switch |Select a fixed endpoint directly from a category or a named batch using a literal lookup table. |Choice |Store a category with Set Property, then compare that property in ordinary when predicates. |Filter |Use a boolean question as the filter predicate. |Validate |Use a boolean question; false follows normal validation failure handling. 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 new file mode 100644 index 0000000000000..42b02391c868e --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc @@ -0,0 +1,208 @@ += Switch EIP +:doctitle: Switch +:shortname: switch +:description: Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values +:since: 4.23 +:supportlevel: Stable +:tabs-sync-option: + +*Since Camel {since}* + +Switch selects one destination from a table declared by the route author. +The selector can use any Camel expression language. Its result selects a case; +it cannot supply a new endpoint URI. + +Use Switch for literal value dispatch. Use xref:choice-eip.adoc[Choice] for +predicates, ranges, overlapping conditions, or nested processing steps. A case can +send to a `direct:` route when more processing is needed. Use xref:toD-eip.adoc[To D] +when the endpoint URI itself must be calculated dynamically. + +== Options + +// eip options: START +include::partial$eip-options.adoc[] +// eip options: END + +== Scalar selectors + +The selector is evaluated once on each entry. Scalar results are converted to strings +and matched case-insensitively using the English locale. An empty string is a valid +literal. An asterisk is literal, not a wildcard. Duplicate normalized values are +rejected at startup. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:tickets") + .doSwitch(header("department")) + .doCase("billing", "direct:billing") + .doCase("technical", "direct:technical") + .otherwise("direct:review") + .end(); +---- +XML:: ++ +[source,xml] +---- + +
department
+ + +
+---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:tickets + steps: + - switch: + selector: + header: + expression: department + case: + - value: billing + uri: direct:billing + - value: technical + uri: direct:technical + otherwise: direct:review +---- +==== + +A null or unmatched result uses `otherwise`. Without `otherwise`, the exchange +continues after Switch. Selector failures follow normal Camel error handling and +never select the fallback. Streams are reset after selector evaluation when stream +caching is enabled. Loops and retries that enter Switch again evaluate the selector +again; results are not cached across entries. + +== Composite selectors + +Configure `keys` when the selector returns a map. Each case supplies every declared +key exactly once. All values must match. Key names are case-sensitive, while string +values are case-insensitive. Entry order is irrelevant and additional result fields +are ignored. Missing keys, null field values and unsupported value types are errors. +A null result map still uses the fallback. + +Composite literals support strings, booleans and finite decimal numbers. These types +are distinct: string `"true"` does not match boolean `true`, and string `"2"` does not +match number `2`. Numbers compare by decimal value, so `2` and `2.0` match. Duplicate +combinations, including differences only in letter case or numeric scale, fail at +startup. Scalar `value` cases and composite `values` cases cannot be mixed. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:tickets") + .doSwitch(header("decision")) + .keys("department", "urgent") + .doCase().value("department", "billing").value("urgent", true) + .id("urgentBilling").to("direct:urgent-billing") + .doCase().value("department", "billing").value("urgent", false) + .to("direct:billing") + .otherwise("direct:review") + .end(); +---- +XML:: ++ +[source,xml] +---- + +
decision
+ department + urgent + + + + + + + + +
+---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:tickets + steps: + - switch: + selector: + header: + expression: decision + keys: [department, urgent] + case: + - id: urgentBilling + values: [department: billing, urgent: true] + uri: direct:urgent-billing + - values: [department: billing, urgent: false] + uri: direct:billing + otherwise: direct:review +---- +==== + +YAML `values` is a sequence of single-entry mappings. Quote strings that would +otherwise be read as booleans or numbers. XML literals default to `type="string"`; +use `type="boolean"` or `type="number"` explicitly. Keys select fields in the returned +map; they do not implicitly read separate message headers. + +Cases are compiled into a lookup table. Constructing a composite key involves the +selected fields, but lookup does not scan the declared cases. + +=== Semantic decisions + +With `camel-semantic` and a provider configured, a named batch can be used directly: + +[source,yaml] +---- +- route: + from: + uri: direct:tickets + steps: + - switch: + selector: + language: + language: semantic + expression: refs:department,urgent + keys: [department, urgent] + case: + - values: [department: billing, urgent: true] + uri: direct:urgent-billing + - values: [department: billing, urgent: false] + uri: direct:billing + otherwise: direct:review +---- + +Declare the questions using the xref:languages:semantic-language.adoc[Semantic language]. +Batching, thresholds and uncertainty policies belong to that language and its provider. +Switch has no dependency on semantic evaluation. Store a result explicitly in a +variable, header or exchange property when it should be reused by later EIPs. + +== Destinations and management + +Case and fallback URIs support property placeholders resolved at startup. Simple +expressions in URIs are rejected. In YAML, a case also accepts `uri` plus `parameters`: + +[source,yaml] +---- +case: + - value: orders + uri: kafka + parameters: + topic: orders +---- + +Each case has an identity for tracing, debugging and management. The Switch MBean's +`extendedInformation` table reports case IDs, literal values, destination URIs and +selection counts. These counts indicate case selection, not successful delivery. +`UnmatchedCount` also records unmatched results when no fallback is configured. 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 6b4b1f0db8fb6..70faa2f60b2a9 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 @@ -13,6 +13,7 @@ beanio bearerToken bindy cache +case cbor choice circuitBreaker @@ -196,6 +197,8 @@ stop streamConfig swiftMt swiftMx +switch +switchValue syslog tarFile templateParameter diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/a2aSubTask.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/a2aSubTask.json index 4e3ac7673b7be..ebf2a61f68a74 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/a2aSubTask.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/a2aSubTask.json @@ -21,6 +21,6 @@ "emitAfter": { "index": 5, "kind": "attribute", "displayName": "Emit After", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Simple expression template to emit after the nested steps complete successfully" }, "emitOnError": { "index": 6, "kind": "attribute", "displayName": "Emit On Error", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Simple expression template to emit when the nested steps fail" }, "failIfNoTaskContext": { "index": 7, "kind": "attribute", "displayName": "Fail If No Task Context", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to fail if the current Exchange does not have an active A2A task context" }, - "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/aggregate.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/aggregate.json index 0fb9ff69a07f9..b44e99f13f841 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/aggregate.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/aggregate.json @@ -45,7 +45,7 @@ "discardOnAggregationFailure": { "index": 29, "kind": "attribute", "displayName": "Discard On Aggregation Failure", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then the partly aggregated message is discarded when aggregation failed (an exception was thrown from AggregationStrategy). Cannot be used together with completionFromBatchConsumer." }, "forceCompletionOnStop": { "index": 30, "kind": "attribute", "displayName": "Force Completion On Stop", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then all current aggregated exchanges are completed when the context is stopped." }, "completeAllOnStop": { "index": 31, "kind": "attribute", "displayName": "Complete All On Stop", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then waits to complete all current and partial (pending) aggregated exchanges when the context is stopped, ensuring the aggregation repository is empty before shutdown." }, - "outputs": { "index": 32, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 32, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelAggregatedSize": { "index": 0, "kind": "exchangeProperty", "displayName": "Aggregated Size", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "important": true, "description": "Number of exchanges that was grouped together." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/cache.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/cache.json index 459a15180559f..08f5ce5c924b6 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/cache.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/cache.json @@ -21,6 +21,6 @@ "keyValueRepository": { "index": 5, "kind": "attribute", "displayName": "Key Value Repository", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.spi.KeyValueRepository", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the reference name of the KeyValueRepository to use as the cache backing store. If not set, a MemoryKeyValueRepository is auto-created." }, "ttl": { "index": 6, "kind": "attribute", "displayName": "Ttl", "group": "common", "required": false, "type": "duration", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "-1", "description": "Sets the time-to-live for cached entries. Supports duration syntax (e.g. 10m, 1h) or milliseconds. Default: -1 (no expiration)." }, "cacheNull": { "index": 7, "kind": "attribute", "displayName": "Cache Null", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to cache null results. By default, null message bodies are not cached." }, - "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json new file mode 100644 index 0000000000000..f7de1b0888ca5 --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json @@ -0,0 +1,22 @@ +{ + "model": { + "kind": "model", + "name": "case", + "title": "Case", + "description": "A literal switch case with one fixed endpoint destination", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.SwitchCaseDefinition", + "abstract": false, + "input": false, + "output": false + }, + "properties": { + "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, + "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, + "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, + "value": { "index": 3, "kind": "attribute", "displayName": "Value", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." }, + "values": { "index": 4, "kind": "element", "displayName": "Values", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Named, typed literal values for a composite selector. Every selected key must occur exactly once." }, + "uri": { "index": 5, "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/models/circuitBreaker.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/circuitBreaker.json index 5880e386c3423..e9b6c76a5f46e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/circuitBreaker.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/circuitBreaker.json @@ -22,7 +22,7 @@ "resilience4jConfiguration": { "index": 6, "kind": "element", "displayName": "Resilience4j Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.Resilience4jConfigurationDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Configures the circuit breaker to use Resilience4j with the given configuration." }, "faultToleranceConfiguration": { "index": 7, "kind": "element", "displayName": "Fault Tolerance Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.FaultToleranceConfigurationDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Configures the circuit breaker to use MicroProfile Fault Tolerance with the given configuration." }, "onFallback": { "index": 8, "kind": "element", "displayName": "On Fallback", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnFallbackDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "The fallback route path to execute when the circuit breaker triggers." }, - "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelResponseSuccessfulExecution": { "index": 0, "kind": "exchangeProperty", "displayName": "Response Successful Execution", "label": "producer", "required": false, "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether the exchange was processed successfully by the circuit breaker" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json index 2ce320d08830f..f71c063b00bde 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json @@ -18,7 +18,7 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "exception": { "index": 4, "kind": "element", "displayName": "Exception", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "The exception class names (fully qualified) to catch." }, "onWhen": { "index": 5, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "An additional predicate that must evaluate to true for this doCatch to trigger. Allows fine-grained control over which exceptions are caught based on the exchange state." }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", "displayName": "Exception Caught", "label": "producer", "required": false, "javaType": "java.lang.Exception", "deprecated": false, "autowired": false, "secret": false, "description": "Stores the caught exception due to a processing error of the current Exchange" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json index 431f292b84515..015e9389917fb 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json @@ -16,7 +16,7 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", "displayName": "Exception Caught", "label": "producer", "required": false, "javaType": "java.lang.Exception", "deprecated": false, "autowired": false, "secret": false, "description": "Stores the caught exception due to a processing error of the current Exchange" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doTry.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doTry.json index 28998908d77aa..1ab592d6688da 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doTry.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doTry.json @@ -16,7 +16,7 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false }, + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false }, "doCatch": { "index": 5, "kind": "element", "displayName": "Do Catch", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "oneOf": [ "doCatch" ], "deprecated": false, "autowired": false, "secret": false, "description": "Catches exceptions as part of a try, catch, finally block" }, "doFinally": { "index": 6, "kind": "element", "displayName": "Do Finally", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.FinallyDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Path traversed when a try, catch, finally block exits" } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/filter.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/filter.json index c1300ea647c1c..14e7752a5be72 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/filter.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/filter.json @@ -19,6 +19,6 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "expression": { "index": 4, "kind": "expression", "displayName": "Expression", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.language.ExpressionDefinition", "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" ], "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "The predicate expression to evaluate. Messages where the predicate returns false are filtered out and not routed further." }, "statusPropertyName": { "index": 5, "kind": "attribute", "displayName": "Status Property Name", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of an exchange property to store whether the filter predicate matched or not. The value is stored as a boolean." }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/idempotentConsumer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/idempotentConsumer.json index ac640cd7983f7..f027166a0bfc3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/idempotentConsumer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/idempotentConsumer.json @@ -23,7 +23,7 @@ "completionEager": { "index": 7, "kind": "attribute", "displayName": "Completion Eager", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Sets whether to complete the idempotent consumer eager or when the exchange is done." }, "skipDuplicate": { "index": 8, "kind": "attribute", "displayName": "Skip Duplicate", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether to skip duplicates or not. The default behavior is to skip duplicates." }, "removeOnFailure": { "index": 9, "kind": "attribute", "displayName": "Remove On Failure", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether to remove or keep the key on failure. The default behavior is to remove the key on failure." }, - "outputs": { "index": 10, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 10, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelDuplicateMessage": { "index": 0, "kind": "exchangeProperty", "displayName": "Duplicate Message", "label": "producer", "required": false, "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether this exchange is a duplicate detected by the Idempotent Consumer EIP" } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/intercept.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/intercept.json index d630b9c923a84..a828928287df4 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/intercept.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/intercept.json @@ -17,7 +17,7 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "onWhen": { "index": 4, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger intercepting in specific situations" }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelInterceptedNodeId": { "index": 0, "kind": "exchangeProperty", "displayName": "Intercepted Node Id", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The intercepted route's node ID" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptFrom.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptFrom.json index 799330919f7a6..0e1c4ff4c2706 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptFrom.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptFrom.json @@ -18,7 +18,7 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "uri": { "index": 4, "kind": "attribute", "displayName": "Uri", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Intercept incoming messages from the uri or uri pattern. If not configured, then all incoming messages are intercepted." }, "onWhen": { "index": 5, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger intercepting in specific situations" }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelInterceptedEndpoint": { "index": 0, "kind": "exchangeProperty", "displayName": "Intercepted Endpoint", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The endpoint URI that was intercepted" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptSendToEndpoint.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptSendToEndpoint.json index 45ed39b6cbfb8..98f5399696b87 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptSendToEndpoint.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/interceptSendToEndpoint.json @@ -20,7 +20,7 @@ "skipSendToOriginalEndpoint": { "index": 5, "kind": "attribute", "displayName": "Skip Send To Original Endpoint", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to skip sending the message to the original endpoint. By default, the message is both intercepted and then sent to the original endpoint." }, "afterUri": { "index": 6, "kind": "attribute", "displayName": "After Uri", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "After sending to the endpoint then send the message to this uri which allows to process its result." }, "onWhen": { "index": 7, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger intercepting sending to an endpoint in specific situations" }, - "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelInterceptedEndpoint": { "index": 0, "kind": "exchangeProperty", "displayName": "Intercepted Endpoint", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The endpoint URI that was intercepted" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/kamelet.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/kamelet.json index cd8c43e1beacc..1d0749b2d5e10 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/kamelet.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/kamelet.json @@ -17,6 +17,6 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "name": { "index": 4, "kind": "attribute", "displayName": "Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of the Kamelet (templateId\/routeId) to call. Options for the kamelet can be specified using uri syntax, eg mynamecount=4&type=gold." }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loadBalance.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loadBalance.json index de65c909bd967..29654577a69fb 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loadBalance.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loadBalance.json @@ -18,6 +18,6 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "loadBalancerType": { "index": 4, "kind": "element", "displayName": "Load Balancer Type", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.LoadBalancerDefinition", "oneOf": [ "customLoadBalancer", "failoverLoadBalancer", "randomLoadBalancer", "roundRobinLoadBalancer", "stickyLoadBalancer", "topicLoadBalancer", "weightedLoadBalancer" ], "deprecated": false, "autowired": false, "secret": false, "description": "The load balancing strategy to use, such as round-robin, random, sticky, topic, failover, weighted, or a custom load balancer." }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loop.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loop.json index 333d1d3cad860..08cffb3e6a881 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loop.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/loop.json @@ -22,7 +22,7 @@ "doWhile": { "index": 6, "kind": "attribute", "displayName": "Do While", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enables the while loop that loops until the predicate evaluates to false or null." }, "breakOnShutdown": { "index": 7, "kind": "attribute", "displayName": "Break On Shutdown", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, the loop will not iterate until it reaches the end when Camel is shut down." }, "onPrepare": { "index": 8, "kind": "attribute", "displayName": "On Prepare", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.Processor", "deprecated": false, "autowired": false, "secret": false, "description": "Uses a processor when preparing the exchange for each loop iteration. This can be used to deep-clone messages, or any custom logic needed before the looping executes." }, - "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelLoopIndex": { "index": 0, "kind": "exchangeProperty", "displayName": "Loop Index", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Index of the current iteration (0 based)." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/multicast.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/multicast.json index 37d9cd457cd2c..29f44c5b3e1c4 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/multicast.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/multicast.json @@ -29,7 +29,7 @@ "executorService": { "index": 13, "kind": "attribute", "displayName": "Executor Service", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.concurrent.ExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Reference to a custom thread pool to use for parallel processing. Setting this option implies parallel processing." }, "onPrepare": { "index": 14, "kind": "attribute", "displayName": "On Prepare", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.Processor", "deprecated": false, "autowired": false, "secret": false, "description": "Reference to a processor for preparing the exchange to be sent. Can be used to deep-clone messages that should be sent." }, "shareUnitOfWork": { "index": 15, "kind": "attribute", "displayName": "Share Unit Of Work", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Shares the unit of work with the parent and each of the multicast exchanges. By default each multicast exchange has its own individual unit of work." }, - "outputs": { "index": 16, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 16, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelMulticastIndex": { "index": 0, "kind": "exchangeProperty", "displayName": "Multicast Index", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "An index counter that increases for each Exchange being multicasted. The counter starts from 0." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onCompletion.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onCompletion.json index ffb6fa48a43d9..262a77cea7fc1 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onCompletion.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onCompletion.json @@ -23,7 +23,7 @@ "executorService": { "index": 8, "kind": "attribute", "displayName": "Executor Service", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.concurrent.ExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Refers to a custom Thread Pool to be used for parallel processing. Notice if you set this option, then parallel processing is automatically implied, and you do not have to enable that option as well." }, "useOriginalMessage": { "index": 9, "kind": "attribute", "displayName": "Use Original Message", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Will use the original input message when an Exchange for this on completion. By default this feature is off." }, "onWhen": { "index": 10, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger routing this completion steps in specific situations" }, - "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelOnCompletion": { "index": 0, "kind": "exchangeProperty", "displayName": "On Completion", "label": "producer", "required": false, "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Flag to mark that this exchange is currently being executed as onCompletion" } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json index 886d297162cbe..e0ed0003e5046 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json @@ -27,7 +27,7 @@ "onExceptionOccurredRef": { "index": 12, "kind": "attribute", "displayName": "On Exception Occurred Ref", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a reference to a processor that is invoked just after an exception occurred. Can be used to perform custom logging. Any exception thrown from this processor is ignored." }, "useOriginalMessage": { "index": 13, "kind": "attribute", "displayName": "Use Original Message", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, uses the original input message (body and headers) when the exchange is moved to the dead letter queue after all redelivery attempts have been exhausted. Cannot be used together with useOriginalBody." }, "useOriginalBody": { "index": 14, "kind": "attribute", "displayName": "Use Original Body", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, uses the original input message body (but not headers) when the exchange is moved to the dead letter queue after all redelivery attempts have been exhausted. This allows enriching the message with custom headers while keeping the original body. Cannot be used together with useOriginalMessage." }, - "outputs": { "index": 15, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 15, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", "displayName": "Exception Caught", "label": "producer", "required": false, "javaType": "java.lang.Exception", "deprecated": false, "autowired": false, "secret": false, "description": "Stores the caught exception due to a processing error of the current Exchange" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onFallback.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onFallback.json index fda944929370d..839e56088a003 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onFallback.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onFallback.json @@ -17,6 +17,6 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "fallbackViaNetwork": { "index": 3, "kind": "attribute", "displayName": "Fallback Via Network", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": true, "deprecationNote": "Not supported by any circuit breaker implementation.", "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the fallback goes over the network. If so, the fallback is executed on a separate thread-pool to avoid exhausting the main thread-pool." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/otherwise.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/otherwise.json index 201bd53c297c5..41494913752ff 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/otherwise.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/otherwise.json @@ -16,6 +16,6 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Disables this EIP from the route." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pipeline.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pipeline.json index 8c2e503fed5c7..3c5106ddb4855 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pipeline.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pipeline.json @@ -17,6 +17,6 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resequence.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resequence.json index 7cc32a64df664..a001f1f6d674c 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resequence.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/resequence.json @@ -18,6 +18,6 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "expression": { "index": 4, "kind": "expression", "displayName": "Expression", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.language.ExpressionDefinition", "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression to use for re-ordering the messages, such as a header with a sequence number." }, "resequencerConfig": { "index": 5, "kind": "element", "displayName": "Resequencer Config", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.config.ResequencerConfig", "oneOf": [ "batchConfig", "streamConfig" ], "deprecated": false, "autowired": false, "secret": false, "description": "Resequencer configuration using either batch or stream mode. Defaults to batch mode." }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/route.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/route.json index 16356d0e9ad39..ad5d3453d0f70 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/route.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/route.json @@ -34,6 +34,6 @@ "inputType": { "index": 19, "kind": "element", "displayName": "Input Type", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.InputTypeDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Declare the expected data type of the input message. If the actual message type is different at runtime, camel looks for a required Transformer and applies if exists." }, "outputType": { "index": 20, "kind": "element", "displayName": "Output Type", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.OutputTypeDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Declare the expected data type of the output message. If the actual message type is different at runtime, camel looks for a required Transformer and applies if exists." }, "input": { "index": 21, "kind": "element", "displayName": "Input", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.FromDefinition", "oneOf": [ "from" ], "deprecated": false, "autowired": false, "secret": false, "description": "The input (from) endpoint that starts this route and acts as the consumer that receives incoming messages." }, - "outputs": { "index": 22, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 22, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/saga.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/saga.json index 0ea9948ae92c6..de47ff1502019 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/saga.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/saga.json @@ -24,6 +24,6 @@ "compensation": { "index": 8, "kind": "attribute", "displayName": "Compensation", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The compensation endpoint URI that must be called to compensate all changes done in the route. The route corresponding to the compensation URI must perform compensation and complete without error." }, "completion": { "index": 9, "kind": "attribute", "displayName": "Completion", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The completion endpoint URI that will be called when the Saga is completed successfully. The route corresponding to the completion URI must perform completion tasks and terminate without error." }, "option": { "index": 10, "kind": "element", "displayName": "Option", "group": "advanced", "label": "advanced", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Allows to save properties of the current exchange in order to re-use them in a compensation\/completion callback route. Option values will be transformed into input headers of the compensation\/completion exchange." }, - "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/split.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/split.json index ca0c9e4909a8f..9ac5f17714ad1 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/split.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/split.json @@ -37,7 +37,7 @@ "resumeStrategy": { "index": 21, "kind": "attribute", "displayName": "Resume Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.resume.ResumeStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a ResumeStrategy for resume-from-last-position support. The watermark key must also be configured via watermarkKey(String) ." }, "watermarkKey": { "index": 22, "kind": "attribute", "displayName": "Watermark Key", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the key to use in the watermark store." }, "watermarkExpression": { "index": 23, "kind": "attribute", "displayName": "Watermark Expression", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a Simple expression to evaluate on each completed sub-exchange to determine the new watermark value. When set, enables value-based watermarking instead of index-based. The expression is evaluated using the Simple language." }, - "outputs": { "index": 24, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 24, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelSplitIndex": { "index": 0, "kind": "exchangeProperty", "displayName": "Split Index", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "important": true, "description": "A split counter that increases for each Exchange being split. The counter starts from 0." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/step.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/step.json index 4b05956cb882d..710b522ee3010 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/step.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/step.json @@ -16,7 +16,7 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelStepId": { "index": 0, "kind": "exchangeProperty", "displayName": "Step Id", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of the Step EIP" } 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 new file mode 100644 index 0000000000000..c557fd3550a3f --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json @@ -0,0 +1,25 @@ +{ + "model": { + "kind": "model", + "name": "switch", + "title": "Switch", + "description": "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "deprecated": false, + "firstVersion": "4.23.0", + "label": "eip,routing", + "javaType": "org.apache.camel.model.SwitchDefinition", + "abstract": false, + "input": true, + "output": false + }, + "properties": { + "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, + "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, + "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, + "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, + "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured." }, + "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "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", "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." } + } +} diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json new file mode 100644 index 0000000000000..aa07fe73e9bd7 --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json @@ -0,0 +1,19 @@ +{ + "model": { + "kind": "model", + "name": "switchValue", + "title": "Switch Value", + "description": "A named literal in a composite switch case", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.SwitchValueDefinition", + "abstract": false, + "input": false, + "output": false + }, + "properties": { + "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The exact name of the selected field." }, + "value": { "index": 1, "kind": "attribute", "displayName": "Value", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The literal value, never an expression." }, + "type": { "index": 2, "kind": "attribute", "displayName": "Type", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "string", "boolean", "number" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "string", "description": "The literal type." } + } +} diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/when.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/when.json index e04a77617a656..44b0e97480e41 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/when.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/when.json @@ -17,6 +17,6 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Disables this EIP from the route." }, "expression": { "index": 4, "kind": "expression", "displayName": "Expression", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.language.ExpressionDefinition", "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" ], "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "Expression used as the predicate to evaluate whether this when should trigger and route the message or not." }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } 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 ac1f0016ee7c6..2477aa7cfd2bc 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 @@ -173,6 +173,15 @@ configuration + + + + + + + @@ -2000,6 +2009,24 @@ Encode and decode SWIFT MT messages + + + + + + + + + + + + + + @@ -3171,6 +3198,7 @@ configuration. + @@ -3385,6 +3413,7 @@ values. + @@ -4043,6 +4072,7 @@ Whether the script should support using Camel property placeholder syntax {{ }}. + @@ -4164,6 +4194,7 @@ The exception class names (fully qualified) to catch. + @@ -4332,6 +4363,7 @@ placeholders or simple expressions using only property placeholders are supporte + @@ -4423,6 +4455,7 @@ Disables this EIP from the route. Default value: false + @@ -4516,6 +4549,7 @@ Disables this EIP from the route. Default value: false + @@ -5019,6 +5053,7 @@ References a custom thread pool to use when offloading a guarded action to anoth + @@ -6138,6 +6173,7 @@ The logging level to use for logging transactional rollback. Default is WARN. De + @@ -6229,6 +6265,7 @@ Name of an exchange property to store whether the filter predicate matched or no + @@ -6393,6 +6430,7 @@ Global option value. + @@ -6549,6 +6587,7 @@ Whether validation is required for this input type. Default value: false + @@ -6648,6 +6687,7 @@ Intercept incoming messages from the uri or uri pattern. If not configured, then + @@ -6758,6 +6798,7 @@ After sending to the endpoint then send the message to this uri which allows to + @@ -6859,6 +6900,7 @@ mynamecount=4&type=gold. + @@ -7188,6 +7230,7 @@ The language to use for evaluating the log message, such as simple, groovy, or o + @@ -10944,6 +10987,7 @@ gigabyte. Set to -1 to disable setting a maximum decompressed size. Default valu + @@ -11147,6 +11191,7 @@ own individual unit of work. Default value: false + @@ -11333,6 +11378,7 @@ onException steps complete. The exception is considered handled as well. + @@ -11546,6 +11592,7 @@ Predicate to evaluate whether the processing can resume. Returns true if consump + @@ -11628,6 +11675,7 @@ Predicate to evaluate whether the processing can resume. Returns true if consump + @@ -12295,6 +12343,7 @@ Name of the variable to remove. + @@ -12654,6 +12703,7 @@ Reference to the routes in the xml-dsl. + @@ -13052,6 +13102,7 @@ Option values will be transformed into input headers of the compensation/complet + @@ -13557,6 +13608,7 @@ Sets a reference to lookup for the comparator to use for sorting. + @@ -13833,6 +13885,7 @@ enables value-based watermarking instead of index-based. The expression is evalu + @@ -13858,6 +13911,110 @@ enables value-based watermarking instead of index-based. The expression is evalu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -14557,6 +14714,7 @@ The underlying model name used by the tokenizer. Providing this switches to comp + @@ -14711,6 +14869,7 @@ into. + 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 c35e47ebc6bf5..4b76d226029dd 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 @@ -155,6 +155,15 @@ miss, executes the block and caches the result body. + + + + + + + @@ -1829,6 +1838,24 @@ Encode and decode SWIFT MT messages + + + + + + + + + + + + + + @@ -2281,6 +2308,7 @@ Compression and decompress streams using java.util.zip.Zip*Stream + @@ -2495,6 +2523,7 @@ values. + @@ -3153,6 +3182,7 @@ Whether the script should support using Camel property placeholder syntax {{ }}. + @@ -3274,6 +3304,7 @@ The exception class names (fully qualified) to catch. + @@ -3442,6 +3473,7 @@ placeholders or simple expressions using only property placeholders are supporte + @@ -3533,6 +3565,7 @@ Disables this EIP from the route. Default value: false + @@ -3626,6 +3659,7 @@ Disables this EIP from the route. Default value: false + @@ -4141,6 +4175,7 @@ References a custom thread pool to use when offloading a guarded action to anoth + @@ -5260,6 +5295,7 @@ The logging level to use for logging transactional rollback. Default is WARN. De + @@ -5351,6 +5387,7 @@ Name of an exchange property to store whether the filter predicate matched or no + @@ -5515,6 +5552,7 @@ Global option value. + @@ -5671,6 +5709,7 @@ Whether validation is required for this input type. Default value: false + @@ -5770,6 +5809,7 @@ Intercept incoming messages from the uri or uri pattern. If not configured, then + @@ -5880,6 +5920,7 @@ After sending to the endpoint then send the message to this uri which allows to + @@ -5981,6 +6022,7 @@ mynamecount=4&type=gold. + @@ -6310,6 +6352,7 @@ The language to use for evaluating the log message, such as simple, groovy, or o + @@ -10066,6 +10109,7 @@ gigabyte. Set to -1 to disable setting a maximum decompressed size. Default valu + @@ -10269,6 +10313,7 @@ own individual unit of work. Default value: false + @@ -10455,6 +10500,7 @@ onException steps complete. The exception is considered handled as well. + @@ -10668,6 +10714,7 @@ Predicate to evaluate whether the processing can resume. Returns true if consump + @@ -10750,6 +10797,7 @@ Predicate to evaluate whether the processing can resume. Returns true if consump + @@ -11438,6 +11486,7 @@ Name of the variable to remove. + @@ -11797,6 +11846,7 @@ Reference to the routes in the xml-dsl. + @@ -12195,6 +12245,7 @@ Option values will be transformed into input headers of the compensation/complet + @@ -12700,6 +12751,7 @@ Sets a reference to lookup for the comparator to use for sorting. + @@ -12976,6 +13028,7 @@ enables value-based watermarking instead of index-based. The expression is evalu + @@ -13001,6 +13054,110 @@ enables value-based watermarking instead of index-based. The expression is evalu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -13700,6 +13857,7 @@ The underlying model name used by the tokenizer. Providing this switches to comp + @@ -13854,6 +14012,7 @@ into. + 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 f8c769681a031..50e78118351f5 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 @@ -505,6 +505,13 @@ "title" : "Stop", "description" : "Stops processing of the current message and marks the exchange as completed without sending it to any further steps", "label" : "eip,flowcontrol,routing" + }, { + "name" : "switch", + "type" : "object", + "ref" : "switch", + "title" : "Switch", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "label" : "eip,routing" }, { "name" : "threads", "type" : "object", @@ -8323,6 +8330,137 @@ "label" : "advanced" } ] }, + "switchCase" : { + "title" : "Case", + "description" : "A literal switch case with one fixed endpoint destination", + "children" : [ { + "name" : "description", + "type" : "string", + "description" : "The description for this node", + "title" : "Description" + }, { + "name" : "id", + "type" : "string", + "description" : "The id of this node", + "title" : "Id" + }, { + "name" : "note", + "type" : "string", + "description" : "The note for this node", + "title" : "Note" + }, { + "name" : "parameters", + "type" : "object" + }, { + "name" : "uri", + "type" : "string", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions.", + "title" : "Uri", + "required" : true + }, { + "name" : "value", + "type" : "string", + "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.", + "title" : "Value" + }, { + "name" : "values", + "type" : "array", + "ref" : "switchValue", + "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", + "title" : "Values" + } ] + }, + "switch" : { + "title" : "Switch", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "label" : "eip,routing", + "input" : true, + "isListItem" : true, + "children" : [ { + "name" : "id", + "type" : "string", + "description" : "The id of this node", + "title" : "Id", + "displayName" : "Id", + "kind" : "attribute", + "index" : 0, + "group" : "common" + }, { + "name" : "note", + "type" : "string", + "description" : "The note for this node", + "title" : "Note", + "displayName" : "Note", + "kind" : "attribute", + "index" : 1, + "group" : "common" + }, { + "name" : "description", + "type" : "string", + "description" : "The description for this node", + "title" : "Description", + "displayName" : "Description", + "kind" : "attribute", + "index" : 2, + "group" : "common" + }, { + "name" : "disabled", + "type" : "boolean", + "description" : "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "title" : "Disabled", + "default" : "false", + "displayName" : "Disabled", + "kind" : "attribute", + "index" : 3, + "group" : "advanced", + "label" : "advanced" + }, { + "name" : "selector", + "type" : "object", + "ref" : "expressionSubElement", + "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "title" : "Selector", + "required" : true, + "displayName" : "Selector", + "kind" : "expression", + "index" : 4, + "group" : "common" + }, { + "name" : "keys", + "type" : "array", + "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", + "title" : "Keys", + "displayName" : "Keys", + "kind" : "element", + "index" : 5, + "group" : "common" + }, { + "name" : "case", + "type" : "array", + "ref" : "switchCase", + "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "title" : "Case", + "displayName" : "Case", + "kind" : "element", + "index" : 6, + "group" : "common" + }, { + "name" : "otherwise", + "type" : "string", + "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", + "title" : "Otherwise", + "displayName" : "Otherwise", + "kind" : "attribute", + "index" : 7, + "group" : "common" + } ] + }, + "switchValue" : { + "title" : "Switch Value", + "description" : "A named literal in a composite switch case", + "label" : "configuration", + "children" : [ ] + }, "templatedRoute" : { "title" : "Templated Route", "description" : "Creates a route instance from a route template, binding specific parameter values to the template's parameters", diff --git a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc index f8ec5e5b7e3d0..a58c8402ca306 100644 --- a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc +++ b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc @@ -223,6 +223,7 @@ Use `language("semantic", "ref:name")` wherever an expression or boolean predica [cols="1,3"] |=== |EIP / integration point |Usage +|Switch |Select a fixed endpoint directly from a category or a named batch using a literal lookup table. |Choice |Store a category with Set Property, then compare that property in ordinary when predicates. |Filter |Use a boolean question as the filter predicate. |Validate |Use a boolean question; false follows normal validation failure handling. diff --git a/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java b/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java index 868f5e68d3b6f..133bef5927cfe 100644 --- a/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java +++ b/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java @@ -74,6 +74,10 @@ public SemanticResult evaluate(SemanticQuestion question, Object state) { "unfinished", question("unfinished", SemanticQuestion.Type.BOOLEAN), "urgency", question("urgency", SemanticQuestion.Type.SCORE))); + from("direct:switch").doSwitch().language("semantic", "refs:department,actionable") + .keys("department", "actionable") + .doCase().value("department", "billing").value("actionable", true).to("mock:switchBilling") + .otherwise("mock:switchReview"); from("direct:batch").setProperty("decision").language("semantic", "refs:actionable,department,urgency") .setHeader("urgency").simple("${exchangeProperty.decision[urgency]}") .choice() @@ -158,6 +162,16 @@ private static SemanticQuestion question(String name, SemanticQuestion.Type type 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL); } + @Test + void batchDecisionsSelectOneSwitchDestination() throws Exception { + getMockEndpoint("mock:switchBilling").expectedBodiesReceived("please fix this invoice"); + getMockEndpoint("mock:switchReview").expectedMessageCount(0); + template.sendBody("direct:switch", "please fix this invoice"); + MockEndpoint.assertIsSatisfied(context); + assertThat(calls.get("department").get()).isEqualTo(1); + assertThat(calls.get("actionable").get()).isEqualTo(1); + } + @Test void batchDecisionsAreReusedByOrdinaryEips() throws Exception { getMockEndpoint("mock:batchBilling").expectedBodiesReceived("please fix this urgent invoice"); 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 new file mode 100644 index 0000000000000..5c3eec5f775eb --- /dev/null +++ b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/SwitchJaxbTest.java @@ -0,0 +1,81 @@ +/* + * 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.spring; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.RoutesDefinition; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.language.XPathExpression; +import org.apache.camel.xml.jaxb.JaxbHelper; +import org.apache.camel.xml.jaxb.JaxbModelToXMLDumper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SwitchJaxbTest { + @Test + void compositeLiteralsSurviveJaxbRoundTripAndExecute() throws Exception { + try (var context = new DefaultCamelContext()) { + RoutesDefinition routes = new RoutesDefinition(); + routes.from("direct:start").doSwitch().header("decision").keys("department", "urgent", "score") + .doCase().value("department", "billing").value("urgent", true).value("score", 2) + .id("billingCase").to("mock:billing") + .otherwise("mock:other"); + String xml = new JaxbModelToXMLDumper().dumpModelAsXml(context, routes); + try (var input = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + RoutesDefinition restored = JaxbHelper.loadRoutesDefinition(context, input); + context.addRouteDefinitions(restored.getRoutes()); + } + context.start(); + context.getEndpoint("mock:billing", MockEndpoint.class).expectedBodiesReceived("matched"); + context.getEndpoint("mock:other", MockEndpoint.class).expectedBodiesReceived("other"); + try (var template = context.createProducerTemplate()) { + template.sendBodyAndHeader("direct:start", "matched", "decision", + Map.of("department", "BILLING", "urgent", true, "score", 2.0)); + template.sendBodyAndHeader("direct:start", "other", "decision", + Map.of("department", "billing", "urgent", "true", "score", 2)); + } + MockEndpoint.assertIsSatisfied(context); + } + } + + @Test + void selectorNamespacesAndLiteralBranchesSurviveJaxbRoundTrip() throws Exception { + try (var context = new DefaultCamelContext()) { + XPathExpression selector = new XPathExpression("/t:ticket/t:department/text()"); + selector.setNamespaces(Map.of("t", "urn:tickets")); + RoutesDefinition routes = new RoutesDefinition(); + routes.from("direct:start").routeId("selector").doSwitch(selector) + .doCase("billing", "mock:billing").otherwise("mock:other"); + String xml = new JaxbModelToXMLDumper().dumpModelAsXml(context, routes); + try (var input = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + RoutesDefinition restored = JaxbHelper.loadRoutesDefinition(context, input); + SwitchDefinition choice = (SwitchDefinition) restored.getRoutes().get(0).getOutputs().get(0); + XPathExpression xpath = (XPathExpression) choice.getSelector().getExpressionType(); + assertEquals("/t:ticket/t:department/text()", xpath.getExpression()); + assertEquals("urn:tickets", xpath.getNamespaces().get("t")); + assertEquals("billing", choice.getCases().get(0).getValue()); + assertEquals("mock:billing", choice.getCases().get(0).getUri()); + } + } + } +} diff --git a/core/camel-core-engine/src/main/docs/modules/eips/examples/json/switch.json b/core/camel-core-engine/src/main/docs/modules/eips/examples/json/switch.json new file mode 120000 index 0000000000000..3a45d21072f40 --- /dev/null +++ b/core/camel-core-engine/src/main/docs/modules/eips/examples/json/switch.json @@ -0,0 +1 @@ +../../../../../../../../camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json \ No newline at end of file diff --git a/core/camel-core-engine/src/main/docs/modules/eips/nav.adoc b/core/camel-core-engine/src/main/docs/modules/eips/nav.adoc index f4817f74d9ad4..6b3248b532e5f 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/nav.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/nav.adoc @@ -106,6 +106,7 @@ ** xref:stop-eip.adoc[Stop] ** xref:throwException-eip.adoc[Stop] ** xref:streamConfig-eip.adoc[StreamConfig] +** xref:switch-eip.adoc[Switch] ** xref:threads-eip.adoc[Threads] ** xref:throttle-eip.adoc[Throttle] ** xref:to-eip.adoc[To] 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 new file mode 100644 index 0000000000000..42b02391c868e --- /dev/null +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc @@ -0,0 +1,208 @@ += Switch EIP +:doctitle: Switch +:shortname: switch +:description: Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values +:since: 4.23 +:supportlevel: Stable +:tabs-sync-option: + +*Since Camel {since}* + +Switch selects one destination from a table declared by the route author. +The selector can use any Camel expression language. Its result selects a case; +it cannot supply a new endpoint URI. + +Use Switch for literal value dispatch. Use xref:choice-eip.adoc[Choice] for +predicates, ranges, overlapping conditions, or nested processing steps. A case can +send to a `direct:` route when more processing is needed. Use xref:toD-eip.adoc[To D] +when the endpoint URI itself must be calculated dynamically. + +== Options + +// eip options: START +include::partial$eip-options.adoc[] +// eip options: END + +== Scalar selectors + +The selector is evaluated once on each entry. Scalar results are converted to strings +and matched case-insensitively using the English locale. An empty string is a valid +literal. An asterisk is literal, not a wildcard. Duplicate normalized values are +rejected at startup. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:tickets") + .doSwitch(header("department")) + .doCase("billing", "direct:billing") + .doCase("technical", "direct:technical") + .otherwise("direct:review") + .end(); +---- +XML:: ++ +[source,xml] +---- + +
department
+ + +
+---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:tickets + steps: + - switch: + selector: + header: + expression: department + case: + - value: billing + uri: direct:billing + - value: technical + uri: direct:technical + otherwise: direct:review +---- +==== + +A null or unmatched result uses `otherwise`. Without `otherwise`, the exchange +continues after Switch. Selector failures follow normal Camel error handling and +never select the fallback. Streams are reset after selector evaluation when stream +caching is enabled. Loops and retries that enter Switch again evaluate the selector +again; results are not cached across entries. + +== Composite selectors + +Configure `keys` when the selector returns a map. Each case supplies every declared +key exactly once. All values must match. Key names are case-sensitive, while string +values are case-insensitive. Entry order is irrelevant and additional result fields +are ignored. Missing keys, null field values and unsupported value types are errors. +A null result map still uses the fallback. + +Composite literals support strings, booleans and finite decimal numbers. These types +are distinct: string `"true"` does not match boolean `true`, and string `"2"` does not +match number `2`. Numbers compare by decimal value, so `2` and `2.0` match. Duplicate +combinations, including differences only in letter case or numeric scale, fail at +startup. Scalar `value` cases and composite `values` cases cannot be mixed. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:tickets") + .doSwitch(header("decision")) + .keys("department", "urgent") + .doCase().value("department", "billing").value("urgent", true) + .id("urgentBilling").to("direct:urgent-billing") + .doCase().value("department", "billing").value("urgent", false) + .to("direct:billing") + .otherwise("direct:review") + .end(); +---- +XML:: ++ +[source,xml] +---- + +
decision
+ department + urgent + + + + + + + + +
+---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:tickets + steps: + - switch: + selector: + header: + expression: decision + keys: [department, urgent] + case: + - id: urgentBilling + values: [department: billing, urgent: true] + uri: direct:urgent-billing + - values: [department: billing, urgent: false] + uri: direct:billing + otherwise: direct:review +---- +==== + +YAML `values` is a sequence of single-entry mappings. Quote strings that would +otherwise be read as booleans or numbers. XML literals default to `type="string"`; +use `type="boolean"` or `type="number"` explicitly. Keys select fields in the returned +map; they do not implicitly read separate message headers. + +Cases are compiled into a lookup table. Constructing a composite key involves the +selected fields, but lookup does not scan the declared cases. + +=== Semantic decisions + +With `camel-semantic` and a provider configured, a named batch can be used directly: + +[source,yaml] +---- +- route: + from: + uri: direct:tickets + steps: + - switch: + selector: + language: + language: semantic + expression: refs:department,urgent + keys: [department, urgent] + case: + - values: [department: billing, urgent: true] + uri: direct:urgent-billing + - values: [department: billing, urgent: false] + uri: direct:billing + otherwise: direct:review +---- + +Declare the questions using the xref:languages:semantic-language.adoc[Semantic language]. +Batching, thresholds and uncertainty policies belong to that language and its provider. +Switch has no dependency on semantic evaluation. Store a result explicitly in a +variable, header or exchange property when it should be reused by later EIPs. + +== Destinations and management + +Case and fallback URIs support property placeholders resolved at startup. Simple +expressions in URIs are rejected. In YAML, a case also accepts `uri` plus `parameters`: + +[source,yaml] +---- +case: + - value: orders + uri: kafka + parameters: + topic: orders +---- + +Each case has an identity for tracing, debugging and management. The Switch MBean's +`extendedInformation` table reports case IDs, literal values, destination URIs and +selection counts. These counts indicate case selection, not successful delivery. +`UnmatchedCount` also records unmatched results when no fallback is configured. diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/a2aSubTask.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/a2aSubTask.json index 4e3ac7673b7be..ebf2a61f68a74 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/a2aSubTask.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/a2aSubTask.json @@ -21,6 +21,6 @@ "emitAfter": { "index": 5, "kind": "attribute", "displayName": "Emit After", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Simple expression template to emit after the nested steps complete successfully" }, "emitOnError": { "index": 6, "kind": "attribute", "displayName": "Emit On Error", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Simple expression template to emit when the nested steps fail" }, "failIfNoTaskContext": { "index": 7, "kind": "attribute", "displayName": "Fail If No Task Context", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to fail if the current Exchange does not have an active A2A task context" }, - "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/aggregate.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/aggregate.json index 0fb9ff69a07f9..b44e99f13f841 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/aggregate.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/aggregate.json @@ -45,7 +45,7 @@ "discardOnAggregationFailure": { "index": 29, "kind": "attribute", "displayName": "Discard On Aggregation Failure", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then the partly aggregated message is discarded when aggregation failed (an exception was thrown from AggregationStrategy). Cannot be used together with completionFromBatchConsumer." }, "forceCompletionOnStop": { "index": 30, "kind": "attribute", "displayName": "Force Completion On Stop", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then all current aggregated exchanges are completed when the context is stopped." }, "completeAllOnStop": { "index": 31, "kind": "attribute", "displayName": "Complete All On Stop", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then waits to complete all current and partial (pending) aggregated exchanges when the context is stopped, ensuring the aggregation repository is empty before shutdown." }, - "outputs": { "index": 32, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 32, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelAggregatedSize": { "index": 0, "kind": "exchangeProperty", "displayName": "Aggregated Size", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "important": true, "description": "Number of exchanges that was grouped together." }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/cache.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/cache.json index 459a15180559f..08f5ce5c924b6 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/cache.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/cache.json @@ -21,6 +21,6 @@ "keyValueRepository": { "index": 5, "kind": "attribute", "displayName": "Key Value Repository", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.spi.KeyValueRepository", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the reference name of the KeyValueRepository to use as the cache backing store. If not set, a MemoryKeyValueRepository is auto-created." }, "ttl": { "index": 6, "kind": "attribute", "displayName": "Ttl", "group": "common", "required": false, "type": "duration", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "-1", "description": "Sets the time-to-live for cached entries. Supports duration syntax (e.g. 10m, 1h) or milliseconds. Default: -1 (no expiration)." }, "cacheNull": { "index": 7, "kind": "attribute", "displayName": "Cache Null", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to cache null results. By default, null message bodies are not cached." }, - "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json new file mode 100644 index 0000000000000..f7de1b0888ca5 --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json @@ -0,0 +1,22 @@ +{ + "model": { + "kind": "model", + "name": "case", + "title": "Case", + "description": "A literal switch case with one fixed endpoint destination", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.SwitchCaseDefinition", + "abstract": false, + "input": false, + "output": false + }, + "properties": { + "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, + "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, + "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, + "value": { "index": 3, "kind": "attribute", "displayName": "Value", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." }, + "values": { "index": 4, "kind": "element", "displayName": "Values", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Named, typed literal values for a composite selector. Every selected key must occur exactly once." }, + "uri": { "index": 5, "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/org/apache/camel/model/circuitBreaker.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/circuitBreaker.json index 5880e386c3423..e9b6c76a5f46e 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/circuitBreaker.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/circuitBreaker.json @@ -22,7 +22,7 @@ "resilience4jConfiguration": { "index": 6, "kind": "element", "displayName": "Resilience4j Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.Resilience4jConfigurationDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Configures the circuit breaker to use Resilience4j with the given configuration." }, "faultToleranceConfiguration": { "index": 7, "kind": "element", "displayName": "Fault Tolerance Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.FaultToleranceConfigurationDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Configures the circuit breaker to use MicroProfile Fault Tolerance with the given configuration." }, "onFallback": { "index": 8, "kind": "element", "displayName": "On Fallback", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnFallbackDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "The fallback route path to execute when the circuit breaker triggers." }, - "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelResponseSuccessfulExecution": { "index": 0, "kind": "exchangeProperty", "displayName": "Response Successful Execution", "label": "producer", "required": false, "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether the exchange was processed successfully by the circuit breaker" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json index 2ce320d08830f..f71c063b00bde 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json @@ -18,7 +18,7 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "exception": { "index": 4, "kind": "element", "displayName": "Exception", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "The exception class names (fully qualified) to catch." }, "onWhen": { "index": 5, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "An additional predicate that must evaluate to true for this doCatch to trigger. Allows fine-grained control over which exceptions are caught based on the exchange state." }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", "displayName": "Exception Caught", "label": "producer", "required": false, "javaType": "java.lang.Exception", "deprecated": false, "autowired": false, "secret": false, "description": "Stores the caught exception due to a processing error of the current Exchange" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json index 431f292b84515..015e9389917fb 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json @@ -16,7 +16,7 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", "displayName": "Exception Caught", "label": "producer", "required": false, "javaType": "java.lang.Exception", "deprecated": false, "autowired": false, "secret": false, "description": "Stores the caught exception due to a processing error of the current Exchange" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doTry.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doTry.json index 28998908d77aa..1ab592d6688da 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doTry.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doTry.json @@ -16,7 +16,7 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false }, + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false }, "doCatch": { "index": 5, "kind": "element", "displayName": "Do Catch", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "oneOf": [ "doCatch" ], "deprecated": false, "autowired": false, "secret": false, "description": "Catches exceptions as part of a try, catch, finally block" }, "doFinally": { "index": 6, "kind": "element", "displayName": "Do Finally", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.FinallyDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Path traversed when a try, catch, finally block exits" } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/filter.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/filter.json index c1300ea647c1c..14e7752a5be72 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/filter.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/filter.json @@ -19,6 +19,6 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "expression": { "index": 4, "kind": "expression", "displayName": "Expression", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.language.ExpressionDefinition", "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" ], "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "The predicate expression to evaluate. Messages where the predicate returns false are filtered out and not routed further." }, "statusPropertyName": { "index": 5, "kind": "attribute", "displayName": "Status Property Name", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of an exchange property to store whether the filter predicate matched or not. The value is stored as a boolean." }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/idempotentConsumer.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/idempotentConsumer.json index ac640cd7983f7..f027166a0bfc3 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/idempotentConsumer.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/idempotentConsumer.json @@ -23,7 +23,7 @@ "completionEager": { "index": 7, "kind": "attribute", "displayName": "Completion Eager", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Sets whether to complete the idempotent consumer eager or when the exchange is done." }, "skipDuplicate": { "index": 8, "kind": "attribute", "displayName": "Skip Duplicate", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether to skip duplicates or not. The default behavior is to skip duplicates." }, "removeOnFailure": { "index": 9, "kind": "attribute", "displayName": "Remove On Failure", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Sets whether to remove or keep the key on failure. The default behavior is to remove the key on failure." }, - "outputs": { "index": 10, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 10, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelDuplicateMessage": { "index": 0, "kind": "exchangeProperty", "displayName": "Duplicate Message", "label": "producer", "required": false, "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Whether this exchange is a duplicate detected by the Idempotent Consumer EIP" } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/intercept.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/intercept.json index d630b9c923a84..a828928287df4 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/intercept.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/intercept.json @@ -17,7 +17,7 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "onWhen": { "index": 4, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger intercepting in specific situations" }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelInterceptedNodeId": { "index": 0, "kind": "exchangeProperty", "displayName": "Intercepted Node Id", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The intercepted route's node ID" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptFrom.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptFrom.json index 799330919f7a6..0e1c4ff4c2706 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptFrom.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptFrom.json @@ -18,7 +18,7 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "uri": { "index": 4, "kind": "attribute", "displayName": "Uri", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Intercept incoming messages from the uri or uri pattern. If not configured, then all incoming messages are intercepted." }, "onWhen": { "index": 5, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger intercepting in specific situations" }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelInterceptedEndpoint": { "index": 0, "kind": "exchangeProperty", "displayName": "Intercepted Endpoint", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The endpoint URI that was intercepted" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptSendToEndpoint.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptSendToEndpoint.json index 45ed39b6cbfb8..98f5399696b87 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptSendToEndpoint.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/interceptSendToEndpoint.json @@ -20,7 +20,7 @@ "skipSendToOriginalEndpoint": { "index": 5, "kind": "attribute", "displayName": "Skip Send To Original Endpoint", "group": "common", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to skip sending the message to the original endpoint. By default, the message is both intercepted and then sent to the original endpoint." }, "afterUri": { "index": 6, "kind": "attribute", "displayName": "After Uri", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "After sending to the endpoint then send the message to this uri which allows to process its result." }, "onWhen": { "index": 7, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger intercepting sending to an endpoint in specific situations" }, - "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 8, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelInterceptedEndpoint": { "index": 0, "kind": "exchangeProperty", "displayName": "Intercepted Endpoint", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The endpoint URI that was intercepted" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/kamelet.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/kamelet.json index cd8c43e1beacc..1d0749b2d5e10 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/kamelet.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/kamelet.json @@ -17,6 +17,6 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "name": { "index": 4, "kind": "attribute", "displayName": "Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of the Kamelet (templateId\/routeId) to call. Options for the kamelet can be specified using uri syntax, eg mynamecount=4&type=gold." }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loadBalance.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loadBalance.json index de65c909bd967..29654577a69fb 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loadBalance.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loadBalance.json @@ -18,6 +18,6 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "loadBalancerType": { "index": 4, "kind": "element", "displayName": "Load Balancer Type", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.LoadBalancerDefinition", "oneOf": [ "customLoadBalancer", "failoverLoadBalancer", "randomLoadBalancer", "roundRobinLoadBalancer", "stickyLoadBalancer", "topicLoadBalancer", "weightedLoadBalancer" ], "deprecated": false, "autowired": false, "secret": false, "description": "The load balancing strategy to use, such as round-robin, random, sticky, topic, failover, weighted, or a custom load balancer." }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loop.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loop.json index 333d1d3cad860..08cffb3e6a881 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loop.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/loop.json @@ -22,7 +22,7 @@ "doWhile": { "index": 6, "kind": "attribute", "displayName": "Do While", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enables the while loop that loops until the predicate evaluates to false or null." }, "breakOnShutdown": { "index": 7, "kind": "attribute", "displayName": "Break On Shutdown", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, the loop will not iterate until it reaches the end when Camel is shut down." }, "onPrepare": { "index": 8, "kind": "attribute", "displayName": "On Prepare", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.Processor", "deprecated": false, "autowired": false, "secret": false, "description": "Uses a processor when preparing the exchange for each loop iteration. This can be used to deep-clone messages, or any custom logic needed before the looping executes." }, - "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 9, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelLoopIndex": { "index": 0, "kind": "exchangeProperty", "displayName": "Loop Index", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "Index of the current iteration (0 based)." }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/multicast.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/multicast.json index 37d9cd457cd2c..29f44c5b3e1c4 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/multicast.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/multicast.json @@ -29,7 +29,7 @@ "executorService": { "index": 13, "kind": "attribute", "displayName": "Executor Service", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.concurrent.ExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Reference to a custom thread pool to use for parallel processing. Setting this option implies parallel processing." }, "onPrepare": { "index": 14, "kind": "attribute", "displayName": "On Prepare", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.Processor", "deprecated": false, "autowired": false, "secret": false, "description": "Reference to a processor for preparing the exchange to be sent. Can be used to deep-clone messages that should be sent." }, "shareUnitOfWork": { "index": 15, "kind": "attribute", "displayName": "Share Unit Of Work", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Shares the unit of work with the parent and each of the multicast exchanges. By default each multicast exchange has its own individual unit of work." }, - "outputs": { "index": 16, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 16, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelMulticastIndex": { "index": 0, "kind": "exchangeProperty", "displayName": "Multicast Index", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "An index counter that increases for each Exchange being multicasted. The counter starts from 0." }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onCompletion.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onCompletion.json index ffb6fa48a43d9..262a77cea7fc1 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onCompletion.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onCompletion.json @@ -23,7 +23,7 @@ "executorService": { "index": 8, "kind": "attribute", "displayName": "Executor Service", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.concurrent.ExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Refers to a custom Thread Pool to be used for parallel processing. Notice if you set this option, then parallel processing is automatically implied, and you do not have to enable that option as well." }, "useOriginalMessage": { "index": 9, "kind": "attribute", "displayName": "Use Original Message", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Will use the original input message when an Exchange for this on completion. By default this feature is off." }, "onWhen": { "index": 10, "kind": "element", "displayName": "On When", "group": "common", "required": false, "type": "object", "javaType": "org.apache.camel.model.OnWhenDefinition", "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "To use an expression to only trigger routing this completion steps in specific situations" }, - "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelOnCompletion": { "index": 0, "kind": "exchangeProperty", "displayName": "On Completion", "label": "producer", "required": false, "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "description": "Flag to mark that this exchange is currently being executed as onCompletion" } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json index 886d297162cbe..e0ed0003e5046 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json @@ -27,7 +27,7 @@ "onExceptionOccurredRef": { "index": 12, "kind": "attribute", "displayName": "On Exception Occurred Ref", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a reference to a processor that is invoked just after an exception occurred. Can be used to perform custom logging. Any exception thrown from this processor is ignored." }, "useOriginalMessage": { "index": 13, "kind": "attribute", "displayName": "Use Original Message", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, uses the original input message (body and headers) when the exchange is moved to the dead letter queue after all redelivery attempts have been exhausted. Cannot be used together with useOriginalBody." }, "useOriginalBody": { "index": 14, "kind": "attribute", "displayName": "Use Original Body", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, uses the original input message body (but not headers) when the exchange is moved to the dead letter queue after all redelivery attempts have been exhausted. This allows enriching the message with custom headers while keeping the original body. Cannot be used together with useOriginalMessage." }, - "outputs": { "index": 15, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 15, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", "displayName": "Exception Caught", "label": "producer", "required": false, "javaType": "java.lang.Exception", "deprecated": false, "autowired": false, "secret": false, "description": "Stores the caught exception due to a processing error of the current Exchange" }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onFallback.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onFallback.json index fda944929370d..839e56088a003 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onFallback.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onFallback.json @@ -17,6 +17,6 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "fallbackViaNetwork": { "index": 3, "kind": "attribute", "displayName": "Fallback Via Network", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": true, "deprecationNote": "Not supported by any circuit breaker implementation.", "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the fallback goes over the network. If so, the fallback is executed on a separate thread-pool to avoid exhausting the main thread-pool." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/otherwise.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/otherwise.json index 201bd53c297c5..41494913752ff 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/otherwise.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/otherwise.json @@ -16,6 +16,6 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Disables this EIP from the route." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pipeline.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pipeline.json index 8c2e503fed5c7..3c5106ddb4855 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pipeline.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pipeline.json @@ -17,6 +17,6 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resequence.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resequence.json index 7cc32a64df664..a001f1f6d674c 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resequence.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/resequence.json @@ -18,6 +18,6 @@ "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, "expression": { "index": 4, "kind": "expression", "displayName": "Expression", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.language.ExpressionDefinition", "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression to use for re-ordering the messages, such as a header with a sequence number." }, "resequencerConfig": { "index": 5, "kind": "element", "displayName": "Resequencer Config", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.config.ResequencerConfig", "oneOf": [ "batchConfig", "streamConfig" ], "deprecated": false, "autowired": false, "secret": false, "description": "Resequencer configuration using either batch or stream mode. Defaults to batch mode." }, - "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 6, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/route.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/route.json index 16356d0e9ad39..ad5d3453d0f70 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/route.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/route.json @@ -34,6 +34,6 @@ "inputType": { "index": 19, "kind": "element", "displayName": "Input Type", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.InputTypeDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Declare the expected data type of the input message. If the actual message type is different at runtime, camel looks for a required Transformer and applies if exists." }, "outputType": { "index": 20, "kind": "element", "displayName": "Output Type", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.model.OutputTypeDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Declare the expected data type of the output message. If the actual message type is different at runtime, camel looks for a required Transformer and applies if exists." }, "input": { "index": 21, "kind": "element", "displayName": "Input", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.FromDefinition", "oneOf": [ "from" ], "deprecated": false, "autowired": false, "secret": false, "description": "The input (from) endpoint that starts this route and acts as the consumer that receives incoming messages." }, - "outputs": { "index": 22, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 22, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/saga.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/saga.json index 0ea9948ae92c6..de47ff1502019 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/saga.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/saga.json @@ -24,6 +24,6 @@ "compensation": { "index": 8, "kind": "attribute", "displayName": "Compensation", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The compensation endpoint URI that must be called to compensate all changes done in the route. The route corresponding to the compensation URI must perform compensation and complete without error." }, "completion": { "index": 9, "kind": "attribute", "displayName": "Completion", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The completion endpoint URI that will be called when the Saga is completed successfully. The route corresponding to the completion URI must perform completion tasks and terminate without error." }, "option": { "index": 10, "kind": "element", "displayName": "Option", "group": "advanced", "label": "advanced", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Allows to save properties of the current exchange in order to re-use them in a compensation\/completion callback route. Option values will be transformed into input headers of the compensation\/completion exchange." }, - "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 11, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/split.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/split.json index ca0c9e4909a8f..9ac5f17714ad1 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/split.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/split.json @@ -37,7 +37,7 @@ "resumeStrategy": { "index": 21, "kind": "attribute", "displayName": "Resume Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.resume.ResumeStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a ResumeStrategy for resume-from-last-position support. The watermark key must also be configured via watermarkKey(String) ." }, "watermarkKey": { "index": 22, "kind": "attribute", "displayName": "Watermark Key", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the key to use in the watermark store." }, "watermarkExpression": { "index": 23, "kind": "attribute", "displayName": "Watermark Expression", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a Simple expression to evaluate on each completed sub-exchange to determine the new watermark value. When set, enables value-based watermarking instead of index-based. The expression is evaluated using the Simple language." }, - "outputs": { "index": 24, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 24, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelSplitIndex": { "index": 0, "kind": "exchangeProperty", "displayName": "Split Index", "label": "producer", "required": false, "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "important": true, "description": "A split counter that increases for each Exchange being split. The counter starts from 0." }, diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/step.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/step.json index 4b05956cb882d..710b522ee3010 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/step.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/step.json @@ -16,7 +16,7 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 4, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } }, "exchangeProperties": { "CamelStepId": { "index": 0, "kind": "exchangeProperty", "displayName": "Step Id", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of the Step EIP" } 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 new file mode 100644 index 0000000000000..c557fd3550a3f --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json @@ -0,0 +1,25 @@ +{ + "model": { + "kind": "model", + "name": "switch", + "title": "Switch", + "description": "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "deprecated": false, + "firstVersion": "4.23.0", + "label": "eip,routing", + "javaType": "org.apache.camel.model.SwitchDefinition", + "abstract": false, + "input": true, + "output": false + }, + "properties": { + "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, + "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, + "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, + "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, + "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured." }, + "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "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", "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." } + } +} diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json new file mode 100644 index 0000000000000..aa07fe73e9bd7 --- /dev/null +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json @@ -0,0 +1,19 @@ +{ + "model": { + "kind": "model", + "name": "switchValue", + "title": "Switch Value", + "description": "A named literal in a composite switch case", + "deprecated": false, + "label": "configuration", + "javaType": "org.apache.camel.model.SwitchValueDefinition", + "abstract": false, + "input": false, + "output": false + }, + "properties": { + "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The exact name of the selected field." }, + "value": { "index": 1, "kind": "attribute", "displayName": "Value", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The literal value, never an expression." }, + "type": { "index": 2, "kind": "attribute", "displayName": "Type", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "string", "boolean", "number" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "string", "description": "The literal type." } + } +} diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/when.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/when.json index e04a77617a656..44b0e97480e41 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/when.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/when.json @@ -17,6 +17,6 @@ "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Disables this EIP from the route." }, "expression": { "index": 4, "kind": "expression", "displayName": "Expression", "group": "common", "required": true, "type": "object", "javaType": "org.apache.camel.model.language.ExpressionDefinition", "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" ], "deprecated": false, "autowired": false, "secret": false, "asPredicate": true, "description": "Expression used as the predicate to evaluate whether this when should trigger and route the message or not." }, - "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } + "outputs": { "index": 5, "kind": "element", "displayName": "Outputs", "group": "common", "required": true, "type": "array", "javaType": "java.util.List>", "oneOf": [ "a2aSubTask", "aggregate", "bean", "cache", "choice", "circuitBreaker", "claimCheck", "convertBodyTo", "convertHeaderTo", "convertVariableTo", "delay", "doCatch", "doFinally", "doTry", "dynamicRouter", "enrich", "filter", "idempotentConsumer", "intercept", "interceptFrom", "interceptSendToEndpoint", "kamelet", "loadBalance", "log", "loop", "marshal", "multicast", "onCompletion", "onException", "pausable", "pipeline", "policy", "poll", "pollEnrich", "process", "recipientList", "removeHeader", "removeHeaders", "removeProperties", "removeProperty", "removeVariable", "resequence", "resumable", "rollback", "routingSlip", "saga", "sample", "script", "setBody", "setExchangePattern", "setHeader", "setHeaders", "setProperty", "setVariable", "setVariables", "sort", "split", "step", "stop", "switch", "threads", "throttle", "throwException", "to", "toD", "tokenizer", "transacted", "transform", "transformDataType", "unmarshal", "validate", "wireTap" ], "deprecated": false, "autowired": false, "secret": false } } } 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 e56e3291c84dc..bfdfc89a0ebf7 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 @@ -14,6 +14,7 @@ beanio bearerToken bindy cache +case cbor choice circuitBreaker @@ -197,6 +198,8 @@ stop streamConfig swiftMt swiftMx +switch +switchValue syslog tarFile templateParameter 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 c74b9dece4f1a..13090d116b524 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 @@ -97,6 +97,9 @@ SortDefinition SplitDefinition StepDefinition StopDefinition +SwitchCaseDefinition +SwitchDefinition +SwitchValueDefinition TemplatedRouteDefinition TemplatedRouteParameterDefinition TemplatedRoutesDefinition diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java index d3c8cb78b2961..d1fd12db95c61 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java @@ -1639,6 +1639,31 @@ public ChoiceDefinition choice() { return answer; } + /** + * Routes to a fixed endpoint using a literal selector lookup. + * + * @param selector the expression to evaluate once per entry + * @return the switch builder + */ + public SwitchDefinition doSwitch(Expression selector) { + SwitchDefinition answer = new SwitchDefinition(selector); + addOutput(answer); + return answer; + } + + /** + * Routes to a fixed endpoint using a literal selector lookup. + * + * @return the selector expression builder + */ + public ExpressionClause doSwitch() { + SwitchDefinition answer = new SwitchDefinition(); + ExpressionClause clause = new ExpressionClause<>(answer); + answer.setSelector(new ExpressionSubElementDefinition((Expression) clause)); + addOutput(answer); + return clause; + } + /** * Creates a try/catch block * diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java new file mode 100644 index 0000000000000..60a4232d976da --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java @@ -0,0 +1,143 @@ +/* + * 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.ArrayList; +import java.util.List; + +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; + +import org.apache.camel.spi.Metadata; + +/** One literal combination and one fixed destination. */ +@Metadata(label = "configuration", description = "A literal switch case with one fixed endpoint destination") +@XmlRootElement(name = "case") +@XmlAccessorType(XmlAccessType.FIELD) +public class SwitchCaseDefinition extends OptionalIdentifiedDefinition + implements EndpointRequiredDefinition { + @XmlAttribute + @Metadata(description = "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.") + private String value; + @XmlElement(name = "values") + @Metadata(description = "Named, typed literal values for a composite selector. Every selected key must occur exactly once.") + private List values = new ArrayList<>(); + @XmlAttribute(required = true) + @Metadata(description = "The fixed destination URI. Supports property placeholders, but not Simple expressions.") + private String uri; + @XmlTransient + private SwitchDefinition parent; + @XmlTransient + private ToDefinition toDefinition; + + public SwitchCaseDefinition() { + } + + public SwitchCaseDefinition(String value, String uri) { + this.value = value; + this.uri = uri; + } + + protected SwitchCaseDefinition(SwitchCaseDefinition source) { + super(source); + this.value = source.value; + this.uri = source.uri; + for (SwitchValueDefinition literal : source.values) { + values.add(literal.copyDefinition()); + } + } + + public SwitchCaseDefinition copyDefinition() { + return new SwitchCaseDefinition(this); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + /** The destination node shared by reification and management instrumentation. */ + @XmlTransient + public ToDefinition getToDefinition() { + if (toDefinition == null) { + toDefinition = new ToDefinition(uri); + } + toDefinition.setParent(parent); + if (hasCustomIdAssigned()) { + toDefinition.setId(getId()); + } else { + toDefinition.setGeneratedId(getId()); + } + toDefinition.setDescription(getDescription()); + toDefinition.setNote(getNote()); + toDefinition.setLocation(getLocation()); + toDefinition.setLineNumber(getLineNumber()); + return toDefinition; + } + + @Override + public String getEndpointUri() { + return uri; + } + + @Override + public SwitchDefinition getParent() { + return parent; + } + + public void setParent(SwitchDefinition parent) { + this.parent = parent; + } + + @Override + public String getShortName() { + return "case"; + } + + @Override + public String getLabel() { + return "case[" + (value != null ? value : values) + "]"; + } + + @Override + public String toString() { + return getLabel() + " -> " + uri; + } +} 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 new file mode 100644 index 0000000000000..32284c8d8f15e --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java @@ -0,0 +1,255 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.List; +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; +import jakarta.xml.bind.annotation.XmlType; + +import org.apache.camel.Expression; +import org.apache.camel.NamedNode; +import org.apache.camel.builder.ExpressionClause; +import org.apache.camel.model.language.ExpressionDefinition; +import org.apache.camel.spi.Metadata; + +/** Routes a message to one fixed endpoint using a literal lookup table. */ +@Metadata(firstVersion = "4.23.0", label = "eip,routing", + 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" }) +public class SwitchDefinition extends NoOutputDefinition { + @XmlElement(required = true) + @Metadata(required = true, + description = "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.") + private ExpressionSubElementDefinition selector; + @XmlElement(name = "keys") + @Metadata(description = "Exact map field names forming a composite key. Additional result fields are ignored.") + private List keys = new ArrayList<>(); + @XmlElement(name = "case") + @Metadata(description = "Literal cases. Duplicate combinations are rejected at startup.") + private List cases = new ArrayList<>(); + @XmlAttribute + @Metadata(description = "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.") + private String otherwise; + @XmlTransient + private ToDefinition otherwiseDefinition; + + public SwitchDefinition() { + } + + public SwitchDefinition(Expression selector) { + this.selector = new ExpressionSubElementDefinition(ExpressionNodeHelper.toExpressionDefinition(selector)); + } + + protected SwitchDefinition(SwitchDefinition source) { + super(source); + this.selector = source.selector != null ? source.selector.copyDefinition() : null; + this.keys = new ArrayList<>(source.keys); + this.otherwise = source.otherwise; + for (SwitchCaseDefinition c : source.cases) { + SwitchCaseDefinition copy = c.copyDefinition(); + copy.setParent(this); + cases.add(copy); + } + } + + @Override + public SwitchDefinition copyDefinition() { + return new SwitchDefinition(this); + } + + public ExpressionSubElementDefinition getSelector() { + preCreateProcessor(); + return selector; + } + + @Override + public void preCreateProcessor() { + if (selector != null && selector.getExpressionType() != null + && selector.getExpressionType().getExpressionValue() instanceof ExpressionClause clause + && clause.getExpressionType() instanceof ExpressionDefinition expression) { + selector.setExpressionType(expression); + } + } + + public void setSelector(ExpressionSubElementDefinition selector) { + this.selector = selector; + } + + public List getKeys() { + return keys; + } + + public void setKeys(List keys) { + this.keys = keys; + } + + public List getCases() { + return cases; + } + + public void setCases(List cases) { + this.cases = cases; + } + + public String getOtherwise() { + return otherwise; + } + + public void setOtherwise(String otherwise) { + this.otherwise = otherwise; + this.otherwiseDefinition = null; + } + + /** The fallback send node, used by route traversal and processor creation. */ + @XmlTransient + public ToDefinition getOtherwiseDefinition() { + if (otherwise == null) { + return null; + } + if (otherwiseDefinition == null) { + otherwiseDefinition = new ToDefinition(otherwise); + otherwiseDefinition.setParent(this); + } + if (getId() != null) { + if (hasCustomIdAssigned()) { + otherwiseDefinition.setId(getId() + "-otherwise"); + } else { + otherwiseDefinition.setGeneratedId(getId() + "-otherwise"); + } + } + return otherwiseDefinition; + } + + /** Select the named fields participating in composite matching. */ + public SwitchDefinition keys(String... keys) { + setKeys(new ArrayList<>(Arrays.asList(keys))); + return this; + } + + /** Add a scalar literal and its destination. */ + public SwitchDefinition doCase(String value, String uri) { + return doCase(new SwitchCaseDefinition(value, uri)); + } + + /** Add a composite combination and its destination. */ + public SwitchDefinition doCase(Map values, String uri) { + SwitchCaseDefinition c = new SwitchCaseDefinition(); + values.forEach((name, value) -> c.getValues().add(new SwitchValueDefinition(name, value))); + c.setUri(uri); + return doCase(c); + } + + /** Add a case definition. */ + public SwitchDefinition doCase(SwitchCaseDefinition value) { + value.setParent(this); + cases.add(value); + if (getCamelContext() != null && (getCamelContext().isSourceLocationEnabled() + || getCamelContext().isDebugging() || getCamelContext().isDebugStandby() + || getCamelContext().isTracing() || getCamelContext().isTracingStandby())) { + ProcessorDefinitionHelper.prepareSourceLocation(ProcessorDefinitionHelper.getResource(this), value); + } + return this; + } + + /** Build a composite case with named literal values. */ + public CaseBuilder doCase() { + return new CaseBuilder(this, new SwitchCaseDefinition()); + } + + /** Build a scalar case. */ + public CaseBuilder doCase(String value) { + return new CaseBuilder(this, new SwitchCaseDefinition(value, null)); + } + + /** Set the fixed fallback destination. */ + public SwitchDefinition otherwise(String uri) { + setOtherwise(uri); + return this; + } + + @Override + public List getChildren() { + List children = new ArrayList<>(cases); + ToDefinition fallback = getOtherwiseDefinition(); + if (fallback != null) { + children.add(fallback); + } + return children; + } + + @Override + public String getShortName() { + return "switch"; + } + + @Override + public String getLabel() { + return "switch[" + selector + "]"; + } + + @Override + public String toString() { + return getLabel() + cases; + } + + /** A case builder that permits exactly one endpoint and no nested route steps. */ + public static final class CaseBuilder { + private final SwitchDefinition parent; + private final SwitchCaseDefinition definition; + + private CaseBuilder(SwitchDefinition parent, SwitchCaseDefinition definition) { + this.parent = parent; + this.definition = definition; + } + + public CaseBuilder value(String name, Object value) { + definition.getValues().add(new SwitchValueDefinition(name, value)); + return this; + } + + public CaseBuilder id(String id) { + definition.setId(id); + return this; + } + + public CaseBuilder description(String description) { + definition.setDescription(description); + return this; + } + + public CaseBuilder note(String note) { + definition.setNote(note); + return this; + } + + public SwitchDefinition to(String uri) { + definition.setUri(uri); + return parent.doCase(definition); + } + } +} diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java new file mode 100644 index 0000000000000..339c47e2ae532 --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java @@ -0,0 +1,115 @@ +/* + * 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.math.BigDecimal; + +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; + +/** A named, typed literal in a switch case. */ +@Metadata(label = "configuration", description = "A named literal in a composite switch case") +@XmlRootElement(name = "switchValue") +@XmlAccessorType(XmlAccessType.FIELD) +public class SwitchValueDefinition { + @XmlAttribute(required = true) + @Metadata(description = "The exact name of the selected field.") + private String name; + @XmlAttribute(required = true) + @Metadata(description = "The literal value, never an expression.") + private String value; + @XmlAttribute + @Metadata(defaultValue = "string", enums = "string,boolean,number", description = "The literal type.") + private String type; + + public SwitchValueDefinition() { + } + + public SwitchValueDefinition(String name, Object value) { + this.name = name; + if (value instanceof String text) { + this.value = text; + this.type = "string"; + } else if (value instanceof Boolean) { + this.value = value.toString(); + this.type = "boolean"; + } else if (value instanceof Number) { + this.value = value.toString(); + this.type = "number"; + } else { + throw new IllegalArgumentException("Switch values must be strings, booleans or numbers"); + } + } + + public SwitchValueDefinition copyDefinition() { + SwitchValueDefinition copy = new SwitchValueDefinition(); + copy.name = name; + copy.value = value; + copy.type = type; + return copy; + } + + public Object asLiteral() { + if (value == null) { + throw new IllegalArgumentException("Switch value is required for key: " + name); + } + return switch (type == null ? "string" : type) { + case "string" -> value; + case "boolean" -> { + if (!"true".equals(value) && !"false".equals(value)) { + throw new IllegalArgumentException("Switch boolean must be true or false for key: " + name); + } + yield Boolean.valueOf(value); + } + case "number" -> new BigDecimal(value); + default -> throw new IllegalArgumentException("Unsupported switch literal type: " + type); + }; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return name + "=" + value; + } +} diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java new file mode 100644 index 0000000000000..7c3b63279a114 --- /dev/null +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java @@ -0,0 +1,232 @@ +/* + * 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; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLongArray; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.AsyncProcessor; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Expression; +import org.apache.camel.Navigate; +import org.apache.camel.Processor; +import org.apache.camel.Traceable; +import org.apache.camel.spi.IdAware; +import org.apache.camel.spi.RouteIdAware; +import org.apache.camel.spi.StepIdAware; +import org.apache.camel.support.AsyncProcessorConverterHelper; +import org.apache.camel.support.MessageHelper; +import org.apache.camel.support.service.ServiceHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.camel.processor.PipelineHelper.continueProcessing; + +/** Evaluates one selector and dispatches through a table of literal keys. */ +public class SwitchProcessor extends BaseProcessorSupport + implements Navigate, Traceable, IdAware, RouteIdAware, StepIdAware { + private static final Logger LOG = LoggerFactory.getLogger(SwitchProcessor.class); + private final CamelContext context; + private final Expression selector; + private final List keys; + private final Map table = new LinkedHashMap<>(); + private final List processors = new ArrayList<>(); + private final AsyncProcessor otherwise; + private final AtomicLongArray counts; + private String id; + private String routeId; + private String stepId; + + public SwitchProcessor(CamelContext context, Expression selector, List keys, + Map cases, Processor otherwise) { + this.context = context; + this.selector = selector; + this.keys = List.copyOf(keys); + cases.forEach((key, processor) -> { + table.put(key, processors.size()); + processors.add(AsyncProcessorConverterHelper.convert(processor)); + }); + this.otherwise = otherwise == null ? null : AsyncProcessorConverterHelper.convert(otherwise); + counts = new AtomicLongArray(processors.size() + 1); + } + + /** Normalize a typed literal, preserving the distinction between strings, booleans and numbers. */ + public static Object normalizeLiteral(Object value) { + if (value instanceof String text) { + return text.toLowerCase(Locale.ENGLISH); + } + if (value instanceof Boolean) { + return value; + } + if (value instanceof Number number) { + try { + return new BigDecimal(number.toString()).stripTrailingZeros(); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Switch numbers must be finite decimal values", e); + } + } + throw new IllegalArgumentException("Switch values must be non-null strings, booleans or numbers"); + } + + /** Build the same immutable composite key for case declarations and selector results. */ + public static List compositeKey(Map values, List keys) { + List key = new ArrayList<>(keys.size()); + for (String name : keys) { + if (!values.containsKey(name)) { + throw new IllegalArgumentException("Missing switch result key: " + name); + } + key.add(normalizeLiteral(values.get(name))); + } + return List.copyOf(key); + } + + @Override + public boolean process(Exchange exchange, AsyncCallback callback) { + Object key = null; + try { + Object result = selector.evaluate(exchange, Object.class); + if (result != null) { + if (!keys.isEmpty()) { + if (!(result instanceof Map values)) { + throw new IllegalArgumentException("Composite switch selector must return a Map"); + } + key = compositeKey(values, keys); + } else { + if (result instanceof Map || result instanceof Iterable || result.getClass().isArray()) { + throw new IllegalArgumentException("Scalar switch selector must return a scalar value"); + } + String text = context.getTypeConverter().mandatoryConvertTo(String.class, exchange, result); + key = text.toLowerCase(Locale.ENGLISH); + } + } + } catch (Exception e) { + exchange.setException(e); + } finally { + MessageHelper.resetStreamCache(exchange.getIn()); + } + if (!continueProcessing(exchange, "so breaking out of switch", LOG)) { + callback.done(true); + return true; + } + Integer index = key == null ? null : table.get(key); + if (index != null) { + counts.incrementAndGet(index); + return processors.get(index).process(exchange, callback); + } + counts.incrementAndGet(processors.size()); + if (otherwise != null) { + return otherwise.process(exchange, callback); + } + callback.done(true); + return true; + } + + public long getMatchedCount(int index) { + return counts.get(index); + } + + public long getUnmatchedCount() { + return counts.get(processors.size()); + } + + public void reset() { + for (int i = 0; i < counts.length(); i++) { + counts.set(i, 0); + } + } + + @Override + public List next() { + List answer = new ArrayList<>(processors); + if (otherwise != null) { + answer.add(otherwise); + } + return answer; + } + + @Override + public boolean hasNext() { + return !processors.isEmpty() || otherwise != null; + } + + @Override + public String getTraceLabel() { + return "switch"; + } + + @Override + public String toString() { + return id; + } + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id) { + this.id = id; + } + + @Override + public String getRouteId() { + return routeId; + } + + @Override + public void setRouteId(String routeId) { + this.routeId = routeId; + } + + @Override + public String getStepId() { + return stepId; + } + + @Override + public void setStepId(String stepId) { + this.stepId = stepId; + } + + @Override + protected void doInit() throws Exception { + ServiceHelper.initService(processors, otherwise); + } + + @Override + protected void doStart() throws Exception { + ServiceHelper.startService(processors, otherwise); + } + + @Override + protected void doStop() throws Exception { + ServiceHelper.stopService(otherwise, processors); + } + + @Override + protected void doShutdown() throws Exception { + ServiceHelper.stopAndShutdownServices(otherwise, processors); + } +} diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java index 45396721e88e9..cd149d697471a 100644 --- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java +++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java @@ -101,6 +101,7 @@ import org.apache.camel.model.SplitDefinition; import org.apache.camel.model.StepDefinition; import org.apache.camel.model.StopDefinition; +import org.apache.camel.model.SwitchDefinition; import org.apache.camel.model.ThreadsDefinition; import org.apache.camel.model.ThrottleDefinition; import org.apache.camel.model.ThrowExceptionDefinition; @@ -217,6 +218,8 @@ public static ProcessorReifier> coreReifier( return new CacheReifier(route, definition); } else if (definition instanceof CatchDefinition) { return new CatchReifier(route, definition); + } else if (definition instanceof SwitchDefinition) { + return new SwitchReifier(route, definition); } else if (definition instanceof ChoiceDefinition) { return new ChoiceReifier(route, definition); } else if (definition instanceof CircuitBreakerDefinition) { 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 new file mode 100644 index 0000000000000..d156792a84825 --- /dev/null +++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SwitchReifier.java @@ -0,0 +1,127 @@ +/* + * 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.reifier; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.camel.Expression; +import org.apache.camel.Processor; +import org.apache.camel.Route; +import org.apache.camel.model.ProcessorDefinition; +import org.apache.camel.model.SwitchCaseDefinition; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.SwitchValueDefinition; +import org.apache.camel.model.ToDefinition; +import org.apache.camel.processor.SwitchProcessor; +import org.apache.camel.spi.NodeIdFactory; + +public class SwitchReifier extends ProcessorReifier { + public SwitchReifier(Route route, ProcessorDefinition definition) { + super(route, (SwitchDefinition) definition); + } + + @Override + public Processor createProcessor() throws Exception { + if (definition.getSelector() == null || definition.getSelector().getExpressionType() == null) { + throw new IllegalArgumentException("Switch selector requires an expression"); + } + List keys = definition.getKeys(); + if (keys.stream().anyMatch(k -> k == null || k.isBlank()) || new HashSet<>(keys).size() != keys.size()) { + throw new IllegalArgumentException("Switch keys must be nonblank and unique"); + } + // Validate the complete table before creating endpoint processors. + List caseKeys = new ArrayList<>(); + List uris = new ArrayList<>(); + for (SwitchCaseDefinition c : definition.getCases()) { + Object key; + if (keys.isEmpty()) { + if (c.getValue() == null || !c.getValues().isEmpty()) { + throw new IllegalArgumentException("Scalar switch cases require value and cannot declare values"); + } + key = c.getValue().toLowerCase(Locale.ENGLISH); + } else { + if (c.getValue() != null) { + throw new IllegalArgumentException("Composite switch cases cannot declare scalar value"); + } + Map values = new LinkedHashMap<>(); + for (SwitchValueDefinition literal : c.getValues()) { + if (literal.getName() == null || values.containsKey(literal.getName())) { + throw new IllegalArgumentException("Duplicate or missing switch value name: " + literal.getName()); + } + values.put(literal.getName(), literal.asLiteral()); + } + if (!values.keySet().equals(new HashSet<>(keys))) { + throw new IllegalArgumentException("Switch case values must contain exactly the declared keys: " + keys); + } + key = SwitchProcessor.compositeKey(values, keys); + } + caseKeys.add(key); + uris.add(staticUri(c.getUri())); + } + 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()); + Expression selector = createExpression(definition.getSelector().getExpressionType()); + NodeIdFactory ids = camelContext.getCamelContextExtension().getContextPlugin(NodeIdFactory.class); + Map cases = new LinkedHashMap<>(); + for (int i = 0; i < definition.getCases().size(); i++) { + SwitchCaseDefinition c = definition.getCases().get(i); + c.setParent(definition); + c.setCamelContext(camelContext); + c.idOrCreate(ids); + ToDefinition send = c.getToDefinition(); + send.setUri(uris.get(i)); + cases.put(caseKeys.get(i), createSend(send)); + } + Processor otherwise = null; + if (otherwiseUri != null) { + definition.idOrCreate(ids); + ToDefinition send = definition.getOtherwiseDefinition(); + send.setUri(otherwiseUri); + otherwise = createSend(send); + } + SwitchProcessor answer = new SwitchProcessor(camelContext, selector, keys, cases, otherwise); + answer.setDisabled(isDisabled(camelContext, definition)); + return answer; + } + + private Processor createSend(ToDefinition send) throws Exception { + send.setParent(definition); + send.setCamelContext(camelContext); + return createOutputsProcessor(List.of(send)); + } + + private String staticUri(String uri) { + if (uri == null || uri.isBlank()) { + throw new IllegalArgumentException("Switch case requires a nonblank uri"); + } + String resolved = parseString(uri); + if (resolved == null || resolved.isBlank()) { + throw new IllegalArgumentException("Switch URI must resolve to a nonblank destination"); + } + if (resolved.contains("${") || resolved.contains("$simple{")) { + throw new IllegalArgumentException("Switch URI must be static; Simple expressions are not supported"); + } + return resolved; + } +} 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 new file mode 100644 index 0000000000000..fc3aa5b880a04 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchEndpointTest.java @@ -0,0 +1,103 @@ +/* + * 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; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.RouteDefinitionHelper; +import org.apache.camel.model.language.HeaderExpression; +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.assertTrue; + +class SwitchEndpointTest extends ContextTestSupport { + private final AtomicInteger initializations = new AtomicInteger(); + private final AtomicInteger evaluations = new AtomicInteger(); + private final AtomicInteger attempts = new AtomicInteger(); + + @Test + void routeEndpointDiscoveryIncludesCaseAndFallback() { + RouteDefinition route = new RouteDefinition("direct:discovery"); + 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()); + } + + @Test + void resolvedDestinationsUseInterceptorsAndRestartWithTheRoute() throws Exception { + getMockEndpoint("mock:intercepted").expectedMessageCount(2); + getMockEndpoint("mock:destination").expectedBodiesReceived("first", "second"); + template.sendBody("direct:intercepted", "first"); + context.getRouteController().stopRoute("intercepted"); + context.getRouteController().startRoute("intercepted"); + template.sendBody("direct:intercepted", "second"); + assertMockEndpointsSatisfied(); + } + + @Test + void selectedEndpointRedeliveryDoesNotRepeatSelector() throws Exception { + getMockEndpoint("mock:delivered").expectedBodiesReceived("message"); + template.sendBody("direct:redelivery", "message"); + assertMockEndpointsSatisfied(); + assertEquals(1, evaluations.get()); + assertEquals(1, initializations.get()); + assertEquals(2, attempts.get()); + } + + @Override + protected RouteBuilder createRouteBuilder() { + Properties properties = new Properties(); + properties.setProperty("switch.destination", "mock:destination"); + context.getPropertiesComponent().setInitialProperties(properties); + return new RouteBuilder() { + @Override + public void configure() { + interceptSendToEndpoint("mock:destination").to("mock:intercepted"); + from("direct:intercepted").routeId("intercepted").doSwitch(constant("match")) + .doCase("match", "{{switch.destination}}").otherwise("mock:fallback"); + from("direct:redelivery") + .errorHandler(defaultErrorHandler().maximumRedeliveries(1).redeliveryDelay(0)) + .doSwitch(new ExpressionAdapter() { + @Override + public void init(CamelContext context) { + initializations.incrementAndGet(); + } + + @Override + public Object evaluate(Exchange exchange) { + evaluations.incrementAndGet(); + return "match"; + } + }).doCase("match", "direct:destination"); + from("direct:destination").errorHandler(noErrorHandler()).process(exchange -> { + if (attempts.incrementAndGet() == 1) { + throw new IllegalStateException("retry destination"); + } + }).to("mock:delivered"); + } + }; + } +} 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 new file mode 100644 index 0000000000000..ac089634b840f --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java @@ -0,0 +1,196 @@ +/* + * 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; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SwitchTest extends ContextTestSupport { + private final AtomicInteger evaluations = new AtomicInteger(); + + @Test + void scalarMatchingAndFallback() throws Exception { + getMockEndpoint("mock:billing").expectedMessageCount(2); + getMockEndpoint("mock:empty").expectedMessageCount(1); + getMockEndpoint("mock:literal").expectedMessageCount(1); + getMockEndpoint("mock:review").expectedMessageCount(3); + getMockEndpoint("mock:after").expectedMessageCount(7); + for (String value : new String[] { "billing", "BILLING", "", "billing*", "billing123", "other" }) { + template.sendBodyAndHeader("direct:scalar", "message", "department", value); + } + template.sendBody("direct:scalar", "message"); + assertMockEndpointsSatisfied(); + } + + @Test + void compositeMatchesNamedTypedValuesAndIgnoresExtraFields() throws Exception { + getMockEndpoint("mock:urgent").expectedBodiesReceived("first", "second"); + getMockEndpoint("mock:billing").expectedBodiesReceived("third"); + getMockEndpoint("mock:review").expectedBodiesReceived("string", "unknown", "null"); + template.sendBodyAndHeader("direct:composite", "first", "decision", Map.of("department", "billing", "urgent", true)); + template.sendBodyAndHeader("direct:composite", "second", "decision", + Map.of("urgent", true, "department", "BILLING", "score", 3)); + template.sendBodyAndHeader("direct:composite", "third", "decision", Map.of("urgent", false, "department", "billing")); + template.sendBodyAndHeader("direct:composite", "string", "decision", Map.of("department", "billing", "urgent", "true")); + template.sendBodyAndHeader("direct:composite", "unknown", "decision", Map.of("department", "other", "urgent", true)); + template.sendBody("direct:composite", "null"); + assertMockEndpointsSatisfied(); + } + + @Test + void malformedCompositeDoesNotUseFallback() throws Exception { + getMockEndpoint("mock:review").expectedMessageCount(0); + for (Object result : new Object[] { + "billing", Map.of("department", "billing"), Map.of("department", "billing", "urgent", Map.of()) }) { + Exchange answer = template.request("direct:composite", e -> e.getIn().setHeader("decision", result)); + assertNotNull(answer.getException()); + } + assertMockEndpointsSatisfied(); + } + + @Test + void numericTypesShareAKeyButStringsRemainDistinct() throws Exception { + getMockEndpoint("mock:number").expectedMessageCount(3); + getMockEndpoint("mock:string").expectedMessageCount(1); + for (Object number : new Object[] { 2, 2.0, new BigDecimal("2.00"), "2" }) { + template.sendBodyAndHeader("direct:numbers", "message", "decision", Map.of("score", number)); + } + assertMockEndpointsSatisfied(); + } + + @Test + void noFallbackContinues() throws Exception { + getMockEndpoint("mock:after").expectedMessageCount(2); + template.sendBodyAndHeader("direct:noFallback", "other", "department", "other"); + template.sendBody("direct:noFallback", "null"); + assertMockEndpointsSatisfied(); + } + + @Test + void repeatedEntryEvaluatesOncePerEntry() throws Exception { + getMockEndpoint("mock:one").expectedMessageCount(1); + getMockEndpoint("mock:two").expectedMessageCount(1); + getMockEndpoint("mock:three").expectedMessageCount(1); + template.sendBody("direct:loop", "message"); + assertEquals(3, evaluations.get()); + assertMockEndpointsSatisfied(); + } + + @Test + void nestedSwitchesSelectIndependently() throws Exception { + getMockEndpoint("mock:urgent").expectedMessageCount(1); + getMockEndpoint("mock:after").expectedMessageCount(1); + template.sendBodyAndHeaders("direct:nested", "message", + Map.of("department", "billing", "decision", Map.of("department", "billing", "urgent", true))); + assertMockEndpointsSatisfied(); + } + + @Test + void selectorExceptionFollowsErrorHandling() throws Exception { + getMockEndpoint("mock:error").expectedMessageCount(1); + getMockEndpoint("mock:review").expectedMessageCount(0); + getMockEndpoint("mock:after").expectedMessageCount(0); + Exchange answer = template.request("direct:failure", e -> e.getIn().setBody("message")); + assertNull(answer.getException()); + assertTrue(answer.getProperty(Exchange.EXCEPTION_CAUGHT) instanceof IllegalStateException); + assertMockEndpointsSatisfied(); + } + + @Test + void selectorRewindsStreamForDestination() throws Exception { + getMockEndpoint("mock:stream").expectedBodiesReceived("hello"); + template.sendBody("direct:stream", new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8))); + assertMockEndpointsSatisfied(); + } + + @Test + void normalizationDoesNotDependOnDefaultLocale() throws Exception { + Locale original = Locale.getDefault(); + try { + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + getMockEndpoint("mock:billing").expectedMessageCount(1); + template.sendBodyAndHeader("direct:scalar", "message", "department", "BILLING"); + assertMockEndpointsSatisfied(); + } finally { + Locale.setDefault(original); + } + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:scalar").doSwitch().header("department") + .doCase("billing", "mock:billing").doCase("", "mock:empty").doCase("billing*", "mock:literal") + .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") + .doCase().value("urgent", false).value("department", "billing").to("mock:billing") + .otherwise("mock:review"); + from("direct:numbers").doSwitch(header("decision")).keys("score") + .doCase().value("score", 2).to("mock:number") + .doCase().value("score", "2").to("mock:string"); + from("direct:noFallback").doSwitch(header("department")).doCase("billing", "mock:billing") + .end().to("mock:after"); + from("direct:loop").loop(3).doSwitch(new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + return evaluations.incrementAndGet(); + } + }).doCase("1", "mock:one").doCase("2", "mock:two").doCase("3", "mock:three").end().end(); + from("direct:nested").choice().when(header("department").isEqualTo("billing")) + .doSwitch(header("department")).doCase("billing", "direct:composite") + .end().end().to("mock:after"); + from("direct:failure").onException(IllegalStateException.class).handled(true).to("mock:error").end() + .doSwitch(new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { throw new IllegalStateException("selector failed"); + } + }).otherwise("mock:review").end().to("mock:after"); + from("direct:stream").streamCaching().doSwitch(new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + try { + exchange.getIn().getBody(InputStream.class).readAllBytes(); + return "read"; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + }).doCase("read", "direct:readStream"); + from("direct:readStream").convertBodyTo(String.class).to("mock:stream"); + } + }; + } +} 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 new file mode 100644 index 0000000000000..5b1271eeb56f5 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchValidationTest.java @@ -0,0 +1,139 @@ +/* + * 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; + +import java.util.function.Consumer; + +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.SwitchValueDefinition; +import org.apache.camel.model.language.HeaderExpression; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SwitchValidationTest { + @Test + void rejectsCaseIdCollidingWithFallback() { + rejects(s -> s.id("dispatch").doCase("billing").id("dispatch-otherwise").to("mock:a").otherwise("mock:b"), + "Duplicate id detected"); + } + + @Test + void rejectsDuplicateScalarValues() { + rejects(s -> s.doCase("Billing", "mock:a").doCase("billing", "mock:b"), "Duplicate switch case"); + } + + @Test + void rejectsMissingUri() { + rejects(s -> s.doCase("billing", null), "nonblank uri"); + } + + @Test + void rejectsDynamicUri() { + rejects(s -> s.doCase("billing", "mock:${header.target}"), "must be static"); + } + + @Test + void rejectsMissingValue() { + rejects(s -> s.doCase((String) null, "mock:a"), "require value"); + } + + @Test + void rejectsMissingSelector() { + rejects(s -> s.setSelector(null), "selector requires an expression"); + } + + @Test + void rejectsEmptySelector() { + rejects(s -> s.setSelector(new ExpressionSubElementDefinition()), "selector requires an expression"); + } + + @Test + void rejectsDuplicateKeys() { + rejects(s -> s.keys("department", "department"), "keys must be nonblank and unique"); + } + + @Test + void rejectsMissingCompositeKey() { + rejects(s -> s.keys("department", "urgent").doCase().value("department", "billing").to("mock:a"), + "exactly the declared keys"); + } + + @Test + void rejectsExtraCompositeKey() { + rejects(s -> s.keys("department").doCase().value("department", "billing").value("urgent", true).to("mock:a"), + "exactly the declared keys"); + } + + @Test + void rejectsRepeatedCompositeKey() { + rejects(s -> s.keys("department").doCase().value("department", "billing").value("department", "technical").to("mock:a"), + "Duplicate or missing switch value name"); + } + + @Test + void rejectsMixedForms() { + rejects(s -> s.keys("department").doCase("billing", "mock:a"), "cannot declare scalar value"); + } + + @Test + void rejectsDuplicateCompositeValues() { + rejects(s -> s.keys("department", "score") + .doCase().value("department", "billing").value("score", 2).to("mock:a") + .doCase().value("score", 2.0).value("department", "BILLING").to("mock:b"), "Duplicate switch case"); + } + + @Test + void rejectsNonFiniteNumbers() { + rejects(s -> s.keys("score").doCase().value("score", Double.NaN).to("mock:a"), "NaN"); + } + + @Test + void rejectsInvalidBoolean() { + rejects(s -> { + s.keys("urgent").doCase().value("urgent", true).to("mock:a"); + SwitchValueDefinition value = s.getCases().get(0).getValues().get(0); + value.setValue("yes"); + }, "boolean must be true or false"); + } + + private void rejects(Consumer configure, String message) { + Exception failure = assertThrows(Exception.class, () -> { + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + SwitchDefinition s = new SwitchDefinition(); + s.setSelector(new ExpressionSubElementDefinition(new HeaderExpression("decision"))); + configure.accept(s); + from("direct:start").addOutput(s); + } + }); + context.start(); + } + }); + StringBuilder chain = new StringBuilder(); + for (Throwable cause = failure; cause != null; cause = cause.getCause()) { + chain.append(cause.getMessage()).append('\n'); + } + assertTrue(chain.toString().contains(message), chain.toString()); + } +} 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 6bd836608b24d..360a82b599251 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 @@ -663,6 +663,26 @@ public String writeStopDefinition(StopDefinition def) { doWriteStopDefinition(sb, def); return sb.toString(); } + public String writeSwitchCaseDefinition(SwitchCaseDefinition def) { + resetState(); + StringBuilder sb = new StringBuilder(); + beginStep(sb, "case", def); + doWriteSwitchCaseDefinition(sb, def); + return sb.toString(); + } + public String writeSwitchDefinition(SwitchDefinition def) { + resetState(); + StringBuilder sb = new StringBuilder(); + writeSwitch(sb, def); + return sb.toString(); + } + public String writeSwitchValueDefinition(SwitchValueDefinition def) { + resetState(); + StringBuilder sb = new StringBuilder(); + beginStep(sb, "switchValue", def); + doWriteSwitchValueDefinition(sb, def); + return sb.toString(); + } public String writeTemplatedRouteDefinition(TemplatedRouteDefinition def) { resetState(); StringBuilder sb = new StringBuilder(); @@ -2576,6 +2596,24 @@ protected void doWriteStepDefinition(StringBuilder sb, StepDefinition def) { protected void doWriteStopDefinition(StringBuilder sb, StopDefinition def) { doWriteProcessorDefinitionAttributes(sb, def); } + protected void doWriteSwitchCaseDefinition(StringBuilder sb, SwitchCaseDefinition def) { + doWriteOptionalIdentifiedDefinitionAttributes(sb, def); + doWriteAttribute(sb, "value", def.getValue(), null); + doWriteAttribute(sb, "uri", def.getUri(), null); + doWriteChildList(sb, "values", def.getValues(), this::doWriteSwitchValueDefinition); + } + 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); + } + protected void doWriteSwitchValueDefinition(StringBuilder sb, SwitchValueDefinition def) { + doWriteAttribute(sb, "name", def.getName(), null); + doWriteAttribute(sb, "value", def.getValue(), null); + doWriteAttribute(sb, "type", def.getType(), "string"); + } protected void doWriteTemplatedRouteDefinition(StringBuilder sb, TemplatedRouteDefinition def) { doWriteAttribute(sb, "routeTemplateRef", def.getRouteTemplateRef(), null); doWriteAttribute(sb, "routeId", def.getRouteId(), null); @@ -4885,6 +4923,12 @@ protected void doWriteOptionalIdentifiedDefinitionRef(StringBuilder sb, Optional doWriteStopDefinition(sb, (StopDefinition) v); endStep(sb, "stop", v); } + case "SwitchCaseDefinition" -> { + beginStep(sb, "case", v); + doWriteSwitchCaseDefinition(sb, (SwitchCaseDefinition) v); + endStep(sb, "case", v); + } + case "SwitchDefinition" -> writeSwitch(sb, (SwitchDefinition) v); case "TemplatedRoutesDefinition" -> { beginStep(sb, "templatedRoutes", v); doWriteTemplatedRoutesDefinition(sb, (TemplatedRoutesDefinition) v); @@ -5977,6 +6021,7 @@ protected void doWriteProcessorDefinitionRef(StringBuilder sb, ProcessorDefiniti doWriteStopDefinition(sb, (StopDefinition) v); endStep(sb, "stop", v); } + case "SwitchDefinition" -> writeSwitch(sb, (SwitchDefinition) v); case "ThreadsDefinition" -> { ThreadsDefinition _d = (ThreadsDefinition) v; handledAttributes.clear(); 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 f54e1dcd2aa5b..f163c5e50026c 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 @@ -32,6 +32,7 @@ import org.apache.camel.model.InterceptSendToEndpointDefinition; import org.apache.camel.model.LoadBalancerDefinition; import org.apache.camel.model.OnWhenDefinition; +import org.apache.camel.model.OptionalIdentifiedDefinition; import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.PropertyDefinition; import org.apache.camel.model.PropertyExpressionDefinition; @@ -39,6 +40,9 @@ import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.RouteTemplateDefinition; import org.apache.camel.model.RouteTemplateParameterDefinition; +import org.apache.camel.model.SwitchCaseDefinition; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.SwitchValueDefinition; import org.apache.camel.model.TemplatedRouteDefinition; import org.apache.camel.model.TemplatedRouteParameterDefinition; import org.apache.camel.model.ToDefinition; @@ -87,6 +91,54 @@ */ public abstract class JavaDslModelWriterSupport { + protected void writeSwitch(StringBuilder sb, SwitchDefinition definition) { + handledAttributes.clear(); + sb.append(NL).append(indent()).append(".doSwitch(") + .append(expressionDsl(definition.getSelector().getExpressionType())).append(")"); + doWriteProcessorDefinitionAttributes(sb, definition); + if (!definition.getKeys().isEmpty()) { + sb.append(NL).append(indent()).append(".keys("); + for (int i = 0; i < definition.getKeys().size(); i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(quote(definition.getKeys().get(i))); + } + sb.append(")"); + } + for (SwitchCaseDefinition c : definition.getCases()) { + sb.append(NL).append(indent()).append(".doCase("); + if (c.getValue() != null) { + sb.append(quote(c.getValue())); + } + sb.append(")"); + for (SwitchValueDefinition value : c.getValues()) { + Object literal = value.asLiteral(); + sb.append(".value(").append(quote(value.getName())).append(", "); + if (literal instanceof String text) { + sb.append(quote(text)); + } else if (literal instanceof Boolean) { + sb.append(literal); + } else { + sb.append("new java.math.BigDecimal(").append(quote(literal.toString())).append(")"); + } + sb.append(")"); + } + handledAttributes.clear(); + doWriteOptionalIdentifiedDefinitionAttributes(sb, c); + 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(".end()"); + } + + protected abstract void doWriteProcessorDefinitionAttributes(StringBuilder sb, ProcessorDefinition definition); + + protected abstract void doWriteOptionalIdentifiedDefinitionAttributes( + StringBuilder sb, OptionalIdentifiedDefinition definition); + private static final String NL = "\n"; private static final Set BLOCK_EIPS = Set.of( 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 618831f97cb69..f7abd5e1d43c2 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 @@ -32,6 +32,7 @@ import org.apache.camel.model.SetHeaderDefinition; import org.apache.camel.model.SetVariableDefinition; import org.apache.camel.model.SplitDefinition; +import org.apache.camel.model.SwitchDefinition; import org.apache.camel.model.ToDefinition; import org.apache.camel.model.ToDynamicDefinition; import org.apache.camel.model.TransformDefinition; @@ -46,6 +47,25 @@ public class JavaDslModelWriterTest { + @Test + void switchExportPreservesDisabledAndCaseMetadata() { + 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"); + JavaDslModelWriter writer = new JavaDslModelWriter(); + String java = writer.writeSwitchDefinition(sw); + Assertions.assertTrue(java.contains(".doSwitch(header(\"decision\"))"), java); + Assertions.assertTrue(java.contains(".disabled(\"true\")"), java); + Assertions.assertTrue(java.contains(".id(\"dispatch\")"), java); + Assertions.assertTrue(java.contains(".description(\"Table dispatch\")"), java); + Assertions.assertTrue(java.contains(".id(\"billingCase\")"), java); + Assertions.assertTrue(java.contains(".note(\"case note\")"), java); + sw.setCustomId(false); + sw.getCases().get(0).setCustomId(false); + java = writer.writeSwitchDefinition(sw); + Assertions.assertFalse(java.contains(".id("), java); + } + private String loadExpected(String name) throws IOException { return Files.readString(Paths.get("src/test/resources/" + name)); } diff --git a/core/camel-java-io/src/test/resources/switchLiteral.xml b/core/camel-java-io/src/test/resources/switchLiteral.xml new file mode 100644 index 0000000000000..da1c216ada3ef --- /dev/null +++ b/core/camel-java-io/src/test/resources/switchLiteral.xml @@ -0,0 +1,38 @@ + + + + + + +
department
+ +
+ +
decision
+ departmenturgentscore + + + + + +
+ +
+
diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java index 5d5a652490259..248b3ba2f1153 100644 --- a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java +++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java @@ -205,6 +205,19 @@ public static CompositeType choiceCompositeType() throws OpenDataException { new OpenType[] { SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING, SimpleType.LONG }); } + public static TabularType switchTabularType() throws OpenDataException { + return new TabularType("switch", "Switch statistics", switchCompositeType(), new String[] { "index" }); + } + + public static CompositeType switchCompositeType() throws OpenDataException { + return new CompositeType( + "cases", "Switch cases", + new String[] { "index", "id", "value", "uri", "matches" }, + new String[] { "Index", "Case id", "Literal values", "Destination", "Selections" }, + new OpenType[] { + SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.LONG }); + } + public static TabularType doTryTabularType() throws OpenDataException { CompositeType ct = doTryCompositeType(); return new TabularType("doTry", "doTry statistics", ct, new String[] { "index" }); diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedSwitchMBean.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedSwitchMBean.java new file mode 100644 index 0000000000000..a0689efc790cf --- /dev/null +++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedSwitchMBean.java @@ -0,0 +1,31 @@ +/* + * 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.api.management.mbean; + +import javax.management.openmbean.TabularData; + +import org.apache.camel.api.management.ManagedAttribute; +import org.apache.camel.api.management.ManagedOperation; + +public interface ManagedSwitchMBean extends ManagedProcessorMBean, ManagedExtendedInformation { + @Override + @ManagedOperation(description = "Literal switch cases, destinations and selection counts") + TabularData extendedInformation(); + + @ManagedAttribute(description = "Number of null or unmatched selector results") + long getUnmatchedCount(); +} diff --git a/core/camel-management/src/main/java/org/apache/camel/management/DefaultManagementObjectStrategy.java b/core/camel-management/src/main/java/org/apache/camel/management/DefaultManagementObjectStrategy.java index c5beb08537e5a..d5dd85ab0fe17 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/DefaultManagementObjectStrategy.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/DefaultManagementObjectStrategy.java @@ -103,6 +103,7 @@ import org.apache.camel.management.mbean.ManagedStop; import org.apache.camel.management.mbean.ManagedSupervisingRouteController; import org.apache.camel.management.mbean.ManagedSuspendableRoute; +import org.apache.camel.management.mbean.ManagedSwitch; import org.apache.camel.management.mbean.ManagedThreadPool; import org.apache.camel.management.mbean.ManagedThreads; import org.apache.camel.management.mbean.ManagedThrottler; @@ -160,6 +161,7 @@ import org.apache.camel.processor.StepProcessor; import org.apache.camel.processor.StopProcessor; import org.apache.camel.processor.StreamResequencer; +import org.apache.camel.processor.SwitchProcessor; import org.apache.camel.processor.ThreadsProcessor; import org.apache.camel.processor.Throttler; import org.apache.camel.processor.ThrowExceptionProcessor; @@ -366,6 +368,8 @@ public Object getManagedObjectForProcessor( answer = new ManagedConvertHeader(context, chp, definition); } else if (target instanceof ConvertVariableProcessor cvp) { answer = new ManagedConvertVariable(context, cvp, definition); + } else if (target instanceof SwitchProcessor sp) { + answer = new ManagedSwitch(context, sp, definition); } else if (target instanceof ChoiceProcessor cp) { answer = new ManagedChoice(context, cp, definition); } else if (target instanceof ClaimCheckProcessor ccp) { diff --git a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java index 8f39265ef7868..ef76c74cad798 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java @@ -90,6 +90,7 @@ import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.ProcessorDefinitionHelper; import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.SwitchDefinition; import org.apache.camel.spi.AsyncProcessorAwaitManager; import org.apache.camel.spi.BeanIntrospection; import org.apache.camel.spi.BrowsableVariableRepository; @@ -966,6 +967,16 @@ private void registerPerformanceCounters( registerPerformanceCounters(route, child, registeredCounters); } + // Switch destinations are literal sends rather than nested processor outputs. + if (processor instanceof SwitchDefinition sw) { + for (var c : sw.getCases()) { + registerPerformanceCounters(route, c.getToDefinition(), registeredCounters); + } + if (sw.getOtherwiseDefinition() != null) { + registerPerformanceCounters(route, sw.getOtherwiseDefinition(), registeredCounters); + } + } + // skip processors that should not be registered if (!registerProcessor(processor)) { return; 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 new file mode 100644 index 0000000000000..ad11eb423ec40 --- /dev/null +++ b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedSwitch.java @@ -0,0 +1,90 @@ +/* + * 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.management.mbean; + +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; + +import org.apache.camel.CamelContext; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.api.management.mbean.CamelOpenMBeanTypes; +import org.apache.camel.api.management.mbean.ManagedSwitchMBean; +import org.apache.camel.model.ProcessorDefinition; +import org.apache.camel.model.SwitchCaseDefinition; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.processor.SwitchProcessor; + +@ManagedResource(description = "Managed Switch") +public class ManagedSwitch extends ManagedProcessor implements ManagedSwitchMBean { + public ManagedSwitch(CamelContext context, SwitchProcessor processor, ProcessorDefinition definition) { + super(context, processor, definition); + } + + @Override + public SwitchProcessor getProcessor() { + return (SwitchProcessor) super.getProcessor(); + } + + @Override + public SwitchDefinition getDefinition() { + return (SwitchDefinition) super.getDefinition(); + } + + @Override + public Boolean getSupportExtendedInformation() { + return true; + } + + @Override + public long getUnmatchedCount() { + return getProcessor().getUnmatchedCount(); + } + + @Override + public void reset() { + getProcessor().reset(); + super.reset(); + } + + @Override + public TabularData extendedInformation() { + try { + TabularDataSupport table = new TabularDataSupport(CamelOpenMBeanTypes.switchTabularType()); + for (int i = 0; i < getDefinition().getCases().size(); i++) { + SwitchCaseDefinition c = getDefinition().getCases().get(i); + addRow(table, c.getId(), c.getValue() != null ? c.getValue() : c.getValues().toString(), + c.getUri(), getProcessor().getMatchedCount(i)); + } + if (getDefinition().getOtherwise() != null) { + addRow(table, getDefinition().getId() + "-otherwise", "otherwise", getDefinition().getOtherwise(), + getUnmatchedCount()); + } + return table; + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeCamelException(e); + } + } + + private void addRow(TabularDataSupport table, String id, String value, String uri, long matches) throws Exception { + table.put(new CompositeDataSupport( + CamelOpenMBeanTypes.switchCompositeType(), + new String[] { "index", "id", "value", "uri", "matches" }, + new Object[] { table.size(), id, value, uri, matches })); + } +} 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 new file mode 100644 index 0000000000000..e41f21a22c5a7 --- /dev/null +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java @@ -0,0 +1,73 @@ +/* + * 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.management; + +import java.util.Map; + +import javax.management.ObjectName; +import javax.management.openmbean.TabularData; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.processor.SendProcessor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_PROCESSOR; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DisabledOnOs(OS.AIX) +class ManagedSwitchTest extends ManagementTestSupport { + @Test + void exposesCaseDestinationsCountsAndCaseMBeans() throws Exception { + template.sendBodyAndHeader("direct:start", "first", "decision", Map.of("department", "BILLING", "urgent", true)); + template.sendBodyAndHeader("direct:start", "second", "decision", Map.of("department", "billing", "urgent", true)); + template.sendBody("direct:start", "unmatched"); + ObjectName name = getCamelObjectName(TYPE_PROCESSOR, "p-dispatch"); + TabularData table = (TabularData) getMBeanServer().invoke(name, "extendedInformation", null, null); + assertEquals(2, table.size()); + var first = table.get(new Object[] { 0 }); + assertEquals("urgentCase", first.get("id")); + assertEquals("mock:urgent", first.get("uri")); + assertEquals(2L, first.get("matches")); + assertEquals(1L, table.get(new Object[] { 1 }).get("matches")); + 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"))); + assertNotNull(context.getProcessor("p-urgentCase", SendProcessor.class)); + assertNotNull(context.getProcessor("p-dispatch-otherwise", SendProcessor.class)); + getMBeanServer().invoke(name, "reset", null, null); + table = (TabularData) getMBeanServer().invoke(name, "extendedInformation", null, null); + assertEquals(0L, table.get(new Object[] { 0 }).get("matches")); + assertEquals(0L, getMBeanServer().getAttribute(name, "UnmatchedCount")); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").routeId("switchRoute").nodePrefixId("p-") + .doSwitch(header("decision")).id("dispatch").keys("department", "urgent") + .doCase().value("department", "billing").value("urgent", true).id("urgentCase").to("mock:urgent") + .otherwise("mock:review"); + } + }; + } +} 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 10f7d4edf7366..8675447cb92fe 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 @@ -1181,6 +1181,35 @@ protected StepDefinition doParseStepDefinition() throws IOException, XmlPullPars protected StopDefinition doParseStopDefinition() throws IOException, XmlPullParserException { return doParse(new StopDefinition(), processorDefinitionAttributeHandler(), optionalIdentifiedDefinitionElementHandler(), noValueHandler()); } + protected SwitchCaseDefinition doParseSwitchCaseDefinition() throws IOException, XmlPullParserException { + return doParse(new SwitchCaseDefinition(), (def, key, val) -> switch (key) { + case "uri": def.setUri(sanitizeUri(val)); yield true; + case "value": def.setValue(val); yield true; + default: yield optionalIdentifiedDefinitionAttributeHandler().accept(def, key, val); + }, (def, key) -> switch (key) { + case "values": doAdd(doParseSwitchValueDefinition(), def.getValues(), def::setValues); yield true; + default: yield optionalIdentifiedDefinitionElementHandler().accept(def, key); + }, noValueHandler()); + } + protected SwitchValueDefinition doParseSwitchValueDefinition() throws IOException, XmlPullParserException { + return doParse(new SwitchValueDefinition(), (def, key, val) -> switch (key) { + case "name": def.setName(val); yield true; + case "type": def.setType(val); yield true; + case "value": def.setValue(val); yield true; + default: yield false; + }, 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) { + case "case": doAdd(doParseSwitchCaseDefinition(), def.getCases(), def::setCases); yield true; + case "keys": doAdd(doParseText(), def.getKeys(), def::setKeys); yield true; + case "selector": def.setSelector(doParseExpressionSubElementDefinition()); yield true; + default: yield optionalIdentifiedDefinitionElementHandler().accept(def, key); + }, noValueHandler()); + } protected TemplatedRouteDefinition doParseTemplatedRouteDefinition() throws IOException, XmlPullParserException { return doParse(new TemplatedRouteDefinition(), (def, key, val) -> switch (key) { case "group": def.setGroup(val); yield true; @@ -2836,6 +2865,7 @@ protected ProcessorDefinition doParseProcessorDefinitionRef(String key) throws I case "split": return doParseSplitDefinition(); case "step": return doParseStepDefinition(); case "stop": return doParseStopDefinition(); + case "switch": return doParseSwitchDefinition(); case "threads": return doParseThreadsDefinition(); case "throttle": return doParseThrottleDefinition(); case "throwException": return doParseThrowExceptionDefinition(); 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 cf985566ea488..65530d7a495b3 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 @@ -320,6 +320,15 @@ public void writeStepDefinition(StepDefinition def) throws IOException { public void writeStopDefinition(StopDefinition def) throws IOException { doWriteStopDefinition("stop", def); } + public void writeSwitchCaseDefinition(SwitchCaseDefinition def) throws IOException { + doWriteSwitchCaseDefinition("case", def); + } + public void writeSwitchDefinition(SwitchDefinition def) throws IOException { + doWriteSwitchDefinition("switch", def); + } + public void writeSwitchValueDefinition(SwitchValueDefinition def) throws IOException { + doWriteSwitchValueDefinition("switchValue", def); + } public void writeTemplatedRouteDefinition(TemplatedRouteDefinition def) throws IOException { doWriteTemplatedRouteDefinition("templatedRoute", def); } @@ -1820,6 +1829,30 @@ protected void doWriteStopDefinition(String name, StopDefinition def) throws IOE doWriteProcessorDefinitionAttributes(def); endElement(name); } + protected void doWriteSwitchCaseDefinition(String name, SwitchCaseDefinition def) throws IOException { + startElement(name); + doWriteOptionalIdentifiedDefinitionAttributes(def); + doWriteAttribute("value", def.getValue(), null); + doWriteAttribute("uri", def.getUri(), null); + doWriteList(null, "values", def.getValues(), this::doWriteSwitchValueDefinition); + endElement(name); + } + 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); + endElement(name); + } + protected void doWriteSwitchValueDefinition(String name, SwitchValueDefinition def) throws IOException { + startElement(name); + doWriteAttribute("name", def.getName(), null); + doWriteAttribute("value", def.getValue(), null); + doWriteAttribute("type", def.getType(), "string"); + endElement(name); + } protected void doWriteTemplatedRouteDefinition(String name, TemplatedRouteDefinition def) throws IOException { startElement(name); doWriteAttribute("routeTemplateRef", def.getRouteTemplateRef(), null); @@ -3725,6 +3758,8 @@ protected void doWriteOptionalIdentifiedDefinitionRef(String n, OptionalIdentifi case "SplitDefinition" -> doWriteSplitDefinition("split", (SplitDefinition) v); case "StepDefinition" -> doWriteStepDefinition("step", (StepDefinition) v); case "StopDefinition" -> doWriteStopDefinition("stop", (StopDefinition) v); + case "SwitchCaseDefinition" -> doWriteSwitchCaseDefinition("case", (SwitchCaseDefinition) v); + case "SwitchDefinition" -> doWriteSwitchDefinition("switch", (SwitchDefinition) v); case "TemplatedRoutesDefinition" -> doWriteTemplatedRoutesDefinition("templatedRoutes", (TemplatedRoutesDefinition) v); case "ThreadPoolProfileDefinition" -> doWriteThreadPoolProfileDefinition("threadPoolProfile", (ThreadPoolProfileDefinition) v); case "ThreadsDefinition" -> doWriteThreadsDefinition("threads", (ThreadsDefinition) v); @@ -3823,6 +3858,7 @@ protected void doWriteProcessorDefinitionRef(String n, ProcessorDefinition v) th case "SplitDefinition" -> doWriteSplitDefinition("split", (SplitDefinition) v); case "StepDefinition" -> doWriteStepDefinition("step", (StepDefinition) v); case "StopDefinition" -> doWriteStopDefinition("stop", (StopDefinition) v); + case "SwitchDefinition" -> doWriteSwitchDefinition("switch", (SwitchDefinition) v); case "ThreadsDefinition" -> doWriteThreadsDefinition("threads", (ThreadsDefinition) v); case "ThrottleDefinition" -> doWriteThrottleDefinition("throttle", (ThrottleDefinition) v); case "ThrowExceptionDefinition" -> doWriteThrowExceptionDefinition("throwException", (ThrowExceptionDefinition) v); diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index 842c451ade16f..b1a9bfd9743d1 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -44,6 +44,7 @@ import org.apache.camel.model.RouteTemplatesDefinition; import org.apache.camel.model.RoutesDefinition; import org.apache.camel.model.SendDefinition; +import org.apache.camel.model.SwitchDefinition; import org.apache.camel.model.ToDynamicDefinition; import org.apache.camel.model.dataformat.DataFormatsDefinition; import org.apache.camel.model.language.ExpressionDefinition; @@ -247,6 +248,12 @@ public String dumpDataFormatsAsXml(CamelContext context, Map dat * @param namespaces the map of namespaces to add discovered XML namespaces into */ private static void extractNamespaces(RouteDefinition route, Map namespaces) { + for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + NamespaceAware aware = getNamespaceAwareFromExpression(sw); + if (aware != null && aware.getNamespaces() != null) { + namespaces.putAll(aware.getNamespaces()); + } + } for (ExpressionNode en : filterTypeInOutputs(route.getOutputs(), ExpressionNode.class)) { NamespaceAware na = getNamespaceAwareFromExpression(en); if (na != null) { @@ -327,6 +334,17 @@ private static Runnable resolveEndpointDslUris(RouteDefinition route) { return () -> restorers.forEach(Runnable::run); } + private static NamespaceAware getNamespaceAwareFromExpression(SwitchDefinition definition) { + if (definition.getSelector() == null || definition.getSelector().getExpressionType() == null) { + return null; + } + ExpressionDefinition expression = definition.getSelector().getExpressionType(); + if (expression.getExpressionValue() instanceof NamespaceAware aware) { + return aware; + } + return expression instanceof NamespaceAware aware ? aware : null; + } + private static NamespaceAware getNamespaceAwareFromExpression(ExpressionNode expressionNode) { ExpressionDefinition ed = expressionNode.getExpression(); diff --git a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java index 510ced30b7517..8d23ca24a32b7 100644 --- a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java +++ b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java @@ -56,6 +56,7 @@ import org.apache.camel.model.RouteTemplatesDefinition; import org.apache.camel.model.RoutesDefinition; import org.apache.camel.model.SendDefinition; +import org.apache.camel.model.SwitchDefinition; import org.apache.camel.model.TemplatedRouteDefinition; import org.apache.camel.model.TemplatedRoutesDefinition; import org.apache.camel.model.ToDynamicDefinition; @@ -87,6 +88,12 @@ public static JAXBContext getJAXBContext(CamelContext context) throws Exception * @param namespaces the map of namespaces to add discovered XML namespaces into */ public static void extractNamespaces(RouteDefinition route, Map namespaces) { + for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + NamespaceAware aware = getNamespaceAwareFromExpression(sw); + if (aware != null && aware.getNamespaces() != null) { + namespaces.putAll(aware.getNamespaces()); + } + } for (ExpressionNode en : filterTypeInOutputs(route.getOutputs(), ExpressionNode.class)) { NamespaceAware na = getNamespaceAwareFromExpression(en); if (na != null) { @@ -165,6 +172,17 @@ public static Runnable resolveEndpointDslUris(RouteDefinition route) { return () -> restorers.forEach(Runnable::run); } + private static NamespaceAware getNamespaceAwareFromExpression(SwitchDefinition definition) { + if (definition.getSelector() == null || definition.getSelector().getExpressionType() == null) { + return null; + } + ExpressionDefinition expression = definition.getSelector().getExpressionType(); + if (expression.getExpressionValue() instanceof NamespaceAware aware) { + return aware; + } + return expression instanceof NamespaceAware aware ? aware : null; + } + private static NamespaceAware getNamespaceAwareFromExpression(ExpressionNode expressionNode) { ExpressionDefinition ed = expressionNode.getExpression(); @@ -265,6 +283,12 @@ public static void extractSourceLocations(Element element, Map namespaces) { + for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + NamespaceAware aware = getNamespaceAwareFromExpression(sw); + if (aware != null) { + aware.setNamespaces(namespaces); + } + } Collection col = filterTypeInOutputs(route.getOutputs(), ExpressionNode.class); for (ExpressionNode en : col) { NamespaceAware na = getNamespaceAwareFromExpression(en); @@ -289,6 +313,12 @@ public static void applyNamespaces(RouteConfigurationDefinition config, Map def : defs) { + for (SwitchDefinition sw : filterTypeInOutputs(def.getOutputs(), SwitchDefinition.class)) { + NamespaceAware aware = getNamespaceAwareFromExpression(sw); + if (aware != null) { + aware.setNamespaces(namespaces); + } + } Collection col = filterTypeInOutputs(def.getOutputs(), ExpressionNode.class); for (ExpressionNode en : col) { NamespaceAware na = getNamespaceAwareFromExpression(en); 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 0753ef039cb4c..02633f4f7c067 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 @@ -313,6 +313,15 @@ public JsonObject writeStepDefinition(StepDefinition def) { public JsonObject writeStopDefinition(StopDefinition def) { return wrapNode("stop", doWriteStopDefinition(def)); } + public JsonObject writeSwitchCaseDefinition(SwitchCaseDefinition def) { + return wrapNode("case", doWriteSwitchCaseDefinition(def)); + } + public JsonObject writeSwitchDefinition(SwitchDefinition def) { + return wrapNode("switch", doWriteSwitchDefinition(def)); + } + public JsonObject writeSwitchValueDefinition(SwitchValueDefinition def) { + return wrapNode("switchValue", doWriteSwitchValueDefinition(def)); + } public JsonObject writeTemplatedRouteDefinition(TemplatedRouteDefinition def) { return wrapNode("templatedRoute", doWriteTemplatedRouteDefinition(def)); } @@ -1816,6 +1825,35 @@ protected JsonObject doWriteStopDefinition(StopDefinition def) { doWriteProcessorDefinitionAttributes(jo, def); return jo; } + protected JsonObject doWriteSwitchCaseDefinition(SwitchCaseDefinition def) { + JsonObject jo = new JsonObject(); + doWriteOptionalIdentifiedDefinitionAttributes(jo, def); + doWriteAttribute(jo, "value", def.getValue(), null); + doWriteAttribute(jo, "uri", def.getUri(), null); + doWriteChildList(jo, null, "values", def.getValues(), this::doWriteSwitchValueDefinition); + if (def.getValue() != null) { + jo.put("value", def.getValue()); + } + return jo; + } + 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); + return jo; + } + protected JsonObject doWriteSwitchValueDefinition(SwitchValueDefinition def) { + JsonObject jo = new JsonObject(); + doWriteAttribute(jo, "name", def.getName(), null); + doWriteAttribute(jo, "value", def.getValue(), null); + doWriteAttribute(jo, "type", def.getType(), "string"); + jo.clear(); + jo.put(def.getName(), def.asLiteral()); + return jo; + } protected JsonObject doWriteTemplatedRouteDefinition(TemplatedRouteDefinition def) { JsonObject jo = new JsonObject(); doWriteAttribute(jo, "routeTemplateRef", def.getRouteTemplateRef(), null); @@ -3733,6 +3771,8 @@ protected JsonObject doWriteOptionalIdentifiedDefinitionRef(OptionalIdentifiedDe case "SplitDefinition" -> wrapNode("split", doWriteSplitDefinition((SplitDefinition) v)); case "StepDefinition" -> wrapNode("step", doWriteStepDefinition((StepDefinition) v)); case "StopDefinition" -> wrapNode("stop", doWriteStopDefinition((StopDefinition) v)); + case "SwitchCaseDefinition" -> wrapNode("case", doWriteSwitchCaseDefinition((SwitchCaseDefinition) v)); + case "SwitchDefinition" -> wrapNode("switch", doWriteSwitchDefinition((SwitchDefinition) v)); case "TemplatedRoutesDefinition" -> wrapNode("templatedRoutes", doWriteTemplatedRoutesDefinition((TemplatedRoutesDefinition) v)); case "ThreadPoolProfileDefinition" -> wrapNode("threadPoolProfile", doWriteThreadPoolProfileDefinition((ThreadPoolProfileDefinition) v)); case "ThreadsDefinition" -> wrapNode("threads", doWriteThreadsDefinition((ThreadsDefinition) v)); @@ -3835,6 +3875,7 @@ protected JsonObject doWriteProcessorDefinitionRef(ProcessorDefinition v) { case "SplitDefinition" -> wrapNode("split", doWriteSplitDefinition((SplitDefinition) v)); case "StepDefinition" -> wrapNode("step", doWriteStepDefinition((StepDefinition) v)); case "StopDefinition" -> wrapNode("stop", doWriteStopDefinition((StopDefinition) v)); + case "SwitchDefinition" -> wrapNode("switch", doWriteSwitchDefinition((SwitchDefinition) v)); case "ThreadsDefinition" -> wrapNode("threads", doWriteThreadsDefinition((ThreadsDefinition) v)); case "ThrottleDefinition" -> wrapNode("throttle", doWriteThrottleDefinition((ThrottleDefinition) v)); case "ThrowExceptionDefinition" -> wrapNode("throwException", doWriteThrowExceptionDefinition((ThrowExceptionDefinition) v)); diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java index 9d4f14b3e024c..85e7763102fed 100644 --- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java +++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java @@ -45,6 +45,7 @@ import org.apache.camel.model.RouteTemplatesDefinition; import org.apache.camel.model.RoutesDefinition; import org.apache.camel.model.SendDefinition; +import org.apache.camel.model.SwitchDefinition; import org.apache.camel.model.ToDynamicDefinition; import org.apache.camel.model.dataformat.DataFormatsDefinition; import org.apache.camel.model.language.ExpressionDefinition; @@ -256,6 +257,12 @@ public String dumpDataFormatsAsYaml(CamelContext context, Map da * @param namespaces the map of namespaces to add discovered XML namespaces into */ private static void extractNamespaces(RouteDefinition route, Map namespaces) { + for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + NamespaceAware aware = getNamespaceAwareFromExpression(sw); + if (aware != null && aware.getNamespaces() != null) { + namespaces.putAll(aware.getNamespaces()); + } + } Collection col = filterTypeInOutputs(route.getOutputs(), ExpressionNode.class); for (ExpressionNode en : col) { NamespaceAware na = getNamespaceAwareFromExpression(en); @@ -325,6 +332,17 @@ private static Runnable resolveEndpointDslUris(RouteDefinition route) { return () -> restorers.forEach(Runnable::run); } + private static NamespaceAware getNamespaceAwareFromExpression(SwitchDefinition definition) { + if (definition.getSelector() == null || definition.getSelector().getExpressionType() == null) { + return null; + } + ExpressionDefinition expression = definition.getSelector().getExpressionType(); + if (expression.getExpressionValue() instanceof NamespaceAware aware) { + return aware; + } + return expression instanceof NamespaceAware aware ? aware : null; + } + private static NamespaceAware getNamespaceAwareFromExpression(ExpressionNode expressionNode) { ExpressionDefinition ed = expressionNode.getExpression(); 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 529cbbc4044d6..1effef028fcfe 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 @@ -1403,6 +1403,20 @@ "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - choice:\n when:\n - simple: \"${body} contains 'Hello'\"\n steps:\n - to:\n uri: mock:hello\n - simple: \"${body} contains 'Bye'\"\n steps:\n - to:\n uri: mock:bye\n - stop: {}\n otherwise:\n steps:\n - to:\n uri: mock:other\n - to:\n uri: mock:result\n" } ], + "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" + }, + { + "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 otherwise: direct:review\n" + }, + { + "source": "switch-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n language:\n language: semantic\n expression: refs:department,urgent\n keys: [department, urgent]\n case:\n - values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n otherwise: direct:review\n" + } + ], "threads": [ { "source": "threads-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 new file mode 100644 index 0000000000000..bdc1bdb340207 --- /dev/null +++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSwitchTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.xml.io; + +import java.util.Map; + +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.language.XPathExpression; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.ResourceHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class XmlSwitchTest { + @Test + void selectorNamespacesSurviveModelDump() throws Exception { + String xml = """ + + + + + string(/t:ticket/t:department) + + + + matched + + """; + try (var context = new DefaultCamelContext(); var restored = new DefaultCamelContext()) { + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("selector.xml", xml)); + String roundTrip = PluginHelper.getModelToXMLDumper(context) + .dumpModelAsXml(context, context.getRouteDefinition("selector")); + PluginHelper.getRoutesLoader(restored).loadRoutes(ResourceHelper.fromString("restored.xml", roundTrip)); + SwitchDefinition sw = (SwitchDefinition) restored.getRouteDefinition("selector").getOutputs().get(0); + assertEquals("urn:tickets", ((XPathExpression) sw.getSelector().getExpressionType()).getNamespaces().get("t")); + context.start(); + try (var template = context.createProducerTemplate()) { + assertEquals("matched", template.requestBody("direct:start", + "billing")); + } + } + } + + @Test + void compositeRoundTripPreservesTypesAndExecutes() throws Exception { + String xml = """ + + + + +
decision
+ departmenturgent + + + + +
+
+ matched + other +
+ """; + try (var context = new DefaultCamelContext(); var restored = new DefaultCamelContext()) { + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("selector.xml", xml)); + for (var route : context.getRouteDefinitions()) { + String roundTrip = PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, route); + PluginHelper.getRoutesLoader(restored).loadRoutes(ResourceHelper.fromString(route.getId() + ".xml", roundTrip)); + } + restored.start(); + try (var template = restored.createProducerTemplate()) { + assertEquals("matched", template.requestBodyAndHeader("direct:start", "original", "decision", + Map.of("department", "BILLING", "urgent", true))); + assertEquals("other", template.requestBodyAndHeader("direct:start", "original", "decision", + Map.of("department", "billing", "urgent", "true"))); + } + } + } +} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml index 1b4947b1ac581..23322c1321ef4 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml @@ -87,6 +87,7 @@ org.apache.camel.dsl.yaml.deserializers + org.apache.camel.model.SwitchValueDefinition org.apache.camel.model.FromDefinition org.apache.camel.model.language.ExpressionDefinition org.apache.camel.model.ExpressionSubElementDefinition 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 024263761443c..ea2ecd39a2e15 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 @@ -93,6 +93,8 @@ import org.apache.camel.model.SplitDefinition; 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.TemplatedRouteParameterDefinition; import org.apache.camel.model.ThreadPoolProfileDefinition; import org.apache.camel.model.ThreadsDefinition; @@ -18034,6 +18036,163 @@ protected boolean setProperty(SwiftMxDataFormat target, String propertyKey, } } + @YamlType( + nodes = "case", + types = org.apache.camel.model.SwitchCaseDefinition.class, + order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, + displayName = "Case", + description = "A literal switch case with one fixed endpoint destination", + deprecated = false, + properties = { + @YamlProperty(name = "description", type = "string", description = "The description for this node", displayName = "Description"), + @YamlProperty(name = "id", type = "string", description = "The id of this node", displayName = "Id"), + @YamlProperty(name = "note", type = "string", description = "The note for this node", displayName = "Note"), + @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"), + @YamlProperty(name = "value", type = "string", description = "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.", displayName = "Value"), + @YamlProperty(name = "values", type = "array:org.apache.camel.model.SwitchValueDefinition", description = "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", displayName = "Values") + } + ) + public static class SwitchCaseDefinitionDeserializer extends YamlDeserializerEndpointAwareBase { + public SwitchCaseDefinitionDeserializer() { + super(SwitchCaseDefinition.class); + } + + @Override + protected SwitchCaseDefinition newInstance() { + return new SwitchCaseDefinition(); + } + + @Override + protected void setEndpointUri(CamelContext camelContext, Node node, + SwitchCaseDefinition target, Map parameters) { + target.setUri(org.apache.camel.dsl.yaml.common.YamlSupport.createEndpointUri(camelContext, node, target.getUri(), parameters)); + } + + @Override + protected boolean setProperty(SwitchCaseDefinition 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; + } + case "value": { + String val = asText(node); + target.setValue(val); + break; + } + case "values": { + java.util.List val = asFlatList(node, org.apache.camel.model.SwitchValueDefinition.class); + target.setValues(val); + break; + } + case "id": { + String val = asText(node); + target.setId(val); + break; + } + case "description": { + String val = asText(node); + target.setDescription(val); + break; + } + case "note": { + String val = asText(node); + target.setNote(val); + break; + } + default: { + return false; + } + } + return true; + } + } + + @YamlType( + nodes = "switch", + types = org.apache.camel.model.SwitchDefinition.class, + order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, + displayName = "Switch", + description = "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + deprecated = false, + properties = { + @YamlProperty(name = "case", type = "array:org.apache.camel.model.SwitchCaseDefinition", description = "Literal cases. Duplicate combinations are rejected at startup.", displayName = "Case"), + @YamlProperty(name = "description", type = "string", description = "The description for this node", displayName = "Description"), + @YamlProperty(name = "disabled", type = "boolean", defaultValue = "false", description = "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", displayName = "Disabled"), + @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 = "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") + } + ) + public static class SwitchDefinitionDeserializer extends YamlDeserializerBase { + public SwitchDefinitionDeserializer() { + super(SwitchDefinition.class); + } + + @Override + protected SwitchDefinition newInstance() { + return new SwitchDefinition(); + } + + @Override + protected boolean setProperty(SwitchDefinition target, String propertyKey, + String propertyName, Node node) { + propertyKey = org.apache.camel.util.StringHelper.dashToCamelCase(propertyKey); + switch(propertyKey) { + case "case": { + java.util.List val = asFlatList(node, org.apache.camel.model.SwitchCaseDefinition.class); + target.setCases(val); + break; + } + case "disabled": { + String val = asText(node); + target.setDisabled(val); + break; + } + case "keys": { + java.util.List val = asStringList(node); + target.setKeys(val); + break; + } + case "otherwise": { + String val = asText(node); + target.setOtherwise(val); + break; + } + case "selector": { + org.apache.camel.model.ExpressionSubElementDefinition val = asType(node, org.apache.camel.model.ExpressionSubElementDefinition.class); + target.setSelector(val); + break; + } + case "id": { + String val = asText(node); + target.setId(val); + break; + } + case "description": { + String val = asText(node); + target.setDescription(val); + break; + } + case "note": { + String val = asText(node); + target.setNote(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 03f919086535b..097b74b24f354 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 @@ -403,6 +403,10 @@ public ConstructNode resolve(String id) { case "org.apache.camel.model.dataformat.SwiftMtDataFormat": return new ModelDeserializers.SwiftMtDataFormatDeserializer(); case "swiftMx": return new ModelDeserializers.SwiftMxDataFormatDeserializer(); case "org.apache.camel.model.dataformat.SwiftMxDataFormat": return new ModelDeserializers.SwiftMxDataFormatDeserializer(); + case "case": return new ModelDeserializers.SwitchCaseDefinitionDeserializer(); + 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 "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-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java index be0fbf6042d01..09ae6018fe231 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java @@ -72,6 +72,8 @@ public ConstructNode resolve(String id) { // // Misc // + case "org.apache.camel.model.SwitchValueDefinition": + return new SwitchValueDefinitionDeserializer(); case "beans": return beansDeserializer; case "dataFormats": diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java new file mode 100644 index 0000000000000..a2ae468ebb285 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java @@ -0,0 +1,63 @@ +/* + * 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.deserializers; + +import java.math.BigDecimal; + +import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver; +import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport; +import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException; +import org.apache.camel.model.SwitchValueDefinition; +import org.apache.camel.spi.annotations.YamlType; +import org.snakeyaml.engine.v2.api.ConstructNode; +import org.snakeyaml.engine.v2.nodes.MappingNode; +import org.snakeyaml.engine.v2.nodes.Node; +import org.snakeyaml.engine.v2.nodes.NodeTuple; +import org.snakeyaml.engine.v2.nodes.ScalarNode; +import org.snakeyaml.engine.v2.nodes.Tag; + +@YamlType(types = SwitchValueDefinition.class, order = YamlDeserializerResolver.ORDER_DEFAULT) +public class SwitchValueDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode { + @Override + public Object construct(Node node) { + if (!(node instanceof MappingNode mapping) || mapping.getValue().size() != 1) { + throw new YamlDeserializationException(node, "Each switch value must be a single-entry mapping"); + } + NodeTuple entry = mapping.getValue().get(0); + if (!(entry.getKeyNode() instanceof ScalarNode key) || !Tag.STR.equals(key.getTag())) { + throw new YamlDeserializationException(node, "Switch value names must be strings"); + } + if (!(entry.getValueNode() instanceof ScalarNode value)) { + throw new YamlDeserializationException(node, "Switch values must be scalar literals"); + } + Object literal; + if (Tag.STR.equals(value.getTag())) { + literal = value.getValue(); + } else if (Tag.BOOL.equals(value.getTag())) { + literal = Boolean.valueOf(value.getValue()); + } else if (Tag.INT.equals(value.getTag()) || Tag.FLOAT.equals(value.getTag())) { + try { + literal = new BigDecimal(value.getValue()); + } catch (NumberFormatException e) { + throw new YamlDeserializationException(node, "Switch numbers must be finite decimal literals", e); + } + } else { + throw new YamlDeserializationException(node, "Switch values must be strings, booleans or numbers"); + } + return new SwitchValueDefinition(key.getValue(), literal); + } +} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java index 3c01b90d1e7e3..5f57e840897df 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java @@ -407,8 +407,7 @@ private TypeSpecHolder generateParser(ClassInfo info) { builder.addModifiers(Modifier.PUBLIC, Modifier.STATIC); - if (extendsType(info, SEND_DEFINITION_CLASS) || extendsType(info, TO_DYNAMIC_DEFINITION_CLASS) - || extendsType(info, POLL_DEFINITION_CLASS)) { + if (implementType(info, ENDPOINT_REQUIRED_DEFINITION_CLASS) || extendsType(info, TO_DYNAMIC_DEFINITION_CLASS)) { builder.superclass(ParameterizedTypeName.get(CN_ENDPOINT_AWARE_DESERIALIZER_BASE, targetType)); } else { builder.superclass(ParameterizedTypeName.get(CN_DESERIALIZER_BASE, targetType)); @@ -588,8 +587,7 @@ private TypeSpecHolder generateParser(ClassInfo info) { "array:org.apache.camel.model.ProcessorDefinition")); } - if (extendsType(info, SEND_DEFINITION_CLASS) || extendsType(info, TO_DYNAMIC_DEFINITION_CLASS) - || extendsType(info, POLL_DEFINITION_CLASS)) { + if (implementType(info, ENDPOINT_REQUIRED_DEFINITION_CLASS) || extendsType(info, TO_DYNAMIC_DEFINITION_CLASS)) { setProperty.beginControlFlow("default:"); setProperty.addStatement("return false"); setProperty.endControlFlow(); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java index a8b7f029af42c..398f9ad36f86f 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java @@ -172,6 +172,16 @@ protected void generate() throws MojoFailureException { postProcessInheritance(inheritedDefinitions, inlineDefinitions); } + // Switch's YAML shorthand is one named, typed scalar per list entry. + String switchValue = "org.apache.camel.model.SwitchValueDefinition"; + if (definitions.has(switchValue)) { + ObjectNode literal = mapper.createObjectNode(); + literal.put("type", "object").put("minProperties", 1).put("maxProperties", 1); + literal.putObject("additionalProperties").putArray("type") + .add("string").add("boolean").add("number"); + definitions.set(switchValue, literal); + } + try { ToolingSupport.mkparents(outputFile); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSupportMojo.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSupportMojo.java index d79d6ecbfc6c3..c843862441074 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSupportMojo.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSupportMojo.java @@ -101,12 +101,10 @@ public abstract class GenerateYamlSupportMojo extends AbstractMojo { = DotName.createSimple("org.apache.camel.model.OutputNode"); public static final DotName PROCESSOR_DEFINITION_CLASS = DotName.createSimple("org.apache.camel.model.ProcessorDefinition"); - public static final DotName SEND_DEFINITION_CLASS - = DotName.createSimple("org.apache.camel.model.SendDefinition"); + public static final DotName ENDPOINT_REQUIRED_DEFINITION_CLASS + = DotName.createSimple("org.apache.camel.model.EndpointRequiredDefinition"); public static final DotName TO_DYNAMIC_DEFINITION_CLASS = DotName.createSimple("org.apache.camel.model.ToDynamicDefinition"); - public static final DotName POLL_DEFINITION_CLASS - = DotName.createSimple("org.apache.camel.model.PollDefinition"); public static final DotName ERROR_HANDLER_BUILDER_CLASS = DotName.createSimple("org.apache.camel.builder.ErrorHandlerBuilder"); public static final DotName VERB_DEFINITION_CLASS diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml index ab1611cab578a..341553734b810 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml @@ -67,6 +67,11 @@ assertj-core test + + org.apache.camel + camel-yaml-io + test + org.apache.camel camel-main 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 4a8457cd9b096..d23a4def3fe56 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 @@ -169,6 +169,9 @@ "stop" : { "$ref" : "#/items/definitions/org.apache.camel.model.StopDefinition" }, + "switch" : { + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchDefinition" + }, "threads" : { "$ref" : "#/items/definitions/org.apache.camel.model.ThreadsDefinition" }, @@ -4935,6 +4938,115 @@ } } }, + "org.apache.camel.model.SwitchCaseDefinition" : { + "title" : "Case", + "description" : "A literal switch case with one fixed endpoint destination", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "description" : { + "type" : "string", + "title" : "Description", + "description" : "The description for this node" + }, + "id" : { + "type" : "string", + "title" : "Id", + "description" : "The id of this node" + }, + "note" : { + "type" : "string", + "title" : "Note", + "description" : "The note for this node" + }, + "parameters" : { + "type" : "object" + }, + "uri" : { + "type" : "string", + "title" : "Uri", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions." + }, + "value" : { + "type" : "string", + "title" : "Value", + "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." + }, + "values" : { + "type" : "array", + "title" : "Values", + "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", + "items" : { + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchValueDefinition" + } + } + }, + "required" : [ "uri" ] + }, + "org.apache.camel.model.SwitchDefinition" : { + "title" : "Switch", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "case" : { + "type" : "array", + "title" : "Case", + "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "items" : { + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchCaseDefinition" + } + }, + "description" : { + "type" : "string", + "title" : "Description", + "description" : "The description for this node" + }, + "disabled" : { + "type" : "boolean", + "title" : "Disabled", + "description" : "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "default" : false + }, + "id" : { + "type" : "string", + "title" : "Id", + "description" : "The id of this node" + }, + "keys" : { + "type" : "array", + "title" : "Keys", + "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", + "items" : { + "type" : "string" + } + }, + "note" : { + "type" : "string", + "title" : "Note", + "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." + }, + "selector" : { + "title" : "Selector", + "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "$ref" : "#/items/definitions/org.apache.camel.model.ExpressionSubElementDefinition" + } + }, + "required" : [ "selector" ] + }, + "org.apache.camel.model.SwitchValueDefinition" : { + "type" : "object", + "minProperties" : 1, + "maxProperties" : 1, + "additionalProperties" : { + "type" : [ "string", "boolean", "number" ] + } + }, "org.apache.camel.model.TemplatedRouteDefinition" : { "type" : "object", "additionalProperties" : false, 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 f8c769681a031..50e78118351f5 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 @@ -505,6 +505,13 @@ "title" : "Stop", "description" : "Stops processing of the current message and marks the exchange as completed without sending it to any further steps", "label" : "eip,flowcontrol,routing" + }, { + "name" : "switch", + "type" : "object", + "ref" : "switch", + "title" : "Switch", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "label" : "eip,routing" }, { "name" : "threads", "type" : "object", @@ -8323,6 +8330,137 @@ "label" : "advanced" } ] }, + "switchCase" : { + "title" : "Case", + "description" : "A literal switch case with one fixed endpoint destination", + "children" : [ { + "name" : "description", + "type" : "string", + "description" : "The description for this node", + "title" : "Description" + }, { + "name" : "id", + "type" : "string", + "description" : "The id of this node", + "title" : "Id" + }, { + "name" : "note", + "type" : "string", + "description" : "The note for this node", + "title" : "Note" + }, { + "name" : "parameters", + "type" : "object" + }, { + "name" : "uri", + "type" : "string", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions.", + "title" : "Uri", + "required" : true + }, { + "name" : "value", + "type" : "string", + "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.", + "title" : "Value" + }, { + "name" : "values", + "type" : "array", + "ref" : "switchValue", + "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", + "title" : "Values" + } ] + }, + "switch" : { + "title" : "Switch", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "label" : "eip,routing", + "input" : true, + "isListItem" : true, + "children" : [ { + "name" : "id", + "type" : "string", + "description" : "The id of this node", + "title" : "Id", + "displayName" : "Id", + "kind" : "attribute", + "index" : 0, + "group" : "common" + }, { + "name" : "note", + "type" : "string", + "description" : "The note for this node", + "title" : "Note", + "displayName" : "Note", + "kind" : "attribute", + "index" : 1, + "group" : "common" + }, { + "name" : "description", + "type" : "string", + "description" : "The description for this node", + "title" : "Description", + "displayName" : "Description", + "kind" : "attribute", + "index" : 2, + "group" : "common" + }, { + "name" : "disabled", + "type" : "boolean", + "description" : "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "title" : "Disabled", + "default" : "false", + "displayName" : "Disabled", + "kind" : "attribute", + "index" : 3, + "group" : "advanced", + "label" : "advanced" + }, { + "name" : "selector", + "type" : "object", + "ref" : "expressionSubElement", + "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "title" : "Selector", + "required" : true, + "displayName" : "Selector", + "kind" : "expression", + "index" : 4, + "group" : "common" + }, { + "name" : "keys", + "type" : "array", + "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", + "title" : "Keys", + "displayName" : "Keys", + "kind" : "element", + "index" : 5, + "group" : "common" + }, { + "name" : "case", + "type" : "array", + "ref" : "switchCase", + "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "title" : "Case", + "displayName" : "Case", + "kind" : "element", + "index" : 6, + "group" : "common" + }, { + "name" : "otherwise", + "type" : "string", + "description" : "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.", + "title" : "Otherwise", + "displayName" : "Otherwise", + "kind" : "attribute", + "index" : 7, + "group" : "common" + } ] + }, + "switchValue" : { + "title" : "Switch Value", + "description" : "A named literal in a composite switch case", + "label" : "configuration", + "children" : [ ] + }, "templatedRoute" : { "title" : "Templated Route", "description" : "Creates a route instance from a route template, binding specific parameter values to the template's parameters", 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 81ddd7f2f1435..e677a187f48d4 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 @@ -169,6 +169,9 @@ "stop" : { "$ref" : "#/items/definitions/org.apache.camel.model.StopDefinition" }, + "switch" : { + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchDefinition" + }, "threads" : { "$ref" : "#/items/definitions/org.apache.camel.model.ThreadsDefinition" }, @@ -6588,6 +6591,115 @@ } } }, + "org.apache.camel.model.SwitchCaseDefinition" : { + "title" : "Case", + "description" : "A literal switch case with one fixed endpoint destination", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "description" : { + "type" : "string", + "title" : "Description", + "description" : "The description for this node" + }, + "id" : { + "type" : "string", + "title" : "Id", + "description" : "The id of this node" + }, + "note" : { + "type" : "string", + "title" : "Note", + "description" : "The note for this node" + }, + "parameters" : { + "type" : "object" + }, + "uri" : { + "type" : "string", + "title" : "Uri", + "description" : "The fixed destination URI. Supports property placeholders, but not Simple expressions." + }, + "value" : { + "type" : "string", + "title" : "Value", + "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." + }, + "values" : { + "type" : "array", + "title" : "Values", + "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", + "items" : { + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchValueDefinition" + } + } + }, + "required" : [ "uri" ] + }, + "org.apache.camel.model.SwitchDefinition" : { + "title" : "Switch", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "type" : "object", + "additionalProperties" : false, + "properties" : { + "case" : { + "type" : "array", + "title" : "Case", + "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "items" : { + "$ref" : "#/items/definitions/org.apache.camel.model.SwitchCaseDefinition" + } + }, + "description" : { + "type" : "string", + "title" : "Description", + "description" : "The description for this node" + }, + "disabled" : { + "type" : "boolean", + "title" : "Disabled", + "description" : "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", + "default" : false + }, + "id" : { + "type" : "string", + "title" : "Id", + "description" : "The id of this node" + }, + "keys" : { + "type" : "array", + "title" : "Keys", + "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", + "items" : { + "type" : "string" + } + }, + "note" : { + "type" : "string", + "title" : "Note", + "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." + }, + "selector" : { + "title" : "Selector", + "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "$ref" : "#/items/definitions/org.apache.camel.model.ExpressionSubElementDefinition" + } + }, + "required" : [ "selector" ] + }, + "org.apache.camel.model.SwitchValueDefinition" : { + "type" : "object", + "minProperties" : 1, + "maxProperties" : 1, + "additionalProperties" : { + "type" : [ "string", "boolean", "number" ] + } + }, "org.apache.camel.model.TemplatedRouteDefinition" : { "type" : "object", "additionalProperties" : false, 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 new file mode 100644 index 0000000000000..5892672450583 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchTest.java @@ -0,0 +1,168 @@ +/* + * 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.math.BigDecimal; +import java.util.Map; + +import org.apache.camel.dsl.yaml.support.YamlTestSupport; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.ToDynamicDefinition; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.yaml.LwModelToYAMLDumper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SwitchTest extends YamlTestSupport { + @Test + void compositeValuesAndEndpointParametersRoundTrip() throws Exception { + loadRoutes(""" + - route: + id: decision + from: + uri: direct:start + steps: + - switch: + selector: + header: + expression: decision + keys: [department, urgent] + case: + - values: [department: billing, urgent: true] + uri: direct + parameters: + name: urgent + - values: [urgent: false, department: billing] + uri: direct:billing + otherwise: direct:review + - route: + from: + uri: direct:urgent + steps: + - setBody: + constant: urgent + - route: + from: + uri: direct:billing + steps: + - setBody: + constant: billing + - route: + from: + uri: direct:review + steps: + - setBody: + constant: review + """); + 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); + try (var restored = new DefaultCamelContext()) { + for (var route : context.getRouteDefinitions()) { + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + PluginHelper.getRoutesLoader(restored).loadRoutes(ResourceHelper.fromString(route.getId() + ".yaml", yaml)); + } + restored.start(); + try (var template = restored.createProducerTemplate()) { + assertThat(template.requestBodyAndHeader("direct:start", "original", "decision", + Map.of("department", "BILLING", "urgent", true))).isEqualTo("urgent"); + assertThat(template.requestBodyAndHeader("direct:start", "original", "decision", + Map.of("department", "billing", "urgent", false))).isEqualTo("billing"); + assertThat(template.requestBodyAndHeader("direct:start", "original", "decision", + Map.of("department", "billing", "urgent", "true"))).isEqualTo("review"); + } + } + } + + @Test + void quotedScalarsRetainTheirTypesAfterDump() throws Exception { + loadRoutes(""" + - route: + id: quoted + from: + uri: direct:start + steps: + - switch: + selector: + header: decision + keys: [urgent, score] + case: + - values: [urgent: 'true', score: '2'] + uri: mock:string + - values: [urgent: true, score: 2.0] + uri: mock:boolean + """); + var route = context.getRouteDefinition("quoted"); + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + try (var restored = new DefaultCamelContext()) { + PluginHelper.getRoutesLoader(restored).loadRoutes(ResourceHelper.fromString("restored.yaml", yaml)); + SwitchDefinition sw = (SwitchDefinition) restored.getRouteDefinition("quoted").getOutputs().get(0); + assertThat(sw.getCases().get(0).getValues().get(0).asLiteral()).isEqualTo("true"); + assertThat(sw.getCases().get(1).getValues().get(0).asLiteral()).isEqualTo(true); + assertThat(sw.getCases().get(0).getValues().get(1).asLiteral()).isEqualTo("2"); + assertThat(sw.getCases().get(1).getValues().get(1).asLiteral()).isInstanceOf(BigDecimal.class); + assertThat((BigDecimal) sw.getCases().get(1).getValues().get(1).asLiteral()).isEqualByComparingTo("2.0"); + } + } + + @Test + void rejectsNonScalarOrMultipleBindingsPerEntry() { + for (String values : new String[] { "[{urgent: true, department: billing}]", "[urgent: [true]]", "[urgent: null]" }) { + String yaml = """ + - route: + from: + uri: direct:start + steps: + - switch: + selector: + header: decision + keys: [urgent] + case: + - values: %s + uri: mock:a + """.formatted(values); + assertThatThrownBy(() -> loadRoutes(yaml)).isInstanceOf(Exception.class); + } + } + + @Test + void toDStillAcceptsParametersAndRoundTrips() throws Exception { + loadRoutes(""" + - route: + id: dynamic + from: + uri: direct:start + steps: + - toD: + uri: direct + parameters: + name: "${header.destination}" + """); + var route = context.getRouteDefinition("dynamic"); + assertThat(((ToDynamicDefinition) route.getOutputs().get(0)).getUri()).isEqualTo("direct:${header.destination}"); + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + try (var restored = new DefaultCamelContext()) { + PluginHelper.getRoutesLoader(restored).loadRoutes(ResourceHelper.fromString("restored.yaml", yaml)); + assertThat(((ToDynamicDefinition) restored.getRouteDefinition("dynamic").getOutputs().get(0)).getUri()) + .isEqualTo("direct:${header.destination}"); + } + } +} 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 new file mode 100644 index 0000000000000..5300b0724b866 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SwitchYamlDumpTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.yaml; + +import java.util.Map; + +import org.apache.camel.dsl.yaml.support.YamlTestSupport; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.language.XPathExpression; +import org.apache.camel.yaml.LwModelToYAMLDumper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class SwitchYamlDumpTest extends YamlTestSupport { + @Test + void fluentSelectorSurvivesYamlDump() throws Exception { + RouteDefinition route = new RouteDefinition().from("direct:start").routeId("fluent"); + route.doSwitch().header("department").doCase("billing", "mock:billing"); + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + loadRoutes(yaml); + SwitchDefinition restored = (SwitchDefinition) context.getRouteDefinition("fluent").getOutputs().get(0); + assertThat(restored.getSelector().getExpressionType().getExpression()).isEqualTo("department"); + assertThat(restored.getSelector().getExpressionType().getLanguage()).isEqualTo("header"); + } + + @Test + void selectorNamespacesAndLiteralBranchesSurviveYamlDump() throws Exception { + XPathExpression selector = new XPathExpression("/t:ticket/t:department/text()"); + selector.setNamespaces(Map.of("t", "urn:tickets")); + RouteDefinition route = new RouteDefinition().from("direct:start").routeId("selector"); + route.doSwitch(selector).doCase("billing", "mock:billing").otherwise("mock:other"); + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + loadRoutes(yaml); + SwitchDefinition restored = (SwitchDefinition) context.getRouteDefinition("selector").getOutputs().get(0); + XPathExpression xpath = (XPathExpression) restored.getSelector().getExpressionType(); + assertThat(xpath.getExpression()).isEqualTo("/t:ticket/t:department/text()"); + assertThat(xpath.getNamespaces()).containsEntry("t", "urn:tickets"); + assertThat(restored.getCases().get(0).getValue()).isEqualTo("billing"); + assertThat(restored.getCases().get(0).getUri()).isEqualTo("mock:billing"); + } +} 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 ac93150b1b3d3..8d389c26aabb3 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 @@ -67,6 +67,8 @@ public class JavaDslModelWriter extends JavaDslModelWriterSupport { StringBuilder sb = new StringBuilder(); #if( $name == "RouteDefinition" ) writeRoute(sb, def); + #elseif( $name == "SwitchDefinition" ) + writeSwitch(sb, def); #else beginStep(sb, "${element}", def); doWrite${name}(sb, def); @@ -300,7 +302,9 @@ public class JavaDslModelWriter extends JavaDslModelWriterSupport { #set( $n = $mojo.lowercase($t) ) #end #set( $primaryArgs = $mojo.getPrimaryArgs($n, $cl) ) - #if( !$primaryArgs.isEmpty() ) + #if( $t == "SwitchDefinition" ) + case "SwitchDefinition" -> writeSwitch(sb, (SwitchDefinition) v); + #elseif( !$primaryArgs.isEmpty() ) case "${t}" -> { ${t} _d = (${t}) v; handledAttributes.clear(); 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 8b3da44d0e4ce..e82bc9b8e726b 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 @@ -279,6 +279,14 @@ public class YamlModelWriter extends YamlModelWriterSupport { #if( $name == "RouteDefinition" ) doMoveStepsUnderFrom(jo); #end + #if( $name == "SwitchValueDefinition" ) + jo.clear(); + jo.put(def.getName(), def.asLiteral()); + #elseif( $name == "SwitchCaseDefinition" ) + if (def.getValue() != null) { + jo.put("value", def.getValue()); + } + #end return jo; } #end From e96fab8b763142a4d5df4fe62e36210938323946 Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Tue, 29 Sep 2026 12:28:43 +0200 Subject: [PATCH 2/7] CAMEL-24988: Document scalar and composite Switch routing examples Show header, map-header and semantic batch routes in Java, XML and YAML, including question registration, example inputs and expected destinations. Provide logging routes for trying the examples and regenerate the catalog documentation and JBang samples. Co-authored-by: Codex Signed-off-by: Luigi De Masi --- .../apache/camel/catalog/docs/switch-eip.adoc | 276 ++++++++++++++++-- .../docs/modules/eips/pages/switch-eip.adoc | 276 ++++++++++++++++-- .../jbang/core/commands/ai/eip-samples.json | 8 +- 3 files changed, 510 insertions(+), 50 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 42b02391c868e..d53fc1cf089b7 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 @@ -30,6 +30,12 @@ and matched case-insensitively using the English locale. An empty string is a va literal. An asterisk is literal, not a wildcard. Duplicate normalized values are rejected at startup. +=== Route from a header + +This route reads the `department` header. The body is passed unchanged to the +selected destination. The examples on this page are independent; load one at a +time and connect the `direct:` destinations to your application's handling routes. + [tabs] ==== Java:: @@ -47,11 +53,14 @@ XML:: + [source,xml] ---- - -
department
- - -
+ + + +
department
+ + +
+
---- YAML:: + @@ -74,6 +83,22 @@ YAML:: ---- ==== +Send a message to any of these equivalent routes with a `ProducerTemplate`: + +[source,java] +---- +template.sendBodyAndHeader("direct:tickets", "Please check this invoice", + "department", "BILLING"); +---- + +[cols="1,2",options="header"] +|=== +|Header value |Destination +|`billing` or `BILLING` |`direct:billing` +|`technical` |`direct:technical` +|`sales` or an absent header |`direct:review` +|=== + A null or unmatched result uses `otherwise`. Without `otherwise`, the exchange continues after Switch. Selector failures follow normal Camel error handling and never select the fallback. Streams are reset after selector evaluation when stream @@ -94,6 +119,14 @@ match number `2`. Numbers compare by decimal value, so `2` and `2.0` match. Dupl combinations, including differences only in letter case or numeric scale, fail at startup. Scalar `value` cases and composite `values` cases cannot be mixed. +=== Route from a map in a header + +The `decision` header now contains a map, for example +`Map.of("department", "billing", "urgent", true)`. The table declares the +combinations directly, without a helper expression that concatenates values into +a synthetic category. Any expression returning the same map can replace the +header selector. + [tabs] ==== Java:: @@ -107,6 +140,8 @@ from("direct:tickets") .id("urgentBilling").to("direct:urgent-billing") .doCase().value("department", "billing").value("urgent", false) .to("direct:billing") + .doCase().value("department", "technical").value("urgent", true) + .to("direct:incident") .otherwise("direct:review") .end(); ---- @@ -114,19 +149,26 @@ XML:: + [source,xml] ---- - -
decision
- department - urgent - - - - - - - - -
+ + + +
decision
+ department + urgent + + + + + + + + + + + + +
+
---- YAML:: + @@ -147,6 +189,8 @@ YAML:: uri: direct:urgent-billing - values: [department: billing, urgent: false] uri: direct:billing + - values: [department: technical, urgent: true] + uri: direct:incident otherwise: direct:review ---- ==== @@ -156,15 +200,124 @@ otherwise be read as booleans or numbers. XML literals default to `type="string" use `type="boolean"` or `type="number"` explicitly. Keys select fields in the returned map; they do not implicitly read separate message headers. +For example, send this input with `java.util.Map`: + +[source,java] +---- +template.sendBodyAndHeader("direct:tickets", "Please check this invoice", + "decision", Map.of("department", "billing", "urgent", true)); +---- + +[cols="1,1,2",options="header"] +|=== +|`department` |`urgent` |Destination +|`billing` |boolean `true` |`direct:urgent-billing` +|`billing` |boolean `false` |`direct:billing` +|`technical` |boolean `true` |`direct:incident` +|`technical` |boolean `false` |`direct:review` +|`billing` |string `"true"` |`direct:review` +|=== + +A map missing `urgent` fails the exchange instead of selecting `direct:review`. +The table does not require every possible combination to have a case. + Cases are compiled into a lookup table. Constructing a composite key involves the selected fields, but lookup does not scan the declared cases. -=== Semantic decisions +=== Route from a semantic batch -With `camel-semantic` and a provider configured, a named batch can be used directly: +The same table can consume two named semantic questions directly. Add +`camel-semantic` and configure an adapter such as +xref:ROOT:typesafe-ai-component.adoc[TypeSafe AI], as described in the +xref:languages:semantic-language.adoc[Semantic language]. Provider credentials, +timeouts and inference configuration belong to the adapter. + +The `department` question produces a category string and `urgent` produces a +boolean. The selector evaluates `refs:department,urgent` once per Switch entry +and returns both decisions in one map. An adapter can evaluate them in one provider +request; adapters without batch support evaluate the questions sequentially. +Switch does not invoke the selector again for each case. + +The YAML example includes the question declarations. For the Java and XML routes, +register the same definitions before loading the routes: + +[source,java] +---- +import java.util.List; +import java.util.Map; +import org.apache.camel.semantic.SemanticQuestion; +import org.apache.camel.semantic.SemanticQuestions; + +SemanticQuestions.get(context).replace("ticket-routing", Map.of( + "department", new SemanticQuestion( + SemanticQuestion.Type.CHOICE, "Which department should handle this message?", null, + Map.of("billing", "Invoices, payments and refunds", + "technical", "Bugs, outages and technical problems"), + List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL), + "urgent", new SemanticQuestion( + SemanticQuestion.Type.BOOLEAN, "Does this request need immediate attention?", null, + Map.of(), List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL))); +---- + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:tickets") + .doSwitch().language("semantic", "refs:department,urgent") + .keys("department", "urgent") + .doCase().value("department", "billing").value("urgent", true) + .to("direct:urgent-billing") + .doCase().value("department", "billing").value("urgent", false) + .to("direct:billing") + .doCase().value("department", "technical").value("urgent", true) + .to("direct:incident") + .otherwise("direct:review") + .end(); +---- +XML:: ++ +[source,xml] +---- + + + + refs:department,urgent + department + urgent + + + + + + + + + + + + + + +---- +YAML:: ++ [source,yaml] ---- +- semantic: + question: + department: + type: choice + instructions: Which department should handle this message? + criteria: + billing: Invoices, payments and refunds + technical: Bugs, outages and technical problems + urgent: + type: boolean + instructions: Does this request need immediate attention? - route: from: uri: direct:tickets @@ -180,13 +333,88 @@ With `camel-semantic` and a provider configured, a named batch can be used direc uri: direct:urgent-billing - values: [department: billing, urgent: false] uri: direct:billing + - values: [department: technical, urgent: true] + uri: direct:incident otherwise: direct:review ---- +==== + +Submit a message body using `template.sendBody("direct:tickets", "Our invoice is due today; please help immediately")`. +If the adapter returns `{department: "billing", urgent: true}`, the message goes +to `direct:urgent-billing`. The destination depends on the returned decisions, +not on a guaranteed classification of that example sentence. The other combinations +use the same routing table as the map-header example above. Provider failures follow +normal error handling and do not select the fallback. + +For a single Choice question, use `ref:department` as the selector and the scalar +`value` cases from the first example, without `keys`. No intermediate property or +header is needed to route the answer. -Declare the questions using the xref:languages:semantic-language.adoc[Semantic language]. -Batching, thresholds and uncertainty policies belong to that language and its provider. -Switch has no dependency on semantic evaluation. Store a result explicitly in a -variable, header or exchange property when it should be reused by later EIPs. +Batching, thresholds and uncertainty policies belong to the semantic language and +its provider. Switch has no dependency on semantic evaluation. Store a result +explicitly in a variable, header or exchange property when later EIPs should reuse it. + +=== Connect the example destinations + +For a local demonstration, the destination routes can simply log the message. +Load these routes alongside one of the examples above; replace the logging steps +with the application's processing when integrating the example. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:billing").log("Billing: ${body}"); +from("direct:technical").log("Technical: ${body}"); +from("direct:urgent-billing").log("Urgent billing: ${body}"); +from("direct:incident").log("Incident: ${body}"); +from("direct:review").log("Review: ${body}"); +---- +XML:: ++ +[source,xml] +---- + + + + + + + +---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:billing + steps: + - log: "Billing: ${body}" +- route: + from: + uri: direct:technical + steps: + - log: "Technical: ${body}" +- route: + from: + uri: direct:urgent-billing + steps: + - log: "Urgent billing: ${body}" +- route: + from: + uri: direct:incident + steps: + - log: "Incident: ${body}" +- route: + from: + uri: direct:review + steps: + - log: "Review: ${body}" +---- +==== == Destinations and management 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 42b02391c868e..d53fc1cf089b7 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 @@ -30,6 +30,12 @@ and matched case-insensitively using the English locale. An empty string is a va literal. An asterisk is literal, not a wildcard. Duplicate normalized values are rejected at startup. +=== Route from a header + +This route reads the `department` header. The body is passed unchanged to the +selected destination. The examples on this page are independent; load one at a +time and connect the `direct:` destinations to your application's handling routes. + [tabs] ==== Java:: @@ -47,11 +53,14 @@ XML:: + [source,xml] ---- - -
department
- - -
+ + + +
department
+ + +
+
---- YAML:: + @@ -74,6 +83,22 @@ YAML:: ---- ==== +Send a message to any of these equivalent routes with a `ProducerTemplate`: + +[source,java] +---- +template.sendBodyAndHeader("direct:tickets", "Please check this invoice", + "department", "BILLING"); +---- + +[cols="1,2",options="header"] +|=== +|Header value |Destination +|`billing` or `BILLING` |`direct:billing` +|`technical` |`direct:technical` +|`sales` or an absent header |`direct:review` +|=== + A null or unmatched result uses `otherwise`. Without `otherwise`, the exchange continues after Switch. Selector failures follow normal Camel error handling and never select the fallback. Streams are reset after selector evaluation when stream @@ -94,6 +119,14 @@ match number `2`. Numbers compare by decimal value, so `2` and `2.0` match. Dupl combinations, including differences only in letter case or numeric scale, fail at startup. Scalar `value` cases and composite `values` cases cannot be mixed. +=== Route from a map in a header + +The `decision` header now contains a map, for example +`Map.of("department", "billing", "urgent", true)`. The table declares the +combinations directly, without a helper expression that concatenates values into +a synthetic category. Any expression returning the same map can replace the +header selector. + [tabs] ==== Java:: @@ -107,6 +140,8 @@ from("direct:tickets") .id("urgentBilling").to("direct:urgent-billing") .doCase().value("department", "billing").value("urgent", false) .to("direct:billing") + .doCase().value("department", "technical").value("urgent", true) + .to("direct:incident") .otherwise("direct:review") .end(); ---- @@ -114,19 +149,26 @@ XML:: + [source,xml] ---- - -
decision
- department - urgent - - - - - - - - -
+ + + +
decision
+ department + urgent + + + + + + + + + + + + +
+
---- YAML:: + @@ -147,6 +189,8 @@ YAML:: uri: direct:urgent-billing - values: [department: billing, urgent: false] uri: direct:billing + - values: [department: technical, urgent: true] + uri: direct:incident otherwise: direct:review ---- ==== @@ -156,15 +200,124 @@ otherwise be read as booleans or numbers. XML literals default to `type="string" use `type="boolean"` or `type="number"` explicitly. Keys select fields in the returned map; they do not implicitly read separate message headers. +For example, send this input with `java.util.Map`: + +[source,java] +---- +template.sendBodyAndHeader("direct:tickets", "Please check this invoice", + "decision", Map.of("department", "billing", "urgent", true)); +---- + +[cols="1,1,2",options="header"] +|=== +|`department` |`urgent` |Destination +|`billing` |boolean `true` |`direct:urgent-billing` +|`billing` |boolean `false` |`direct:billing` +|`technical` |boolean `true` |`direct:incident` +|`technical` |boolean `false` |`direct:review` +|`billing` |string `"true"` |`direct:review` +|=== + +A map missing `urgent` fails the exchange instead of selecting `direct:review`. +The table does not require every possible combination to have a case. + Cases are compiled into a lookup table. Constructing a composite key involves the selected fields, but lookup does not scan the declared cases. -=== Semantic decisions +=== Route from a semantic batch -With `camel-semantic` and a provider configured, a named batch can be used directly: +The same table can consume two named semantic questions directly. Add +`camel-semantic` and configure an adapter such as +xref:ROOT:typesafe-ai-component.adoc[TypeSafe AI], as described in the +xref:languages:semantic-language.adoc[Semantic language]. Provider credentials, +timeouts and inference configuration belong to the adapter. + +The `department` question produces a category string and `urgent` produces a +boolean. The selector evaluates `refs:department,urgent` once per Switch entry +and returns both decisions in one map. An adapter can evaluate them in one provider +request; adapters without batch support evaluate the questions sequentially. +Switch does not invoke the selector again for each case. + +The YAML example includes the question declarations. For the Java and XML routes, +register the same definitions before loading the routes: + +[source,java] +---- +import java.util.List; +import java.util.Map; +import org.apache.camel.semantic.SemanticQuestion; +import org.apache.camel.semantic.SemanticQuestions; + +SemanticQuestions.get(context).replace("ticket-routing", Map.of( + "department", new SemanticQuestion( + SemanticQuestion.Type.CHOICE, "Which department should handle this message?", null, + Map.of("billing", "Invoices, payments and refunds", + "technical", "Bugs, outages and technical problems"), + List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL), + "urgent", new SemanticQuestion( + SemanticQuestion.Type.BOOLEAN, "Does this request need immediate attention?", null, + Map.of(), List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL))); +---- + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:tickets") + .doSwitch().language("semantic", "refs:department,urgent") + .keys("department", "urgent") + .doCase().value("department", "billing").value("urgent", true) + .to("direct:urgent-billing") + .doCase().value("department", "billing").value("urgent", false) + .to("direct:billing") + .doCase().value("department", "technical").value("urgent", true) + .to("direct:incident") + .otherwise("direct:review") + .end(); +---- +XML:: ++ +[source,xml] +---- + + + + refs:department,urgent + department + urgent + + + + + + + + + + + + + + +---- +YAML:: ++ [source,yaml] ---- +- semantic: + question: + department: + type: choice + instructions: Which department should handle this message? + criteria: + billing: Invoices, payments and refunds + technical: Bugs, outages and technical problems + urgent: + type: boolean + instructions: Does this request need immediate attention? - route: from: uri: direct:tickets @@ -180,13 +333,88 @@ With `camel-semantic` and a provider configured, a named batch can be used direc uri: direct:urgent-billing - values: [department: billing, urgent: false] uri: direct:billing + - values: [department: technical, urgent: true] + uri: direct:incident otherwise: direct:review ---- +==== + +Submit a message body using `template.sendBody("direct:tickets", "Our invoice is due today; please help immediately")`. +If the adapter returns `{department: "billing", urgent: true}`, the message goes +to `direct:urgent-billing`. The destination depends on the returned decisions, +not on a guaranteed classification of that example sentence. The other combinations +use the same routing table as the map-header example above. Provider failures follow +normal error handling and do not select the fallback. + +For a single Choice question, use `ref:department` as the selector and the scalar +`value` cases from the first example, without `keys`. No intermediate property or +header is needed to route the answer. -Declare the questions using the xref:languages:semantic-language.adoc[Semantic language]. -Batching, thresholds and uncertainty policies belong to that language and its provider. -Switch has no dependency on semantic evaluation. Store a result explicitly in a -variable, header or exchange property when it should be reused by later EIPs. +Batching, thresholds and uncertainty policies belong to the semantic language and +its provider. Switch has no dependency on semantic evaluation. Store a result +explicitly in a variable, header or exchange property when later EIPs should reuse it. + +=== Connect the example destinations + +For a local demonstration, the destination routes can simply log the message. +Load these routes alongside one of the examples above; replace the logging steps +with the application's processing when integrating the example. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:billing").log("Billing: ${body}"); +from("direct:technical").log("Technical: ${body}"); +from("direct:urgent-billing").log("Urgent billing: ${body}"); +from("direct:incident").log("Incident: ${body}"); +from("direct:review").log("Review: ${body}"); +---- +XML:: ++ +[source,xml] +---- + + + + + + + +---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:billing + steps: + - log: "Billing: ${body}" +- route: + from: + uri: direct:technical + steps: + - log: "Technical: ${body}" +- route: + from: + uri: direct:urgent-billing + steps: + - log: "Urgent billing: ${body}" +- route: + from: + uri: direct:incident + steps: + - log: "Incident: ${body}" +- route: + from: + uri: direct:review + steps: + - log: "Review: ${body}" +---- +==== == Destinations and management 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 1effef028fcfe..d330b5fbc7b79 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 @@ -1410,11 +1410,15 @@ }, { "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 otherwise: direct:review\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 - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise: direct:review\n" }, { "source": "switch-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:tickets\n steps:\n - switch:\n selector:\n language:\n language: semantic\n expression: refs:department,urgent\n keys: [department, urgent]\n case:\n - values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n otherwise: direct:review\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 language:\n language: semantic\n expression: refs:department,urgent\n keys: [department, urgent]\n case:\n - values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise: direct:review\n" + }, + { + "source": "switch-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:billing\n steps:\n - log: \"Billing: ${body}\"\n- route:\n from:\n uri: direct:technical\n steps:\n - log: \"Technical: ${body}\"\n- route:\n from:\n uri: direct:urgent-billing\n steps:\n - log: \"Urgent billing: ${body}\"\n- route:\n from:\n uri: direct:incident\n steps:\n - log: \"Incident: ${body}\"\n- route:\n from:\n uri: direct:review\n steps:\n - log: \"Review: ${body}\"\n" } ], "threads": [ From a6c6efb530f1c7188817c008ff21e8760663c2e6 Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Tue, 29 Sep 2026 12:59:46 +0200 Subject: [PATCH 3/7] CAMEL-24988: Fix XML namespace in Switch destination example Use the XML IO namespace for the standalone routes document so the catalog documentation schema checks validate it. Co-authored-by: Codex Signed-off-by: Luigi De Masi --- .../resources/org/apache/camel/catalog/docs/switch-eip.adoc | 2 +- .../src/main/docs/modules/eips/pages/switch-eip.adoc | 2 +- 2 files changed, 2 insertions(+), 2 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 d53fc1cf089b7..610202d2c2c01 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 @@ -376,7 +376,7 @@ XML:: + [source,xml] ---- - + 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 d53fc1cf089b7..610202d2c2c01 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 @@ -376,7 +376,7 @@ XML:: + [source,xml] ---- - + From 225768e5ce8ba671b0e039615216e32c7361f79b Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Tue, 29 Sep 2026 15:58:12 +0200 Subject: [PATCH 4/7] 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 Signed-off-by: Luigi De Masi --- .../apache/camel/catalog/docs/switch-eip.adoc | 18 ++++--- .../apache/camel/catalog/models.properties | 1 + .../apache/camel/catalog/models/switch.json | 2 +- .../camel/catalog/models/switchOtherwise.json | 17 ++++++ .../camel/catalog/schemas/camel-spring.xsd | 35 +++++++++--- .../camel/catalog/schemas/camel-xml-io.xsd | 35 +++++++++--- .../catalog/schemas/camelYamlDsl-model.json | 24 ++++++++- .../apache/camel/spring/SwitchJaxbTest.java | 1 + .../docs/modules/eips/pages/switch-eip.adoc | 18 ++++--- .../org/apache/camel/model/switch.json | 2 +- .../apache/camel/model/switchOtherwise.json | 17 ++++++ .../org/apache/camel/model.properties | 1 + .../org/apache/camel/model/jaxb.index | 1 + .../apache/camel/model/SwitchDefinition.java | 23 ++++---- .../model/SwitchOtherwiseDefinition.java | 53 +++++++++++++++++++ .../apache/camel/reifier/SwitchReifier.java | 2 +- .../camel/processor/SwitchEndpointTest.java | 8 ++- .../apache/camel/processor/SwitchTest.java | 6 ++- .../camel/processor/SwitchValidationTest.java | 11 ++++ .../camel/java/out/JavaDslModelWriter.java | 12 ++++- .../java/out/JavaDslModelWriterSupport.java | 2 +- .../java/out/JavaDslModelWriterTest.java | 2 + .../src/test/resources/switchLiteral.xml | 6 ++- .../camel/management/mbean/ManagedSwitch.java | 2 +- .../camel/management/ManagedSwitchTest.java | 1 + .../org/apache/camel/xml/in/ModelParser.java | 12 +++-- .../org/apache/camel/xml/out/ModelWriter.java | 10 +++- .../camel/yaml/out/YamlModelWriter.java | 10 +++- .../jbang/core/commands/ai/eip-samples.json | 6 +-- .../camel/dsl/xml/io/XmlSwitchTest.java | 3 +- .../deserializers/ModelDeserializers.java | 51 +++++++++++++++++- .../ModelDeserializersResolver.java | 2 + .../dsl/yaml/validator/SwitchSchemaTest.java | 50 +++++++++++++++++ .../schema/camelYamlDsl-canonical.json | 21 +++++++- .../resources/schema/camelYamlDsl-model.json | 24 ++++++++- .../resources/schema/camelYamlDsl.json | 21 +++++++- .../org/apache/camel/dsl/yaml/SwitchTest.java | 25 ++++++++- .../camel/dsl/yaml/SwitchYamlDumpTest.java | 5 ++ 38 files changed, 473 insertions(+), 67 deletions(-) create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchOtherwise.json create mode 100644 core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchOtherwise.json create mode 100644 core/camel-core-model/src/main/java/org/apache/camel/model/SwitchOtherwiseDefinition.java create mode 100644 dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/SwitchSchemaTest.java 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 610202d2c2c01..1f20dde1adbb2 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:: ---- - +
department
+
---- @@ -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:: ---- - +
decision
department urgent @@ -167,6 +169,7 @@ XML:: +
---- @@ -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:: ---- - + refs:department,urgent department urgent @@ -300,6 +304,7 @@ XML:: + ---- @@ -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 70faa2f60b2a9..0e50bde32a499 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 c557fd3550a3f..d4158f770eff2 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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured." }, "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "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", "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 0000000000000..ef34bee1569a8 --- /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 2477aa7cfd2bc..54d85d44a379e 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 + + + + + + + @@ -13999,19 +14008,31 @@ Exact map field names forming a composite key. Additional result fields are igno - - - - + + + - - - + + + + + + + + + + + + + + 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 4b76d226029dd..4902984793f58 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 + + + + + + + @@ -13142,19 +13151,31 @@ Exact map field names forming a composite key. Additional result fields are igno - - - - + + + - - - + + + + + + + + + + + + + + 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 50e78118351f5..d505bf5061b65 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 5c3eec5f775eb..b52ff8f335f66 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 @@ void selectorNamespacesAndLiteralBranchesSurviveJaxbRoundTrip() throws Exception 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 610202d2c2c01..1f20dde1adbb2 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:: ---- - +
department
+
---- @@ -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:: ---- - +
decision
department urgent @@ -167,6 +169,7 @@ XML:: +
---- @@ -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:: ---- - + refs:department,urgent department urgent @@ -300,6 +304,7 @@ XML:: + ---- @@ -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 c557fd3550a3f..d4158f770eff2 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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured." }, "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "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", "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 0000000000000..ef34bee1569a8 --- /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 bfdfc89a0ebf7..b68bb04e5d7f6 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 13090d116b524..88d41e0cfb88b 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 32284c8d8f15e..badf6610b6569 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 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 @@ 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 { @XmlElement(required = true) @Metadata(required = true, @@ -52,9 +51,9 @@ public class SwitchDefinition extends NoOutputDefinition { @XmlElement(name = "case") @Metadata(description = "Literal cases. Duplicate combinations are rejected at startup.") private List 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 @@ protected SwitchDefinition(SwitchDefinition source) { 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 void setCases(List cases) { 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 ToDefinition getOtherwiseDefinition() { 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 CaseBuilder doCase(String value) { /** 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 0000000000000..0c5e79d282bde --- /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 d156792a84825..b96ebd1152c39 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 Processor createProcessor() throws Exception { 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 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 fc3aa5b880a04..eaf5952a4da7d 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.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 ac089634b840f..de30c94bb77f7 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 @@ void scalarMatchingAndFallback() throws Exception { 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 @@ protected RouteBuilder createRouteBuilder() { 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 5b1271eeb56f5..078175d64d392 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.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 @@ void rejectsMissingUri() { 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 360a82b599251..1d5aefcd29a16 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 String writeSwitchDefinition(SwitchDefinition def) { 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 @@ protected void doWriteSwitchCaseDefinition(StringBuilder sb, SwitchCaseDefinitio } 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 f163c5e50026c..bd41f75324992 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 @@ protected void writeSwitch(StringBuilder sb, SwitchDefinition definition) { 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 f7abd5e1d43c2..c75129c610771 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 @@ void switchExportPreservesDisabledAndCaseMetadata() { 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 @@ void switchExportPreservesDisabledAndCaseMetadata() { 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 da1c216ada3ef..eaeaf5aea17c2 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 @@ - +
department
+
- +
decision
departmenturgentscore @@ -32,6 +33,7 @@ +
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 ad11eb423ec40..e2cfd746e6c1a 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 TabularData extendedInformation() { 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 e41f21a22c5a7..689556d512315 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 @@ void exposesCaseDestinationsCountsAndCaseMBeans() throws Exception { 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 8675447cb92fe..5cfe23f033d09 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 @@ protected SwitchValueDefinition doParseSwitchValueDefinition() throws IOExceptio }, 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 65530d7a495b3..5d77377031ddb 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 void writeSwitchCaseDefinition(SwitchCaseDefinition def) throws IOExcepti 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 @@ protected void doWriteSwitchCaseDefinition(String name, SwitchCaseDefinition def 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 02633f4f7c067..3b2a3eac565f8 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 JsonObject writeSwitchCaseDefinition(SwitchCaseDefinition def) { 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 @@ protected JsonObject doWriteSwitchCaseDefinition(SwitchCaseDefinition def) { 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 d330b5fbc7b79..0d9fd35b20733 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 - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise: direct:review\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 - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise:\n uri: direct:review\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 language:\n language: semantic\n expression: refs:department,urgent\n keys: [department, urgent]\n case:\n - values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise: direct:review\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 language:\n language: semantic\n expression: refs:department,urgent\n keys: [department, urgent]\n case:\n - values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise:\n uri: direct:review\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 bdc1bdb340207..5a2f494fe6524 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 @@ void compositeRoundTripPreservesTypesAndExecutes() throws Exception { - +
decision
departmenturgent +
matched 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 ea2ecd39a2e15..2633e7186105c 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.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 @@ protected boolean setProperty(SwitchCaseDefinition target, String propertyKey, @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 @@ protected boolean setProperty(SwitchDefinition target, String propertyKey, 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 @@ protected boolean setProperty(SwitchDefinition target, String propertyKey, } } + @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 { + public SwitchOtherwiseDefinitionDeserializer() { + super(SwitchOtherwiseDefinition.class); + } + + @Override + protected SwitchOtherwiseDefinition newInstance() { + return new SwitchOtherwiseDefinition(); + } + + @Override + protected void setEndpointUri(CamelContext camelContext, Node node, + SwitchOtherwiseDefinition target, Map 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 097b74b24f354..853a854e8332f 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 ConstructNode resolve(String id) { 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 0000000000000..641d883aeb4e5 --- /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 d23a4def3fe56..470ac159bf014 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 50e78118351f5..d505bf5061b65 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 e677a187f48d4..d0ae29c4ab007 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 5892672450583..cced5921f312c 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 @@ void compositeValuesAndEndpointParametersRoundTrip() throws Exception { 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 @@ void compositeValuesAndEndpointParametersRoundTrip() throws Exception { 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 @@ void rejectsNonScalarOrMultipleBindingsPerEntry() { } } + @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 5300b0724b866..0ad730d9f2cad 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 @@ void selectorNamespacesAndLiteralBranchesSurviveYamlDump() throws Exception { 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"); } } From e590a5ada46a8e48eb48f6f8ea268f97002e5e11 Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Wed, 30 Sep 2026 10:02:55 +0200 Subject: [PATCH 5/7] CAMEL-24988: Limit Switch to scalar routing and address review feedback Remove composite keys, typed values, and their DSL and semantic additions. Preserve the structured otherwise endpoint and existing fallback rules. Reject structured selector results through normal Camel error handling, honor management URI masking, and prepare fluent selectors explicitly during reification and export instead of mutating them in the getter. Preserve literal strings when exporting YAML, including leading zeros, and regenerate the model metadata, schemas, writers, and documentation. Co-authored-by: Codex Signed-off-by: Luigi De Masi --- .../camel/catalog/docs/semantic-language.adoc | 1 - .../apache/camel/catalog/docs/switch-eip.adoc | 282 +----------------- .../apache/camel/catalog/models.properties | 1 - .../org/apache/camel/catalog/models/case.json | 5 +- .../apache/camel/catalog/models/switch.json | 9 +- .../camel/catalog/models/switchValue.json | 19 -- .../camel/catalog/schemas/camel-spring.xsd | 68 +---- .../camel/catalog/schemas/camel-xml-io.xsd | 68 +---- .../catalog/schemas/camelYamlDsl-model.json | 38 +-- .../src/main/docs/semantic-language.adoc | 1 - .../camel/semantic/SemanticEipTest.java | 14 - .../apache/camel/spring/SwitchJaxbTest.java | 10 +- .../docs/modules/eips/pages/switch-eip.adoc | 282 +----------------- .../META-INF/org/apache/camel/model/case.json | 5 +- .../org/apache/camel/model/switch.json | 9 +- .../org/apache/camel/model/switchValue.json | 19 -- .../org/apache/camel/model.properties | 1 - .../org/apache/camel/model/jaxb.index | 1 - .../camel/model/SwitchCaseDefinition.java | 26 +- .../apache/camel/model/SwitchDefinition.java | 47 +-- .../camel/model/SwitchValueDefinition.java | 115 ------- .../camel/processor/SwitchProcessor.java | 61 +--- .../apache/camel/reifier/SwitchReifier.java | 36 +-- .../apache/camel/processor/SwitchTest.java | 65 ++-- .../camel/processor/SwitchValidationTest.java | 52 +--- .../camel/java/out/JavaDslModelWriter.java | 14 - .../java/out/JavaDslModelWriterSupport.java | 28 +- .../java/out/JavaDslModelWriterTest.java | 2 +- .../src/test/resources/switchLiteral.xml | 10 - .../camel/management/mbean/ManagedSwitch.java | 14 +- .../camel/management/ManagedSwitchTest.java | 39 ++- .../org/apache/camel/xml/in/ModelParser.java | 14 +- .../org/apache/camel/xml/out/ModelWriter.java | 12 - .../apache/camel/xml/LwModelToXMLDumper.java | 1 + .../org/apache/camel/xml/jaxb/JaxbHelper.java | 1 + .../camel/yaml/out/YamlModelWriter.java | 15 +- .../camel/yaml/LwModelToYAMLDumper.java | 1 + .../apache/camel/yaml/io/YamlPrinterTest.java | 1 + .../jbang/core/commands/ai/eip-samples.json | 10 +- .../camel/dsl/xml/io/XmlSwitchTest.java | 28 +- .../camel-yaml-dsl-deserializers/pom.xml | 1 - .../deserializers/ModelDeserializers.java | 20 +- .../yaml/deserializers/CustomResolver.java | 2 - .../SwitchValueDefinitionDeserializer.java | 63 ---- .../dsl/yaml/GenerateYamlSchemaMojo.java | 10 - .../dsl/yaml/validator/SwitchSchemaTest.java | 4 + .../schema/camelYamlDsl-canonical.json | 34 +-- .../resources/schema/camelYamlDsl-model.json | 38 +-- .../resources/schema/camelYamlDsl.json | 34 +-- .../org/apache/camel/dsl/yaml/SwitchTest.java | 72 ++--- .../camel/dsl/yaml/SwitchYamlDumpTest.java | 18 +- .../resources/velocity/model-yaml-writer.vm | 6 +- 52 files changed, 257 insertions(+), 1470 deletions(-) delete mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json delete mode 100644 core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json delete mode 100644 core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java delete mode 100644 dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc index a58c8402ca306..f8ec5e5b7e3d0 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc @@ -223,7 +223,6 @@ Use `language("semantic", "ref:name")` wherever an expression or boolean predica [cols="1,3"] |=== |EIP / integration point |Usage -|Switch |Select a fixed endpoint directly from a category or a named batch using a literal lookup table. |Choice |Store a category with Set Property, then compare that property in ordinary when predicates. |Filter |Use a boolean question as the filter predicate. |Validate |Use a boolean question; false follows normal validation failure handling. 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 1f20dde1adbb2..79b1b441406f5 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 @@ -1,7 +1,7 @@ = Switch EIP :doctitle: Switch :shortname: switch -:description: Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values +:description: Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values :since: 4.23 :supportlevel: Stable :tabs-sync-option: @@ -30,6 +30,9 @@ and matched case-insensitively using the English locale. An empty string is a va literal. An asterisk is literal, not a wildcard. Duplicate normalized values are rejected at startup. +Map, collection and array results are rejected with an `IllegalArgumentException`. +They follow normal Camel error handling and do not select `otherwise`. + === Route from a header This route reads the `department` header. The body is passed unchanged to the @@ -107,259 +110,6 @@ never select the fallback. Streams are reset after selector evaluation when stre caching is enabled. Loops and retries that enter Switch again evaluate the selector again; results are not cached across entries. -== Composite selectors - -Configure `keys` when the selector returns a map. Each case supplies every declared -key exactly once. All values must match. Key names are case-sensitive, while string -values are case-insensitive. Entry order is irrelevant and additional result fields -are ignored. Missing keys, null field values and unsupported value types are errors. -A null result map still uses the fallback. - -Composite literals support strings, booleans and finite decimal numbers. These types -are distinct: string `"true"` does not match boolean `true`, and string `"2"` does not -match number `2`. Numbers compare by decimal value, so `2` and `2.0` match. Duplicate -combinations, including differences only in letter case or numeric scale, fail at -startup. Scalar `value` cases and composite `values` cases cannot be mixed. - -=== Route from a map in a header - -The `decision` header now contains a map, for example -`Map.of("department", "billing", "urgent", true)`. The table declares the -combinations directly, without a helper expression that concatenates values into -a synthetic category. Any expression returning the same map can replace the -header selector. - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:tickets") - .doSwitch(header("decision")) - .keys("department", "urgent") - .doCase().value("department", "billing").value("urgent", true) - .id("urgentBilling").to("direct:urgent-billing") - .doCase().value("department", "billing").value("urgent", false) - .to("direct:billing") - .doCase().value("department", "technical").value("urgent", true) - .to("direct:incident") - .otherwise("direct:review") - .end(); ----- -XML:: -+ -[source,xml] ----- - - - -
decision
- department - urgent - - - - - - - - - - - - - -
-
----- -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: direct:tickets - steps: - - switch: - selector: - header: - expression: decision - keys: [department, urgent] - case: - - id: urgentBilling - values: [department: billing, urgent: true] - uri: direct:urgent-billing - - values: [department: billing, urgent: false] - uri: direct:billing - - values: [department: technical, urgent: true] - uri: direct:incident - otherwise: - uri: direct:review ----- -==== - -YAML `values` is a sequence of single-entry mappings. Quote strings that would -otherwise be read as booleans or numbers. XML literals default to `type="string"`; -use `type="boolean"` or `type="number"` explicitly. Keys select fields in the returned -map; they do not implicitly read separate message headers. - -For example, send this input with `java.util.Map`: - -[source,java] ----- -template.sendBodyAndHeader("direct:tickets", "Please check this invoice", - "decision", Map.of("department", "billing", "urgent", true)); ----- - -[cols="1,1,2",options="header"] -|=== -|`department` |`urgent` |Destination -|`billing` |boolean `true` |`direct:urgent-billing` -|`billing` |boolean `false` |`direct:billing` -|`technical` |boolean `true` |`direct:incident` -|`technical` |boolean `false` |`direct:review` -|`billing` |string `"true"` |`direct:review` -|=== - -A map missing `urgent` fails the exchange instead of selecting `direct:review`. -The table does not require every possible combination to have a case. - -Cases are compiled into a lookup table. Constructing a composite key involves the -selected fields, but lookup does not scan the declared cases. - -=== Route from a semantic batch - -The same table can consume two named semantic questions directly. Add -`camel-semantic` and configure an adapter such as -xref:ROOT:typesafe-ai-component.adoc[TypeSafe AI], as described in the -xref:languages:semantic-language.adoc[Semantic language]. Provider credentials, -timeouts and inference configuration belong to the adapter. - -The `department` question produces a category string and `urgent` produces a -boolean. The selector evaluates `refs:department,urgent` once per Switch entry -and returns both decisions in one map. An adapter can evaluate them in one provider -request; adapters without batch support evaluate the questions sequentially. -Switch does not invoke the selector again for each case. - -The YAML example includes the question declarations. For the Java and XML routes, -register the same definitions before loading the routes: - -[source,java] ----- -import java.util.List; -import java.util.Map; - -import org.apache.camel.semantic.SemanticQuestion; -import org.apache.camel.semantic.SemanticQuestions; - -SemanticQuestions.get(context).replace("ticket-routing", Map.of( - "department", new SemanticQuestion( - SemanticQuestion.Type.CHOICE, "Which department should handle this message?", null, - Map.of("billing", "Invoices, payments and refunds", - "technical", "Bugs, outages and technical problems"), - List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL), - "urgent", new SemanticQuestion( - SemanticQuestion.Type.BOOLEAN, "Does this request need immediate attention?", null, - Map.of(), List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL))); ----- - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:tickets") - .doSwitch().language("semantic", "refs:department,urgent") - .keys("department", "urgent") - .doCase().value("department", "billing").value("urgent", true) - .to("direct:urgent-billing") - .doCase().value("department", "billing").value("urgent", false) - .to("direct:billing") - .doCase().value("department", "technical").value("urgent", true) - .to("direct:incident") - .otherwise("direct:review") - .end(); ----- -XML:: -+ -[source,xml] ----- - - - - refs:department,urgent - department - urgent - - - - - - - - - - - - - - - ----- -YAML:: -+ -[source,yaml] ----- -- semantic: - question: - department: - type: choice - instructions: Which department should handle this message? - criteria: - billing: Invoices, payments and refunds - technical: Bugs, outages and technical problems - urgent: - type: boolean - instructions: Does this request need immediate attention? -- route: - from: - uri: direct:tickets - steps: - - switch: - selector: - language: - language: semantic - expression: refs:department,urgent - keys: [department, urgent] - case: - - values: [department: billing, urgent: true] - uri: direct:urgent-billing - - values: [department: billing, urgent: false] - uri: direct:billing - - values: [department: technical, urgent: true] - uri: direct:incident - otherwise: - uri: direct:review ----- -==== - -Submit a message body using `template.sendBody("direct:tickets", "Our invoice is due today; please help immediately")`. -If the adapter returns `{department: "billing", urgent: true}`, the message goes -to `direct:urgent-billing`. The destination depends on the returned decisions, -not on a guaranteed classification of that example sentence. The other combinations -use the same routing table as the map-header example above. Provider failures follow -normal error handling and do not select the fallback. - -For a single Choice question, use `ref:department` as the selector and the scalar -`value` cases from the first example, without `keys`. No intermediate property or -header is needed to route the answer. - -Batching, thresholds and uncertainty policies belong to the semantic language and -its provider. Switch has no dependency on semantic evaluation. Store a result -explicitly in a variable, header or exchange property when later EIPs should reuse it. - === Connect the example destinations For a local demonstration, the destination routes can simply log the message. @@ -374,8 +124,6 @@ Java:: ---- from("direct:billing").log("Billing: ${body}"); from("direct:technical").log("Technical: ${body}"); -from("direct:urgent-billing").log("Urgent billing: ${body}"); -from("direct:incident").log("Incident: ${body}"); from("direct:review").log("Review: ${body}"); ---- XML:: @@ -385,8 +133,6 @@ XML:: - - ---- @@ -404,16 +150,6 @@ YAML:: uri: direct:technical steps: - log: "Technical: ${body}" -- route: - from: - uri: direct:urgent-billing - steps: - - log: "Urgent billing: ${body}" -- route: - from: - uri: direct:incident - steps: - - log: "Incident: ${body}" - route: from: uri: direct:review @@ -425,7 +161,8 @@ YAML:: == Destinations and management Case and fallback URIs support property placeholders resolved at startup. Simple -expressions in URIs are rejected. In YAML, a case also accepts `uri` plus `parameters`: +expressions in URIs are rejected. In YAML, both cases and `otherwise` accept `uri` +plus `parameters`: [source,yaml] ---- @@ -434,9 +171,14 @@ case: uri: kafka parameters: topic: orders +otherwise: + uri: direct + parameters: + name: review ---- Each case has an identity for tracing, debugging and management. The Switch MBean's `extendedInformation` table reports case IDs, literal values, destination URIs and -selection counts. These counts indicate case selection, not successful delivery. +selection counts. URIs are masked when management masking is enabled (the default). +These counts indicate case selection, not successful delivery. `UnmatchedCount` also records unmatched results when no fallback is configured. 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 0e50bde32a499..387da664462d4 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 @@ -199,7 +199,6 @@ swiftMt swiftMx switch switchOtherwise -switchValue syslog tarFile templateParameter diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json index f7de1b0888ca5..39ad4ab00f2c9 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/case.json @@ -15,8 +15,7 @@ "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, - "value": { "index": 3, "kind": "attribute", "displayName": "Value", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." }, - "values": { "index": 4, "kind": "element", "displayName": "Values", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Named, typed literal values for a composite selector. Every selected key must occur exactly once." }, - "uri": { "index": 5, "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." } + "value": { "index": 3, "kind": "attribute", "displayName": "Value", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The case-insensitive literal value for a scalar selector." }, + "uri": { "index": 4, "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/models/switch.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switch.json index d4158f770eff2..1da2d69acf5ba 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 @@ -3,7 +3,7 @@ "kind": "model", "name": "switch", "title": "Switch", - "description": "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description": "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "deprecated": false, "firstVersion": "4.23.0", "label": "eip,routing", @@ -17,9 +17,8 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured." }, - "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "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", "deprecated": false, "autowired": false, "secret": false, "description": "Literal cases. Duplicate combinations are rejected at startup." }, - "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." } + "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar value to match against the literal cases." }, + "case": { "index": 5, "kind": "element", "displayName": "Case", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Literal cases. Duplicate values are rejected at startup, ignoring case." }, + "otherwise": { "index": 6, "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/switchValue.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json deleted file mode 100644 index aa07fe73e9bd7..0000000000000 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/switchValue.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "model": { - "kind": "model", - "name": "switchValue", - "title": "Switch Value", - "description": "A named literal in a composite switch case", - "deprecated": false, - "label": "configuration", - "javaType": "org.apache.camel.model.SwitchValueDefinition", - "abstract": false, - "input": false, - "output": false - }, - "properties": { - "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The exact name of the selected field." }, - "value": { "index": 1, "kind": "attribute", "displayName": "Value", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The literal value, never an expression." }, - "type": { "index": 2, "kind": "attribute", "displayName": "Type", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "string", "boolean", "number" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "string", "description": "The literal type." } - } -} 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 54d85d44a379e..8a2094f0800be 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 @@ -2017,7 +2017,7 @@ Encode and decode SWIFT MX messages @@ -2027,15 +2027,6 @@ Evaluates a selector once and dispatches to a fixed endpoint by literal scalar o - - - - - - - @@ -13923,22 +13914,12 @@ enables value-based watermarking instead of index-based. The expression is evalu - - - - - - - - - - + + @@ -13955,36 +13936,6 @@ The fixed destination URI. Supports property placeholders, but not Simple expres - - - - - - - - - - - - - - - - - - - - - - - - @@ -13993,16 +13944,7 @@ The literal type. Default value: string - - - - - - - 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 4902984793f58..d925ea5aeaeee 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 @@ -1846,7 +1846,7 @@ Encode and decode SWIFT MX messages @@ -1856,15 +1856,6 @@ Evaluates a selector once and dispatches to a fixed endpoint by literal scalar o - - - - - - - @@ -13066,22 +13057,12 @@ enables value-based watermarking instead of index-based. The expression is evalu - - - - - - - - - - + + @@ -13098,36 +13079,6 @@ The fixed destination URI. Supports property placeholders, but not Simple expres - - - - - - - - - - - - - - - - - - - - - - - - @@ -13136,16 +13087,7 @@ The literal type. Default value: string - - - - - - - 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 d505bf5061b65..d14e31756b498 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 @@ -510,7 +510,7 @@ "type" : "object", "ref" : "switch", "title" : "Switch", - "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "label" : "eip,routing" }, { "name" : "threads", @@ -8360,19 +8360,14 @@ }, { "name" : "value", "type" : "string", - "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.", - "title" : "Value" - }, { - "name" : "values", - "type" : "array", - "ref" : "switchValue", - "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", - "title" : "Values" + "description" : "The case-insensitive literal value for a scalar selector.", + "title" : "Value", + "required" : true } ] }, "switch" : { "title" : "Switch", - "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "label" : "eip,routing", "input" : true, "isListItem" : true, @@ -8418,31 +8413,22 @@ "name" : "selector", "type" : "object", "ref" : "expressionSubElement", - "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "description" : "Expression evaluated once per entry. Returns a scalar value to match against the literal cases.", "title" : "Selector", "required" : true, "displayName" : "Selector", "kind" : "expression", "index" : 4, "group" : "common" - }, { - "name" : "keys", - "type" : "array", - "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", - "title" : "Keys", - "displayName" : "Keys", - "kind" : "element", - "index" : 5, - "group" : "common" }, { "name" : "case", "type" : "array", "ref" : "switchCase", - "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "description" : "Literal cases. Duplicate values are rejected at startup, ignoring case.", "title" : "Case", "displayName" : "Case", "kind" : "element", - "index" : 6, + "index" : 5, "group" : "common" }, { "name" : "otherwise", @@ -8452,7 +8438,7 @@ "title" : "Otherwise", "displayName" : "Otherwise", "kind" : "element", - "index" : 7, + "index" : 6, "group" : "common" } ] }, @@ -8475,12 +8461,6 @@ "type" : "object" } ] }, - "switchValue" : { - "title" : "Switch Value", - "description" : "A named literal in a composite switch case", - "label" : "configuration", - "children" : [ ] - }, "templatedRoute" : { "title" : "Templated Route", "description" : "Creates a route instance from a route template, binding specific parameter values to the template's parameters", diff --git a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc index a58c8402ca306..f8ec5e5b7e3d0 100644 --- a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc +++ b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc @@ -223,7 +223,6 @@ Use `language("semantic", "ref:name")` wherever an expression or boolean predica [cols="1,3"] |=== |EIP / integration point |Usage -|Switch |Select a fixed endpoint directly from a category or a named batch using a literal lookup table. |Choice |Store a category with Set Property, then compare that property in ordinary when predicates. |Filter |Use a boolean question as the filter predicate. |Validate |Use a boolean question; false follows normal validation failure handling. diff --git a/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java b/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java index 133bef5927cfe..868f5e68d3b6f 100644 --- a/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java +++ b/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticEipTest.java @@ -74,10 +74,6 @@ public SemanticResult evaluate(SemanticQuestion question, Object state) { "unfinished", question("unfinished", SemanticQuestion.Type.BOOLEAN), "urgency", question("urgency", SemanticQuestion.Type.SCORE))); - from("direct:switch").doSwitch().language("semantic", "refs:department,actionable") - .keys("department", "actionable") - .doCase().value("department", "billing").value("actionable", true).to("mock:switchBilling") - .otherwise("mock:switchReview"); from("direct:batch").setProperty("decision").language("semantic", "refs:actionable,department,urgency") .setHeader("urgency").simple("${exchangeProperty.decision[urgency]}") .choice() @@ -162,16 +158,6 @@ private static SemanticQuestion question(String name, SemanticQuestion.Type type 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL); } - @Test - void batchDecisionsSelectOneSwitchDestination() throws Exception { - getMockEndpoint("mock:switchBilling").expectedBodiesReceived("please fix this invoice"); - getMockEndpoint("mock:switchReview").expectedMessageCount(0); - template.sendBody("direct:switch", "please fix this invoice"); - MockEndpoint.assertIsSatisfied(context); - assertThat(calls.get("department").get()).isEqualTo(1); - assertThat(calls.get("actionable").get()).isEqualTo(1); - } - @Test void batchDecisionsAreReusedByOrdinaryEips() throws Exception { getMockEndpoint("mock:batchBilling").expectedBodiesReceived("please fix this urgent invoice"); 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 b52ff8f335f66..62fd99470391b 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 @@ -33,11 +33,11 @@ class SwitchJaxbTest { @Test - void compositeLiteralsSurviveJaxbRoundTripAndExecute() throws Exception { + void scalarLiteralsSurviveJaxbRoundTripAndExecute() throws Exception { try (var context = new DefaultCamelContext()) { RoutesDefinition routes = new RoutesDefinition(); - routes.from("direct:start").doSwitch().header("decision").keys("department", "urgent", "score") - .doCase().value("department", "billing").value("urgent", true).value("score", 2) + routes.from("direct:start").doSwitch().header("decision") + .doCase("billing") .id("billingCase").to("mock:billing") .otherwise("mock:other"); String xml = new JaxbModelToXMLDumper().dumpModelAsXml(context, routes); @@ -50,9 +50,9 @@ void compositeLiteralsSurviveJaxbRoundTripAndExecute() throws Exception { context.getEndpoint("mock:other", MockEndpoint.class).expectedBodiesReceived("other"); try (var template = context.createProducerTemplate()) { template.sendBodyAndHeader("direct:start", "matched", "decision", - Map.of("department", "BILLING", "urgent", true, "score", 2.0)); + "BILLING"); template.sendBodyAndHeader("direct:start", "other", "decision", - Map.of("department", "billing", "urgent", "true", "score", 2)); + "other"); } MockEndpoint.assertIsSatisfied(context); } 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 1f20dde1adbb2..79b1b441406f5 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 @@ -1,7 +1,7 @@ = Switch EIP :doctitle: Switch :shortname: switch -:description: Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values +:description: Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values :since: 4.23 :supportlevel: Stable :tabs-sync-option: @@ -30,6 +30,9 @@ and matched case-insensitively using the English locale. An empty string is a va literal. An asterisk is literal, not a wildcard. Duplicate normalized values are rejected at startup. +Map, collection and array results are rejected with an `IllegalArgumentException`. +They follow normal Camel error handling and do not select `otherwise`. + === Route from a header This route reads the `department` header. The body is passed unchanged to the @@ -107,259 +110,6 @@ never select the fallback. Streams are reset after selector evaluation when stre caching is enabled. Loops and retries that enter Switch again evaluate the selector again; results are not cached across entries. -== Composite selectors - -Configure `keys` when the selector returns a map. Each case supplies every declared -key exactly once. All values must match. Key names are case-sensitive, while string -values are case-insensitive. Entry order is irrelevant and additional result fields -are ignored. Missing keys, null field values and unsupported value types are errors. -A null result map still uses the fallback. - -Composite literals support strings, booleans and finite decimal numbers. These types -are distinct: string `"true"` does not match boolean `true`, and string `"2"` does not -match number `2`. Numbers compare by decimal value, so `2` and `2.0` match. Duplicate -combinations, including differences only in letter case or numeric scale, fail at -startup. Scalar `value` cases and composite `values` cases cannot be mixed. - -=== Route from a map in a header - -The `decision` header now contains a map, for example -`Map.of("department", "billing", "urgent", true)`. The table declares the -combinations directly, without a helper expression that concatenates values into -a synthetic category. Any expression returning the same map can replace the -header selector. - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:tickets") - .doSwitch(header("decision")) - .keys("department", "urgent") - .doCase().value("department", "billing").value("urgent", true) - .id("urgentBilling").to("direct:urgent-billing") - .doCase().value("department", "billing").value("urgent", false) - .to("direct:billing") - .doCase().value("department", "technical").value("urgent", true) - .to("direct:incident") - .otherwise("direct:review") - .end(); ----- -XML:: -+ -[source,xml] ----- - - - -
decision
- department - urgent - - - - - - - - - - - - - -
-
----- -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: direct:tickets - steps: - - switch: - selector: - header: - expression: decision - keys: [department, urgent] - case: - - id: urgentBilling - values: [department: billing, urgent: true] - uri: direct:urgent-billing - - values: [department: billing, urgent: false] - uri: direct:billing - - values: [department: technical, urgent: true] - uri: direct:incident - otherwise: - uri: direct:review ----- -==== - -YAML `values` is a sequence of single-entry mappings. Quote strings that would -otherwise be read as booleans or numbers. XML literals default to `type="string"`; -use `type="boolean"` or `type="number"` explicitly. Keys select fields in the returned -map; they do not implicitly read separate message headers. - -For example, send this input with `java.util.Map`: - -[source,java] ----- -template.sendBodyAndHeader("direct:tickets", "Please check this invoice", - "decision", Map.of("department", "billing", "urgent", true)); ----- - -[cols="1,1,2",options="header"] -|=== -|`department` |`urgent` |Destination -|`billing` |boolean `true` |`direct:urgent-billing` -|`billing` |boolean `false` |`direct:billing` -|`technical` |boolean `true` |`direct:incident` -|`technical` |boolean `false` |`direct:review` -|`billing` |string `"true"` |`direct:review` -|=== - -A map missing `urgent` fails the exchange instead of selecting `direct:review`. -The table does not require every possible combination to have a case. - -Cases are compiled into a lookup table. Constructing a composite key involves the -selected fields, but lookup does not scan the declared cases. - -=== Route from a semantic batch - -The same table can consume two named semantic questions directly. Add -`camel-semantic` and configure an adapter such as -xref:ROOT:typesafe-ai-component.adoc[TypeSafe AI], as described in the -xref:languages:semantic-language.adoc[Semantic language]. Provider credentials, -timeouts and inference configuration belong to the adapter. - -The `department` question produces a category string and `urgent` produces a -boolean. The selector evaluates `refs:department,urgent` once per Switch entry -and returns both decisions in one map. An adapter can evaluate them in one provider -request; adapters without batch support evaluate the questions sequentially. -Switch does not invoke the selector again for each case. - -The YAML example includes the question declarations. For the Java and XML routes, -register the same definitions before loading the routes: - -[source,java] ----- -import java.util.List; -import java.util.Map; - -import org.apache.camel.semantic.SemanticQuestion; -import org.apache.camel.semantic.SemanticQuestions; - -SemanticQuestions.get(context).replace("ticket-routing", Map.of( - "department", new SemanticQuestion( - SemanticQuestion.Type.CHOICE, "Which department should handle this message?", null, - Map.of("billing", "Invoices, payments and refunds", - "technical", "Bugs, outages and technical problems"), - List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL), - "urgent", new SemanticQuestion( - SemanticQuestion.Type.BOOLEAN, "Does this request need immediate attention?", null, - Map.of(), List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL))); ----- - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:tickets") - .doSwitch().language("semantic", "refs:department,urgent") - .keys("department", "urgent") - .doCase().value("department", "billing").value("urgent", true) - .to("direct:urgent-billing") - .doCase().value("department", "billing").value("urgent", false) - .to("direct:billing") - .doCase().value("department", "technical").value("urgent", true) - .to("direct:incident") - .otherwise("direct:review") - .end(); ----- -XML:: -+ -[source,xml] ----- - - - - refs:department,urgent - department - urgent - - - - - - - - - - - - - - - ----- -YAML:: -+ -[source,yaml] ----- -- semantic: - question: - department: - type: choice - instructions: Which department should handle this message? - criteria: - billing: Invoices, payments and refunds - technical: Bugs, outages and technical problems - urgent: - type: boolean - instructions: Does this request need immediate attention? -- route: - from: - uri: direct:tickets - steps: - - switch: - selector: - language: - language: semantic - expression: refs:department,urgent - keys: [department, urgent] - case: - - values: [department: billing, urgent: true] - uri: direct:urgent-billing - - values: [department: billing, urgent: false] - uri: direct:billing - - values: [department: technical, urgent: true] - uri: direct:incident - otherwise: - uri: direct:review ----- -==== - -Submit a message body using `template.sendBody("direct:tickets", "Our invoice is due today; please help immediately")`. -If the adapter returns `{department: "billing", urgent: true}`, the message goes -to `direct:urgent-billing`. The destination depends on the returned decisions, -not on a guaranteed classification of that example sentence. The other combinations -use the same routing table as the map-header example above. Provider failures follow -normal error handling and do not select the fallback. - -For a single Choice question, use `ref:department` as the selector and the scalar -`value` cases from the first example, without `keys`. No intermediate property or -header is needed to route the answer. - -Batching, thresholds and uncertainty policies belong to the semantic language and -its provider. Switch has no dependency on semantic evaluation. Store a result -explicitly in a variable, header or exchange property when later EIPs should reuse it. - === Connect the example destinations For a local demonstration, the destination routes can simply log the message. @@ -374,8 +124,6 @@ Java:: ---- from("direct:billing").log("Billing: ${body}"); from("direct:technical").log("Technical: ${body}"); -from("direct:urgent-billing").log("Urgent billing: ${body}"); -from("direct:incident").log("Incident: ${body}"); from("direct:review").log("Review: ${body}"); ---- XML:: @@ -385,8 +133,6 @@ XML:: - - ---- @@ -404,16 +150,6 @@ YAML:: uri: direct:technical steps: - log: "Technical: ${body}" -- route: - from: - uri: direct:urgent-billing - steps: - - log: "Urgent billing: ${body}" -- route: - from: - uri: direct:incident - steps: - - log: "Incident: ${body}" - route: from: uri: direct:review @@ -425,7 +161,8 @@ YAML:: == Destinations and management Case and fallback URIs support property placeholders resolved at startup. Simple -expressions in URIs are rejected. In YAML, a case also accepts `uri` plus `parameters`: +expressions in URIs are rejected. In YAML, both cases and `otherwise` accept `uri` +plus `parameters`: [source,yaml] ---- @@ -434,9 +171,14 @@ case: uri: kafka parameters: topic: orders +otherwise: + uri: direct + parameters: + name: review ---- Each case has an identity for tracing, debugging and management. The Switch MBean's `extendedInformation` table reports case IDs, literal values, destination URIs and -selection counts. These counts indicate case selection, not successful delivery. +selection counts. URIs are masked when management masking is enabled (the default). +These counts indicate case selection, not successful delivery. `UnmatchedCount` also records unmatched results when no fallback is configured. diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json index f7de1b0888ca5..39ad4ab00f2c9 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/case.json @@ -15,8 +15,7 @@ "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, - "value": { "index": 3, "kind": "attribute", "displayName": "Value", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." }, - "values": { "index": 4, "kind": "element", "displayName": "Values", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Named, typed literal values for a composite selector. Every selected key must occur exactly once." }, - "uri": { "index": 5, "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." } + "value": { "index": 3, "kind": "attribute", "displayName": "Value", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The case-insensitive literal value for a scalar selector." }, + "uri": { "index": 4, "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/org/apache/camel/model/switch.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switch.json index d4158f770eff2..1da2d69acf5ba 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 @@ -3,7 +3,7 @@ "kind": "model", "name": "switch", "title": "Switch", - "description": "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description": "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "deprecated": false, "firstVersion": "4.23.0", "label": "eip,routing", @@ -17,9 +17,8 @@ "note": { "index": 1, "kind": "attribute", "displayName": "Note", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The note for this node" }, "description": { "index": 2, "kind": "attribute", "displayName": "Description", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The description for this node" }, "disabled": { "index": 3, "kind": "attribute", "displayName": "Disabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." }, - "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured." }, - "keys": { "index": 5, "kind": "element", "displayName": "Keys", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "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", "deprecated": false, "autowired": false, "secret": false, "description": "Literal cases. Duplicate combinations are rejected at startup." }, - "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." } + "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" ], "deprecated": false, "autowired": false, "secret": false, "description": "Expression evaluated once per entry. Returns a scalar value to match against the literal cases." }, + "case": { "index": 5, "kind": "element", "displayName": "Case", "group": "common", "required": false, "type": "array", "javaType": "java.util.List", "deprecated": false, "autowired": false, "secret": false, "description": "Literal cases. Duplicate values are rejected at startup, ignoring case." }, + "otherwise": { "index": 6, "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/switchValue.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json deleted file mode 100644 index aa07fe73e9bd7..0000000000000 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/switchValue.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "model": { - "kind": "model", - "name": "switchValue", - "title": "Switch Value", - "description": "A named literal in a composite switch case", - "deprecated": false, - "label": "configuration", - "javaType": "org.apache.camel.model.SwitchValueDefinition", - "abstract": false, - "input": false, - "output": false - }, - "properties": { - "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The exact name of the selected field." }, - "value": { "index": 1, "kind": "attribute", "displayName": "Value", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The literal value, never an expression." }, - "type": { "index": 2, "kind": "attribute", "displayName": "Type", "group": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "string", "boolean", "number" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "string", "description": "The literal type." } - } -} 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 b68bb04e5d7f6..b03878ae40e5f 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 @@ -200,7 +200,6 @@ swiftMt swiftMx switch switchOtherwise -switchValue syslog tarFile templateParameter 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 88d41e0cfb88b..24edfb49a2ddb 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 @@ -100,7 +100,6 @@ StopDefinition SwitchCaseDefinition SwitchDefinition SwitchOtherwiseDefinition -SwitchValueDefinition TemplatedRouteDefinition TemplatedRouteParameterDefinition TemplatedRoutesDefinition diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java index 60a4232d976da..f875ec0514010 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java @@ -16,30 +16,23 @@ */ package org.apache.camel.model; -import java.util.ArrayList; -import java.util.List; - 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; import org.apache.camel.spi.Metadata; -/** One literal combination and one fixed destination. */ +/** One literal value and one fixed destination. */ @Metadata(label = "configuration", description = "A literal switch case with one fixed endpoint destination") @XmlRootElement(name = "case") @XmlAccessorType(XmlAccessType.FIELD) public class SwitchCaseDefinition extends OptionalIdentifiedDefinition implements EndpointRequiredDefinition { - @XmlAttribute - @Metadata(description = "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.") + @XmlAttribute(required = true) + @Metadata(description = "The case-insensitive literal value for a scalar selector.") private String value; - @XmlElement(name = "values") - @Metadata(description = "Named, typed literal values for a composite selector. Every selected key must occur exactly once.") - private List values = new ArrayList<>(); @XmlAttribute(required = true) @Metadata(description = "The fixed destination URI. Supports property placeholders, but not Simple expressions.") private String uri; @@ -60,9 +53,6 @@ protected SwitchCaseDefinition(SwitchCaseDefinition source) { super(source); this.value = source.value; this.uri = source.uri; - for (SwitchValueDefinition literal : source.values) { - values.add(literal.copyDefinition()); - } } public SwitchCaseDefinition copyDefinition() { @@ -77,14 +67,6 @@ public void setValue(String value) { this.value = value; } - public List getValues() { - return values; - } - - public void setValues(List values) { - this.values = values; - } - public String getUri() { return uri; } @@ -133,7 +115,7 @@ public String getShortName() { @Override public String getLabel() { - return "case[" + (value != null ? value : values) + "]"; + return "case[" + value + "]"; } @Override 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 badf6610b6569..61b56043cd933 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 @@ -17,9 +17,7 @@ package org.apache.camel.model; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Map; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; @@ -36,20 +34,17 @@ /** Routes a message to one fixed endpoint using a literal lookup table. */ @Metadata(firstVersion = "4.23.0", label = "eip,routing", - description = "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values") + description = "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values") @XmlRootElement(name = "switch") @XmlAccessorType(XmlAccessType.FIELD) -@XmlType(propOrder = { "selector", "keys", "cases", "otherwise" }) +@XmlType(propOrder = { "selector", "cases", "otherwise" }) public class SwitchDefinition extends NoOutputDefinition { @XmlElement(required = true) @Metadata(required = true, - description = "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.") + description = "Expression evaluated once per entry. Returns a scalar value to match against the literal cases.") private ExpressionSubElementDefinition selector; - @XmlElement(name = "keys") - @Metadata(description = "Exact map field names forming a composite key. Additional result fields are ignored.") - private List keys = new ArrayList<>(); @XmlElement(name = "case") - @Metadata(description = "Literal cases. Duplicate combinations are rejected at startup.") + @Metadata(description = "Literal cases. Duplicate values are rejected at startup, ignoring case.") private List cases = new ArrayList<>(); @XmlElement @Metadata(description = "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.") @@ -67,7 +62,6 @@ public SwitchDefinition(Expression selector) { protected SwitchDefinition(SwitchDefinition source) { super(source); this.selector = source.selector != null ? source.selector.copyDefinition() : null; - this.keys = new ArrayList<>(source.keys); this.otherwise = source.otherwise != null ? source.otherwise.copyDefinition() : null; for (SwitchCaseDefinition c : source.cases) { SwitchCaseDefinition copy = c.copyDefinition(); @@ -82,7 +76,6 @@ public SwitchDefinition copyDefinition() { } public ExpressionSubElementDefinition getSelector() { - preCreateProcessor(); return selector; } @@ -99,14 +92,6 @@ public void setSelector(ExpressionSubElementDefinition selector) { this.selector = selector; } - public List getKeys() { - return keys; - } - - public void setKeys(List keys) { - this.keys = keys; - } - public List getCases() { return cases; } @@ -145,25 +130,11 @@ public ToDefinition getOtherwiseDefinition() { return otherwiseDefinition; } - /** Select the named fields participating in composite matching. */ - public SwitchDefinition keys(String... keys) { - setKeys(new ArrayList<>(Arrays.asList(keys))); - return this; - } - /** Add a scalar literal and its destination. */ public SwitchDefinition doCase(String value, String uri) { return doCase(new SwitchCaseDefinition(value, uri)); } - /** Add a composite combination and its destination. */ - public SwitchDefinition doCase(Map values, String uri) { - SwitchCaseDefinition c = new SwitchCaseDefinition(); - values.forEach((name, value) -> c.getValues().add(new SwitchValueDefinition(name, value))); - c.setUri(uri); - return doCase(c); - } - /** Add a case definition. */ public SwitchDefinition doCase(SwitchCaseDefinition value) { value.setParent(this); @@ -176,11 +147,6 @@ public SwitchDefinition doCase(SwitchCaseDefinition value) { return this; } - /** Build a composite case with named literal values. */ - public CaseBuilder doCase() { - return new CaseBuilder(this, new SwitchCaseDefinition()); - } - /** Build a scalar case. */ public CaseBuilder doCase(String value) { return new CaseBuilder(this, new SwitchCaseDefinition(value, null)); @@ -232,11 +198,6 @@ private CaseBuilder(SwitchDefinition parent, SwitchCaseDefinition definition) { this.definition = definition; } - public CaseBuilder value(String name, Object value) { - definition.getValues().add(new SwitchValueDefinition(name, value)); - return this; - } - public CaseBuilder id(String id) { definition.setId(id); return this; diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java deleted file mode 100644 index 339c47e2ae532..0000000000000 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchValueDefinition.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.math.BigDecimal; - -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; - -/** A named, typed literal in a switch case. */ -@Metadata(label = "configuration", description = "A named literal in a composite switch case") -@XmlRootElement(name = "switchValue") -@XmlAccessorType(XmlAccessType.FIELD) -public class SwitchValueDefinition { - @XmlAttribute(required = true) - @Metadata(description = "The exact name of the selected field.") - private String name; - @XmlAttribute(required = true) - @Metadata(description = "The literal value, never an expression.") - private String value; - @XmlAttribute - @Metadata(defaultValue = "string", enums = "string,boolean,number", description = "The literal type.") - private String type; - - public SwitchValueDefinition() { - } - - public SwitchValueDefinition(String name, Object value) { - this.name = name; - if (value instanceof String text) { - this.value = text; - this.type = "string"; - } else if (value instanceof Boolean) { - this.value = value.toString(); - this.type = "boolean"; - } else if (value instanceof Number) { - this.value = value.toString(); - this.type = "number"; - } else { - throw new IllegalArgumentException("Switch values must be strings, booleans or numbers"); - } - } - - public SwitchValueDefinition copyDefinition() { - SwitchValueDefinition copy = new SwitchValueDefinition(); - copy.name = name; - copy.value = value; - copy.type = type; - return copy; - } - - public Object asLiteral() { - if (value == null) { - throw new IllegalArgumentException("Switch value is required for key: " + name); - } - return switch (type == null ? "string" : type) { - case "string" -> value; - case "boolean" -> { - if (!"true".equals(value) && !"false".equals(value)) { - throw new IllegalArgumentException("Switch boolean must be true or false for key: " + name); - } - yield Boolean.valueOf(value); - } - case "number" -> new BigDecimal(value); - default -> throw new IllegalArgumentException("Unsupported switch literal type: " + type); - }; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - @Override - public String toString() { - return name + "=" + value; - } -} diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java index 7c3b63279a114..064d33f0ddb7c 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SwitchProcessor.java @@ -16,7 +16,6 @@ */ package org.apache.camel.processor; -import java.math.BigDecimal; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -49,8 +48,7 @@ public class SwitchProcessor extends BaseProcessorSupport private static final Logger LOG = LoggerFactory.getLogger(SwitchProcessor.class); private final CamelContext context; private final Expression selector; - private final List keys; - private final Map table = new LinkedHashMap<>(); + private final Map table = new LinkedHashMap<>(); private final List processors = new ArrayList<>(); private final AsyncProcessor otherwise; private final AtomicLongArray counts; @@ -58,11 +56,10 @@ public class SwitchProcessor extends BaseProcessorSupport private String routeId; private String stepId; - public SwitchProcessor(CamelContext context, Expression selector, List keys, - Map cases, Processor otherwise) { + public SwitchProcessor(CamelContext context, Expression selector, + Map cases, Processor otherwise) { this.context = context; this.selector = selector; - this.keys = List.copyOf(keys); cases.forEach((key, processor) -> { table.put(key, processors.size()); processors.add(AsyncProcessorConverterHelper.convert(processor)); @@ -71,54 +68,22 @@ public SwitchProcessor(CamelContext context, Expression selector, List k counts = new AtomicLongArray(processors.size() + 1); } - /** Normalize a typed literal, preserving the distinction between strings, booleans and numbers. */ - public static Object normalizeLiteral(Object value) { - if (value instanceof String text) { - return text.toLowerCase(Locale.ENGLISH); - } - if (value instanceof Boolean) { - return value; - } - if (value instanceof Number number) { - try { - return new BigDecimal(number.toString()).stripTrailingZeros(); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Switch numbers must be finite decimal values", e); - } - } - throw new IllegalArgumentException("Switch values must be non-null strings, booleans or numbers"); - } - - /** Build the same immutable composite key for case declarations and selector results. */ - public static List compositeKey(Map values, List keys) { - List key = new ArrayList<>(keys.size()); - for (String name : keys) { - if (!values.containsKey(name)) { - throw new IllegalArgumentException("Missing switch result key: " + name); - } - key.add(normalizeLiteral(values.get(name))); - } - return List.copyOf(key); - } - @Override public boolean process(Exchange exchange, AsyncCallback callback) { - Object key = null; + String key = null; try { Object result = selector.evaluate(exchange, Object.class); if (result != null) { - if (!keys.isEmpty()) { - if (!(result instanceof Map values)) { - throw new IllegalArgumentException("Composite switch selector must return a Map"); - } - key = compositeKey(values, keys); - } else { - if (result instanceof Map || result instanceof Iterable || result.getClass().isArray()) { - throw new IllegalArgumentException("Scalar switch selector must return a scalar value"); - } - String text = context.getTypeConverter().mandatoryConvertTo(String.class, exchange, result); - key = text.toLowerCase(Locale.ENGLISH); + if (result instanceof Map) { + throw new IllegalArgumentException( + "Switch requires a scalar selector result, but received a Map. Composite selector results are not supported."); + } + if (result instanceof Iterable || result.getClass().isArray()) { + throw new IllegalArgumentException( + "Switch requires a scalar selector result, but received a collection or array"); } + String text = context.getTypeConverter().mandatoryConvertTo(String.class, exchange, result); + key = text.toLowerCase(Locale.ENGLISH); } } catch (Exception e) { exchange.setException(e); 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 b96ebd1152c39..d5d1caef8d0f4 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 @@ -29,7 +29,6 @@ import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.SwitchCaseDefinition; import org.apache.camel.model.SwitchDefinition; -import org.apache.camel.model.SwitchValueDefinition; import org.apache.camel.model.ToDefinition; import org.apache.camel.processor.SwitchProcessor; import org.apache.camel.spi.NodeIdFactory; @@ -41,39 +40,18 @@ public SwitchReifier(Route route, ProcessorDefinition definition) { @Override public Processor createProcessor() throws Exception { + definition.preCreateProcessor(); if (definition.getSelector() == null || definition.getSelector().getExpressionType() == null) { throw new IllegalArgumentException("Switch selector requires an expression"); } - List keys = definition.getKeys(); - if (keys.stream().anyMatch(k -> k == null || k.isBlank()) || new HashSet<>(keys).size() != keys.size()) { - throw new IllegalArgumentException("Switch keys must be nonblank and unique"); - } // Validate the complete table before creating endpoint processors. - List caseKeys = new ArrayList<>(); + List caseKeys = new ArrayList<>(); List uris = new ArrayList<>(); for (SwitchCaseDefinition c : definition.getCases()) { - Object key; - if (keys.isEmpty()) { - if (c.getValue() == null || !c.getValues().isEmpty()) { - throw new IllegalArgumentException("Scalar switch cases require value and cannot declare values"); - } - key = c.getValue().toLowerCase(Locale.ENGLISH); - } else { - if (c.getValue() != null) { - throw new IllegalArgumentException("Composite switch cases cannot declare scalar value"); - } - Map values = new LinkedHashMap<>(); - for (SwitchValueDefinition literal : c.getValues()) { - if (literal.getName() == null || values.containsKey(literal.getName())) { - throw new IllegalArgumentException("Duplicate or missing switch value name: " + literal.getName()); - } - values.put(literal.getName(), literal.asLiteral()); - } - if (!values.keySet().equals(new HashSet<>(keys))) { - throw new IllegalArgumentException("Switch case values must contain exactly the declared keys: " + keys); - } - key = SwitchProcessor.compositeKey(values, keys); + if (c.getValue() == null) { + throw new IllegalArgumentException("Switch cases require value"); } + String key = c.getValue().toLowerCase(Locale.ENGLISH); caseKeys.add(key); uris.add(staticUri(c.getUri())); } @@ -83,7 +61,7 @@ public Processor createProcessor() throws Exception { String otherwiseUri = definition.getOtherwise() == null ? null : staticUri(definition.getOtherwise().getUri()); Expression selector = createExpression(definition.getSelector().getExpressionType()); NodeIdFactory ids = camelContext.getCamelContextExtension().getContextPlugin(NodeIdFactory.class); - Map cases = new LinkedHashMap<>(); + Map cases = new LinkedHashMap<>(); for (int i = 0; i < definition.getCases().size(); i++) { SwitchCaseDefinition c = definition.getCases().get(i); c.setParent(definition); @@ -100,7 +78,7 @@ public Processor createProcessor() throws Exception { send.setUri(otherwiseUri); otherwise = createSend(send); } - SwitchProcessor answer = new SwitchProcessor(camelContext, selector, keys, cases, otherwise); + SwitchProcessor answer = new SwitchProcessor(camelContext, selector, cases, otherwise); answer.setDisabled(isDisabled(camelContext, definition)); return answer; } 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 de30c94bb77f7..1abc11b399544 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 @@ -18,8 +18,8 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.math.BigDecimal; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -54,38 +54,30 @@ void scalarMatchingAndFallback() throws Exception { } @Test - void compositeMatchesNamedTypedValuesAndIgnoresExtraFields() throws Exception { - getMockEndpoint("mock:urgent").expectedBodiesReceived("first", "second"); - getMockEndpoint("mock:billing").expectedBodiesReceived("third"); - getMockEndpoint("mock:review").expectedBodiesReceived("string", "unknown", "null"); - template.sendBodyAndHeader("direct:composite", "first", "decision", Map.of("department", "billing", "urgent", true)); - template.sendBodyAndHeader("direct:composite", "second", "decision", - Map.of("urgent", true, "department", "BILLING", "score", 3)); - template.sendBodyAndHeader("direct:composite", "third", "decision", Map.of("urgent", false, "department", "billing")); - template.sendBodyAndHeader("direct:composite", "string", "decision", Map.of("department", "billing", "urgent", "true")); - template.sendBodyAndHeader("direct:composite", "unknown", "decision", Map.of("department", "other", "urgent", true)); - template.sendBody("direct:composite", "null"); - assertMockEndpointsSatisfied(); - } - - @Test - void malformedCompositeDoesNotUseFallback() throws Exception { + void structuredSelectorResultsFailWithoutUsingFallback() throws Exception { getMockEndpoint("mock:review").expectedMessageCount(0); + getMockEndpoint("mock:after").expectedMessageCount(0); for (Object result : new Object[] { - "billing", Map.of("department", "billing"), Map.of("department", "billing", "urgent", Map.of()) }) { - Exchange answer = template.request("direct:composite", e -> e.getIn().setHeader("decision", result)); - assertNotNull(answer.getException()); + Map.of("department", "billing", "urgent", true), + List.of("billing"), new String[] { "billing" } }) { + Exchange answer = template.request("direct:scalar", e -> e.getIn().setHeader("department", result)); + IllegalArgumentException failure = assertInstanceOf(IllegalArgumentException.class, answer.getException()); + assertTrue(failure.getMessage().contains("Switch requires a scalar selector result")); + if (result instanceof Map) { + assertTrue(failure.getMessage().contains("Composite selector results are not supported")); + } } assertMockEndpointsSatisfied(); } @Test - void numericTypesShareAKeyButStringsRemainDistinct() throws Exception { - getMockEndpoint("mock:number").expectedMessageCount(3); - getMockEndpoint("mock:string").expectedMessageCount(1); - for (Object number : new Object[] { 2, 2.0, new BigDecimal("2.00"), "2" }) { - template.sendBodyAndHeader("direct:numbers", "message", "decision", Map.of("score", number)); - } + void mapSelectorResultFollowsErrorHandling() throws Exception { + getMockEndpoint("mock:error").expectedMessageCount(1); + getMockEndpoint("mock:review").expectedMessageCount(0); + getMockEndpoint("mock:after").expectedMessageCount(0); + Exchange answer = template.request("direct:map", e -> e.getIn().setBody("message")); + assertNull(answer.getException()); + assertInstanceOf(IllegalArgumentException.class, answer.getProperty(Exchange.EXCEPTION_CAUGHT)); assertMockEndpointsSatisfied(); } @@ -112,7 +104,7 @@ void nestedSwitchesSelectIndependently() throws Exception { getMockEndpoint("mock:urgent").expectedMessageCount(1); getMockEndpoint("mock:after").expectedMessageCount(1); template.sendBodyAndHeaders("direct:nested", "message", - Map.of("department", "billing", "decision", Map.of("department", "billing", "urgent", true))); + Map.of("department", "billing", "decision", "urgent")); assertMockEndpointsSatisfied(); } @@ -156,13 +148,6 @@ public void configure() { .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") - .doCase().value("urgent", false).value("department", "billing").to("mock:billing") - .otherwise("mock:review"); - from("direct:numbers").doSwitch(header("decision")).keys("score") - .doCase().value("score", 2).to("mock:number") - .doCase().value("score", "2").to("mock:string"); from("direct:noFallback").doSwitch(header("department")).doCase("billing", "mock:billing") .end().to("mock:after"); from("direct:loop").loop(3).doSwitch(new ExpressionAdapter() { @@ -172,8 +157,16 @@ public Object evaluate(Exchange exchange) { } }).doCase("1", "mock:one").doCase("2", "mock:two").doCase("3", "mock:three").end().end(); from("direct:nested").choice().when(header("department").isEqualTo("billing")) - .doSwitch(header("department")).doCase("billing", "direct:composite") + .doSwitch(header("department")).doCase("billing", "direct:priority") .end().end().to("mock:after"); + from("direct:priority").doSwitch(header("decision")).doCase("urgent", "mock:urgent"); + from("direct:map").onException(IllegalArgumentException.class).handled(true).to("mock:error").end() + .doSwitch(new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + return Map.of("department", "billing", "urgent", true); + } + }).otherwise("mock:review").end().to("mock:after"); from("direct:failure").onException(IllegalStateException.class).handled(true).to("mock:error").end() .doSwitch(new ExpressionAdapter() { @Override 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 078175d64d392..7c6eca2798dd1 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 @@ -23,7 +23,6 @@ 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; @@ -64,7 +63,7 @@ void rejectsDynamicUri() { @Test void rejectsMissingValue() { - rejects(s -> s.doCase((String) null, "mock:a"), "require value"); + rejects(s -> s.doCase(null, "mock:a"), "require value"); } @Test @@ -77,55 +76,6 @@ void rejectsEmptySelector() { rejects(s -> s.setSelector(new ExpressionSubElementDefinition()), "selector requires an expression"); } - @Test - void rejectsDuplicateKeys() { - rejects(s -> s.keys("department", "department"), "keys must be nonblank and unique"); - } - - @Test - void rejectsMissingCompositeKey() { - rejects(s -> s.keys("department", "urgent").doCase().value("department", "billing").to("mock:a"), - "exactly the declared keys"); - } - - @Test - void rejectsExtraCompositeKey() { - rejects(s -> s.keys("department").doCase().value("department", "billing").value("urgent", true).to("mock:a"), - "exactly the declared keys"); - } - - @Test - void rejectsRepeatedCompositeKey() { - rejects(s -> s.keys("department").doCase().value("department", "billing").value("department", "technical").to("mock:a"), - "Duplicate or missing switch value name"); - } - - @Test - void rejectsMixedForms() { - rejects(s -> s.keys("department").doCase("billing", "mock:a"), "cannot declare scalar value"); - } - - @Test - void rejectsDuplicateCompositeValues() { - rejects(s -> s.keys("department", "score") - .doCase().value("department", "billing").value("score", 2).to("mock:a") - .doCase().value("score", 2.0).value("department", "BILLING").to("mock:b"), "Duplicate switch case"); - } - - @Test - void rejectsNonFiniteNumbers() { - rejects(s -> s.keys("score").doCase().value("score", Double.NaN).to("mock:a"), "NaN"); - } - - @Test - void rejectsInvalidBoolean() { - rejects(s -> { - s.keys("urgent").doCase().value("urgent", true).to("mock:a"); - SwitchValueDefinition value = s.getCases().get(0).getValues().get(0); - value.setValue("yes"); - }, "boolean must be true or false"); - } - private void rejects(Consumer configure, String message) { Exception failure = assertThrows(Exception.class, () -> { try (DefaultCamelContext context = new DefaultCamelContext()) { 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 1d5aefcd29a16..df5d2c563441e 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 @@ -683,13 +683,6 @@ public String writeSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) { doWriteSwitchOtherwiseDefinition(sb, def); return sb.toString(); } - public String writeSwitchValueDefinition(SwitchValueDefinition def) { - resetState(); - StringBuilder sb = new StringBuilder(); - beginStep(sb, "switchValue", def); - doWriteSwitchValueDefinition(sb, def); - return sb.toString(); - } public String writeTemplatedRouteDefinition(TemplatedRouteDefinition def) { resetState(); StringBuilder sb = new StringBuilder(); @@ -2607,23 +2600,16 @@ protected void doWriteSwitchCaseDefinition(StringBuilder sb, SwitchCaseDefinitio doWriteOptionalIdentifiedDefinitionAttributes(sb, def); doWriteAttribute(sb, "value", def.getValue(), null); doWriteAttribute(sb, "uri", def.getUri(), null); - doWriteChildList(sb, "values", def.getValues(), this::doWriteSwitchValueDefinition); } protected void doWriteSwitchDefinition(StringBuilder sb, SwitchDefinition def) { doWriteProcessorDefinitionAttributes(sb, def); 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); - doWriteAttribute(sb, "value", def.getValue(), null); - doWriteAttribute(sb, "type", def.getType(), "string"); - } protected void doWriteTemplatedRouteDefinition(StringBuilder sb, TemplatedRouteDefinition def) { doWriteAttribute(sb, "routeTemplateRef", def.getRouteTemplateRef(), null); doWriteAttribute(sb, "routeId", def.getRouteId(), 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 bd41f75324992..afbffa2a0cb28 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 @@ -42,7 +42,6 @@ import org.apache.camel.model.RouteTemplateParameterDefinition; import org.apache.camel.model.SwitchCaseDefinition; import org.apache.camel.model.SwitchDefinition; -import org.apache.camel.model.SwitchValueDefinition; import org.apache.camel.model.TemplatedRouteDefinition; import org.apache.camel.model.TemplatedRouteParameterDefinition; import org.apache.camel.model.ToDefinition; @@ -92,38 +91,15 @@ public abstract class JavaDslModelWriterSupport { protected void writeSwitch(StringBuilder sb, SwitchDefinition definition) { + definition.preCreateProcessor(); handledAttributes.clear(); sb.append(NL).append(indent()).append(".doSwitch(") .append(expressionDsl(definition.getSelector().getExpressionType())).append(")"); doWriteProcessorDefinitionAttributes(sb, definition); - if (!definition.getKeys().isEmpty()) { - sb.append(NL).append(indent()).append(".keys("); - for (int i = 0; i < definition.getKeys().size(); i++) { - if (i > 0) { - sb.append(", "); - } - sb.append(quote(definition.getKeys().get(i))); - } - sb.append(")"); - } for (SwitchCaseDefinition c : definition.getCases()) { sb.append(NL).append(indent()).append(".doCase("); - if (c.getValue() != null) { - sb.append(quote(c.getValue())); - } + sb.append(quote(c.getValue())); sb.append(")"); - for (SwitchValueDefinition value : c.getValues()) { - Object literal = value.asLiteral(); - sb.append(".value(").append(quote(value.getName())).append(", "); - if (literal instanceof String text) { - sb.append(quote(text)); - } else if (literal instanceof Boolean) { - sb.append(literal); - } else { - sb.append("new java.math.BigDecimal(").append(quote(literal.toString())).append(")"); - } - sb.append(")"); - } handledAttributes.clear(); doWriteOptionalIdentifiedDefinitionAttributes(sb, c); sb.append(".to(").append(quote(c.getUri())).append(")"); 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 c75129c610771..4db0f71425721 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 @@ -49,7 +49,7 @@ public class JavaDslModelWriterTest { @Test void switchExportPreservesDisabledAndCaseMetadata() { - SwitchDefinition sw = new SwitchDefinition(new HeaderExpression("decision")); + SwitchDefinition sw = new RouteDefinition().doSwitch().header("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"); diff --git a/core/camel-java-io/src/test/resources/switchLiteral.xml b/core/camel-java-io/src/test/resources/switchLiteral.xml index eaeaf5aea17c2..ec3bd1fe70c6d 100644 --- a/core/camel-java-io/src/test/resources/switchLiteral.xml +++ b/core/camel-java-io/src/test/resources/switchLiteral.xml @@ -25,16 +25,6 @@ - -
decision
- departmenturgentscore - - - - - - -
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 e2cfd746e6c1a..f16205dc31a4b 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 @@ -29,13 +29,23 @@ import org.apache.camel.model.SwitchCaseDefinition; import org.apache.camel.model.SwitchDefinition; import org.apache.camel.processor.SwitchProcessor; +import org.apache.camel.spi.ManagementStrategy; +import org.apache.camel.util.URISupport; @ManagedResource(description = "Managed Switch") public class ManagedSwitch extends ManagedProcessor implements ManagedSwitchMBean { + private boolean mask; + public ManagedSwitch(CamelContext context, SwitchProcessor processor, ProcessorDefinition definition) { super(context, processor, definition); } + @Override + public void init(ManagementStrategy strategy) { + super.init(strategy); + mask = strategy.getManagementAgent().getMask() != null ? strategy.getManagementAgent().getMask() : true; + } + @Override public SwitchProcessor getProcessor() { return (SwitchProcessor) super.getProcessor(); @@ -68,7 +78,7 @@ public TabularData extendedInformation() { TabularDataSupport table = new TabularDataSupport(CamelOpenMBeanTypes.switchTabularType()); for (int i = 0; i < getDefinition().getCases().size(); i++) { SwitchCaseDefinition c = getDefinition().getCases().get(i); - addRow(table, c.getId(), c.getValue() != null ? c.getValue() : c.getValues().toString(), + addRow(table, c.getId(), c.getValue(), c.getUri(), getProcessor().getMatchedCount(i)); } if (getDefinition().getOtherwise() != null) { @@ -85,6 +95,6 @@ private void addRow(TabularDataSupport table, String id, String value, String ur table.put(new CompositeDataSupport( CamelOpenMBeanTypes.switchCompositeType(), new String[] { "index", "id", "value", "uri", "matches" }, - new Object[] { table.size(), id, value, uri, matches })); + new Object[] { table.size(), id, value, mask ? URISupport.sanitizeUri(uri) : uri, matches })); } } 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 689556d512315..d8f6520adfe0f 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 @@ -16,8 +16,6 @@ */ package org.apache.camel.management; -import java.util.Map; - import javax.management.ObjectName; import javax.management.openmbean.TabularData; @@ -26,6 +24,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_PROCESSOR; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -36,8 +37,8 @@ class ManagedSwitchTest extends ManagementTestSupport { @Test void exposesCaseDestinationsCountsAndCaseMBeans() throws Exception { - template.sendBodyAndHeader("direct:start", "first", "decision", Map.of("department", "BILLING", "urgent", true)); - template.sendBodyAndHeader("direct:start", "second", "decision", Map.of("department", "billing", "urgent", true)); + template.sendBodyAndHeader("direct:start", "first", "decision", "BILLING"); + template.sendBodyAndHeader("direct:start", "second", "decision", "billing"); template.sendBody("direct:start", "unmatched"); ObjectName name = getCamelObjectName(TYPE_PROCESSOR, "p-dispatch"); TabularData table = (TabularData) getMBeanServer().invoke(name, "extendedInformation", null, null); @@ -59,14 +60,40 @@ void exposesCaseDestinationsCountsAndCaseMBeans() throws Exception { assertEquals(0L, getMBeanServer().getAttribute(name, "UnmatchedCount")); } + @ParameterizedTest + @NullSource + @ValueSource(booleans = { true, false }) + void masksCaseAndFallbackUris(Boolean mask) throws Exception { + // A null parameter exercises the agent's default without overriding it. + if (mask != null) { + context.getManagementStrategy().getManagementAgent().setMask(mask); + } + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("direct:masked").nodePrefixId("p-") + .doSwitch(header("decision")).id("masked") + .doCase("billing", "mock:billing?password=caseSecret") + .otherwise("mock:review?password=fallbackSecret"); + } + }); + TabularData table = (TabularData) getMBeanServer().invoke( + getCamelObjectName(TYPE_PROCESSOR, "p-masked"), "extendedInformation", null, null); + boolean sanitized = !Boolean.FALSE.equals(mask); + assertEquals("mock:billing?password=" + (sanitized ? "xxxxxx" : "caseSecret"), + table.get(new Object[] { 0 }).get("uri")); + assertEquals("mock:review?password=" + (sanitized ? "xxxxxx" : "fallbackSecret"), + table.get(new Object[] { 1 }).get("uri")); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { from("direct:start").routeId("switchRoute").nodePrefixId("p-") - .doSwitch(header("decision")).id("dispatch").keys("department", "urgent") - .doCase().value("department", "billing").value("urgent", true).id("urgentCase").to("mock:urgent") + .doSwitch(header("decision")).id("dispatch") + .doCase("billing").id("urgentCase").to("mock:urgent") .otherwise("mock:review"); } }; 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 5cfe23f033d09..bc3bfa23bd4d8 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 @@ -1186,23 +1186,11 @@ protected SwitchCaseDefinition doParseSwitchCaseDefinition() throws IOException, case "uri": def.setUri(sanitizeUri(val)); yield true; case "value": def.setValue(val); yield true; default: yield optionalIdentifiedDefinitionAttributeHandler().accept(def, key, val); - }, (def, key) -> switch (key) { - case "values": doAdd(doParseSwitchValueDefinition(), def.getValues(), def::setValues); yield true; - default: yield optionalIdentifiedDefinitionElementHandler().accept(def, key); - }, noValueHandler()); - } - protected SwitchValueDefinition doParseSwitchValueDefinition() throws IOException, XmlPullParserException { - return doParse(new SwitchValueDefinition(), (def, key, val) -> switch (key) { - case "name": def.setName(val); yield true; - case "type": def.setType(val); yield true; - case "value": def.setValue(val); yield true; - default: yield false; - }, noElementHandler(), noValueHandler()); + }, optionalIdentifiedDefinitionElementHandler(), noValueHandler()); } protected SwitchDefinition doParseSwitchDefinition() throws IOException, XmlPullParserException { 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); 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 5d77377031ddb..0e5b7879ac1ea 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 @@ -329,9 +329,6 @@ public void writeSwitchDefinition(SwitchDefinition def) throws IOException { public void writeSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) throws IOException { doWriteSwitchOtherwiseDefinition("switchOtherwise", def); } - public void writeSwitchValueDefinition(SwitchValueDefinition def) throws IOException { - doWriteSwitchValueDefinition("switchValue", def); - } public void writeTemplatedRouteDefinition(TemplatedRouteDefinition def) throws IOException { doWriteTemplatedRouteDefinition("templatedRoute", def); } @@ -1837,14 +1834,12 @@ protected void doWriteSwitchCaseDefinition(String name, SwitchCaseDefinition def doWriteOptionalIdentifiedDefinitionAttributes(def); doWriteAttribute("value", def.getValue(), null); doWriteAttribute("uri", def.getUri(), null); - doWriteList(null, "values", def.getValues(), this::doWriteSwitchValueDefinition); endElement(name); } protected void doWriteSwitchDefinition(String name, SwitchDefinition def) throws IOException { startElement(name); doWriteProcessorDefinitionAttributes(def); 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); @@ -1854,13 +1849,6 @@ protected void doWriteSwitchOtherwiseDefinition(String name, SwitchOtherwiseDefi doWriteAttribute("uri", def.getUri(), null); endElement(name); } - protected void doWriteSwitchValueDefinition(String name, SwitchValueDefinition def) throws IOException { - startElement(name); - doWriteAttribute("name", def.getName(), null); - doWriteAttribute("value", def.getValue(), null); - doWriteAttribute("type", def.getType(), "string"); - endElement(name); - } protected void doWriteTemplatedRouteDefinition(String name, TemplatedRouteDefinition def) throws IOException { startElement(name); doWriteAttribute("routeTemplateRef", def.getRouteTemplateRef(), null); diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index b1a9bfd9743d1..9ebc1f2338684 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -249,6 +249,7 @@ public String dumpDataFormatsAsXml(CamelContext context, Map dat */ private static void extractNamespaces(RouteDefinition route, Map namespaces) { for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + sw.preCreateProcessor(); NamespaceAware aware = getNamespaceAwareFromExpression(sw); if (aware != null && aware.getNamespaces() != null) { namespaces.putAll(aware.getNamespaces()); diff --git a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java index 8d23ca24a32b7..62c746934a2f8 100644 --- a/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java +++ b/core/camel-xml-jaxb/src/main/java/org/apache/camel/xml/jaxb/JaxbHelper.java @@ -89,6 +89,7 @@ public static JAXBContext getJAXBContext(CamelContext context) throws Exception */ public static void extractNamespaces(RouteDefinition route, Map namespaces) { for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + sw.preCreateProcessor(); NamespaceAware aware = getNamespaceAwareFromExpression(sw); if (aware != null && aware.getNamespaces() != null) { namespaces.putAll(aware.getNamespaces()); 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 3b2a3eac565f8..55f7b50c48ca6 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 @@ -322,9 +322,6 @@ public JsonObject writeSwitchDefinition(SwitchDefinition def) { public JsonObject writeSwitchOtherwiseDefinition(SwitchOtherwiseDefinition def) { return wrapNode("switchOtherwise", doWriteSwitchOtherwiseDefinition(def)); } - public JsonObject writeSwitchValueDefinition(SwitchValueDefinition def) { - return wrapNode("switchValue", doWriteSwitchValueDefinition(def)); - } public JsonObject writeTemplatedRouteDefinition(TemplatedRouteDefinition def) { return wrapNode("templatedRoute", doWriteTemplatedRouteDefinition(def)); } @@ -1833,7 +1830,7 @@ protected JsonObject doWriteSwitchCaseDefinition(SwitchCaseDefinition def) { doWriteOptionalIdentifiedDefinitionAttributes(jo, def); doWriteAttribute(jo, "value", def.getValue(), null); doWriteAttribute(jo, "uri", def.getUri(), null); - doWriteChildList(jo, null, "values", def.getValues(), this::doWriteSwitchValueDefinition); + // Preserve literal strings such as "001" instead of converting them to numbers. if (def.getValue() != null) { jo.put("value", def.getValue()); } @@ -1843,7 +1840,6 @@ protected JsonObject doWriteSwitchDefinition(SwitchDefinition def) { JsonObject jo = new JsonObject(); doWriteProcessorDefinitionAttributes(jo, def); 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; @@ -1853,15 +1849,6 @@ protected JsonObject doWriteSwitchOtherwiseDefinition(SwitchOtherwiseDefinition doWriteAttribute(jo, "uri", def.getUri(), null); return jo; } - protected JsonObject doWriteSwitchValueDefinition(SwitchValueDefinition def) { - JsonObject jo = new JsonObject(); - doWriteAttribute(jo, "name", def.getName(), null); - doWriteAttribute(jo, "value", def.getValue(), null); - doWriteAttribute(jo, "type", def.getType(), "string"); - jo.clear(); - jo.put(def.getName(), def.asLiteral()); - return jo; - } protected JsonObject doWriteTemplatedRouteDefinition(TemplatedRouteDefinition def) { JsonObject jo = new JsonObject(); doWriteAttribute(jo, "routeTemplateRef", def.getRouteTemplateRef(), null); diff --git a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java index 85e7763102fed..318e20db2cf95 100644 --- a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java +++ b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java @@ -258,6 +258,7 @@ public String dumpDataFormatsAsYaml(CamelContext context, Map da */ private static void extractNamespaces(RouteDefinition route, Map namespaces) { for (SwitchDefinition sw : filterTypeInOutputs(route.getOutputs(), SwitchDefinition.class)) { + sw.preCreateProcessor(); NamespaceAware aware = getNamespaceAwareFromExpression(sw); if (aware != null && aware.getNamespaces() != null) { namespaces.putAll(aware.getNamespaces()); diff --git a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/io/YamlPrinterTest.java b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/io/YamlPrinterTest.java index d07fbc82de04e..d2c5760a3c723 100644 --- a/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/io/YamlPrinterTest.java +++ b/core/camel-yaml-io/src/test/java/org/apache/camel/yaml/io/YamlPrinterTest.java @@ -142,6 +142,7 @@ void quotingRules() { assertTrue(YamlPrinter.needsQuoting("null"), "null"); assertTrue(YamlPrinter.needsQuoting("~"), "tilde"); assertTrue(YamlPrinter.needsQuoting("123"), "integer"); + assertTrue(YamlPrinter.needsQuoting("001"), "integer with leading zeros"); assertTrue(YamlPrinter.needsQuoting("3.14"), "float"); assertTrue(YamlPrinter.needsQuoting("-42"), "negative number"); assertTrue(YamlPrinter.needsQuoting("foo: bar"), "contains colon-space"); 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 0d9fd35b20733..583da9fce2858 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 @@ -1410,15 +1410,7 @@ }, { "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 - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise:\n uri: direct:review\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 language:\n language: semantic\n expression: refs:department,urgent\n keys: [department, urgent]\n case:\n - values: [department: billing, urgent: true]\n uri: direct:urgent-billing\n - values: [department: billing, urgent: false]\n uri: direct:billing\n - values: [department: technical, urgent: true]\n uri: direct:incident\n otherwise:\n uri: direct:review\n" - }, - { - "source": "switch-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:billing\n steps:\n - log: \"Billing: ${body}\"\n- route:\n from:\n uri: direct:technical\n steps:\n - log: \"Technical: ${body}\"\n- route:\n from:\n uri: direct:urgent-billing\n steps:\n - log: \"Urgent billing: ${body}\"\n- route:\n from:\n uri: direct:incident\n steps:\n - log: \"Incident: ${body}\"\n- route:\n from:\n uri: direct:review\n steps:\n - log: \"Review: ${body}\"\n" + "yaml": "- route:\n from:\n uri: direct:billing\n steps:\n - log: \"Billing: ${body}\"\n- route:\n from:\n uri: direct:technical\n steps:\n - log: \"Technical: ${body}\"\n- route:\n from:\n uri: direct:review\n steps:\n - log: \"Review: ${body}\"\n" } ], "threads": [ 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 5a2f494fe6524..11903a0c3a9e8 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 @@ -16,9 +16,8 @@ */ package org.apache.camel.dsl.xml.io; -import java.util.Map; - import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.SwitchDefinition; import org.apache.camel.model.language.XPathExpression; import org.apache.camel.support.PluginHelper; @@ -28,6 +27,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class XmlSwitchTest { + @Test + void fluentSelectorSurvivesDumpBeforeStartup() throws Exception { + try (var context = new DefaultCamelContext()) { + RouteDefinition route = new RouteDefinition("direct:start").routeId("fluent"); + route.doSwitch().header("department").otherwise("mock:other"); + String xml = PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, route); + PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("fluent.xml", xml)); + SwitchDefinition sw = (SwitchDefinition) context.getRouteDefinition("fluent").getOutputs().get(0); + assertEquals("header", sw.getSelector().getExpressionType().getLanguage()); + assertEquals("department", sw.getSelector().getExpressionType().getExpression()); + } + } + @Test void selectorNamespacesSurviveModelDump() throws Exception { String xml = """ @@ -58,18 +70,14 @@ void selectorNamespacesSurviveModelDump() throws Exception { } @Test - void compositeRoundTripPreservesTypesAndExecutes() throws Exception { + void scalarRoundTripPreservesFallbackAndExecutes() throws Exception { String xml = """
decision
- departmenturgent - - - - +
@@ -86,9 +94,9 @@ void compositeRoundTripPreservesTypesAndExecutes() throws Exception { restored.start(); try (var template = restored.createProducerTemplate()) { assertEquals("matched", template.requestBodyAndHeader("direct:start", "original", "decision", - Map.of("department", "BILLING", "urgent", true))); + "BILLING")); assertEquals("other", template.requestBodyAndHeader("direct:start", "original", "decision", - Map.of("department", "billing", "urgent", "true"))); + "other")); } } } diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml index 23322c1321ef4..1b4947b1ac581 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml @@ -87,7 +87,6 @@ org.apache.camel.dsl.yaml.deserializers - org.apache.camel.model.SwitchValueDefinition org.apache.camel.model.FromDefinition org.apache.camel.model.language.ExpressionDefinition org.apache.camel.model.ExpressionSubElementDefinition 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 2633e7186105c..883bd2d30b8d7 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 @@ -18050,8 +18050,7 @@ protected boolean setProperty(SwiftMxDataFormat target, String propertyKey, @YamlProperty(name = "note", type = "string", description = "The note for this node", displayName = "Note"), @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"), - @YamlProperty(name = "value", type = "string", description = "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.", displayName = "Value"), - @YamlProperty(name = "values", type = "array:org.apache.camel.model.SwitchValueDefinition", description = "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", displayName = "Values") + @YamlProperty(name = "value", type = "string", required = true, description = "The case-insensitive literal value for a scalar selector.", displayName = "Value") } ) public static class SwitchCaseDefinitionDeserializer extends YamlDeserializerEndpointAwareBase { @@ -18085,11 +18084,6 @@ protected boolean setProperty(SwitchCaseDefinition target, String propertyKey, target.setValue(val); break; } - case "values": { - java.util.List val = asFlatList(node, org.apache.camel.model.SwitchValueDefinition.class); - target.setValues(val); - break; - } case "id": { String val = asText(node); target.setId(val); @@ -18118,17 +18112,16 @@ protected boolean setProperty(SwitchCaseDefinition target, String propertyKey, types = org.apache.camel.model.SwitchDefinition.class, order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1, displayName = "Switch", - description = "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + description = "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", deprecated = false, properties = { - @YamlProperty(name = "case", type = "array:org.apache.camel.model.SwitchCaseDefinition", description = "Literal cases. Duplicate combinations are rejected at startup.", displayName = "Case"), + @YamlProperty(name = "case", type = "array:org.apache.camel.model.SwitchCaseDefinition", description = "Literal cases. Duplicate values are rejected at startup, ignoring case.", displayName = "Case"), @YamlProperty(name = "description", type = "string", description = "The description for this node", displayName = "Description"), @YamlProperty(name = "disabled", type = "boolean", defaultValue = "false", description = "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime.", displayName = "Disabled"), @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 = "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") + @YamlProperty(name = "selector", type = "object:org.apache.camel.model.ExpressionSubElementDefinition", required = true, description = "Expression evaluated once per entry. Returns a scalar value to match against the literal cases.", displayName = "Selector") } ) public static class SwitchDefinitionDeserializer extends YamlDeserializerBase { @@ -18156,11 +18149,6 @@ protected boolean setProperty(SwitchDefinition target, String propertyKey, target.setDisabled(val); break; } - case "keys": { - java.util.List val = asStringList(node); - target.setKeys(val); - break; - } case "otherwise": { org.apache.camel.model.SwitchOtherwiseDefinition val = asType(node, org.apache.camel.model.SwitchOtherwiseDefinition.class); target.setOtherwise(val); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java index 09ae6018fe231..be0fbf6042d01 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/CustomResolver.java @@ -72,8 +72,6 @@ public ConstructNode resolve(String id) { // // Misc // - case "org.apache.camel.model.SwitchValueDefinition": - return new SwitchValueDefinitionDeserializer(); case "beans": return beansDeserializer; case "dataFormats": diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java deleted file mode 100644 index a2ae468ebb285..0000000000000 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/SwitchValueDefinitionDeserializer.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.deserializers; - -import java.math.BigDecimal; - -import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver; -import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport; -import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException; -import org.apache.camel.model.SwitchValueDefinition; -import org.apache.camel.spi.annotations.YamlType; -import org.snakeyaml.engine.v2.api.ConstructNode; -import org.snakeyaml.engine.v2.nodes.MappingNode; -import org.snakeyaml.engine.v2.nodes.Node; -import org.snakeyaml.engine.v2.nodes.NodeTuple; -import org.snakeyaml.engine.v2.nodes.ScalarNode; -import org.snakeyaml.engine.v2.nodes.Tag; - -@YamlType(types = SwitchValueDefinition.class, order = YamlDeserializerResolver.ORDER_DEFAULT) -public class SwitchValueDefinitionDeserializer extends YamlDeserializerSupport implements ConstructNode { - @Override - public Object construct(Node node) { - if (!(node instanceof MappingNode mapping) || mapping.getValue().size() != 1) { - throw new YamlDeserializationException(node, "Each switch value must be a single-entry mapping"); - } - NodeTuple entry = mapping.getValue().get(0); - if (!(entry.getKeyNode() instanceof ScalarNode key) || !Tag.STR.equals(key.getTag())) { - throw new YamlDeserializationException(node, "Switch value names must be strings"); - } - if (!(entry.getValueNode() instanceof ScalarNode value)) { - throw new YamlDeserializationException(node, "Switch values must be scalar literals"); - } - Object literal; - if (Tag.STR.equals(value.getTag())) { - literal = value.getValue(); - } else if (Tag.BOOL.equals(value.getTag())) { - literal = Boolean.valueOf(value.getValue()); - } else if (Tag.INT.equals(value.getTag()) || Tag.FLOAT.equals(value.getTag())) { - try { - literal = new BigDecimal(value.getValue()); - } catch (NumberFormatException e) { - throw new YamlDeserializationException(node, "Switch numbers must be finite decimal literals", e); - } - } else { - throw new YamlDeserializationException(node, "Switch values must be strings, booleans or numbers"); - } - return new SwitchValueDefinition(key.getValue(), literal); - } -} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java index 398f9ad36f86f..a8b7f029af42c 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java @@ -172,16 +172,6 @@ protected void generate() throws MojoFailureException { postProcessInheritance(inheritedDefinitions, inlineDefinitions); } - // Switch's YAML shorthand is one named, typed scalar per list entry. - String switchValue = "org.apache.camel.model.SwitchValueDefinition"; - if (definitions.has(switchValue)) { - ObjectNode literal = mapper.createObjectNode(); - literal.put("type", "object").put("minProperties", 1).put("maxProperties", 1); - literal.putObject("additionalProperties").putArray("type") - .add("string").add("boolean").add("number"); - definitions.set(switchValue, literal); - } - try { ToolingSupport.mkparents(outputFile); 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 index 641d883aeb4e5..9d853b1943434 100644 --- 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 @@ -42,6 +42,10 @@ void fallbackIsAnEndpointObject(boolean canonical) throws Exception { uri: direct:review """; assertThat(validator.validate(route)).isEmpty(); + assertThat(validator.validate(route.replace("case:", "keys: [department, urgent]\n case:"))) + .isNotEmpty(); + assertThat(validator.validate(route.replace("value: billing", "values: [department: billing]"))) + .isNotEmpty(); 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 470ac159bf014..1e6fd6f8c93da 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 @@ -4970,29 +4970,21 @@ "value" : { "type" : "string", "title" : "Value", - "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." - }, - "values" : { - "type" : "array", - "title" : "Values", - "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", - "items" : { - "$ref" : "#/items/definitions/org.apache.camel.model.SwitchValueDefinition" - } + "description" : "The case-insensitive literal value for a scalar selector." } }, - "required" : [ "uri" ] + "required" : [ "uri", "value" ] }, "org.apache.camel.model.SwitchDefinition" : { "title" : "Switch", - "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "type" : "object", "additionalProperties" : false, "properties" : { "case" : { "type" : "array", "title" : "Case", - "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "description" : "Literal cases. Duplicate values are rejected at startup, ignoring case.", "items" : { "$ref" : "#/items/definitions/org.apache.camel.model.SwitchCaseDefinition" } @@ -5013,14 +5005,6 @@ "title" : "Id", "description" : "The id of this node" }, - "keys" : { - "type" : "array", - "title" : "Keys", - "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", - "items" : { - "type" : "string" - } - }, "note" : { "type" : "string", "title" : "Note", @@ -5033,7 +5017,7 @@ }, "selector" : { "title" : "Selector", - "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "description" : "Expression evaluated once per entry. Returns a scalar value to match against the literal cases.", "$ref" : "#/items/definitions/org.apache.camel.model.ExpressionSubElementDefinition" } }, @@ -5056,14 +5040,6 @@ }, "required" : [ "uri" ] }, - "org.apache.camel.model.SwitchValueDefinition" : { - "type" : "object", - "minProperties" : 1, - "maxProperties" : 1, - "additionalProperties" : { - "type" : [ "string", "boolean", "number" ] - } - }, "org.apache.camel.model.TemplatedRouteDefinition" : { "type" : "object", "additionalProperties" : false, 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 d505bf5061b65..d14e31756b498 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 @@ -510,7 +510,7 @@ "type" : "object", "ref" : "switch", "title" : "Switch", - "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "label" : "eip,routing" }, { "name" : "threads", @@ -8360,19 +8360,14 @@ }, { "name" : "value", "type" : "string", - "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values.", - "title" : "Value" - }, { - "name" : "values", - "type" : "array", - "ref" : "switchValue", - "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", - "title" : "Values" + "description" : "The case-insensitive literal value for a scalar selector.", + "title" : "Value", + "required" : true } ] }, "switch" : { "title" : "Switch", - "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "label" : "eip,routing", "input" : true, "isListItem" : true, @@ -8418,31 +8413,22 @@ "name" : "selector", "type" : "object", "ref" : "expressionSubElement", - "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "description" : "Expression evaluated once per entry. Returns a scalar value to match against the literal cases.", "title" : "Selector", "required" : true, "displayName" : "Selector", "kind" : "expression", "index" : 4, "group" : "common" - }, { - "name" : "keys", - "type" : "array", - "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", - "title" : "Keys", - "displayName" : "Keys", - "kind" : "element", - "index" : 5, - "group" : "common" }, { "name" : "case", "type" : "array", "ref" : "switchCase", - "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "description" : "Literal cases. Duplicate values are rejected at startup, ignoring case.", "title" : "Case", "displayName" : "Case", "kind" : "element", - "index" : 6, + "index" : 5, "group" : "common" }, { "name" : "otherwise", @@ -8452,7 +8438,7 @@ "title" : "Otherwise", "displayName" : "Otherwise", "kind" : "element", - "index" : 7, + "index" : 6, "group" : "common" } ] }, @@ -8475,12 +8461,6 @@ "type" : "object" } ] }, - "switchValue" : { - "title" : "Switch Value", - "description" : "A named literal in a composite switch case", - "label" : "configuration", - "children" : [ ] - }, "templatedRoute" : { "title" : "Templated Route", "description" : "Creates a route instance from a route template, binding specific parameter values to the template's parameters", 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 d0ae29c4ab007..b998d8b107b74 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 @@ -6623,29 +6623,21 @@ "value" : { "type" : "string", "title" : "Value", - "description" : "The case-insensitive literal value for a scalar selector. Mutually exclusive with values." - }, - "values" : { - "type" : "array", - "title" : "Values", - "description" : "Named, typed literal values for a composite selector. Every selected key must occur exactly once.", - "items" : { - "$ref" : "#/items/definitions/org.apache.camel.model.SwitchValueDefinition" - } + "description" : "The case-insensitive literal value for a scalar selector." } }, - "required" : [ "uri" ] + "required" : [ "uri", "value" ] }, "org.apache.camel.model.SwitchDefinition" : { "title" : "Switch", - "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar or composite values", + "description" : "Evaluates a selector once and dispatches to a fixed endpoint by literal scalar values", "type" : "object", "additionalProperties" : false, "properties" : { "case" : { "type" : "array", "title" : "Case", - "description" : "Literal cases. Duplicate combinations are rejected at startup.", + "description" : "Literal cases. Duplicate values are rejected at startup, ignoring case.", "items" : { "$ref" : "#/items/definitions/org.apache.camel.model.SwitchCaseDefinition" } @@ -6666,14 +6658,6 @@ "title" : "Id", "description" : "The id of this node" }, - "keys" : { - "type" : "array", - "title" : "Keys", - "description" : "Exact map field names forming a composite key. Additional result fields are ignored.", - "items" : { - "type" : "string" - } - }, "note" : { "type" : "string", "title" : "Note", @@ -6686,7 +6670,7 @@ }, "selector" : { "title" : "Selector", - "description" : "Expression evaluated once per entry. Returns a scalar, or a map when keys are configured.", + "description" : "Expression evaluated once per entry. Returns a scalar value to match against the literal cases.", "$ref" : "#/items/definitions/org.apache.camel.model.ExpressionSubElementDefinition" } }, @@ -6709,14 +6693,6 @@ }, "required" : [ "uri" ] }, - "org.apache.camel.model.SwitchValueDefinition" : { - "type" : "object", - "minProperties" : 1, - "maxProperties" : 1, - "additionalProperties" : { - "type" : [ "string", "boolean", "number" ] - } - }, "org.apache.camel.model.TemplatedRouteDefinition" : { "type" : "object", "additionalProperties" : false, 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 cced5921f312c..6150f21829165 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 @@ -16,9 +16,6 @@ */ package org.apache.camel.dsl.yaml; -import java.math.BigDecimal; -import java.util.Map; - import org.apache.camel.dsl.yaml.support.YamlTestSupport; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.model.SwitchDefinition; @@ -33,7 +30,7 @@ class SwitchTest extends YamlTestSupport { @Test - void compositeValuesAndEndpointParametersRoundTrip() throws Exception { + void scalarValuesAndEndpointParametersRoundTrip() throws Exception { loadRoutes(""" - route: id: decision @@ -44,16 +41,17 @@ void compositeValuesAndEndpointParametersRoundTrip() throws Exception { selector: header: expression: decision - keys: [department, urgent] case: - - values: [department: billing, urgent: true] + - value: urgent uri: direct parameters: name: urgent - - values: [urgent: false, department: billing] + - value: billing uri: direct:billing otherwise: - uri: direct:review + uri: direct + parameters: + name: review - route: from: uri: direct:urgent @@ -75,7 +73,7 @@ void compositeValuesAndEndpointParametersRoundTrip() throws Exception { """); 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.getCases().get(0).getValue()).isEqualTo("urgent"); assertThat(sw.getOtherwise().getUri()).isEqualTo("direct:review"); try (var restored = new DefaultCamelContext()) { for (var route : context.getRouteDefinitions()) { @@ -85,64 +83,34 @@ void compositeValuesAndEndpointParametersRoundTrip() throws Exception { restored.start(); try (var template = restored.createProducerTemplate()) { assertThat(template.requestBodyAndHeader("direct:start", "original", "decision", - Map.of("department", "BILLING", "urgent", true))).isEqualTo("urgent"); + "URGENT")).isEqualTo("urgent"); assertThat(template.requestBodyAndHeader("direct:start", "original", "decision", - Map.of("department", "billing", "urgent", false))).isEqualTo("billing"); + "BILLING")).isEqualTo("billing"); assertThat(template.requestBodyAndHeader("direct:start", "original", "decision", - Map.of("department", "billing", "urgent", "true"))).isEqualTo("review"); + "other")).isEqualTo("review"); } } } @Test - void quotedScalarsRetainTheirTypesAfterDump() throws Exception { - loadRoutes(""" + void rejectsCompositeConfiguration() throws Exception { + String yaml = """ - route: - id: quoted from: uri: direct:start steps: - switch: selector: header: decision - keys: [urgent, score] case: - - values: [urgent: 'true', score: '2'] - uri: mock:string - - values: [urgent: true, score: 2.0] - uri: mock:boolean - """); - var route = context.getRouteDefinition("quoted"); - String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); - try (var restored = new DefaultCamelContext()) { - PluginHelper.getRoutesLoader(restored).loadRoutes(ResourceHelper.fromString("restored.yaml", yaml)); - SwitchDefinition sw = (SwitchDefinition) restored.getRouteDefinition("quoted").getOutputs().get(0); - assertThat(sw.getCases().get(0).getValues().get(0).asLiteral()).isEqualTo("true"); - assertThat(sw.getCases().get(1).getValues().get(0).asLiteral()).isEqualTo(true); - assertThat(sw.getCases().get(0).getValues().get(1).asLiteral()).isEqualTo("2"); - assertThat(sw.getCases().get(1).getValues().get(1).asLiteral()).isInstanceOf(BigDecimal.class); - assertThat((BigDecimal) sw.getCases().get(1).getValues().get(1).asLiteral()).isEqualByComparingTo("2.0"); - } - } - - @Test - void rejectsNonScalarOrMultipleBindingsPerEntry() { - for (String values : new String[] { "[{urgent: true, department: billing}]", "[urgent: [true]]", "[urgent: null]" }) { - String yaml = """ - - route: - from: - uri: direct:start - steps: - - switch: - selector: - header: decision - keys: [urgent] - case: - - values: %s - uri: mock:a - """.formatted(values); - assertThatThrownBy(() -> loadRoutes(yaml)).isInstanceOf(Exception.class); - } + - value: billing + uri: mock:billing + """; + loadRoutes(yaml); + assertThatThrownBy(() -> loadRoutesNoValidate(yaml.replace("case:", "keys: [department, urgent]\n case:"))) + .hasStackTraceContaining("unsupported field: keys"); + assertThatThrownBy(() -> loadRoutesNoValidate(yaml.replace("value: billing", "values: [department: billing]"))) + .hasStackTraceContaining("unsupported field: values"); } @Test 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 0ad730d9f2cad..58b53b5420c7b 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 @@ -18,20 +18,36 @@ import java.util.Map; +import org.apache.camel.builder.ExpressionClause; import org.apache.camel.dsl.yaml.support.YamlTestSupport; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.SwitchDefinition; +import org.apache.camel.model.language.HeaderExpression; import org.apache.camel.model.language.XPathExpression; import org.apache.camel.yaml.LwModelToYAMLDumper; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; class SwitchYamlDumpTest extends YamlTestSupport { + @ParameterizedTest + @ValueSource(strings = { "001", "1.00", "1e2", "true" }) + void scalarLiteralsRemainStringsAfterDump(String value) throws Exception { + RouteDefinition route = new RouteDefinition("direct:start").routeId("literal"); + route.doSwitch(new HeaderExpression("decision")).doCase(value, "mock:matched"); + String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); + loadRoutes(yaml); + SwitchDefinition restored = (SwitchDefinition) context.getRouteDefinition("literal").getOutputs().get(0); + assertThat(restored.getCases().get(0).getValue()).isEqualTo(value); + } + @Test void fluentSelectorSurvivesYamlDump() throws Exception { RouteDefinition route = new RouteDefinition().from("direct:start").routeId("fluent"); - route.doSwitch().header("department").doCase("billing", "mock:billing"); + SwitchDefinition sw = route.doSwitch().header("department").doCase("billing", "mock:billing"); + assertThat(sw.getSelector().getExpressionType().getExpressionValue()).isInstanceOf(ExpressionClause.class); String yaml = new LwModelToYAMLDumper().dumpModelAsYaml(context, route); loadRoutes(yaml); SwitchDefinition restored = (SwitchDefinition) context.getRouteDefinition("fluent").getOutputs().get(0); 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 e82bc9b8e726b..711796bc892a7 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 @@ -279,10 +279,8 @@ public class YamlModelWriter extends YamlModelWriterSupport { #if( $name == "RouteDefinition" ) doMoveStepsUnderFrom(jo); #end - #if( $name == "SwitchValueDefinition" ) - jo.clear(); - jo.put(def.getName(), def.asLiteral()); - #elseif( $name == "SwitchCaseDefinition" ) + #if( $name == "SwitchCaseDefinition" ) + // Preserve literal strings such as "001" instead of converting them to numbers. if (def.getValue() != null) { jo.put("value", def.getValue()); } From d5deafb44a39c5075a188c38b972c5f03b9ea99b Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Wed, 30 Sep 2026 12:56:25 +0200 Subject: [PATCH 6/7] CAMEL-24988: Restore explicit Switch destination preparation Keep the model getters side-effect-free and prepare endpoint nodes during reification. Restore regression coverage for stable prepared nodes and independent copies. Co-authored-by: Codex Signed-off-by: Luigi De Masi --- .../camel/model/SwitchCaseDefinition.java | 9 ++- .../apache/camel/model/SwitchDefinition.java | 32 ++++++--- .../model/SwitchOtherwiseDefinition.java | 22 +++++- .../camel/model/SwitchDefinitionTest.java | 67 +++++++++++++++++++ .../apache/camel/reifier/SwitchReifier.java | 2 + 5 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java index f875ec0514010..fbbabe0c98456 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java @@ -78,9 +78,15 @@ public void setUri(String uri) { /** The destination node shared by reification and management instrumentation. */ @XmlTransient public ToDefinition getToDefinition() { + return toDefinition; + } + + /** Prepare the destination node before processor creation and management instrumentation. */ + public void prepareToDefinition() { if (toDefinition == null) { - toDefinition = new ToDefinition(uri); + toDefinition = new ToDefinition(); } + toDefinition.setUri(uri); toDefinition.setParent(parent); if (hasCustomIdAssigned()) { toDefinition.setId(getId()); @@ -91,7 +97,6 @@ public ToDefinition getToDefinition() { toDefinition.setNote(getNote()); toDefinition.setLocation(getLocation()); toDefinition.setLineNumber(getLineNumber()); - return toDefinition; } @Override 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 61b56043cd933..944d6e059e50b 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 @@ -49,8 +49,6 @@ public class SwitchDefinition extends NoOutputDefinition { @XmlElement @Metadata(description = "Fixed fallback URI for null or unmatched selector results. Without a fallback processing continues.") private SwitchOtherwiseDefinition otherwise; - @XmlTransient - private ToDefinition otherwiseDefinition; public SwitchDefinition() { } @@ -62,7 +60,7 @@ public SwitchDefinition(Expression selector) { protected SwitchDefinition(SwitchDefinition source) { super(source); this.selector = source.selector != null ? source.selector.copyDefinition() : null; - this.otherwise = source.otherwise != null ? source.otherwise.copyDefinition() : null; + setOtherwise(source.otherwise != null ? source.otherwise.copyDefinition() : null); for (SwitchCaseDefinition c : source.cases) { SwitchCaseDefinition copy = c.copyDefinition(); copy.setParent(this); @@ -106,19 +104,34 @@ public SwitchOtherwiseDefinition getOtherwise() { public void setOtherwise(SwitchOtherwiseDefinition otherwise) { this.otherwise = otherwise; - this.otherwiseDefinition = null; + prepareOtherwiseDefinition(); + } + + @Override + public void setId(String id) { + super.setId(id); + prepareOtherwiseDefinition(); + } + + @Override + public void setGeneratedId(String id) { + super.setGeneratedId(id); + prepareOtherwiseDefinition(); } /** The fallback send node, used by route traversal and processor creation. */ @XmlTransient public ToDefinition getOtherwiseDefinition() { - if (otherwise == null) { - return null; - } + return otherwise != null ? otherwise.getToDefinition() : null; + } + + /** Prepare the fallback node before processor creation and management instrumentation. */ + public void prepareOtherwiseDefinition() { + ToDefinition otherwiseDefinition = getOtherwiseDefinition(); if (otherwiseDefinition == null) { - otherwiseDefinition = new ToDefinition(); - otherwiseDefinition.setParent(this); + return; } + otherwiseDefinition.setParent(this); otherwiseDefinition.setUri(otherwise.getUri()); if (getId() != null) { if (hasCustomIdAssigned()) { @@ -127,7 +140,6 @@ public ToDefinition getOtherwiseDefinition() { otherwiseDefinition.setGeneratedId(getId() + "-otherwise"); } } - return otherwiseDefinition; } /** Add a scalar literal and its destination. */ 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 index 0c5e79d282bde..d5cd511b20895 100644 --- 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 @@ -16,10 +16,12 @@ */ package org.apache.camel.model; +import jakarta.xml.bind.Unmarshaller; 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 jakarta.xml.bind.annotation.XmlTransient; import org.apache.camel.spi.Metadata; @@ -31,10 +33,12 @@ public class SwitchOtherwiseDefinition implements EndpointRequiredDefinition { @XmlAttribute(required = true) @Metadata(description = "The fixed destination URI. Supports property placeholders, but not Simple expressions.") private String uri; + @XmlTransient + private final ToDefinition toDefinition = new ToDefinition(); public SwitchOtherwiseDefinition copyDefinition() { SwitchOtherwiseDefinition copy = new SwitchOtherwiseDefinition(); - copy.uri = uri; + copy.setUri(uri); return copy; } @@ -44,9 +48,25 @@ public String getUri() { public void setUri(String uri) { this.uri = uri; + toDefinition.setUri(uri); + } + + /** The destination node, available for endpoint discovery before route startup. */ + @XmlTransient + public ToDefinition getToDefinition() { + return toDefinition; + } + + // JAXB populates fields directly, so initialize the traversal node after loading. + private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { + toDefinition.setUri(uri); + if (parent instanceof SwitchDefinition sw) { + sw.setOtherwise(this); + } } @Override + @XmlTransient public String getEndpointUri() { return uri; } diff --git a/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java b/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java new file mode 100644 index 0000000000000..715e1a31075dd --- /dev/null +++ b/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java @@ -0,0 +1,67 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +class SwitchDefinitionTest { + @Test + void readingDestinationsDoesNotOverwritePreparedNodes() { + SwitchDefinition sw = new SwitchDefinition().id("dispatch") + .doCase("billing", "{{case.uri}}").otherwise("{{fallback.uri}}"); + SwitchCaseDefinition c = sw.getCases().get(0); + c.setId("billing"); + assertNull(c.getToDefinition()); + sw.getChildren(); + assertNull(c.getToDefinition()); + + c.prepareToDefinition(); + sw.prepareOtherwiseDefinition(); + ToDefinition caseSend = c.getToDefinition(); + ToDefinition fallbackSend = sw.getOtherwiseDefinition(); + caseSend.setUri("mock:billing"); + caseSend.setId("prepared-billing"); + fallbackSend.setUri("mock:review"); + fallbackSend.setId("prepared-fallback"); + + assertSame(caseSend, c.getToDefinition()); + assertSame(fallbackSend, sw.getOtherwiseDefinition()); + assertSame(fallbackSend, sw.getChildren().get(1)); + assertEquals("mock:billing", caseSend.getUri()); + assertEquals("prepared-billing", caseSend.getId()); + assertEquals("mock:review", fallbackSend.getUri()); + assertEquals("prepared-fallback", fallbackSend.getId()); + } + + @Test + void copiedFallbackHasIndependentDestinationAndParent() { + SwitchDefinition original = new SwitchDefinition().otherwise("mock:original"); + SwitchDefinition copy = original.copyDefinition(); + copy.getOtherwise().setUri("mock:copy"); + + assertNotSame(original.getOtherwiseDefinition(), copy.getOtherwiseDefinition()); + assertSame(original, original.getOtherwiseDefinition().getParent()); + assertSame(copy, copy.getOtherwiseDefinition().getParent()); + assertEquals("mock:original", original.getOtherwiseDefinition().getUri()); + assertEquals("mock:copy", copy.getOtherwiseDefinition().getUri()); + } +} 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 d5d1caef8d0f4..77adf6751f4cc 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 @@ -67,6 +67,7 @@ public Processor createProcessor() throws Exception { c.setParent(definition); c.setCamelContext(camelContext); c.idOrCreate(ids); + c.prepareToDefinition(); ToDefinition send = c.getToDefinition(); send.setUri(uris.get(i)); cases.put(caseKeys.get(i), createSend(send)); @@ -74,6 +75,7 @@ public Processor createProcessor() throws Exception { Processor otherwise = null; if (otherwiseUri != null) { definition.idOrCreate(ids); + definition.prepareOtherwiseDefinition(); ToDefinition send = definition.getOtherwiseDefinition(); send.setUri(otherwiseUri); otherwise = createSend(send); From 449a6a9cb4600d68775da7693ab2e58cce3e4953 Mon Sep 17 00:00:00 2001 From: Luigi De Masi Date: Wed, 30 Sep 2026 13:20:05 +0200 Subject: [PATCH 7/7] CAMEL-24988: Prepare Switch destinations for management Prepare literal destination nodes before JMX performance-counter traversal. This keeps model getters side-effect-free while making each Switch destination available to management instrumentation. Co-authored-by: Codex Signed-off-by: Luigi De Masi --- .../apache/camel/management/JmxManagementLifecycleStrategy.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java index ef76c74cad798..fb4243c97a3cd 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java @@ -970,8 +970,10 @@ private void registerPerformanceCounters( // Switch destinations are literal sends rather than nested processor outputs. if (processor instanceof SwitchDefinition sw) { for (var c : sw.getCases()) { + c.prepareToDefinition(); registerPerformanceCounters(route, c.getToDefinition(), registeredCounters); } + sw.prepareOtherwiseDefinition(); if (sw.getOtherwiseDefinition() != null) { registerPerformanceCounters(route, sw.getOtherwiseDefinition(), registeredCounters); }