This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new dce3580e8392 CAMEL-25928: camel-core-model - the body with
variableReceive and variableSend, for any value of the option (#28448)
dce3580e8392 is described below
commit dce3580e8392c782e1957f6ed6372bc3b01338d1
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 25 15:22:05 2026 +0100
CAMEL-25928: camel-core-model - the body with variableReceive and
variableSend, for any value of the option (#28448)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../org/apache/camel/catalog/models/enrich.json | 3 +-
.../org/apache/camel/catalog/models/from.json | 3 +
.../org/apache/camel/catalog/models/marshal.json | 4 +-
.../org/apache/camel/catalog/models/poll.json | 2 +-
.../apache/camel/catalog/models/pollEnrich.json | 2 +-
.../org/apache/camel/catalog/models/to.json | 4 +-
.../org/apache/camel/catalog/models/toD.json | 4 +-
.../org/apache/camel/catalog/models/unmarshal.json | 4 +-
.../java/org/apache/camel/spi/BodyType.java | 6 +-
.../META-INF/org/apache/camel/model/enrich.json | 3 +-
.../META-INF/org/apache/camel/model/from.json | 3 +
.../META-INF/org/apache/camel/model/marshal.json | 4 +-
.../META-INF/org/apache/camel/model/poll.json | 2 +-
.../org/apache/camel/model/pollEnrich.json | 2 +-
.../META-INF/org/apache/camel/model/to.json | 4 +-
.../META-INF/org/apache/camel/model/toD.json | 4 +-
.../META-INF/org/apache/camel/model/unmarshal.json | 4 +-
.../org/apache/camel/model/EnrichDefinition.java | 10 +-
.../org/apache/camel/model/FromDefinition.java | 7 ++
.../org/apache/camel/model/MarshalDefinition.java | 7 +-
.../org/apache/camel/model/PollDefinition.java | 4 +-
.../apache/camel/model/PollEnrichDefinition.java | 5 +-
.../java/org/apache/camel/model/ToDefinition.java | 7 +-
.../apache/camel/model/ToDynamicDefinition.java | 7 +-
.../apache/camel/model/UnmarshalDefinition.java | 7 +-
.../apache/camel/processor/EipBodyTypesTest.java | 112 +++++++++++++++++++++
docs/components/modules/ROOT/examples/js/camel.js | 5 +-
.../user-manual/modules/ROOT/pages/body-types.adoc | 13 ++-
.../dsl/jbang/core/commands/ai/CatalogDocs.java | 6 +-
.../jbang/core/commands/ai/CatalogDocsTest.java | 15 +++
.../camel/dsl/yaml/validator/BodyTypeFlow.java | 18 +++-
.../camel/dsl/yaml/validator/CatalogBodyTypes.java | 29 +++++-
.../camel/dsl/yaml/validator/TextBodyFlow.java | 15 ++-
.../camel/dsl/yaml/validator/BodyTypeFlowTest.java | 59 +++++++++++
.../camel/dsl/yaml/validator/TextBodyFlowTest.java | 75 ++++++++++++++
.../apache/camel/tooling/model/BodyTypeModel.java | 2 +
.../camel/tooling/model/BodyTypeResolver.java | 49 +++++++--
.../camel/tooling/model/BodyTypeResolverTest.java | 28 ++++++
.../camel/maven/packaging/BodyTypesHelper.java | 19 +++-
.../maven/packaging/BodyTypesHelperEipTest.java | 47 +++++++++
.../main/java/org/apache/camel/spi/BodyType.java | 6 +-
41 files changed, 548 insertions(+), 62 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/enrich.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/enrich.json
index 94547eb6eb4b..abde5a4edb71 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/enrich.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/enrich.json
@@ -35,6 +35,7 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipOut": { "dependsOn": [ "aggregationStrategy", "variableReceive" ],
"from": "producerOut", "description": "The reply of the endpoint, with the
default aggregation strategy. With variableReceive the reply goes into the
variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "producerIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "dependsOn": [ "aggregationStrategy" ], "byOption": [ {
"option": "variableReceive", "value": "*", "carriers": [ "unchanged" ] } ],
"from": "producerOut", "description": "The reply of the endpoint, with the
default aggregation strategy. With variableReceive the reply goes into the
variable, and the body stays as it was" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/from.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/from.json
index 8e1b21ae2509..f9efd8e503ca 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/from.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/from.json
@@ -18,5 +18,8 @@
"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" },
"uri": { "index": 3, "kind": "attribute", "displayName": "Uri", "group":
"common", "required": true, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
endpoint URI to consume from." },
"variableReceive": { "index": 4, "kind": "attribute", "displayName":
"Variable Receive", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable to store the received
message body (only body, not headers). This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving." }
+ },
+ "bodyTypes": {
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "null" ] } ], "from": "consumerOut", "description": "The message
the consumer of the endpoint receives. With variableReceive it goes into the
variable, and the route starts with no body" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
index 2f60ea775db4..c090c7d7c85b 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/marshal.json
@@ -23,7 +23,7 @@
"allowNullBody": { "index": 7, "kind": "attribute", "displayName": "Allow
Null Body", "group": "advanced", "label": "advanced", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Indicates whether a null body is skipped instead of marshalled. By default the
data format marshals the null, and what that gives depends on the data format
(JSON gives the text null). When tr [...]
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "marshalIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "marshalOut",
"description": "What the data format writes. With variableReceive the result
goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "marshalIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "marshalOut", "description": "What the
data format writes. With variableReceive the result goes into the variable, and
the body stays as it was" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/poll.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/poll.json
index b4a01d5a501a..1f418afbfe37 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/poll.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/poll.json
@@ -22,6 +22,6 @@
"timeout": { "index": 6, "kind": "attribute", "displayName": "Timeout",
"group": "advanced", "label": "advanced", "required": false, "type":
"duration", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "20000", "description": "Timeout in
millis when polling from the external service. The default value is 20000 (20
seconds)." }
},
"bodyTypes": {
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "consumerOut",
"description": "The message the consumer of the endpoint receives, or null when
none came before the timeout. With variableReceive it goes into the variable,
and the body stays as it was" }
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "consumerOut", "description": "The
message the consumer of the endpoint receives, or null when none came before
the timeout. With variableReceive it goes into the variable, and the body stays
as it was" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pollEnrich.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pollEnrich.json
index 5acfdb349c94..acf5a79841cc 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pollEnrich.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/pollEnrich.json
@@ -34,6 +34,6 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipOut": { "dependsOn": [ "aggregationStrategy", "variableReceive" ],
"from": "consumerOut", "description": "The message the consumer of the endpoint
receives, with the default aggregation strategy, or null when none came before
the timeout. With variableReceive it goes into the variable, and the body stays
as it was" }
+ "eipOut": { "dependsOn": [ "aggregationStrategy" ], "byOption": [ {
"option": "variableReceive", "value": "*", "carriers": [ "unchanged" ] } ],
"from": "consumerOut", "description": "The message the consumer of the endpoint
receives, with the default aggregation strategy, or null when none came before
the timeout. With variableReceive it goes into the variable, and the body stays
as it was" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
index 44c6c96e3af5..816ee7383d87 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
@@ -26,7 +26,7 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "producerIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "producerOut",
"description": "What the producer of the endpoint leaves. With variableReceive
the reply goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "producerIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "producerOut", "description": "What
the producer of the endpoint leaves. With variableReceive the reply goes into
the variable, and the body stays as it was" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
index 4e243c0032ef..82252794c797 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/toD.json
@@ -31,7 +31,7 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "producerIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "producerOut",
"description": "What the producer of the endpoint leaves. With variableReceive
the reply goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "producerIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "producerOut", "description": "What
the producer of the endpoint leaves. With variableReceive the reply goes into
the variable, and the body stays as it was" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
index 795d6c5f9d6e..46f38b474c07 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/unmarshal.json
@@ -23,7 +23,7 @@
"allowNullBody": { "index": 7, "kind": "attribute", "displayName": "Allow
Null Body", "group": "advanced", "label": "advanced", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Indicates whether null is allowed as value of a body to unmarshall." }
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "unmarshalIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "unmarshalOut",
"description": "What the data format reads. With variableReceive the result
goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "unmarshalIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "unmarshalOut", "description": "What
the data format reads. With variableReceive the result goes into the variable,
and the body stays as it was" }
}
}
diff --git
a/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java
b/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java
index 6a68a8cde398..2acad5a8d033 100644
--- a/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java
+++ b/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java
@@ -59,7 +59,7 @@ public @interface BodyType {
String[] dependsOn() default {};
/**
- * The body for other values of an option, such as {@code output=bytes}.
+ * The body for other values of an option, such as {@code output=bytes},
or for any value of it.
*/
When[] byOption() default {};
@@ -103,7 +103,9 @@ public @interface BodyType {
String option();
/**
- * The value of the option.
+ * The value of the option. {@code *} is any value: the body when the
option is set at all, such as
+ * {@code variableReceive}, whose reply goes into the variable and
leaves the body as it was. Such a body holds
+ * whatever the other options and {@link BodyType#from()} say.
*/
String value();
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/enrich.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/enrich.json
index 94547eb6eb4b..abde5a4edb71 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/enrich.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/enrich.json
@@ -35,6 +35,7 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipOut": { "dependsOn": [ "aggregationStrategy", "variableReceive" ],
"from": "producerOut", "description": "The reply of the endpoint, with the
default aggregation strategy. With variableReceive the reply goes into the
variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "producerIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "dependsOn": [ "aggregationStrategy" ], "byOption": [ {
"option": "variableReceive", "value": "*", "carriers": [ "unchanged" ] } ],
"from": "producerOut", "description": "The reply of the endpoint, with the
default aggregation strategy. With variableReceive the reply goes into the
variable, and the body stays as it was" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/from.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/from.json
index 8e1b21ae2509..f9efd8e503ca 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/from.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/from.json
@@ -18,5 +18,8 @@
"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" },
"uri": { "index": 3, "kind": "attribute", "displayName": "Uri", "group":
"common", "required": true, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description": "The
endpoint URI to consume from." },
"variableReceive": { "index": 4, "kind": "attribute", "displayName":
"Variable Receive", "group": "common", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "To use a variable to store the received
message body (only body, not headers). This makes it handy to use variables for
user data and to easily control what data to use for sending and receiving." }
+ },
+ "bodyTypes": {
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "null" ] } ], "from": "consumerOut", "description": "The message
the consumer of the endpoint receives. With variableReceive it goes into the
variable, and the route starts with no body" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
index 2f60ea775db4..c090c7d7c85b 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/marshal.json
@@ -23,7 +23,7 @@
"allowNullBody": { "index": 7, "kind": "attribute", "displayName": "Allow
Null Body", "group": "advanced", "label": "advanced", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Indicates whether a null body is skipped instead of marshalled. By default the
data format marshals the null, and what that gives depends on the data format
(JSON gives the text null). When tr [...]
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "marshalIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "marshalOut",
"description": "What the data format writes. With variableReceive the result
goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "marshalIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "marshalOut", "description": "What the
data format writes. With variableReceive the result goes into the variable, and
the body stays as it was" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/poll.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/poll.json
index b4a01d5a501a..1f418afbfe37 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/poll.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/poll.json
@@ -22,6 +22,6 @@
"timeout": { "index": 6, "kind": "attribute", "displayName": "Timeout",
"group": "advanced", "label": "advanced", "required": false, "type":
"duration", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "20000", "description": "Timeout in
millis when polling from the external service. The default value is 20000 (20
seconds)." }
},
"bodyTypes": {
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "consumerOut",
"description": "The message the consumer of the endpoint receives, or null when
none came before the timeout. With variableReceive it goes into the variable,
and the body stays as it was" }
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "consumerOut", "description": "The
message the consumer of the endpoint receives, or null when none came before
the timeout. With variableReceive it goes into the variable, and the body stays
as it was" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pollEnrich.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pollEnrich.json
index 5acfdb349c94..acf5a79841cc 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pollEnrich.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/pollEnrich.json
@@ -34,6 +34,6 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipOut": { "dependsOn": [ "aggregationStrategy", "variableReceive" ],
"from": "consumerOut", "description": "The message the consumer of the endpoint
receives, with the default aggregation strategy, or null when none came before
the timeout. With variableReceive it goes into the variable, and the body stays
as it was" }
+ "eipOut": { "dependsOn": [ "aggregationStrategy" ], "byOption": [ {
"option": "variableReceive", "value": "*", "carriers": [ "unchanged" ] } ],
"from": "consumerOut", "description": "The message the consumer of the endpoint
receives, with the default aggregation strategy, or null when none came before
the timeout. With variableReceive it goes into the variable, and the body stays
as it was" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/to.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/to.json
index 44c6c96e3af5..816ee7383d87 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/to.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/to.json
@@ -26,7 +26,7 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "producerIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "producerOut",
"description": "What the producer of the endpoint leaves. With variableReceive
the reply goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "producerIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "producerOut", "description": "What
the producer of the endpoint leaves. With variableReceive the reply goes into
the variable, and the body stays as it was" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
index 4e243c0032ef..82252794c797 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/toD.json
@@ -31,7 +31,7 @@
"CamelToEndpoint": { "index": 0, "kind": "exchangeProperty",
"displayName": "To Endpoint", "label": "producer", "required": false,
"javaType": "String", "deprecated": false, "autowired": false, "secret": false,
"description": "Endpoint URI where this Exchange is being sent to" }
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "producerIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "producerOut",
"description": "What the producer of the endpoint leaves. With variableReceive
the reply goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "producerIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "producerOut", "description": "What
the producer of the endpoint leaves. With variableReceive the reply goes into
the variable, and the body stays as it was" }
}
}
diff --git
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/unmarshal.json
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/unmarshal.json
index 795d6c5f9d6e..46f38b474c07 100644
---
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/unmarshal.json
+++
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/unmarshal.json
@@ -23,7 +23,7 @@
"allowNullBody": { "index": 7, "kind": "attribute", "displayName": "Allow
Null Body", "group": "advanced", "label": "advanced", "required": false,
"type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description":
"Indicates whether null is allowed as value of a body to unmarshall." }
},
"bodyTypes": {
- "eipIn": { "dependsOn": [ "variableSend" ], "from": "unmarshalIn" },
- "eipOut": { "dependsOn": [ "variableReceive" ], "from": "unmarshalOut",
"description": "What the data format reads. With variableReceive the result
goes into the variable, and the body stays as it was" }
+ "eipIn": { "byOption": [ { "option": "variableSend", "value": "*",
"carriers": [ "any" ] } ], "from": "unmarshalIn", "description": "With
variableSend the variable is sent, and the body is not read" },
+ "eipOut": { "byOption": [ { "option": "variableReceive", "value": "*",
"carriers": [ "unchanged" ] } ], "from": "unmarshalOut", "description": "What
the data format reads. With variableReceive the result goes into the variable,
and the body stays as it was" }
}
}
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/EnrichDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/EnrichDefinition.java
index 57dfbc6016da..c37c8a0ce3d0 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/EnrichDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/EnrichDefinition.java
@@ -25,6 +25,7 @@ import jakarta.xml.bind.annotation.XmlTransient;
import org.apache.camel.AggregationStrategy;
import org.apache.camel.model.language.ExpressionDefinition;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.DslArg;
@@ -41,8 +42,13 @@ import org.apache.camel.spi.annotations.DslArg;
@XmlRootElement(name = "enrich")
@XmlAccessorType(XmlAccessType.FIELD)
@DslArg(exclude = "expression")
-@BodyTypes(eipOut = @BodyType(from = "producerOut",
- dependsOn = { "aggregationStrategy",
"variableReceive" },
+@BodyTypes(eipIn = @BodyType(from = "producerIn",
+ byOption = @BodyType.When(option =
"variableSend", value = "*", carriers = Carrier.ANY),
+ description = "With variableSend the variable is
sent, and the body is not read"),
+ eipOut = @BodyType(from = "producerOut",
+ dependsOn = "aggregationStrategy",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "The reply of the endpoint, with
the default aggregation strategy. With"
+ " variableReceive the reply goes
into the variable, and the body stays as it was"))
public class EnrichDefinition extends ExpressionNode
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
index 6459cf1f90d7..1a1ad7fe9645 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
@@ -25,6 +25,9 @@ import jakarta.xml.bind.annotation.XmlTransient;
import org.apache.camel.Endpoint;
import org.apache.camel.NamedNode;
import org.apache.camel.builder.EndpointConsumerBuilder;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.Metadata;
/**
@@ -35,6 +38,10 @@ import org.apache.camel.spi.Metadata;
description = "Defines the consumer endpoint that acts as the input
source for a route")
@XmlRootElement(name = "from")
@XmlAccessorType(XmlAccessType.FIELD)
+@BodyTypes(eipOut = @BodyType(from = "consumerOut",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*", carriers = Carrier.NULL),
+ description = "The message the consumer of the
endpoint receives. With variableReceive it goes"
+ + " into the variable, and the
route starts with no body"))
public class FromDefinition extends
OptionalIdentifiedDefinition<FromDefinition> implements
EndpointRequiredDefinition {
@XmlTransient
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
index 3b896c291eaf..8cf566abe3a2 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/MarshalDefinition.java
@@ -74,6 +74,7 @@ import org.apache.camel.model.dataformat.YAMLDataFormat;
import org.apache.camel.model.dataformat.ZipDeflaterDataFormat;
import org.apache.camel.model.dataformat.ZipFileDataFormat;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.spi.Metadata;
@@ -87,9 +88,11 @@ import org.apache.camel.spi.Metadata;
@XmlRootElement(name = "marshal")
@XmlAccessorType(XmlAccessType.FIELD)
@BodyTypes(eipIn = @BodyType(from = "marshalIn",
- dependsOn = "variableSend"),
+ byOption = @BodyType.When(option =
"variableSend", value = "*", carriers = Carrier.ANY),
+ description = "With variableSend the variable is
sent, and the body is not read"),
eipOut = @BodyType(from = "marshalOut",
- dependsOn = "variableReceive",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "What the data format writes. With
variableReceive the result goes into the"
+ " variable, and the body stays
as it was"))
public class MarshalDefinition extends NoOutputDefinition<MarshalDefinition>
implements DataFormatDefinitionAware {
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/PollDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/PollDefinition.java
index 917e4897d691..c2271cc5f455 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/PollDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/PollDefinition.java
@@ -25,6 +25,7 @@ import jakarta.xml.bind.annotation.XmlTransient;
import org.apache.camel.Endpoint;
import org.apache.camel.builder.EndpointConsumerBuilder;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.DslArg;
@@ -40,7 +41,8 @@ import org.apache.camel.util.URISupport;
@XmlRootElement(name = "poll")
@XmlAccessorType(XmlAccessType.FIELD)
@BodyTypes(eipOut = @BodyType(from = "consumerOut",
- dependsOn = "variableReceive",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "The message the consumer of the
endpoint receives, or null when none came before"
+ " the timeout. With
variableReceive it goes into the variable, and the body stays"
+ " as it was"))
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/PollEnrichDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/PollEnrichDefinition.java
index 255532472545..5d4693fe5359 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/PollEnrichDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/PollEnrichDefinition.java
@@ -25,6 +25,7 @@ import jakarta.xml.bind.annotation.XmlTransient;
import org.apache.camel.AggregationStrategy;
import org.apache.camel.model.language.ExpressionDefinition;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.DslArg;
@@ -42,7 +43,9 @@ import org.apache.camel.spi.annotations.DslArg;
@XmlAccessorType(XmlAccessType.FIELD)
@DslArg(exclude = "expression")
@BodyTypes(eipOut = @BodyType(from = "consumerOut",
- dependsOn = { "aggregationStrategy",
"variableReceive" },
+ dependsOn = "aggregationStrategy",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "The message the consumer of the
endpoint receives, with the default aggregation"
+ " strategy, or null when none
came before the timeout. With variableReceive it"
+ " goes into the variable, and
the body stays as it was"))
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
index 0ff42dd476ef..cbb669ee839f 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
@@ -25,6 +25,7 @@ import org.apache.camel.Endpoint;
import org.apache.camel.ExchangePattern;
import org.apache.camel.builder.EndpointProducerBuilder;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.DslArg;
@@ -38,9 +39,11 @@ import org.apache.camel.spi.annotations.DslArg;
@XmlRootElement(name = "to")
@XmlAccessorType(XmlAccessType.FIELD)
@BodyTypes(eipIn = @BodyType(from = "producerIn",
- dependsOn = "variableSend"),
+ byOption = @BodyType.When(option =
"variableSend", value = "*", carriers = Carrier.ANY),
+ description = "With variableSend the variable is
sent, and the body is not read"),
eipOut = @BodyType(from = "producerOut",
- dependsOn = "variableReceive",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "What the producer of the endpoint
leaves. With variableReceive the reply goes"
+ " into the variable, and the
body stays as it was"))
public class ToDefinition extends SendDefinition<ToDefinition> {
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
index ebf08a148a8e..17978443578a 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDynamicDefinition.java
@@ -27,6 +27,7 @@ import org.apache.camel.Message;
import org.apache.camel.builder.EndpointProducerBuilder;
import org.apache.camel.spi.AsEndpointUri;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.DslArg;
@@ -41,9 +42,11 @@ import org.apache.camel.spi.annotations.DslArg;
@XmlRootElement(name = "toD")
@XmlAccessorType(XmlAccessType.FIELD)
@BodyTypes(eipIn = @BodyType(from = "producerIn",
- dependsOn = "variableSend"),
+ byOption = @BodyType.When(option =
"variableSend", value = "*", carriers = Carrier.ANY),
+ description = "With variableSend the variable is
sent, and the body is not read"),
eipOut = @BodyType(from = "producerOut",
- dependsOn = "variableReceive",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "What the producer of the endpoint
leaves. With variableReceive the reply goes"
+ " into the variable, and the
body stays as it was"))
public class ToDynamicDefinition extends
NoOutputDefinition<ToDynamicDefinition> {
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
index a684f380f56b..2a153234da99 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/UnmarshalDefinition.java
@@ -74,6 +74,7 @@ import org.apache.camel.model.dataformat.YAMLDataFormat;
import org.apache.camel.model.dataformat.ZipDeflaterDataFormat;
import org.apache.camel.model.dataformat.ZipFileDataFormat;
import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
import org.apache.camel.spi.DataFormatName;
import org.apache.camel.spi.Metadata;
@@ -87,9 +88,11 @@ import org.apache.camel.spi.Metadata;
@XmlRootElement(name = "unmarshal")
@XmlAccessorType(XmlAccessType.FIELD)
@BodyTypes(eipIn = @BodyType(from = "unmarshalIn",
- dependsOn = "variableSend"),
+ byOption = @BodyType.When(option =
"variableSend", value = "*", carriers = Carrier.ANY),
+ description = "With variableSend the variable is
sent, and the body is not read"),
eipOut = @BodyType(from = "unmarshalOut",
- dependsOn = "variableReceive",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
description = "What the data format reads. With
variableReceive the result goes into the"
+ " variable, and the body stays
as it was"))
public class UnmarshalDefinition extends
NoOutputDefinition<UnmarshalDefinition> implements DataFormatDefinitionAware {
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/EipBodyTypesTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/EipBodyTypesTest.java
index 343c6d37e6e2..22f94d4196a2 100644
---
a/core/camel-core/src/test/java/org/apache/camel/processor/EipBodyTypesTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/EipBodyTypesTest.java
@@ -18,7 +18,9 @@ package org.apache.camel.processor;
import java.io.File;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.Reader;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@@ -49,15 +51,21 @@ import org.apache.camel.model.ClaimCheckOperation;
import org.apache.camel.model.ConvertBodyDefinition;
import org.apache.camel.model.ConvertHeaderDefinition;
import org.apache.camel.model.ConvertVariableDefinition;
+import org.apache.camel.model.DataFormatDefinition;
import org.apache.camel.model.DelayDefinition;
+import org.apache.camel.model.EnrichDefinition;
import org.apache.camel.model.FilterDefinition;
+import org.apache.camel.model.FromDefinition;
import org.apache.camel.model.IdempotentConsumerDefinition;
import org.apache.camel.model.LoadBalanceDefinition;
import org.apache.camel.model.LogDefinition;
import org.apache.camel.model.LoopDefinition;
+import org.apache.camel.model.MarshalDefinition;
import org.apache.camel.model.MulticastDefinition;
import org.apache.camel.model.PipelineDefinition;
import org.apache.camel.model.PolicyDefinition;
+import org.apache.camel.model.PollDefinition;
+import org.apache.camel.model.PollEnrichDefinition;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.RemoveHeaderDefinition;
import org.apache.camel.model.RemoveHeadersDefinition;
@@ -79,16 +87,21 @@ import org.apache.camel.model.StopDefinition;
import org.apache.camel.model.ThreadsDefinition;
import org.apache.camel.model.ThrottleDefinition;
import org.apache.camel.model.ThrowExceptionDefinition;
+import org.apache.camel.model.ToDefinition;
+import org.apache.camel.model.ToDynamicDefinition;
import org.apache.camel.model.TransformDefinition;
import org.apache.camel.model.TryDefinition;
+import org.apache.camel.model.UnmarshalDefinition;
import org.apache.camel.model.ValidateDefinition;
import org.apache.camel.model.WireTapDefinition;
import org.apache.camel.model.language.ConstantExpression;
import org.apache.camel.spi.BodyType;
import org.apache.camel.spi.BodyType.Carrier;
import org.apache.camel.spi.BodyTypes;
+import org.apache.camel.spi.DataFormat;
import org.apache.camel.spi.Policy;
import
org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository;
+import org.apache.camel.support.service.ServiceSupport;
import org.junit.jupiter.api.Test;
import static org.apache.camel.builder.Builder.body;
@@ -97,6 +110,7 @@ import static org.apache.camel.builder.Builder.simple;
import static org.awaitility.Awaitility.await;
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.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -240,6 +254,69 @@ public class EipBodyTypesTest extends ContextTestSupport {
assertJavaType(run(r -> r.setBody(five), new Order()), Long.class);
}
+ @Test
+ public void variableReceiveKeepsTheBody() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:reply").setBody(constant("reply"));
+ }
+ });
+ context.start();
+ anyValue(ToDefinition.class, r -> r.toV("language:constant:reply",
null, "v"));
+ // toD takes language: as the language of its uri
+ anyValue(ToDynamicDefinition.class,
+ r -> r.setVariable("name",
constant("reply")).toD("direct:${variable.name}", null, "v"));
+ anyValue(EnrichDefinition.class, r ->
r.enrich().constant("language:constant:reply").variableReceive("v"));
+ // the reply goes into the variable, whatever the aggregation strategy
does
+ anyValue(EnrichDefinition.class, r ->
r.enrich().constant("language:constant:reply")
+
.aggregationStrategy(AggregationStrategies.useLatest()).variableReceive("v"));
+ template.sendBody("seda:polled", "reply");
+ anyValue(PollEnrichDefinition.class,
+ r ->
r.pollEnrich().constant("seda:polled").timeout(5000).variableReceive("v"));
+ template.sendBody("seda:polled", "reply");
+ anyValue(PollDefinition.class, r -> r.pollV("seda:polled", 5000, "v"));
+ anyValue(MarshalDefinition.class,
+ r -> r.addOutput(new MarshalDefinition(new
DataFormatDefinition(new Reply())).variableReceive("v")));
+ // unmarshal reads the body as a stream
+ anyValue(UnmarshalDefinition.class,
+ r -> r.addOutput(new UnmarshalDefinition(new
DataFormatDefinition(new Reply())).variableReceive("v")),
+ "Hello");
+ }
+
+ @Test
+ public void variableSendSendsTheVariable() throws Exception {
+ for (Class<?> model : List.of(ToDefinition.class,
ToDynamicDefinition.class, EnrichDefinition.class,
+ MarshalDefinition.class, UnmarshalDefinition.class)) {
+ assertEquals(Carrier.ANY, anyValueOf(bodyTypes(model).eipIn(),
"variableSend", model), model.getSimpleName());
+ }
+ Exchange out = run(r -> r.setVariable("v",
constant("sent")).toV("language:simple:${body}", "v", null), new Order());
+ // the endpoint got the variable, not the body
+ assertEquals("sent", out.getMessage().getBody());
+ out = run(r -> r.setVariable("v",
constant("sent")).enrich().constant("language:simple:${body}").variableSend("v"),
+ new Order());
+ assertEquals("sent", out.getMessage().getBody());
+ }
+
+ @Test
+ public void variableReceiveOfFromStartsWithNoBody() throws Exception {
+ assertEquals(Carrier.NULL,
anyValueOf(bodyTypes(FromDefinition.class).eipOut(), "variableReceive",
+ FromDefinition.class));
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ fromV("direct:fromV",
"v").process(EipBodyTypesTest.this::inside);
+ }
+ });
+ context.start();
+ Order body = new Order();
+ inside.clear();
+ Exchange out = template.send("direct:fromV", e ->
e.getMessage().setBody(body));
+ assertEquals(1, inside.size());
+ assertNull(inside.get(0));
+ assertSame(body, out.getVariable("v"));
+ }
+
// the checks of a side
private void unchanged(Class<?> model, Consumer<ProcessorDefinition<?>>
eip) throws Exception {
@@ -249,6 +326,28 @@ public class EipBodyTypesTest extends ContextTestSupport {
assertEquals(Carrier.UNCHANGED, bodyTypes(model).eipOut().value()[0],
model.getSimpleName());
}
+ /** The EIP with variableReceive set: the body stays as it was, and the
reply is in the variable. */
+ private void anyValue(Class<?> model, Consumer<ProcessorDefinition<?>>
eip) throws Exception {
+ anyValue(model, eip, new Order());
+ }
+
+ private void anyValue(Class<?> model, Consumer<ProcessorDefinition<?>>
eip, Object body) throws Exception {
+ assertEquals(Carrier.UNCHANGED, anyValueOf(bodyTypes(model).eipOut(),
"variableReceive", model),
+ model.getSimpleName());
+ Exchange out = run(eip, body);
+ assertNull(out.getException(), model.getSimpleName());
+ assertSame(body, out.getMessage().getBody(), model.getSimpleName() + "
with variableReceive keeps the body");
+ assertEquals("reply", out.getVariable("v", String.class),
model.getSimpleName());
+ }
+
+ private static Carrier anyValueOf(BodyType side, String option, Class<?>
model) {
+ return Arrays.stream(side.byOption())
+ .filter(w -> w.option().equals(option) &&
w.value().equals("*"))
+ .map(w -> w.carriers()[0])
+ .findFirst()
+ .orElseThrow(() -> new AssertionError(model.getSimpleName() +
" has no body for any value of " + option));
+ }
+
private void block(Class<?> model, Block eip) throws Exception {
Order body = new Order();
inside.clear();
@@ -371,6 +470,19 @@ public class EipBodyTypesTest extends ContextTestSupport {
static final String STEPS = "steps";
}
+ /** A data format that marshals and unmarshals anything to reply. */
+ private static final class Reply extends ServiceSupport implements
DataFormat {
+ @Override
+ public void marshal(Exchange exchange, Object graph, OutputStream
stream) throws Exception {
+ stream.write("reply".getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public Object unmarshal(Exchange exchange, InputStream stream) {
+ return "reply";
+ }
+ }
+
/** A body that no converter knows: an EIP that keeps it gives the same
instance. */
private static final class Order {
}
diff --git a/docs/components/modules/ROOT/examples/js/camel.js
b/docs/components/modules/ROOT/examples/js/camel.js
index 1e23e9fbaede..fe0ee8e7c93a 100644
--- a/docs/components/modules/ROOT/examples/js/camel.js
+++ b/docs/components/modules/ROOT/examples/js/camel.js
@@ -98,7 +98,10 @@ module.exports = {
parts.push(`With \`${value.javaTypeOption}\` set: an object of the class
it names, or null.`)
}
if (value.byOption && value.byOption.length) {
- parts.push(value.byOption.map((option) => `* With
\`${option.option}=${option.value}\`: `
+ // the value * is any value of the option
+ parts.push(value.byOption.map((option) => (option.value === '*'
+ ? `* With \`${option.option}\` set: `
+ : `* With \`${option.option}=${option.value}\`: `)
+ module.exports.bodyCarriers(option)
+ (option.javaTypeOption ? `, or an object of the class
\`${option.javaTypeOption}\` names when it is set` : ''))
.join('\n'))
diff --git a/docs/user-manual/modules/ROOT/pages/body-types.adoc
b/docs/user-manual/modules/ROOT/pages/body-types.adoc
index c12adb664145..edf7b4c54156 100644
--- a/docs/user-manual/modules/ROOT/pages/body-types.adoc
+++ b/docs/user-manual/modules/ROOT/pages/body-types.adoc
@@ -203,10 +203,16 @@ instead of copying the body types of every data format,
component and language:
[source,json]
----
-"eipOut": { "from": "marshalOut", "dependsOn": [ "variableReceive" ],
+"eipOut": { "from": "marshalOut",
+ "byOption": [ { "option": "variableReceive", "value": "*", "carriers": [
"unchanged" ] } ],
"description": "What the data format writes. With variableReceive the result
goes into the variable, and the body stays as it was" }
----
+The value `*` is any value of the option: with `variableReceive` set to any
name, the body stays as it was, whatever
+the data format writes. In the same way, with `variableSend` a step sends the
variable instead of the body, and does
+not need the body (`any`). A route starts with the body of the consumer of its
`from` endpoint, and with no body (`null`) when
+`from` has `variableReceive`.
+
An EIP with steps of its own also says what they start with. The steps of a
`split` start with each part, and after
it the body is as it was before the split:
@@ -357,7 +363,7 @@ for a side with `from`.
| `dependsOn` | array of strings | The options the body depends on. The side
holds while they keep their default values.
When the step sets one, only a `byOption` entry for that value says the body;
without one it is not known.
| `byOption` | array of objects | The body for a value of an option: `option`,
`value`, `carriers`, and optionally
-`javaType` and `javaTypeOption`.
+`javaType` and `javaTypeOption`. The value `*` is any value: the body when the
step sets the option at all.
| `javaTypeOption` | string | The option that names the class of the body,
such as `resultType`. When the step sets
it, the body is an object of that class, or null.
| `from` | string | For an EIP, the side of another model the body is:
`expressionIn`, `expressionOut` (the language
@@ -372,6 +378,9 @@ steps end with).
To know the body a step leaves, or the bodies it needs, read its side with the
options the step sets, in this order:
. *No side:* not known.
+. *A `byOption` entry for any value (`*`) whose option the step sets:* its
carriers, whatever the rest of the side
+says, `from` and `dependsOn` too. When two of them say different bodies: not
known. When the step does not set the
+option, but it can be set elsewhere: not known.
. *A side with `from`:* when the step sets an option of `dependsOn`, not
known. Otherwise, read the side `from` names on
the model the step uses (the language of its expression, its data format or
its endpoint), with the options of that
model. `steps` is the body the steps of the EIP end with.
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
index 52629a3fb29e..bddd39fcf548 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
@@ -1671,8 +1671,10 @@ public final class CatalogDocs {
if (!side.getByOption().isEmpty()) {
List<String> values = new ArrayList<>();
for (BodyTypeModel.OptionBodyTypeModel option :
side.getByOption()) {
- String value = option.getOption() + "=" +
option.getValue() + ": "
- + carriers(option.getCarriers(),
option.getJavaType());
+ // * is any value of the option, as variableReceive
(CAMEL-25928)
+ String value = option.getOption()
+ +
(BodyTypeModel.ANY_VALUE.equals(option.getValue()) ? " set" : "=" +
option.getValue())
+ + ": " + carriers(option.getCarriers(),
option.getJavaType());
if (option.getJavaTypeOption() != null) {
value += ", or an object of the class " +
option.getJavaTypeOption() + " names when it is set";
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
index 93b128e388a4..f7ca5818d8c7 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
@@ -198,6 +198,21 @@ class CatalogDocsTest {
assertEquals("text. With output=bytes: unknown",
lines.getString(BodyTypeModel.PRODUCER_OUT));
}
+ @Test
+ void aBodyForAnyValueOfAnOption() {
+ // CAMEL-25928: with variableReceive set, the body stays as it was,
whatever the value
+ BodyTypeModel side = new BodyTypeModel();
+ side.setFrom(BodyTypeModel.PRODUCER_OUT);
+ BodyTypeModel.OptionBodyTypeModel variable = new
BodyTypeModel.OptionBodyTypeModel();
+ variable.setOption("variableReceive");
+ variable.setValue(BodyTypeModel.ANY_VALUE);
+ variable.setCarriers(List.of("unchanged"));
+ side.getByOption().add(variable);
+ JsonObject lines = CatalogDocs.bodyTypes(Map.of(BodyTypeModel.EIP_OUT,
side));
+ assertEquals("what the producer of its endpoint leaves. With
variableReceive set: unchanged",
+ lines.getString(BodyTypeModel.EIP_OUT));
+ }
+
@Test
void anOptionValueCanNameTheClassOfTheBody() throws Exception {
// CAMEL-25928: the class of the one row of sql with
outputType=SelectOne
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlow.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlow.java
index 73756a5e9fc8..a3824b0512db 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlow.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlow.java
@@ -129,6 +129,10 @@ final class BodyTypeFlow {
if (scheme == null) {
return false;
}
+ // with variableReceive the message goes into the variable, and the
route starts with no body
+ if (bodyTypes.startsWithNoBody(from(route.node()))) {
+ return true;
+ }
// a consumer that produces no body of its own, as the catalog says:
timer, cron, scheduler, quartz
CatalogBodyTypes.Answer out = bodyTypes.consumerOut(route.fromUri(),
from(route.node()));
if (out != null && out.none()) {
@@ -198,7 +202,7 @@ final class BodyTypeFlow {
return null;
}
for (JsonNode step : steps) {
- String reader = readsTheBody(step);
+ String reader = readsTheBody(step, bodyTypes);
boolean produces = producesTheBody(step, bodyTypes);
if (reader != null && !produces) {
return reader;
@@ -222,11 +226,14 @@ final class BodyTypeFlow {
return it.hasNext() ? it.next() : "";
}
- /** What reads the body in this step: an expression language, or the step
itself. Null when nothing does. */
- private static String readsTheBody(JsonNode step) {
+ /**
+ * What reads the body in this step: an expression language, or the step
itself, unless it works on a variable
+ * (variableSend). Null when nothing does.
+ */
+ private static String readsTheBody(JsonNode step, CatalogBodyTypes
bodyTypes) {
for (var it = step.fieldNames(); it.hasNext();) {
String name = it.next();
- if (STEPS_THAT_NEED_THE_BODY.contains(name)) {
+ if (STEPS_THAT_NEED_THE_BODY.contains(name) &&
!bodyTypes.needsNoBody(name, step.get(name))) {
return name;
}
String reader = readerIn(step.get(name));
@@ -258,7 +265,8 @@ final class BodyTypeFlow {
}
for (var it = node.fieldNames(); it.hasNext();) {
String name = it.next();
- if (bodyTypes.isEip(name) && bodyTypes.maySetTheBody(name)) {
+ if (bodyTypes.isEip(name) && bodyTypes.maySetTheBody(name)
+ && !bodyTypes.keepsTheBody(name, node.get(name))) {
return true;
}
if (producesTheBody(node.get(name), bodyTypes)) {
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
index 20df56539e19..bb8988cefb55 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/CatalogBodyTypes.java
@@ -311,6 +311,26 @@ final class CatalogBodyTypes {
return steps != null && steps.unchanged();
}
+ /**
+ * Whether the EIP sends or reads a variable instead of the body, as with
variableSend, for the options the step
+ * sets: it needs no body then.
+ */
+ boolean needsNoBody(String name, JsonNode step) {
+ Answer in = eipSide(name, step, BodyTypeModel.EIP_IN);
+ return in != null && in.carriers().equals(List.of("any"));
+ }
+
+ /**
+ * Whether a route starts with no body, as with variableReceive on its
from, which puts the message the consumer
+ * receives into the variable.
+ */
+ boolean startsWithNoBody(JsonNode from) {
+ EipModel model = model("from");
+ BodyTypeModel out = model != null ?
model.getBodyType(BodyTypeModel.EIP_OUT) : null;
+ Answer answer = out != null ? answer(out, values(from), Set.of()) :
null;
+ return answer != null && answer.none();
+ }
+
/**
* Whether the EIP may put a body in the message, whatever its options:
everything but leaving the body as it was,
* or the body its own steps leave, which are looked at by themselves. An
EIP the catalog does not say counts, as
@@ -348,6 +368,12 @@ final class CatalogBodyTypes {
}
private EipModel eip(String name) {
+ EipModel model = model(name);
+ // the languages and data formats have models too, but no input: they
are not steps, nor is from
+ return model != null && model.isInput() ? model : null;
+ }
+
+ private EipModel model(String name) {
if (name == null) {
return null;
}
@@ -359,8 +385,7 @@ final class CatalogBodyTypes {
model = other;
}
}
- // the languages and data formats have models too, but no input:
they are not steps
- return Optional.ofNullable(model != null && model.isInput() ?
model : null);
+ return Optional.ofNullable(model);
}).orElse(null);
}
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java
index e6a0e07f5a6e..10235722e1bb 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java
@@ -224,8 +224,10 @@ final class TextBodyFlow {
String uri = endpointOf(from);
String consumer = scheme(uri);
boolean post = NO_HTTP_HEADERS.contains(consumer) &&
!setsHttpHeaders(steps);
- Body start = uri != null ? consumerBody(uri, from, bodyTypes) :
null;
- if (start == null && uri != null) {
+ // with variableReceive the message goes into the variable, and
the route starts with no body
+ boolean variable = bodyTypes.startsWithNoBody(from);
+ Body start = uri != null && !variable ? consumerBody(uri, from,
bodyTypes) : null;
+ if (start == null && uri != null && !variable) {
// what the consumer gave in a running integration of the route
start = observed(observed, ProjectFacts.consumerKey(uri),
"from: " + normalize(uri) + " gave");
}
@@ -388,6 +390,10 @@ final class TextBodyFlow {
/** What is known about the body after a step. */
private static Body after(String name, JsonNode value, Body body, Walk w) {
+ if (w.bodyTypes().keepsTheBody(name, value)) {
+ // log, setHeader, and a to or an unmarshal whose result goes into
a variable (variableReceive)
+ return body;
+ }
switch (name) {
case "setBody", "transform" -> {
Text text = setBodyText(value);
@@ -436,7 +442,7 @@ final class TextBodyFlow {
return returned;
}
default -> {
- return w.bodyTypes().keepsTheBody(name, value) ? body : null;
+ return null;
}
}
}
@@ -675,7 +681,8 @@ final class TextBodyFlow {
* producer sends the body, and a REST binding may write it for
rest-openapi: they keep their own rule.
*/
private static Need needsText(String name, JsonNode value, Walk w) {
- if (!"to".equals(name) && !"toD".equals(name)) {
+ if (!"to".equals(name) && !"toD".equals(name) ||
w.bodyTypes().needsNoBody(name, value)) {
+ // with variableSend the variable is sent, not the body
return null;
}
String uri = endpointOf(value);
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlowTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlowTest.java
index 634a1e399960..e9f96d300d2b 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlowTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/BodyTypeFlowTest.java
@@ -287,4 +287,63 @@ public class BodyTypeFlowTest {
""";
assertThat(messages(logged)).anyMatch(m -> m.contains("reads the
message body"));
}
+
+ @Test
+ public void testVariableReceiveOfFromStartsWithNoBody() {
+ // the body of the caller goes into the variable: the route starts
with none
+ String yaml = """
+ - route:
+ id: caller
+ from:
+ uri: file:inbox
+ steps:
+ - to:
+ uri: direct:lookup
+ - route:
+ id: lookup
+ from:
+ uri: direct:lookup
+ variableReceive: stock
+ steps:
+ - setBody:
+ expression:
+ jsonpath:
+ expression: "$.sku"
+ """;
+ assertThat(messages(yaml)).anyMatch(m -> m.contains("reads the message
body"));
+ }
+
+ @Test
+ public void testAToWithVariableReceiveSetsNoBody() {
+ // the reply goes into the variable, so the timer route still has no
body
+ String yaml = """
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - to:
+ uri: language:constant:stock
+ variableReceive: stock
+ - setBody:
+ expression:
+ jsonpath:
+ expression: "$.sku"
+ """;
+ assertThat(messages(yaml)).anyMatch(m -> m.contains("reads the message
body"));
+ // marshal of a variable needs no body
+ String marshal = """
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - setVariable:
+ name: order
+ constant: "{}"
+ - marshal:
+ json: {}
+ variableSend: order
+ variableReceive: json
+ """;
+ assertThat(messages(marshal)).noneMatch(m -> m.contains("works on the
message body"));
+ }
}
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
index b75d8675bcef..8dd915ab8e78 100644
---
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
@@ -2355,4 +2355,79 @@ public class TextBodyFlowTest {
""";
assertThat(lines(aggregating)).isEmpty();
}
+
+ @Test
+ public void testVariableReceiveKeepsTheBody() {
+ // the result of the unmarshal goes into the variable, and the body
stays text
+ String yaml = """
+ - route:
+ from:
+ uri: file:inbox
+ steps:
+ - unmarshal:
+ json: {}
+ variableReceive: order
+ - to: file:outbox
+ """;
+ assertThat(lines(yaml)).isEmpty();
+ // the reply of the to goes into the variable, and the body stays
parsed data
+ String parsed = """
+ - route:
+ from:
+ uri: file:inbox
+ steps:
+ - unmarshal:
+ json: {}
+ - to:
+ uri: language:constant:reply
+ variableReceive: reply
+ - to: file:outbox
+ """;
+ assertThat(lines(parsed)).singleElement()
+ .satisfies(m -> assertThat(m).contains("to: file needs the
body as text"));
+ }
+
+ @Test
+ public void testVariableSendSendsTheVariable() {
+ // the to writes the variable, not the parsed body
+ String yaml = """
+ - route:
+ from:
+ uri: file:inbox
+ steps:
+ - unmarshal:
+ json: {}
+ - to:
+ uri: file:outbox
+ variableSend: text
+ """;
+ assertThat(lines(yaml)).isEmpty();
+ }
+
+ @Test
+ public void testVariableReceiveOfFromStartsWithNoBody() {
+ // the file goes into the variable, so the body is not the text of the
file
+ String yaml = """
+ - route:
+ from:
+ uri: file:inbox
+ variableReceive: file
+ steps:
+ - setHeader:
+ name: status
+ simple: "${body[status]}"
+ """;
+ assertThat(lines(yaml)).isEmpty();
+ String text = """
+ - route:
+ from:
+ uri: file:inbox
+ steps:
+ - setHeader:
+ name: status
+ simple: "${body[status]}"
+ """;
+ assertThat(lines(text)).singleElement()
+ .satisfies(m -> assertThat(m).contains("simple reads a field
of the body (${body[status]})"));
+ }
}
diff --git
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java
index fa33f055e05d..bc02704930c6 100644
---
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java
+++
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java
@@ -59,6 +59,8 @@ public class BodyTypeModel {
public static final String EIP_STEPS = "eipSteps";
/** The value of {@link #getFrom()} for the body the steps of an EIP end
with. */
public static final String FROM_STEPS = "steps";
+ /** The value of an option body for any value of the option: the body when
the option is set at all. */
+ public static final String ANY_VALUE = "*";
/**
* The carriers of the classes a type option often names, by name, the
short names Camel takes as well: java.lang
diff --git
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeResolver.java
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeResolver.java
index 838e12c93206..0a45b58ff7ec 100644
---
a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeResolver.java
+++
b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeResolver.java
@@ -31,6 +31,10 @@ import
org.apache.camel.tooling.model.BodyTypeModel.OptionBodyTypeModel;
* <p/>
* The rule, in order:
* <ol>
+ * <li>An option of byOption for any value ({@code *}) the step sets: that
body, whatever the rest says, even for a side
+ * with from, as variableReceive leaves the body as it was; when two say
different bodies, not known. Such an option
+ * that the step does not set but can be set elsewhere: not known.</li>
+ * <li>A side with from: not known here, the caller follows from.</li>
* <li>An option the side names (in dependsOn, byOption or javaTypeOption)
whose value is only known when the route
* runs, a {@code {{placeholder}}} or a {@code ${expression}}, or that the
step does not set but can be set elsewhere,
* such as on the component: not known.</li>
@@ -42,7 +46,7 @@ import
org.apache.camel.tooling.model.BodyTypeModel.OptionBodyTypeModel;
* </ol>
* A body of byOption that has a javaTypeOption the step sets is an object of
that class, or null. The values of an
* option compare in any case, and in any style of an enum constant, as Camel
takes them: convert-to-json is
- * CONVERT_TO_JSON. A side with from is the body of a side of another model,
which the caller resolves.
+ * CONVERT_TO_JSON.
*/
public final class BodyTypeResolver {
@@ -61,7 +65,9 @@ public final class BodyTypeResolver {
}
/**
- * What a side says for the options a step sets.
+ * What a side says for the options a step sets. An option the body is for
any value of ({@code *}) says it when the
+ * step sets the option, whatever the other options say, even for a side
whose body comes from another model;
+ * otherwise such a side says nothing here, and the caller follows {@link
BodyTypeModel#getFrom()}.
*
* @param side the side, null when the catalog has none
* @param options the options the step sets, by name: the endpoint uri,
the parameters or the options of a data
@@ -71,7 +77,32 @@ public final class BodyTypeResolver {
* @return the body, or null when it is not known for the step
*/
public static Resolved resolve(BodyTypeModel side, Map<String, String>
options, Set<String> hidden) {
- if (side == null || side.getFrom() != null) {
+ if (side == null) {
+ return null;
+ }
+ Resolved matched = null;
+ for (OptionBodyTypeModel when : side.getByOption()) {
+ if (!BodyTypeModel.ANY_VALUE.equals(when.getValue())) {
+ continue;
+ }
+ String value = options.get(when.getOption());
+ if (value == null && hidden.contains(when.getOption())) {
+ // it may be set elsewhere
+ return null;
+ }
+ if (value != null) {
+ // any value, whatever the other options and the side of the
other model say, as variableReceive
+ Resolved resolved = bodyOf(side, when, options);
+ if (resolved == null || matched != null &&
!matched.carriers().equals(resolved.carriers())) {
+ return null;
+ }
+ matched = resolved;
+ }
+ }
+ if (matched != null) {
+ return matched;
+ }
+ if (side.getFrom() != null) {
return null;
}
Set<String> names = new LinkedHashSet<>(side.getDependsOn());
@@ -103,13 +134,10 @@ public final class BodyTypeResolver {
if (!dependsOnSet && side.getJavaTypeOption() != null &&
options.containsKey(side.getJavaTypeOption())) {
return ofClass(options.get(side.getJavaTypeOption()));
}
- Resolved matched = null;
for (OptionBodyTypeModel when : side.getByOption()) {
String value = options.get(when.getOption());
if (value != null && isSameValue(value, when.getValue())) {
- Resolved resolved = when.getJavaTypeOption() != null &&
options.containsKey(when.getJavaTypeOption())
- ? ofClass(options.get(when.getJavaTypeOption()))
- : new Resolved(when.getCarriers(), when.getJavaType(),
side.getFormat());
+ Resolved resolved = bodyOf(side, when, options);
if (resolved == null || matched != null &&
!matched.carriers().equals(resolved.carriers())) {
// two options the step sets say different bodies
return null;
@@ -126,6 +154,13 @@ public final class BodyTypeResolver {
return new Resolved(side.getCarriers(), side.getJavaType(),
side.getFormat());
}
+ /** The body an option value says: of the class its javaTypeOption names
when the step sets that. */
+ private static Resolved bodyOf(BodyTypeModel side, OptionBodyTypeModel
when, Map<String, String> options) {
+ return when.getJavaTypeOption() != null &&
options.containsKey(when.getJavaTypeOption())
+ ? ofClass(options.get(when.getJavaTypeOption()))
+ : new Resolved(when.getCarriers(), when.getJavaType(),
side.getFormat());
+ }
+
/**
* The body of the class an option names: an object of it, or null when it
does not convert. Object says nothing.
*/
diff --git
a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/BodyTypeResolverTest.java
b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/BodyTypeResolverTest.java
index bc9f75acbea4..efdaf6f95abd 100644
---
a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/BodyTypeResolverTest.java
+++
b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/BodyTypeResolverTest.java
@@ -122,6 +122,34 @@ class BodyTypeResolverTest {
assertEquals(List.of("bytes"), resolve(side, Map.of("output",
"BYTES")).carriers());
}
+ @Test
+ void anyValueOfAnOptionWinsOverTheRest() {
+ // the body after an enrich: the reply of the endpoint, unless
variableReceive keeps the body
+ BodyTypeModel side = new BodyTypeModel();
+ side.setFrom(BodyTypeModel.PRODUCER_OUT);
+ side.setDependsOn(List.of("aggregationStrategy"));
+ side.getByOption().add(when("variableReceive",
BodyTypeModel.ANY_VALUE, List.of("unchanged"), null));
+ assertEquals(List.of("unchanged"), resolve(side,
Map.of("variableReceive", "reply")).carriers());
+ assertEquals(List.of("unchanged"), resolve(side,
Map.of("variableReceive", "{{name}}")).carriers());
+ assertEquals(List.of("unchanged"),
+ resolve(side, Map.of("variableReceive", "reply",
"aggregationStrategy", "#myStrategy")).carriers());
+ // not set: the caller follows from
+ assertNull(resolve(side, Map.of()));
+ assertNull(resolve(side, Map.of("aggregationStrategy",
"#myStrategy")));
+ // it may be set elsewhere
+ assertNull(BodyTypeResolver.resolve(side, Map.of(),
Set.of("variableReceive")));
+ }
+
+ @Test
+ void anyValueIsNotAValueOfAnotherOption() {
+ BodyTypeModel side = side(List.of("text"));
+ side.getByOption().add(when("variableReceive",
BodyTypeModel.ANY_VALUE, List.of("unchanged"), null));
+ assertEquals(List.of("text"), resolve(side, Map.of("other",
"*")).carriers());
+ side.getByOption().add(when("variableSend", BodyTypeModel.ANY_VALUE,
List.of("null"), null));
+ // two options set that say different bodies
+ assertNull(resolve(side, Map.of("variableReceive", "a",
"variableSend", "b")));
+ }
+
@Test
void theValuesOfAnEnumInAnyStyle() {
assertTrue(BodyTypeResolver.isSameValue("convert-to-json",
"CONVERT_TO_JSON"));
diff --git
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
index 06ab8560de80..95274cef0cf5 100644
---
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
+++
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java
@@ -116,7 +116,11 @@ final class BodyTypesHelper {
}
for (BodyType.When when : bodyType.byOption()) {
checkOption(owner, side, when.option(), options);
- checkValue(owner, side, when, options.get(when.option()));
+ if (BodyTypeModel.ANY_VALUE.equals(when.value())) {
+ checkAnyValue(owner, side, bodyType, when);
+ } else {
+ checkValue(owner, side, when, options.get(when.option()));
+ }
check(owner, side + " " + when.option() + "=" + when.value(),
when.carriers());
if (!when.javaTypeOption().isEmpty()) {
checkWhenJavaTypeOption(owner, side, when, options);
@@ -244,6 +248,19 @@ final class BodyTypesHelper {
}
}
+ /**
+ * The body for any value of an option holds whatever the other options
say: the option has no other values with a
+ * body, and the body does not also depend on it.
+ */
+ private static void checkAnyValue(Class<?> owner, String side, BodyType
bodyType, BodyType.When when) {
+ if (Arrays.asList(bodyType.dependsOn()).contains(when.option())
+ || Arrays.stream(bodyType.byOption()).anyMatch(w -> w != when
&& w.option().equals(when.option()))) {
+ throw new IllegalArgumentException(
+ owner.getName() + ": the @BodyTypes " + side + " has a
body for any value of " + when.option()
+ + ", which must not also be in
dependsOn or have other values in byOption");
+ }
+ }
+
/**
* A body has a carrier: an option value without one would say nothing.
UNCHANGED is for the body after a step, such
* as a sql query that leaves it unchanged unless it is a select. ANY is
for the bodies a step needs, and UNKNOWN
diff --git
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/BodyTypesHelperEipTest.java
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/BodyTypesHelperEipTest.java
index 2a5eb88a97f1..0c1f2cfe77a8 100644
---
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/BodyTypesHelperEipTest.java
+++
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/BodyTypesHelperEipTest.java
@@ -46,6 +46,29 @@ class BodyTypesHelperEipTest {
static class StepsFromSteps {
}
+ @BodyTypes(eipIn = @BodyType(from = "producerIn",
+ byOption = @BodyType.When(option =
"variableSend", value = "*", carriers = Carrier.ANY)),
+ eipOut = @BodyType(from = "producerOut",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED)))
+ static class AnyValue {
+ }
+
+ @BodyTypes(eipOut = @BodyType(from = "producerOut", dependsOn =
"variableReceive",
+ byOption = @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED)))
+ static class AnyValueAndDependsOn {
+ }
+
+ @BodyTypes(eipOut = @BodyType(from = "producerOut",
+ byOption = {
+ @BodyType.When(option =
"variableReceive", value = "*",
+ carriers =
Carrier.UNCHANGED),
+ @BodyType.When(option =
"variableReceive", value = "a",
+ carriers =
Carrier.TEXT) }))
+ static class AnyValueAndAValue {
+ }
+
@BodyTypes(eipOut = @BodyType(value = Carrier.TEXT, from =
"expressionOut"))
static class FromWithCarriers {
}
@@ -76,6 +99,26 @@ class BodyTypesHelperEipTest {
assertNull(model.getBodyType(BodyTypeModel.EIP_STEPS).getFrom());
}
+ @Test
+ void aBodyForAnyValueOfAnOption() {
+ EipModel model = eip();
+ BodyTypesHelper.addBodyTypes(model,
AnyValue.class.getAnnotation(BodyTypes.class), AnyValue.class);
+
+ BodyTypeModel.OptionBodyTypeModel in =
model.getBodyType(BodyTypeModel.EIP_IN).getByOption().get(0);
+ assertEquals("variableSend", in.getOption());
+ assertEquals(BodyTypeModel.ANY_VALUE, in.getValue());
+ assertEquals(List.of("any"), in.getCarriers());
+ BodyTypeModel out = model.getBodyType(BodyTypeModel.EIP_OUT);
+ assertEquals("producerOut", out.getFrom());
+ assertEquals(List.of("unchanged"),
out.getByOption().get(0).getCarriers());
+ }
+
+ @Test
+ void aBodyForAnyValueIsTheOnlyBodyOfTheOption() {
+ assertThrows(IllegalArgumentException.class, () -> add(eip(),
AnyValueAndDependsOn.class));
+ assertThrows(IllegalArgumentException.class, () -> add(eip(),
AnyValueAndAValue.class));
+ }
+
@Test
void stepsCannotComeFromAnotherModel() {
assertThrows(IllegalArgumentException.class, () -> add(eip(),
StepsFromSteps.class));
@@ -104,6 +147,10 @@ class BodyTypesHelperEipTest {
option.setName("variableReceive");
option.setType("string");
model.addOption(option);
+ EipModel.EipOptionModel send = new EipModel.EipOptionModel();
+ send.setName("variableSend");
+ send.setType("string");
+ model.addOption(send);
return model;
}
diff --git
a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java
b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java
index 6a68a8cde398..2acad5a8d033 100644
--- a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java
+++ b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java
@@ -59,7 +59,7 @@ public @interface BodyType {
String[] dependsOn() default {};
/**
- * The body for other values of an option, such as {@code output=bytes}.
+ * The body for other values of an option, such as {@code output=bytes},
or for any value of it.
*/
When[] byOption() default {};
@@ -103,7 +103,9 @@ public @interface BodyType {
String option();
/**
- * The value of the option.
+ * The value of the option. {@code *} is any value: the body when the
option is set at all, such as
+ * {@code variableReceive}, whose reply goes into the variable and
leaves the body as it was. Such a body holds
+ * whatever the other options and {@link BodyType#from()} say.
*/
String value();