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 105afa453098 CAMEL-24961: Catalog body types for spring-redis, 
couchbase, fhir, solr, ssh, slack and telegram (#28309)
105afa453098 is described below

commit 105afa4530988df4143b26906e1bbc4ecd7fb60b
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 24 10:02:33 2026 +0200

    CAMEL-24961: Catalog body types for spring-redis, couchbase, fhir, solr, 
ssh, slack and telegram (#28309)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../apache/camel/catalog/components/couchbase.json |   5 +
 .../org/apache/camel/catalog/components/fhir.json  |   5 +
 .../org/apache/camel/catalog/components/slack.json |   5 +
 .../org/apache/camel/catalog/components/solr.json  |   4 +
 .../camel/catalog/components/spring-redis.json     |   5 +
 .../org/apache/camel/catalog/components/ssh.json   |   5 +
 .../apache/camel/catalog/components/telegram.json  |   5 +
 .../camel/catalog/docs/couchbase-component.adoc    |  16 ++-
 .../apache/camel/catalog/docs/fhir-component.adoc  |   8 ++
 .../apache/camel/catalog/docs/slack-component.adoc |  10 +-
 .../apache/camel/catalog/docs/solr-component.adoc  |  12 +-
 .../camel/catalog/docs/spring-redis-component.adoc |  34 ++---
 .../apache/camel/catalog/docs/ssh-component.adoc   |   4 +
 .../camel/catalog/docs/telegram-component.adoc     |  15 ++-
 .../camel/component/couchbase/couchbase.json       |   5 +
 .../src/main/docs/couchbase-component.adoc         |  16 ++-
 .../component/couchbase/CouchbaseEndpoint.java     |  28 ++++
 .../org/apache/camel/component/fhir/fhir.json      |   5 +
 .../src/main/docs/fhir-component.adoc              |   8 ++
 .../apache/camel/component/fhir/FhirEndpoint.java  |  27 ++++
 .../org/apache/camel/component/slack/slack.json    |   5 +
 .../camel-slack/src/main/docs/slack-component.adoc |  10 +-
 .../camel/component/slack/SlackEndpoint.java       |  14 ++
 .../org/apache/camel/component/solr/solr.json      |   4 +
 .../camel-solr/src/main/docs/solr-component.adoc   |  12 +-
 .../apache/camel/component/solr/SolrEndpoint.java  |  17 +++
 .../apache/camel/component/redis/spring-redis.json |   5 +
 .../src/main/docs/spring-redis-component.adoc      |  34 ++---
 .../camel/component/redis/RedisEndpoint.java       |  85 ++++++++++++
 .../org/apache/camel/component/ssh/ssh.json        |   5 +
 .../camel-ssh/src/main/docs/ssh-component.adoc     |   4 +
 .../apache/camel/component/ssh/SshEndpoint.java    |  15 +++
 .../apache/camel/component/telegram/telegram.json  |   5 +
 .../src/main/docs/telegram-component.adoc          |  15 ++-
 .../camel/component/telegram/TelegramEndpoint.java |  22 +++
 .../camel/dsl/yaml/validator/CatalogBodyTypes.java |  14 +-
 .../camel/dsl/yaml/validator/TextBodyFlowTest.java | 150 +++++++++++++++++++++
 .../camel/maven/packaging/BodyTypesHelper.java     |  19 ++-
 .../packaging/EndpointSchemaGeneratorMojoTest.java |  12 ++
 .../SomeEndpointWithEnumStyleBodyTypes.java        |  35 +++++
 40 files changed, 650 insertions(+), 54 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/couchbase.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/couchbase.json
index 4681d0125811..f87f52c3261d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/couchbase.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/couchbase.json
@@ -38,6 +38,11 @@
     "CamelCouchbaseViewName": { "index": 4, "kind": "header", "displayName": 
"", "group": "consumer", "label": "consumer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The view name", "constantName": 
"org.apache.camel.component.couchbase.CouchbaseConstants#HEADER_VIEWNAME" },
     "CamelCqlResumeQuery": { "index": 5, "kind": "header", "displayName": "", 
"group": "consumer", "label": "consumer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The resume action to execute when resuming.", 
"constantName": 
"org.apache.camel.component.couchbase.CouchbaseConstants#COUCHBASE_RESUME_ACTION"
 }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.couchbase.client.java.kv.GetResult", "byOption": [ { "option": 
"fullDocument", "value": "false", "carriers": [ "text" ], "javaType": 
"java.lang.String" }, { "option": "useView", "value": "true", "carriers": [ 
"pojo" ], "javaType": "com.couchbase.client.java.kv.GetResult" } ], 
"description": "One message per document the query finds, read by its id (the 
CamelCouchbaseId header) as the GetResult of the Couchbase SDK: contentAsOb 
[...]
+    "producerIn": { "carriers": [ "text", "map", "list", "pojo" ], 
"dependsOn": [ "operation" ], "description": "The document that CCB_PUT, the 
default, stores as JSON: a Map, a List or a POJO becomes JSON, and text is 
stored as a JSON string, so unmarshal JSON text first. Bytes, a stream or no 
body fail. CCB_GET and CCB_DEL do not use the body: the id is the 
CamelCouchbaseId header" },
+    "producerOut": { "carriers": [ "value" ], "javaType": "java.lang.Boolean", 
"dependsOn": [ "operation" ], "byOption": [ { "option": "operation", "value": 
"CCB_GET", "carriers": [ "pojo" ], "javaType": 
"com.couchbase.client.java.kv.GetResult" }, { "option": "operation", "value": 
"CCB_DEL", "carriers": [ "text" ], "javaType": "java.lang.String" } ], 
"description": "true for CCB_PUT, the default; the GetResult of the Couchbase 
SDK for CCB_GET, and the MutationResult as text for CCB_DEL.  [...]
+  },
   "properties": {
     "protocol": { "index": 0, "kind": "path", "displayName": "Protocol", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The protocol to use" },
     "hostname": { "index": 1, "kind": "path", "displayName": "Hostname", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The hostname to use" },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fhir.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fhir.json
index e7797c75d1e1..d122ed869f39 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fhir.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fhir.json
@@ -53,6 +53,11 @@
     "password": { "index": 25, "kind": "property", "displayName": "Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.fhir.FhirConfiguration", "configurationField": 
"configuration", "description": "Password to use for basic authentication" },
     "username": { "index": 26, "kind": "property", "displayName": "Username", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.fhir.FhirConfiguration", "configurationField": 
"configuration", "description": "Username to use for basic authentication" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "byOption": [ { "option": 
"apiName", "value": "TRANSACTION", "carriers": [ "pojo", "text" ] } ], 
"description": "One message per poll with the return value of the method: a 
MethodOutcome for create, update, patch, delete and validate, and a HAPI FHIR 
model object of the fhirVersion for the others, such as the Bundle of a search. 
A List is split into one message per resource, and transaction withBundle with 
stringBundle gives the response as text" },
+    "producerIn": { "carriers": [ "any" ], "dependsOn": [ "inBody" ], 
"description": "The body is used only when inBody names the argument of the 
method it is, and then passed as it is, without type conversion: 
resourceAsString, stringBundle and url take text (convertBodyTo String first), 
and resource, bundle and resources a HAPI FHIR object or a List of them 
(unmarshal with fhirJson or fhirXml first). Without inBody the arguments come 
from the options and the CamelFhir. headers" },
+    "producerOut": { "carriers": [ "pojo" ], "byOption": [ { "option": 
"apiName", "value": "TRANSACTION", "carriers": [ "pojo", "text", "list" ] } ], 
"description": "The return value of the method: a MethodOutcome for create, 
update, patch, delete and validate, and a HAPI FHIR model object of the 
fhirVersion for the others, such as the Bundle of a search or the resource of a 
read; marshal a resource or a Bundle with fhirJson or fhirXml to get text. 
transaction gives a Bundle for withBund [...]
+  },
   "properties": {
     "apiName": { "index": 0, "kind": "path", "displayName": "Api Name", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.fhir.internal.FhirApiName", "enum": [ 
"CAPABILITIES", "CREATE", "DELETE", "HISTORY", "LOAD_PAGE", "META", 
"OPERATION", "PATCH", "READ", "SEARCH", "TRANSACTION", "UPDATE", "VALIDATE" ], 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "configurationClass": "org.apache.camel.componen [...]
     "methodName": { "index": 1, "kind": "path", "displayName": "Method Name", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.fhir.FhirConfiguration", "configurationField": 
"configuration", "description": "What sub operation to use for the selected 
operation" },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
index c127837baf01..8fd717ae367a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/slack.json
@@ -32,6 +32,11 @@
     "token": { "index": 5, "kind": "property", "displayName": "Token", 
"group": "token", "label": "token", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The token to access Slack. This app needs to 
have channels:history, groups:history, im:history, mpim:history, channels:read, 
groups:read, im:read and mpim:read permissions. The User OAuth Token is the 
kind of token needed." },
     "webhookUrl": { "index": 6, "kind": "property", "displayName": "Webhook 
Url", "group": "webhook", "label": "webhook", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "description": "The incoming webhook URL" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.slack.api.model.Message", "description": "One message per new message of 
the conversation, the most recent first, or the oldest first with naturalOrder; 
the first poll gives the last message of the history. The text is ${body.text}" 
},
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], 
"description": "The text of the message, or a com.slack.api.model.Message with 
its blocks: bytes, a stream or a file are read as text. A Map or another POJO 
is sent as its toString(): marshal it first. No body sends no text, which Slack 
rejects" },
+    "producerOut": { "carriers": [ "unchanged" ], "description": "Unchanged: 
the answer of Slack is not kept" }
+  },
   "properties": {
     "channel": { "index": 0, "kind": "path", "displayName": "Channel", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The channel name (syntax #name) or 
slack user (syntax userName) to send a message directly to an user." },
     "token": { "index": 1, "kind": "parameter", "displayName": "Token", 
"group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "description": "The token to access 
Slack. This app needs to have channels:history, groups:history, im:history, 
mpim:history, channels:read, groups:read, im:read and mpim:read permissions. 
The User OAuth Token is the kind of token needed." },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
index 698cf8e42fe4..c5806545bcf2 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/solr.json
@@ -51,6 +51,10 @@
     "CamelSolrField.": { "index": 9, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "Object", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The prefix of the headers that carry the fields of the 
document to insert: the header CamelSolrField.id holds the id field.", 
"constantName": 
"org.apache.camel.component.solr.SolrConstants#HEADER_FIELD_PREFIX" },
     "CamelSolrParam.": { "index": 10, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The prefix of the headers that carry extra request 
parameters: the header CamelSolrParam.commit holds the commit parameter.", 
"constantName": 
"org.apache.camel.component.solr.SolrConstants#HEADER_PARAM_PREFIX" }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "DELETE", "carriers": [ "text", "list" ] } ], 
"description": "What the operation needs: INSERT a Map or a SolrInputDocument, 
a List of them or of @Field beans, a JSON array or XML text, or a file; DELETE 
the id or, with deleteByQuery=true, the query, as text or a List of text; 
SEARCH the query as text or a SolrQuery; PING does not use it. Convert bytes or 
a stream to text first. The CamelSolrField. [...]
+    "producerOut": { "carriers": [ "map" ], "description": "The response of 
Solr as a Map (a SimpleOrderedMap) with the responseHeader and, for a search, 
the documents under response as a SolrDocumentList. Convert it to a 
QueryResponse or a SolrPingResponse to read it with SolrJ" }
+  },
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.solr.SolrConfiguration", "configurationField": 
"configuration", "description": "The solr instance host name (set to 'default' 
to use the host name defined on component level)" },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"producer", "label": "", "required": false, "type": "integer", "javaType": 
"int", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": 8983, "configurationClass": 
"org.apache.camel.component.solr.SolrConfiguration", "configurationField": 
"configuration", "description": "The solr instance port number" },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-redis.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-redis.json
index d6b8fc72d591..4af7413f7668 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-redis.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-redis.json
@@ -60,6 +60,11 @@
     "CamelRedis.Longitude": { "index": 27, "kind": "header", "displayName": 
"", "group": "common", "label": "", "required": false, "javaType": "Double", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Longitude.", "constantName": 
"org.apache.camel.component.redis.RedisConstants#LONGITUDE" },
     "CamelRedis.Radius": { "index": 28, "kind": "header", "displayName": "", 
"group": "common", "label": "", "required": false, "javaType": "Double", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Radius.", "constantName": 
"org.apache.camel.component.redis.RedisConstants#RADIUS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "unknown" ], "description": "The message as 
the serializer reads it: with the default JDK serializer the Java object the 
publisher serialized, such as the String a PUBLISH of this component sent, with 
the channel in the CamelRedis.Channel header. A message that is not Java 
serialized, such as one from redis-cli, is dropped with a WARN: set a 
StringRedisSerializer as the serializer to receive text" },
+    "producerIn": { "carriers": [ "any" ], "description": "The body is not 
used: the key, the value and the other arguments of the command come from 
headers, such as CamelRedis.Key and CamelRedis.Value, or CamelRedis.Channel and 
CamelRedis.Message for PUBLISH. Set CamelRedis.Value from the body to store the 
body: with the default serializer it must be Serializable, so a stream fails" },
+    "producerOut": { "carriers": [ "unchanged" ], "dependsOn": [ "command" ], 
"byOption": [ { "option": "command", "value": "SET", "carriers": [ "unchanged" 
] }, { "option": "command", "value": "SETEX", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "MSET", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "HSET", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "HMSET", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "HDEL", "car [...]
+  },
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.redis.RedisConfiguration", "configurationField": 
"configuration", "description": "The host where Redis server is running." },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"common", "label": "", "required": true, "type": "integer", "javaType": 
"java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.redis.RedisConfiguration", "configurationField": 
"configuration", "description": "Redis server port number" },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ssh.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ssh.json
index dd1c9ac5740b..6fa65e1a1bc7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ssh.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ssh.json
@@ -63,6 +63,11 @@
     "CamelSshStderr": { "index": 2, "kind": "header", "displayName": "", 
"group": "common", "label": "", "required": false, "javaType": "InputStream", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The value of this header is a InputStream with the 
standard error stream of the executable.", "constantName": 
"org.apache.camel.component.ssh.SshConstants#STDERR" },
     "CamelSshExitValue": { "index": 3, "kind": "header", "displayName": "", 
"group": "common", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "important": true, "description": "The value of this header is the exit 
value that is returned, after the execution. By convention a non-zero status 
exit value indicates abnormal termination. Note that the exit value is OS 
dependent.", "constantName": "org.apac [...]
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "stream" ], "description": "The standard 
output of the pollCommand as a stream, with the exit value and the standard 
error in the CamelSshExitValue and CamelSshStderr headers" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"description": "The command to run, as text: bytes, a stream or a file are read 
as text. A Map or a POJO is sent as its toString(): build the command as text 
first. No body fails" },
+    "producerOut": { "carriers": [ "stream" ], "description": "The standard 
output of the command as a stream, with the exit value and the standard error 
in the CamelSshExitValue and CamelSshStderr headers; an exit value that is not 
0 does not fail the exchange. Convert it to text to read it more than once" }
+  },
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.ssh.SshConfiguration", "configurationField": 
"configuration", "description": "Sets the hostname of the remote SSH server." },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"common", "label": "", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 22, 
"configurationClass": "org.apache.camel.component.ssh.SshConfiguration", 
"configurationField": "configuration", "description": "Sets the port number for 
the remote SSH server." },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
index a26ad023217c..2c99fd226779 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
@@ -41,6 +41,11 @@
     "CamelTelegramParseMode": { "index": 4, "kind": "header", "displayName": 
"", "group": "common", "label": "", "required": false, "javaType": 
"org.apache.camel.component.telegram.TelegramParseMode", "enum": [ "HTML", 
"MARKDOWN" ], "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "This header is used to format text messages 
using HTML or Markdown", "constantName": 
"org.apache.camel.component.telegram.TelegramConstants#TELEGRAM_PARSE_MODE" },
     "CamelMessageTimestamp": { "index": 5, "kind": "header", "displayName": 
"", "group": "common", "label": "", "required": false, "javaType": "long", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The message timestamp.", "constantName": 
"org.apache.camel.component.telegram.TelegramConstants#MESSAGE_TIMESTAMP" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo", "null" ], "description": "One 
message per update: an IncomingMessage for a message or a channel post, with 
the chat id in the CamelTelegramChatId header, and the IncomingCallbackQuery, 
IncomingInlineQuery, PreCheckoutQuery, ShippingQuery or PaidMediaPurchased of 
the other updates; no body for an update of another kind, such as an edited 
message" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"null" ], "description": "A message of 
org.apache.camel.component.telegram.model, such as an OutgoingTextMessage, or 
text to send as a text message: a stream or a file is read as text, and a Map 
or another POJO is sent as its toString(), so marshal it first. A byte array is 
sent as the photo, audio, video or document the CamelTelegramMediaType header 
names, and fails without it. No body sends nothing" },
+    "producerOut": { "carriers": [ "pojo", "unchanged" ], "description": "The 
MessageResult of the call, with the message sent, or a subclass of it such as 
MessageResultString for createInvoiceLink; an answer with ok=false is a 
MessageResult too. No body is left as it is" }
+  },
   "properties": {
     "type": { "index": 0, "kind": "path", "displayName": "Type", "group": 
"common", "label": "", "required": true, "type": "enum", "javaType": 
"java.lang.String", "enum": [ "bots" ], "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The endpoint type. 
Currently, only the 'bots' type is supported." },
     "limit": { "index": 1, "kind": "parameter", "displayName": "Limit", 
"group": "consumer", "label": "consumer", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "optionalPrefix": "consumer.", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": 100, 
"configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "Limit on the number of 
updates that can be received in a si [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/couchbase-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/couchbase-component.adoc
index 8d5ad8edda5e..707109794a00 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/couchbase-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/couchbase-component.adoc
@@ -116,8 +116,10 @@ 
from("couchbase:http://localhost?bucket=myBucket&username=user&password=pass";
 
 The query must include `META().id AS \__id` in the SELECT clause so the 
consumer can identify each document.
 
-When `fullDocument` is `true` (the default), the consumer fetches the complete 
document from the KV store using
-the document ID. When `false`, the query result row is used directly as the 
message body.
+When `fullDocument` is `true` (the default), the consumer fetches the document 
from the KV store by its id, and the
+message body is the `GetResult` of the Couchbase SDK, with the id in the 
`CamelCouchbaseId` header: `contentAsObject()`
+gives the JSON document. When `false`, the message body is the query result 
row as JSON text, or with views the value the
+view emits.
 
 NOTE: SQL++ queries require a primary index or appropriate secondary index on 
the bucket/collection.
 Create one with: `CREATE PRIMARY INDEX ON \`myBucket\`` or via the Couchbase 
SDK.
@@ -192,6 +194,16 @@ 
from("couchbase:http://localhost?bucket=myBucket&username=user&password=pass";
     + "&statement=SELECT META().id AS __id, * FROM `myBucket` ORDER BY 
META().id DESC LIMIT 10")
 ----
 
+== Producing documents
+
+The producer runs the operation of the `operation` option: `CCB_PUT` (the 
default) stores the message body as the
+document with the id of the `CamelCouchbaseId` header, `CCB_GET` reads the 
document of that id, and `CCB_DEL` deletes
+it. The producer removes the `CamelCouchbaseId` header after each call, so set 
it again before the next one.
+
+`CCB_PUT` stores the body as JSON: a Map, a List or a POJO becomes a JSON 
document, and text is stored as a JSON string,
+so unmarshal JSON text to a Map first. A byte array, a stream or no body fail. 
After the call the message body is `true`
+for `CCB_PUT`, the `GetResult` of the Couchbase SDK for `CCB_GET`, and the 
`MutationResult` as text for `CCB_DEL`.
+
 == Couchbase SDK compatibility
 
 Using collections and scopes is supported only for Couchbase Server 7.0 and 
later.
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fhir-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fhir-component.adoc
index ad69d116e9e7..df7f1a144759 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fhir-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/fhir-component.adoc
@@ -120,3 +120,11 @@ The URI names the API and its method, such as 
`fhir://read/resourceById`. The pa
         - log:
             message: "Found ${body.total} patients"
 ----
+
+The `inBody` option names the parameter of the method that the message body 
is, such as `resourceAsString` in the
+first route. The body is passed as it is, without type conversion, so a 
parameter of type String needs a String body:
+convert a byte array or a stream with `convertBodyTo` first. Without `inBody` 
the body is not used.
+
+After the call the message body is the result of the method: a `MethodOutcome` 
for create, update, patch, delete and
+validate, and a HAPI FHIR model object for the others, such as the `Bundle` of 
a search or the resource of a read. Marshal
+a resource or a `Bundle` with `fhirJson` or `fhirXml` to get text.
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
index b0708b1d502b..7f26de4031be 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
@@ -53,7 +53,12 @@ include::partial$component-endpoint-headers.adoc[]
 == Usage
 
 To send a message contained in the message body, a pre-established
-https://api.slack.com/incoming-webhooks[Slack incoming webhook] must be 
configured in Slack.
+https://api.slack.com/incoming-webhooks[Slack incoming webhook] must be 
configured in Slack, or the producer uses a token
+of a Slack app.
+
+The producer sends the message body as the text of the message: bytes, a 
stream or a file are read as text. A
+`com.slack.api.model.Message` body is sent with its blocks and attachments. 
Marshal a Map or another object to text
+first, as it is otherwise sent as its `toString()`. The message body is 
unchanged after the call.
 
 === Configuring in Spring XML
 
@@ -185,7 +190,8 @@ YAML::
 ----
 ====
 
-This way you'll get the last message from `general` channel.
+This way you'll get the last message from `general` channel, as a 
`com.slack.api.model.Message`, whose text is
+`${body.text}`.
 The consumer will track the timestamp of the last message consumed,
 and in the next poll it will consume only newer messages in the channel.
 When more than `maxResults` new messages were posted since the previous poll, 
the consumer reads the following pages of
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
index 9e574e8b94b5..5e8d8ccca062 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
@@ -62,16 +62,22 @@ the following. Some operations also require the message 
body to be set.
 |`INSERT` | File |inserts/updates a document or documents using the given File 
(using ContentStreamUpdateRequest)
 |`INSERT` | SolrInputDocument or Collection<SolrInputDocument>|inserts/updates 
a document or documents based on the given (collection of) SolrInputDocument
 |`INSERT` | bean or Collection<bean> |inserts/updates a document or documents 
based on values in an 
http://wiki.apache.org/solr/Solrj#Directly_adding_POJOs_to_Solr[annotated bean]
-|`INSERT` | String |inserts/updates a document or documents index based on the 
given XML or json as string (must follow SolrInputDocument format)
+|`INSERT` | Map or Collection<Map> |inserts/updates a document or documents 
with the fields of the Map
+|`INSERT` | String |inserts/updates the documents of the given JSON or XML 
text: a JSON array of documents, or XML documents (an `<add>` element is added 
when the text does not start with one)
 
-|`DELETE` | String or Collection<String> | delete a record by ID (or 
collection of ids) or by a query (or a collection of queries) when header 
"deleteByQuery=true"
+|`DELETE` | String or Collection<String> | delete a record by ID (or 
collection of ids) or by a query (or a collection of queries) when the 
`deleteByQuery` option or the `CamelSolrDeleteByQuery` header is true
 
-|`PING` |SolrPing | Pings the solr instance
+|`PING` |n/a | Pings the solr instance
 
 |`SEARCH` |SolrQuery | Performs a search request to the solr instance
 |`SEARCH` |QueryRequest | Performs a search request to the solr instance
+|`SEARCH` |String | Performs a search with the query as text, unless the 
`CamelSolrQueryString` header holds the query
 |=======================================================================
 
+After each operation the message body is the response of Solr as a Map (a 
`SimpleOrderedMap`) with the
+`responseHeader`, and for a search the documents under `response` as a 
`SolrDocumentList`. Convert it to a
+`QueryResponse` to read it with SolrJ.
+
 == Example
 
 Below is a simple `INSERT`, `DELETE` and `SEARCH` example
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-redis-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-redis-component.adoc
index 302c90ea9268..a9037b6057bb 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-redis-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-redis-component.adoc
@@ -53,7 +53,9 @@ at 
https://github.com/apache/camel/tree/main/components/camel-spring-redis/src/t
 
 The producer runs the command of the `command` option, or of the 
`CamelRedis.Command` header.
 The parameters of the command come from headers, such as `CamelRedis.Key` and 
`CamelRedis.Value`, not from the
-message body. The tables below list the parameters of each command. The result 
of the command is the message body:
+message body. The tables below list the parameters of each command. The result 
of the command becomes the message body,
+and a command without a result, such as `SET`, or a result that is null, such 
as `GET` of a missing key, leaves the body
+as it was:
 
 [source,yaml]
 ----
@@ -156,17 +158,19 @@ these keys:
 === Message headers evaluated by the Redis producer
 
 The producer issues commands to the server, and each command has
-a different set of parameters with specific types. The result from the
-command execution is returned in the message body.
+a different set of parameters with specific types. The result of the
+command becomes the message body. A command whose result is void, and a result 
that is null, leave the body as it was.
+The value commands, such as `GET`, `HGET` and `LPOP`, return the object that 
was stored: with the default serializer the
+Java object the `CamelRedis.Value` header held.
 
 [width="100%",cols="10%,40%,50%,10%",options="header",]
 |=======================================================================
 |Hash Commands |Description |Parameters |Result
 |`HSET` |Set the string value of a hash field 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELD`/"CamelRedis.Field" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value"
 (Object) |void
-|`HGET` |Get the value of a hash field |`RedisConstants.KEY`/"CamelRedis.Key" 
(String), `RedisConstants.FIELD`/"CamelRedis.Field" (String) |String
+|`HGET` |Get the value of a hash field |`RedisConstants.KEY`/"CamelRedis.Key" 
(String), `RedisConstants.FIELD`/"CamelRedis.Field" (String) |Object
 |`HSETNX` |Set the value of a hash field only if the field does not exist 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELD`/"CamelRedis.Field" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value"
-(Object) |void
+(Object) |Boolean
 |`HMSET` |Set multiple hash fields to multiple values 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUES`/"CamelRedis.Values" (Map<String, Object>) |void
 |`HMGET` |Get the values of all the given hash fields 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELDS`/"CamelRedis.Fields" (Collection<String>) 
|Collection<Object>
 |`HINCRBY` |Increment the integer value of a hash field by the given number 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELD`/"CamelRedis.Field" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value"
@@ -195,7 +199,7 @@ command execution is returned in the message body.
 
 |`LTRIM` |Trim a list to the specified range 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.START`/"CamelRedis.Start"Long), 
`RedisConstants.END`/"CamelRedis.End" (Long) |void
 
-|`LINDEX` |Get an element from a list by its index 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.INDEX`/"CamelRedis.Index" (Long) |String
+|`LINDEX` |Get an element from a list by its index 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.INDEX`/"CamelRedis.Index" (Long) |Object
 
 |`LINSERT` |Insert an element before or after another element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object), 
`RedisConstants.PIVOT`/"CamelRedis.Pivot"
 (String), `RedisConstants.POSITION`/"CamelRedis.Position" (String) |Long
@@ -208,7 +212,7 @@ command execution is returned in the message body.
 
 |`LPOP` |Remove and get the first element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 
-|`RPOP` |Remove and get the last element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
+|`RPOP` |Remove and get the last element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 
 |`RPOPLPUSH` |Remove the last element in a list, append it to another list and 
return
 it |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.DESTINATION`/"CamelRedis.Destination" (String) |Object
@@ -221,20 +225,20 @@ until one is available 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), `RedisCo
 available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEOUT`/"CamelRedis.Timeout" (Long) |Object
 
 |`BRPOP` |Remove and get the last element in a list, or block until one is
-available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEOUT`/"CamelRedis.Timeout" (Long) |String
+available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEOUT`/"CamelRedis.Timeout" (Long) |Object
 |=======================================================================
 
 [width="100%",cols="10%,40%,50%,10%",options="header",]
 |=======================================================================
 |Set Commands |Description |Parameters |Result
 
-|`SADD` |Add one or more members to a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Boolean
+|`SADD` |Add one or more members to a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Long
 
 |`SMEMBERS` |Get all the members in a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Set<Object>
 
-|`SREM` |Remove one or more members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Boolean
+|`SREM` |Remove one or more members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Long
 
-|`SPOP` |Remove and return a random member from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
+|`SPOP` |Remove and return a random member from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 
 |`SMOVE` |Move a member from one set to another 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object),
 `RedisConstants.DESTINATION`/"CamelRedis.Destination" (String) |Boolean
@@ -258,7 +262,7 @@ available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEO
 |`SDIFFSTORE` |Subtract multiple sets and store the resulting set in a key 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.KEYS`/"CamelRedis.Keys" (String),
 `RedisConstants.DESTINATION`/"CamelRedis.Destination" (String) |void
 
-|`SRANDMEMBER` |Get one or multiple random members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
+|`SRANDMEMBER` |Get one or multiple random members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 |=======================================================================
 
 [width="100%",cols="10%,40%,50%,10%",options="header",]
@@ -272,7 +276,7 @@ already exists |`RedisConstants.KEY`/"CamelRedis.Key" 
(String), `RedisConstants.
 |`ZRANGE` |Return a range of members in a sorted set, by index 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.START`/"CamelRedis.Start"Long), 
`RedisConstants.END`/"CamelRedis.End" (Long),
 `RedisConstants.WITHSCORE`/"CamelRedis.WithScore" (Boolean) |Object
 
-|`ZREM` |Remove one or more members from a sorted set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Boolean
+|`ZREM` |Remove one or more members from a sorted set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Long
 
 |`ZINCRBY` |Increment the score of a member in a sorted set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object), 
`RedisConstants.INCREMENT`/"CamelRedis.Increment"
 (Double) |Double
@@ -362,9 +366,9 @@ new key |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.KEYS`/"
 
 |`DEL` |Delete a key |`RedisConstants.KEYS`/"CamelRedis.Keys" (String) |void
 
-|`TYPE` |Determine the type stored at key 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |DataType
+|`TYPE` |Determine the type stored at key 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
 
-|`KEYS` |Find all keys matching the given pattern 
|`RedisConstants.PATERN`/"CamelRedis.Pattern" (String) |Collection<String>
+|`KEYS` |Find all keys matching the given pattern 
|`RedisConstants.PATERN`/"CamelRedis.Pattern" (String) |Set<String>
 
 |`RANDOMKEY` |Return a random key from the keyspace 
|`RedisConstants.PATERN`/"CamelRedis.Pattern" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (String) |String
 
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ssh-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ssh-component.adoc
index e1b2e38de453..79f1fabccde9 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ssh-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ssh-component.adoc
@@ -50,6 +50,10 @@ When the SSH Component is used as a Producer 
(`.to("ssh://...")`), it
 will send the message body as the command to execute on the remote SSH
 server.
 
+After the call, the message body is the standard output of the command as a 
stream, with the exit value in the
+`CamelSshExitValue` header and the standard error, as a stream as well, in the 
`CamelSshStderr` header. An exit value
+that is not 0 does not fail the exchange. The polling consumer gives the 
output of its `pollCommand` the same way.
+
 Here is an example of this. Note that the command has
 a newline (`+&#10;+`).
 
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
index 1c01a0531f03..decdf4ee0d9e 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
@@ -123,7 +123,7 @@ The following message bodies are allowed for a producer 
endpoint (messages of ty
 | `EditMessageMediaMessage` | To edit animation, audio, document, photo, or 
video messages. (editMessageMedia)
 | `EditMessageReplyMarkupMessage` | To edit only the reply markup of a 
message. (editMessageReplyMarkup)
 | `EditMessageDelete` | To delete a message, including service messages. 
(deleteMessage)
-| `SendLocationMessage` | To send a location (setSendLocation)
+| `SendLocationMessage` | To send a location (sendLocation)
 | `EditMessageLiveLocationMessage` | To send changes to a live location 
(editMessageLiveLocation)
 | `StopMessageLiveLocationMessage` | To stop updating a live location message 
sent by the bot or via the bot (for inline bots) before live_period expires 
(stopMessageLiveLocation)
 | `SendVenueMessage` | To send information about a venue (sendVenue)
@@ -136,11 +136,15 @@ The following message bodies are allowed for a producer 
endpoint (messages of ty
 | `GetStarTransactionsMessage` | To get the list of bot's Telegram Star 
transactions (getStarTransactions)
 | `RefundStarPaymentMessage` | To refund a successful payment in Telegram 
Stars (refundStarPayment)
 | `EditUserStarSubscriptionMessage` | To cancel or re-enable extension of a 
subscription paid in Telegram Stars (editUserStarSubscription)
-| `byte[]` | To send any media type supported. It requires the 
`CamelTelegramMediaType` header to be set to the appropriate media type
+| `byte[]` | To send a photo, an audio, a video or a document, as the 
`CamelTelegramMediaType` header names (`PHOTO_PNG`, `PHOTO_JPG`, `AUDIO`, 
`VIDEO` or `DOCUMENT`). It fails without the header. A stream or a file is sent 
as text, so convert it to `byte[]` to send it as media
 | `String` | To send a text message to a chat. It gets converted automatically 
into a `OutgoingTextMessage`
 
 |===================================================
 
+Any other body is sent as text, so marshal a Map or another object first, as 
it is otherwise sent as its `toString()`.
+After the call the message body is the `MessageResult` of the call, with the 
message sent, or a subclass of it, such as
+`MessageResultString` for `CreateInvoiceLinkMessage`. A message body that is 
null sends nothing, and stays null.
+
 
 === Consumer
 
@@ -204,11 +208,16 @@ Supported types for incoming messages are
 |===================================================
 | Java Type | Description
 
-| `IncomingMessage` | The full object representation of an incoming message
+| `IncomingMessage` | The full object representation of an incoming message or 
channel post, with the chat id in the `CamelTelegramChatId` header
 | `String` | The content of the message, for text messages only
+| `IncomingCallbackQuery` | A callback query, from a button of an inline 
keyboard
+| `IncomingInlineQuery` | An inline query
+| `PreCheckoutQuery`, `ShippingQuery`, `PaidMediaPurchased` | The payment 
updates, of the package `org.apache.camel.component.telegram.model.payments`
 
 |===================================================
 
+An update of another kind, such as an edited message, gives a message with no 
body.
+
 
 === Reactive Chat-Bot Example
 
diff --git 
a/components/camel-couchbase/src/generated/resources/META-INF/org/apache/camel/component/couchbase/couchbase.json
 
b/components/camel-couchbase/src/generated/resources/META-INF/org/apache/camel/component/couchbase/couchbase.json
index 4681d0125811..f87f52c3261d 100644
--- 
a/components/camel-couchbase/src/generated/resources/META-INF/org/apache/camel/component/couchbase/couchbase.json
+++ 
b/components/camel-couchbase/src/generated/resources/META-INF/org/apache/camel/component/couchbase/couchbase.json
@@ -38,6 +38,11 @@
     "CamelCouchbaseViewName": { "index": 4, "kind": "header", "displayName": 
"", "group": "consumer", "label": "consumer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The view name", "constantName": 
"org.apache.camel.component.couchbase.CouchbaseConstants#HEADER_VIEWNAME" },
     "CamelCqlResumeQuery": { "index": 5, "kind": "header", "displayName": "", 
"group": "consumer", "label": "consumer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The resume action to execute when resuming.", 
"constantName": 
"org.apache.camel.component.couchbase.CouchbaseConstants#COUCHBASE_RESUME_ACTION"
 }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.couchbase.client.java.kv.GetResult", "byOption": [ { "option": 
"fullDocument", "value": "false", "carriers": [ "text" ], "javaType": 
"java.lang.String" }, { "option": "useView", "value": "true", "carriers": [ 
"pojo" ], "javaType": "com.couchbase.client.java.kv.GetResult" } ], 
"description": "One message per document the query finds, read by its id (the 
CamelCouchbaseId header) as the GetResult of the Couchbase SDK: contentAsOb 
[...]
+    "producerIn": { "carriers": [ "text", "map", "list", "pojo" ], 
"dependsOn": [ "operation" ], "description": "The document that CCB_PUT, the 
default, stores as JSON: a Map, a List or a POJO becomes JSON, and text is 
stored as a JSON string, so unmarshal JSON text first. Bytes, a stream or no 
body fail. CCB_GET and CCB_DEL do not use the body: the id is the 
CamelCouchbaseId header" },
+    "producerOut": { "carriers": [ "value" ], "javaType": "java.lang.Boolean", 
"dependsOn": [ "operation" ], "byOption": [ { "option": "operation", "value": 
"CCB_GET", "carriers": [ "pojo" ], "javaType": 
"com.couchbase.client.java.kv.GetResult" }, { "option": "operation", "value": 
"CCB_DEL", "carriers": [ "text" ], "javaType": "java.lang.String" } ], 
"description": "true for CCB_PUT, the default; the GetResult of the Couchbase 
SDK for CCB_GET, and the MutationResult as text for CCB_DEL.  [...]
+  },
   "properties": {
     "protocol": { "index": 0, "kind": "path", "displayName": "Protocol", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The protocol to use" },
     "hostname": { "index": 1, "kind": "path", "displayName": "Hostname", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The hostname to use" },
diff --git a/components/camel-couchbase/src/main/docs/couchbase-component.adoc 
b/components/camel-couchbase/src/main/docs/couchbase-component.adoc
index 8d5ad8edda5e..707109794a00 100644
--- a/components/camel-couchbase/src/main/docs/couchbase-component.adoc
+++ b/components/camel-couchbase/src/main/docs/couchbase-component.adoc
@@ -116,8 +116,10 @@ 
from("couchbase:http://localhost?bucket=myBucket&username=user&password=pass";
 
 The query must include `META().id AS \__id` in the SELECT clause so the 
consumer can identify each document.
 
-When `fullDocument` is `true` (the default), the consumer fetches the complete 
document from the KV store using
-the document ID. When `false`, the query result row is used directly as the 
message body.
+When `fullDocument` is `true` (the default), the consumer fetches the document 
from the KV store by its id, and the
+message body is the `GetResult` of the Couchbase SDK, with the id in the 
`CamelCouchbaseId` header: `contentAsObject()`
+gives the JSON document. When `false`, the message body is the query result 
row as JSON text, or with views the value the
+view emits.
 
 NOTE: SQL++ queries require a primary index or appropriate secondary index on 
the bucket/collection.
 Create one with: `CREATE PRIMARY INDEX ON \`myBucket\`` or via the Couchbase 
SDK.
@@ -192,6 +194,16 @@ 
from("couchbase:http://localhost?bucket=myBucket&username=user&password=pass";
     + "&statement=SELECT META().id AS __id, * FROM `myBucket` ORDER BY 
META().id DESC LIMIT 10")
 ----
 
+== Producing documents
+
+The producer runs the operation of the `operation` option: `CCB_PUT` (the 
default) stores the message body as the
+document with the id of the `CamelCouchbaseId` header, `CCB_GET` reads the 
document of that id, and `CCB_DEL` deletes
+it. The producer removes the `CamelCouchbaseId` header after each call, so set 
it again before the next one.
+
+`CCB_PUT` stores the body as JSON: a Map, a List or a POJO becomes a JSON 
document, and text is stored as a JSON string,
+so unmarshal JSON text to a Map first. A byte array, a stream or no body fail. 
After the call the message body is `true`
+for `CCB_PUT`, the `GetResult` of the Couchbase SDK for `CCB_GET`, and the 
`MutationResult` as text for `CCB_DEL`.
+
 == Couchbase SDK compatibility
 
 Using collections and scopes is supported only for Couchbase Server 7.0 and 
later.
diff --git 
a/components/camel-couchbase/src/main/java/org/apache/camel/component/couchbase/CouchbaseEndpoint.java
 
b/components/camel-couchbase/src/main/java/org/apache/camel/component/couchbase/CouchbaseEndpoint.java
index d83117e80425..60da83bcdfc1 100644
--- 
a/components/camel-couchbase/src/main/java/org/apache/camel/component/couchbase/CouchbaseEndpoint.java
+++ 
b/components/camel-couchbase/src/main/java/org/apache/camel/component/couchbase/CouchbaseEndpoint.java
@@ -39,6 +39,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -63,6 +66,31 @@ import static 
org.apache.camel.component.couchbase.CouchbaseConstants.DEFAULT_VI
  */
 @UriEndpoint(firstVersion = "2.19.0", scheme = "couchbase", title = 
"Couchbase", syntax = "couchbase:protocol://hostname:port",
              category = { Category.DATABASE }, headersClass = 
CouchbaseConstants.class)
+@BodyTypes(consumerOut = @BodyType(value = Carrier.POJO, javaType = 
"com.couchbase.client.java.kv.GetResult",
+                                   byOption = {
+                                           @BodyType.When(option = 
"fullDocument", value = "false", carriers = Carrier.TEXT,
+                                                          javaType = 
"java.lang.String"),
+                                           @BodyType.When(option = "useView", 
value = "true", carriers = Carrier.POJO,
+                                                          javaType = 
"com.couchbase.client.java.kv.GetResult") },
+                                   description = "One message per document the 
query finds, read by its id (the"
+                                                 + " CamelCouchbaseId header) 
as the GetResult of the Couchbase SDK:"
+                                                 + " contentAsObject() gives 
the JSON document. With fullDocument=false the row"
+                                                 + " of the SQL++ query as 
JSON text, or with useView=true the value the view"
+                                                 + " emits"),
+           producerIn = @BodyType(value = { Carrier.TEXT, Carrier.MAP, 
Carrier.LIST, Carrier.POJO }, dependsOn = "operation",
+                                  description = "The document that CCB_PUT, 
the default, stores as JSON: a Map, a List or a"
+                                                + " POJO becomes JSON, and 
text is stored as a JSON string, so unmarshal JSON"
+                                                + " text first. Bytes, a 
stream or no body fail. CCB_GET and CCB_DEL do not use"
+                                                + " the body: the id is the 
CamelCouchbaseId header"),
+           producerOut = @BodyType(value = Carrier.VALUE, javaType = 
"java.lang.Boolean", dependsOn = "operation",
+                                   byOption = {
+                                           @BodyType.When(option = 
"operation", value = "CCB_GET", carriers = Carrier.POJO,
+                                                          javaType = 
"com.couchbase.client.java.kv.GetResult"),
+                                           @BodyType.When(option = 
"operation", value = "CCB_DEL", carriers = Carrier.TEXT,
+                                                          javaType = 
"java.lang.String") },
+                                   description = "true for CCB_PUT, the 
default; the GetResult of the Couchbase SDK for"
+                                                 + " CCB_GET, and the 
MutationResult as text for CCB_DEL. The producer removes"
+                                                 + " the CamelCouchbaseId 
header"))
 public class CouchbaseEndpoint extends ScheduledPollEndpoint implements 
EndpointServiceLocation {
 
     @UriPath
diff --git 
a/components/camel-fhir/camel-fhir-component/src/generated/resources/META-INF/org/apache/camel/component/fhir/fhir.json
 
b/components/camel-fhir/camel-fhir-component/src/generated/resources/META-INF/org/apache/camel/component/fhir/fhir.json
index e7797c75d1e1..d122ed869f39 100644
--- 
a/components/camel-fhir/camel-fhir-component/src/generated/resources/META-INF/org/apache/camel/component/fhir/fhir.json
+++ 
b/components/camel-fhir/camel-fhir-component/src/generated/resources/META-INF/org/apache/camel/component/fhir/fhir.json
@@ -53,6 +53,11 @@
     "password": { "index": 25, "kind": "property", "displayName": "Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.fhir.FhirConfiguration", "configurationField": 
"configuration", "description": "Password to use for basic authentication" },
     "username": { "index": 26, "kind": "property", "displayName": "Username", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.fhir.FhirConfiguration", "configurationField": 
"configuration", "description": "Username to use for basic authentication" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "byOption": [ { "option": 
"apiName", "value": "TRANSACTION", "carriers": [ "pojo", "text" ] } ], 
"description": "One message per poll with the return value of the method: a 
MethodOutcome for create, update, patch, delete and validate, and a HAPI FHIR 
model object of the fhirVersion for the others, such as the Bundle of a search. 
A List is split into one message per resource, and transaction withBundle with 
stringBundle gives the response as text" },
+    "producerIn": { "carriers": [ "any" ], "dependsOn": [ "inBody" ], 
"description": "The body is used only when inBody names the argument of the 
method it is, and then passed as it is, without type conversion: 
resourceAsString, stringBundle and url take text (convertBodyTo String first), 
and resource, bundle and resources a HAPI FHIR object or a List of them 
(unmarshal with fhirJson or fhirXml first). Without inBody the arguments come 
from the options and the CamelFhir. headers" },
+    "producerOut": { "carriers": [ "pojo" ], "byOption": [ { "option": 
"apiName", "value": "TRANSACTION", "carriers": [ "pojo", "text", "list" ] } ], 
"description": "The return value of the method: a MethodOutcome for create, 
update, patch, delete and validate, and a HAPI FHIR model object of the 
fhirVersion for the others, such as the Bundle of a search or the resource of a 
read; marshal a resource or a Bundle with fhirJson or fhirXml to get text. 
transaction gives a Bundle for withBund [...]
+  },
   "properties": {
     "apiName": { "index": 0, "kind": "path", "displayName": "Api Name", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.fhir.internal.FhirApiName", "enum": [ 
"CAPABILITIES", "CREATE", "DELETE", "HISTORY", "LOAD_PAGE", "META", 
"OPERATION", "PATCH", "READ", "SEARCH", "TRANSACTION", "UPDATE", "VALIDATE" ], 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "configurationClass": "org.apache.camel.componen [...]
     "methodName": { "index": 1, "kind": "path", "displayName": "Method Name", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.fhir.FhirConfiguration", "configurationField": 
"configuration", "description": "What sub operation to use for the selected 
operation" },
diff --git 
a/components/camel-fhir/camel-fhir-component/src/main/docs/fhir-component.adoc 
b/components/camel-fhir/camel-fhir-component/src/main/docs/fhir-component.adoc
index ad69d116e9e7..df7f1a144759 100644
--- 
a/components/camel-fhir/camel-fhir-component/src/main/docs/fhir-component.adoc
+++ 
b/components/camel-fhir/camel-fhir-component/src/main/docs/fhir-component.adoc
@@ -120,3 +120,11 @@ The URI names the API and its method, such as 
`fhir://read/resourceById`. The pa
         - log:
             message: "Found ${body.total} patients"
 ----
+
+The `inBody` option names the parameter of the method that the message body 
is, such as `resourceAsString` in the
+first route. The body is passed as it is, without type conversion, so a 
parameter of type String needs a String body:
+convert a byte array or a stream with `convertBodyTo` first. Without `inBody` 
the body is not used.
+
+After the call the message body is the result of the method: a `MethodOutcome` 
for create, update, patch, delete and
+validate, and a HAPI FHIR model object for the others, such as the `Bundle` of 
a search or the resource of a read. Marshal
+a resource or a `Bundle` with `fhirJson` or `fhirXml` to get text.
diff --git 
a/components/camel-fhir/camel-fhir-component/src/main/java/org/apache/camel/component/fhir/FhirEndpoint.java
 
b/components/camel-fhir/camel-fhir-component/src/main/java/org/apache/camel/component/fhir/FhirEndpoint.java
index 63cf16681057..39eef10f8564 100644
--- 
a/components/camel-fhir/camel-fhir-component/src/main/java/org/apache/camel/component/fhir/FhirEndpoint.java
+++ 
b/components/camel-fhir/camel-fhir-component/src/main/java/org/apache/camel/component/fhir/FhirEndpoint.java
@@ -42,6 +42,9 @@ import 
org.apache.camel.component.fhir.internal.FhirApiCollection;
 import org.apache.camel.component.fhir.internal.FhirApiName;
 import org.apache.camel.component.fhir.internal.FhirConstants;
 import org.apache.camel.component.fhir.internal.FhirPropertiesHelper;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -55,6 +58,30 @@ import 
org.apache.camel.support.component.ApiMethodPropertiesHelper;
 @UriEndpoint(firstVersion = "2.23.0", scheme = "fhir", title = "FHIR", syntax 
= "fhir:apiName/methodName",
              apiSyntax = "apiName/methodName",
              category = { Category.API })
+@BodyTypes(consumerOut = @BodyType(value = Carrier.POJO,
+                                   byOption = @BodyType.When(option = 
"apiName", value = "TRANSACTION",
+                                                             carriers = { 
Carrier.POJO, Carrier.TEXT }),
+                                   description = "One message per poll with 
the return value of the method: a MethodOutcome"
+                                                 + " for create, update, 
patch, delete and validate, and a HAPI FHIR model"
+                                                 + " object of the fhirVersion 
for the others, such as the Bundle of a search."
+                                                 + " A List is split into one 
message per resource, and transaction withBundle"
+                                                 + " with stringBundle gives 
the response as text"),
+           producerIn = @BodyType(value = Carrier.ANY, dependsOn = "inBody",
+                                  description = "The body is used only when 
inBody names the argument of the method it is,"
+                                                + " and then passed as it is, 
without type conversion: resourceAsString,"
+                                                + " stringBundle and url take 
text (convertBodyTo String first), and resource,"
+                                                + " bundle and resources a 
HAPI FHIR object or a List of them (unmarshal with"
+                                                + " fhirJson or fhirXml 
first). Without inBody the arguments come from the"
+                                                + " options and the CamelFhir. 
headers"),
+           producerOut = @BodyType(value = Carrier.POJO,
+                                   byOption = @BodyType.When(option = 
"apiName", value = "TRANSACTION",
+                                                             carriers = { 
Carrier.POJO, Carrier.TEXT, Carrier.LIST }),
+                                   description = "The return value of the 
method: a MethodOutcome for create, update, patch,"
+                                                 + " delete and validate, and 
a HAPI FHIR model object of the fhirVersion for"
+                                                 + " the others, such as the 
Bundle of a search or the resource of a read;"
+                                                 + " marshal a resource or a 
Bundle with fhirJson or fhirXml to get text."
+                                                 + " transaction gives a 
Bundle for withBundle with bundle, the response as"
+                                                 + " text with stringBundle, 
and a List of the resources for withResources"))
 public class FhirEndpoint extends AbstractApiEndpoint<FhirApiName, 
FhirConfiguration> implements EndpointServiceLocation {
 
     private static final String EXTRA_PARAMETERS_PROPERTY = "extraParameters";
diff --git 
a/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
 
b/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
index c127837baf01..8fd717ae367a 100644
--- 
a/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
+++ 
b/components/camel-slack/src/generated/resources/META-INF/org/apache/camel/component/slack/slack.json
@@ -32,6 +32,11 @@
     "token": { "index": 5, "kind": "property", "displayName": "Token", 
"group": "token", "label": "token", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The token to access Slack. This app needs to 
have channels:history, groups:history, im:history, mpim:history, channels:read, 
groups:read, im:read and mpim:read permissions. The User OAuth Token is the 
kind of token needed." },
     "webhookUrl": { "index": 6, "kind": "property", "displayName": "Webhook 
Url", "group": "webhook", "label": "webhook", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "description": "The incoming webhook URL" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"com.slack.api.model.Message", "description": "One message per new message of 
the conversation, the most recent first, or the oldest first with naturalOrder; 
the first poll gives the last message of the history. The text is ${body.text}" 
},
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], 
"description": "The text of the message, or a com.slack.api.model.Message with 
its blocks: bytes, a stream or a file are read as text. A Map or another POJO 
is sent as its toString(): marshal it first. No body sends no text, which Slack 
rejects" },
+    "producerOut": { "carriers": [ "unchanged" ], "description": "Unchanged: 
the answer of Slack is not kept" }
+  },
   "properties": {
     "channel": { "index": 0, "kind": "path", "displayName": "Channel", 
"group": "common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The channel name (syntax #name) or 
slack user (syntax userName) to send a message directly to an user." },
     "token": { "index": 1, "kind": "parameter", "displayName": "Token", 
"group": "common", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "description": "The token to access 
Slack. This app needs to have channels:history, groups:history, im:history, 
mpim:history, channels:read, groups:read, im:read and mpim:read permissions. 
The User OAuth Token is the kind of token needed." },
diff --git a/components/camel-slack/src/main/docs/slack-component.adoc 
b/components/camel-slack/src/main/docs/slack-component.adoc
index b0708b1d502b..7f26de4031be 100644
--- a/components/camel-slack/src/main/docs/slack-component.adoc
+++ b/components/camel-slack/src/main/docs/slack-component.adoc
@@ -53,7 +53,12 @@ include::partial$component-endpoint-headers.adoc[]
 == Usage
 
 To send a message contained in the message body, a pre-established
-https://api.slack.com/incoming-webhooks[Slack incoming webhook] must be 
configured in Slack.
+https://api.slack.com/incoming-webhooks[Slack incoming webhook] must be 
configured in Slack, or the producer uses a token
+of a Slack app.
+
+The producer sends the message body as the text of the message: bytes, a 
stream or a file are read as text. A
+`com.slack.api.model.Message` body is sent with its blocks and attachments. 
Marshal a Map or another object to text
+first, as it is otherwise sent as its `toString()`. The message body is 
unchanged after the call.
 
 === Configuring in Spring XML
 
@@ -185,7 +190,8 @@ YAML::
 ----
 ====
 
-This way you'll get the last message from `general` channel.
+This way you'll get the last message from `general` channel, as a 
`com.slack.api.model.Message`, whose text is
+`${body.text}`.
 The consumer will track the timestamp of the last message consumed,
 and in the next poll it will consume only newer messages in the channel.
 When more than `maxResults` new messages were posted since the previous poll, 
the consumer reads the following pages of
diff --git 
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
 
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
index fb94edc48f68..dad140f3fd68 100644
--- 
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
+++ 
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
@@ -22,6 +22,9 @@ import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
 import org.apache.camel.RuntimeCamelException;
+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.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -34,6 +37,17 @@ import org.apache.camel.util.ObjectHelper;
  */
 @UriEndpoint(firstVersion = "2.16.0", scheme = "slack", title = "Slack", 
syntax = "slack:channel",
              category = { Category.SOCIAL })
+@BodyTypes(consumerOut = @BodyType(value = Carrier.POJO, javaType = 
"com.slack.api.model.Message",
+                                   description = "One message per new message 
of the conversation, the most recent first, or"
+                                                 + " the oldest first with 
naturalOrder; the first poll gives the last message"
+                                                 + " of the history. The text 
is ${body.text}"),
+           producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
+                                  description = "The text of the message, or a 
com.slack.api.model.Message with its blocks:"
+                                                + " bytes, a stream or a file 
are read as text. A Map or another POJO is sent"
+                                                + " as its toString(): marshal 
it first. No body sends no text, which Slack"
+                                                + " rejects"),
+           producerOut = @BodyType(value = Carrier.UNCHANGED,
+                                   description = "Unchanged: the answer of 
Slack is not kept"))
 public class SlackEndpoint extends ScheduledPollEndpoint {
 
     @UriParam(defaultValue = "" + SlackConsumer.DEFAULT_CONSUMER_DELAY, 
javaType = "java.time.Duration",
diff --git 
a/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
 
b/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
index 698cf8e42fe4..c5806545bcf2 100644
--- 
a/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
+++ 
b/components/camel-solr/src/generated/resources/META-INF/org/apache/camel/component/solr/solr.json
@@ -51,6 +51,10 @@
     "CamelSolrField.": { "index": 9, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "Object", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The prefix of the headers that carry the fields of the 
document to insert: the header CamelSolrField.id holds the id field.", 
"constantName": 
"org.apache.camel.component.solr.SolrConstants#HEADER_FIELD_PREFIX" },
     "CamelSolrParam.": { "index": 10, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The prefix of the headers that carry extra request 
parameters: the header CamelSolrParam.commit holds the commit parameter.", 
"constantName": 
"org.apache.camel.component.solr.SolrConstants#HEADER_PARAM_PREFIX" }
   },
+  "bodyTypes": {
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": 
"operation", "value": "DELETE", "carriers": [ "text", "list" ] } ], 
"description": "What the operation needs: INSERT a Map or a SolrInputDocument, 
a List of them or of @Field beans, a JSON array or XML text, or a file; DELETE 
the id or, with deleteByQuery=true, the query, as text or a List of text; 
SEARCH the query as text or a SolrQuery; PING does not use it. Convert bytes or 
a stream to text first. The CamelSolrField. [...]
+    "producerOut": { "carriers": [ "map" ], "description": "The response of 
Solr as a Map (a SimpleOrderedMap) with the responseHeader and, for a search, 
the documents under response as a SolrDocumentList. Convert it to a 
QueryResponse or a SolrPingResponse to read it with SolrJ" }
+  },
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"producer", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.solr.SolrConfiguration", "configurationField": 
"configuration", "description": "The solr instance host name (set to 'default' 
to use the host name defined on component level)" },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"producer", "label": "", "required": false, "type": "integer", "javaType": 
"int", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": 8983, "configurationClass": 
"org.apache.camel.component.solr.SolrConfiguration", "configurationField": 
"configuration", "description": "The solr instance port number" },
diff --git a/components/camel-solr/src/main/docs/solr-component.adoc 
b/components/camel-solr/src/main/docs/solr-component.adoc
index 9e574e8b94b5..5e8d8ccca062 100644
--- a/components/camel-solr/src/main/docs/solr-component.adoc
+++ b/components/camel-solr/src/main/docs/solr-component.adoc
@@ -62,16 +62,22 @@ the following. Some operations also require the message 
body to be set.
 |`INSERT` | File |inserts/updates a document or documents using the given File 
(using ContentStreamUpdateRequest)
 |`INSERT` | SolrInputDocument or Collection<SolrInputDocument>|inserts/updates 
a document or documents based on the given (collection of) SolrInputDocument
 |`INSERT` | bean or Collection<bean> |inserts/updates a document or documents 
based on values in an 
http://wiki.apache.org/solr/Solrj#Directly_adding_POJOs_to_Solr[annotated bean]
-|`INSERT` | String |inserts/updates a document or documents index based on the 
given XML or json as string (must follow SolrInputDocument format)
+|`INSERT` | Map or Collection<Map> |inserts/updates a document or documents 
with the fields of the Map
+|`INSERT` | String |inserts/updates the documents of the given JSON or XML 
text: a JSON array of documents, or XML documents (an `<add>` element is added 
when the text does not start with one)
 
-|`DELETE` | String or Collection<String> | delete a record by ID (or 
collection of ids) or by a query (or a collection of queries) when header 
"deleteByQuery=true"
+|`DELETE` | String or Collection<String> | delete a record by ID (or 
collection of ids) or by a query (or a collection of queries) when the 
`deleteByQuery` option or the `CamelSolrDeleteByQuery` header is true
 
-|`PING` |SolrPing | Pings the solr instance
+|`PING` |n/a | Pings the solr instance
 
 |`SEARCH` |SolrQuery | Performs a search request to the solr instance
 |`SEARCH` |QueryRequest | Performs a search request to the solr instance
+|`SEARCH` |String | Performs a search with the query as text, unless the 
`CamelSolrQueryString` header holds the query
 |=======================================================================
 
+After each operation the message body is the response of Solr as a Map (a 
`SimpleOrderedMap`) with the
+`responseHeader`, and for a search the documents under `response` as a 
`SolrDocumentList`. Convert it to a
+`QueryResponse` to read it with SolrJ.
+
 == Example
 
 Below is a simple `INSERT`, `DELETE` and `SEARCH` example
diff --git 
a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrEndpoint.java
 
b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrEndpoint.java
index f5f2fd66dd88..06c5ee7c0183 100644
--- 
a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrEndpoint.java
+++ 
b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrEndpoint.java
@@ -30,6 +30,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -50,6 +53,20 @@ import org.slf4j.LoggerFactory;
  */
 @UriEndpoint(firstVersion = "4.8.0", scheme = "solr", title = "Solr", syntax = 
"solr:host:port/basePath", producerOnly = true,
              category = { Category.SEARCH, Category.MONITORING }, headersClass 
= SolrConstants.class)
+@BodyTypes(producerIn = @BodyType(value = Carrier.ANY,
+                                  byOption = @BodyType.When(option = 
"operation", value = "DELETE",
+                                                            carriers = { 
Carrier.TEXT, Carrier.LIST }),
+                                  description = "What the operation needs: 
INSERT a Map or a SolrInputDocument, a List of"
+                                                + " them or of @Field beans, a 
JSON array or XML text, or a file; DELETE the id"
+                                                + " or, with 
deleteByQuery=true, the query, as text or a List of text; SEARCH"
+                                                + " the query as text or a 
SolrQuery; PING does not use it. Convert bytes or a"
+                                                + " stream to text first. The 
CamelSolrField. and CamelSolrQueryString headers"
+                                                + " can replace the body"),
+           producerOut = @BodyType(value = Carrier.MAP,
+                                   description = "The response of Solr as a 
Map (a SimpleOrderedMap) with the responseHeader"
+                                                 + " and, for a search, the 
documents under response as a SolrDocumentList."
+                                                 + " Convert it to a 
QueryResponse or a SolrPingResponse to read it with"
+                                                 + " SolrJ"))
 public class SolrEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SolrEndpoint.class);
diff --git 
a/components/camel-spring-parent/camel-spring-redis/src/generated/resources/META-INF/org/apache/camel/component/redis/spring-redis.json
 
b/components/camel-spring-parent/camel-spring-redis/src/generated/resources/META-INF/org/apache/camel/component/redis/spring-redis.json
index d6b8fc72d591..4af7413f7668 100644
--- 
a/components/camel-spring-parent/camel-spring-redis/src/generated/resources/META-INF/org/apache/camel/component/redis/spring-redis.json
+++ 
b/components/camel-spring-parent/camel-spring-redis/src/generated/resources/META-INF/org/apache/camel/component/redis/spring-redis.json
@@ -60,6 +60,11 @@
     "CamelRedis.Longitude": { "index": 27, "kind": "header", "displayName": 
"", "group": "common", "label": "", "required": false, "javaType": "Double", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Longitude.", "constantName": 
"org.apache.camel.component.redis.RedisConstants#LONGITUDE" },
     "CamelRedis.Radius": { "index": 28, "kind": "header", "displayName": "", 
"group": "common", "label": "", "required": false, "javaType": "Double", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Radius.", "constantName": 
"org.apache.camel.component.redis.RedisConstants#RADIUS" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "unknown" ], "description": "The message as 
the serializer reads it: with the default JDK serializer the Java object the 
publisher serialized, such as the String a PUBLISH of this component sent, with 
the channel in the CamelRedis.Channel header. A message that is not Java 
serialized, such as one from redis-cli, is dropped with a WARN: set a 
StringRedisSerializer as the serializer to receive text" },
+    "producerIn": { "carriers": [ "any" ], "description": "The body is not 
used: the key, the value and the other arguments of the command come from 
headers, such as CamelRedis.Key and CamelRedis.Value, or CamelRedis.Channel and 
CamelRedis.Message for PUBLISH. Set CamelRedis.Value from the body to store the 
body: with the default serializer it must be Serializable, so a stream fails" },
+    "producerOut": { "carriers": [ "unchanged" ], "dependsOn": [ "command" ], 
"byOption": [ { "option": "command", "value": "SET", "carriers": [ "unchanged" 
] }, { "option": "command", "value": "SETEX", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "MSET", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "HSET", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "HMSET", "carriers": [ "unchanged" ] }, { 
"option": "command", "value": "HDEL", "car [...]
+  },
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.redis.RedisConfiguration", "configurationField": 
"configuration", "description": "The host where Redis server is running." },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"common", "label": "", "required": true, "type": "integer", "javaType": 
"java.lang.Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.redis.RedisConfiguration", "configurationField": 
"configuration", "description": "Redis server port number" },
diff --git 
a/components/camel-spring-parent/camel-spring-redis/src/main/docs/spring-redis-component.adoc
 
b/components/camel-spring-parent/camel-spring-redis/src/main/docs/spring-redis-component.adoc
index 302c90ea9268..a9037b6057bb 100644
--- 
a/components/camel-spring-parent/camel-spring-redis/src/main/docs/spring-redis-component.adoc
+++ 
b/components/camel-spring-parent/camel-spring-redis/src/main/docs/spring-redis-component.adoc
@@ -53,7 +53,9 @@ at 
https://github.com/apache/camel/tree/main/components/camel-spring-redis/src/t
 
 The producer runs the command of the `command` option, or of the 
`CamelRedis.Command` header.
 The parameters of the command come from headers, such as `CamelRedis.Key` and 
`CamelRedis.Value`, not from the
-message body. The tables below list the parameters of each command. The result 
of the command is the message body:
+message body. The tables below list the parameters of each command. The result 
of the command becomes the message body,
+and a command without a result, such as `SET`, or a result that is null, such 
as `GET` of a missing key, leaves the body
+as it was:
 
 [source,yaml]
 ----
@@ -156,17 +158,19 @@ these keys:
 === Message headers evaluated by the Redis producer
 
 The producer issues commands to the server, and each command has
-a different set of parameters with specific types. The result from the
-command execution is returned in the message body.
+a different set of parameters with specific types. The result of the
+command becomes the message body. A command whose result is void, and a result 
that is null, leave the body as it was.
+The value commands, such as `GET`, `HGET` and `LPOP`, return the object that 
was stored: with the default serializer the
+Java object the `CamelRedis.Value` header held.
 
 [width="100%",cols="10%,40%,50%,10%",options="header",]
 |=======================================================================
 |Hash Commands |Description |Parameters |Result
 |`HSET` |Set the string value of a hash field 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELD`/"CamelRedis.Field" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value"
 (Object) |void
-|`HGET` |Get the value of a hash field |`RedisConstants.KEY`/"CamelRedis.Key" 
(String), `RedisConstants.FIELD`/"CamelRedis.Field" (String) |String
+|`HGET` |Get the value of a hash field |`RedisConstants.KEY`/"CamelRedis.Key" 
(String), `RedisConstants.FIELD`/"CamelRedis.Field" (String) |Object
 |`HSETNX` |Set the value of a hash field only if the field does not exist 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELD`/"CamelRedis.Field" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value"
-(Object) |void
+(Object) |Boolean
 |`HMSET` |Set multiple hash fields to multiple values 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUES`/"CamelRedis.Values" (Map<String, Object>) |void
 |`HMGET` |Get the values of all the given hash fields 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELDS`/"CamelRedis.Fields" (Collection<String>) 
|Collection<Object>
 |`HINCRBY` |Increment the integer value of a hash field by the given number 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.FIELD`/"CamelRedis.Field" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value"
@@ -195,7 +199,7 @@ command execution is returned in the message body.
 
 |`LTRIM` |Trim a list to the specified range 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.START`/"CamelRedis.Start"Long), 
`RedisConstants.END`/"CamelRedis.End" (Long) |void
 
-|`LINDEX` |Get an element from a list by its index 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.INDEX`/"CamelRedis.Index" (Long) |String
+|`LINDEX` |Get an element from a list by its index 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.INDEX`/"CamelRedis.Index" (Long) |Object
 
 |`LINSERT` |Insert an element before or after another element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object), 
`RedisConstants.PIVOT`/"CamelRedis.Pivot"
 (String), `RedisConstants.POSITION`/"CamelRedis.Position" (String) |Long
@@ -208,7 +212,7 @@ command execution is returned in the message body.
 
 |`LPOP` |Remove and get the first element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 
-|`RPOP` |Remove and get the last element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
+|`RPOP` |Remove and get the last element in a list 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 
 |`RPOPLPUSH` |Remove the last element in a list, append it to another list and 
return
 it |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.DESTINATION`/"CamelRedis.Destination" (String) |Object
@@ -221,20 +225,20 @@ until one is available 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), `RedisCo
 available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEOUT`/"CamelRedis.Timeout" (Long) |Object
 
 |`BRPOP` |Remove and get the last element in a list, or block until one is
-available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEOUT`/"CamelRedis.Timeout" (Long) |String
+available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEOUT`/"CamelRedis.Timeout" (Long) |Object
 |=======================================================================
 
 [width="100%",cols="10%,40%,50%,10%",options="header",]
 |=======================================================================
 |Set Commands |Description |Parameters |Result
 
-|`SADD` |Add one or more members to a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Boolean
+|`SADD` |Add one or more members to a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Long
 
 |`SMEMBERS` |Get all the members in a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Set<Object>
 
-|`SREM` |Remove one or more members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Boolean
+|`SREM` |Remove one or more members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Long
 
-|`SPOP` |Remove and return a random member from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
+|`SPOP` |Remove and return a random member from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 
 |`SMOVE` |Move a member from one set to another 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object),
 `RedisConstants.DESTINATION`/"CamelRedis.Destination" (String) |Boolean
@@ -258,7 +262,7 @@ available |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.TIMEO
 |`SDIFFSTORE` |Subtract multiple sets and store the resulting set in a key 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.KEYS`/"CamelRedis.Keys" (String),
 `RedisConstants.DESTINATION`/"CamelRedis.Destination" (String) |void
 
-|`SRANDMEMBER` |Get one or multiple random members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
+|`SRANDMEMBER` |Get one or multiple random members from a set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |Object
 |=======================================================================
 
 [width="100%",cols="10%,40%,50%,10%",options="header",]
@@ -272,7 +276,7 @@ already exists |`RedisConstants.KEY`/"CamelRedis.Key" 
(String), `RedisConstants.
 |`ZRANGE` |Return a range of members in a sorted set, by index 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.START`/"CamelRedis.Start"Long), 
`RedisConstants.END`/"CamelRedis.End" (Long),
 `RedisConstants.WITHSCORE`/"CamelRedis.WithScore" (Boolean) |Object
 
-|`ZREM` |Remove one or more members from a sorted set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Boolean
+|`ZREM` |Remove one or more members from a sorted set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object) |Long
 
 |`ZINCRBY` |Increment the score of a member in a sorted set 
|`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (Object), 
`RedisConstants.INCREMENT`/"CamelRedis.Increment"
 (Double) |Double
@@ -362,9 +366,9 @@ new key |`RedisConstants.KEY`/"CamelRedis.Key" (String), 
`RedisConstants.KEYS`/"
 
 |`DEL` |Delete a key |`RedisConstants.KEYS`/"CamelRedis.Keys" (String) |void
 
-|`TYPE` |Determine the type stored at key 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |DataType
+|`TYPE` |Determine the type stored at key 
|`RedisConstants.KEY`/"CamelRedis.Key" (String) |String
 
-|`KEYS` |Find all keys matching the given pattern 
|`RedisConstants.PATERN`/"CamelRedis.Pattern" (String) |Collection<String>
+|`KEYS` |Find all keys matching the given pattern 
|`RedisConstants.PATERN`/"CamelRedis.Pattern" (String) |Set<String>
 
 |`RANDOMKEY` |Return a random key from the keyspace 
|`RedisConstants.PATERN`/"CamelRedis.Pattern" (String), 
`RedisConstants.VALUE`/"CamelRedis.Value" (String) |String
 
diff --git 
a/components/camel-spring-parent/camel-spring-redis/src/main/java/org/apache/camel/component/redis/RedisEndpoint.java
 
b/components/camel-spring-parent/camel-spring-redis/src/main/java/org/apache/camel/component/redis/RedisEndpoint.java
index fdc4049b430c..38e3afc7914a 100644
--- 
a/components/camel-spring-parent/camel-spring-redis/src/main/java/org/apache/camel/component/redis/RedisEndpoint.java
+++ 
b/components/camel-spring-parent/camel-spring-redis/src/main/java/org/apache/camel/component/redis/RedisEndpoint.java
@@ -20,6 +20,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -31,6 +34,88 @@ import org.springframework.data.redis.core.RedisTemplate;
  */
 @UriEndpoint(firstVersion = "2.11.0", scheme = "spring-redis", title = "Spring 
Redis", syntax = "spring-redist:host:port",
              category = { Category.CACHE }, headersClass = 
RedisConstants.class)
+@BodyTypes(consumerOut = @BodyType(value = Carrier.UNKNOWN,
+                                   description = "The message as the 
serializer reads it: with the default JDK serializer the"
+                                                 + " Java object the publisher 
serialized, such as the String a PUBLISH of this"
+                                                 + " component sent, with the 
channel in the CamelRedis.Channel header. A"
+                                                 + " message that is not Java 
serialized, such as one from redis-cli, is"
+                                                 + " dropped with a WARN: set 
a StringRedisSerializer as the serializer to"
+                                                 + " receive text"),
+           producerIn = @BodyType(value = Carrier.ANY,
+                                  description = "The body is not used: the 
key, the value and the other arguments of the"
+                                                + " command come from headers, 
such as CamelRedis.Key and CamelRedis.Value, or"
+                                                + " CamelRedis.Channel and 
CamelRedis.Message for PUBLISH. Set CamelRedis.Value"
+                                                + " from the body to store the 
body: with the default serializer it must be"
+                                                + " Serializable, so a stream 
fails"),
+           producerOut = @BodyType(value = Carrier.UNCHANGED, dependsOn = 
"command",
+                                   byOption = {
+                                           @BodyType.When(option = "command", 
value = "SET", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "SETEX", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "MSET", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "HSET", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "HMSET", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "HDEL", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "DEL", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "PUBLISH", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "LSET", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "LTRIM", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "RENAME", carriers = Carrier.UNCHANGED),
+                                           @BodyType.When(option = "command", 
value = "EXISTS", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Boolean"),
+                                           @BodyType.When(option = "command", 
value = "EXPIRE", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Boolean"),
+                                           @BodyType.When(option = "command", 
value = "SETNX", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Boolean"),
+                                           @BodyType.When(option = "command", 
value = "HEXISTS", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Boolean"),
+                                           @BodyType.When(option = "command", 
value = "SISMEMBER", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Boolean"),
+                                           @BodyType.When(option = "command", 
value = "INCR", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "INCRBY", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "DECR", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "DECRBY", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "LPUSH", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "RPUSH", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "LLEN", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "SADD", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "SREM", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "SCARD", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "HLEN", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "TTL", carriers = Carrier.VALUE,
+                                                          javaType = 
"java.lang.Long"),
+                                           @BodyType.When(option = "command", 
value = "PING", carriers = Carrier.TEXT,
+                                                          javaType = 
"java.lang.String"),
+                                           @BodyType.When(option = "command", 
value = "ECHO", carriers = Carrier.TEXT,
+                                                          javaType = 
"java.lang.String"),
+                                           @BodyType.When(option = "command", 
value = "HGETALL", carriers = Carrier.MAP),
+                                           @BodyType.When(option = "command", 
value = "KEYS", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "HKEYS", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "HVALS", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "SMEMBERS", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "ZRANGE", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "ZREVRANGE", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "LRANGE", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "MGET", carriers = Carrier.LIST),
+                                           @BodyType.When(option = "command", 
value = "HMGET", carriers = Carrier.LIST) },
+                                   description = "The result of the command, 
which the CamelRedis.Command header can pick"
+                                                 + " instead of the command 
option: SET, the default, PUBLISH and the other"
+                                                 + " commands without a result 
keep the body; a Boolean or a number for those"
+                                                 + " that test, count or 
increment, a Set or a List for those that read several"
+                                                 + " members, a Map for 
HGETALL, and the stored object, which can be anything,"
+                                                 + " for GET, HGET, LINDEX, 
LPOP, RPOP, SPOP and the other commands that read"
+                                                 + " or pop one value: these 
are not known. A null result, such as GET of a"
+                                                 + " missing key, keeps the 
body as well"))
 public class RedisEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
     @UriParam
diff --git 
a/components/camel-ssh/src/generated/resources/META-INF/org/apache/camel/component/ssh/ssh.json
 
b/components/camel-ssh/src/generated/resources/META-INF/org/apache/camel/component/ssh/ssh.json
index dd1c9ac5740b..6fa65e1a1bc7 100644
--- 
a/components/camel-ssh/src/generated/resources/META-INF/org/apache/camel/component/ssh/ssh.json
+++ 
b/components/camel-ssh/src/generated/resources/META-INF/org/apache/camel/component/ssh/ssh.json
@@ -63,6 +63,11 @@
     "CamelSshStderr": { "index": 2, "kind": "header", "displayName": "", 
"group": "common", "label": "", "required": false, "javaType": "InputStream", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The value of this header is a InputStream with the 
standard error stream of the executable.", "constantName": 
"org.apache.camel.component.ssh.SshConstants#STDERR" },
     "CamelSshExitValue": { "index": 3, "kind": "header", "displayName": "", 
"group": "common", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "important": true, "description": "The value of this header is the exit 
value that is returned, after the execution. By convention a non-zero status 
exit value indicates abnormal termination. Note that the exit value is OS 
dependent.", "constantName": "org.apac [...]
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "stream" ], "description": "The standard 
output of the pollCommand as a stream, with the exit value and the standard 
error in the CamelSshExitValue and CamelSshStderr headers" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"description": "The command to run, as text: bytes, a stream or a file are read 
as text. A Map or a POJO is sent as its toString(): build the command as text 
first. No body fails" },
+    "producerOut": { "carriers": [ "stream" ], "description": "The standard 
output of the command as a stream, with the exit value and the standard error 
in the CamelSshExitValue and CamelSshStderr headers; an exit value that is not 
0 does not fail the exchange. Convert it to text to read it more than once" }
+  },
   "properties": {
     "host": { "index": 0, "kind": "path", "displayName": "Host", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.ssh.SshConfiguration", "configurationField": 
"configuration", "description": "Sets the hostname of the remote SSH server." },
     "port": { "index": 1, "kind": "path", "displayName": "Port", "group": 
"common", "label": "", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 22, 
"configurationClass": "org.apache.camel.component.ssh.SshConfiguration", 
"configurationField": "configuration", "description": "Sets the port number for 
the remote SSH server." },
diff --git a/components/camel-ssh/src/main/docs/ssh-component.adoc 
b/components/camel-ssh/src/main/docs/ssh-component.adoc
index e1b2e38de453..79f1fabccde9 100644
--- a/components/camel-ssh/src/main/docs/ssh-component.adoc
+++ b/components/camel-ssh/src/main/docs/ssh-component.adoc
@@ -50,6 +50,10 @@ When the SSH Component is used as a Producer 
(`.to("ssh://...")`), it
 will send the message body as the command to execute on the remote SSH
 server.
 
+After the call, the message body is the standard output of the command as a 
stream, with the exit value in the
+`CamelSshExitValue` header and the standard error, as a stream as well, in the 
`CamelSshStderr` header. An exit value
+that is not 0 does not fail the exchange. The polling consumer gives the 
output of its `pollCommand` the same way.
+
 Here is an example of this. Note that the command has
 a newline (`+&#10;+`).
 
diff --git 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
index f2f5298d821e..91874a082274 100644
--- 
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
+++ 
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
@@ -22,6 +22,9 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -34,6 +37,18 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 @UriEndpoint(firstVersion = "2.10.0", scheme = "ssh", title = "SSH", syntax = 
"ssh:host:port",
              alternativeSyntax = "ssh:username:password@host:port", category = 
{ Category.FILE },
              headersClass = SshConstants.class)
+@BodyTypes(consumerOut = @BodyType(value = Carrier.STREAM,
+                                   description = "The standard output of the 
pollCommand as a stream, with the exit value and"
+                                                 + " the standard error in the 
CamelSshExitValue and CamelSshStderr headers"),
+           producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE },
+                                  description = "The command to run, as text: 
bytes, a stream or a file are read as text. A Map"
+                                                + " or a POJO is sent as its 
toString(): build the command as text first. No"
+                                                + " body fails"),
+           producerOut = @BodyType(value = Carrier.STREAM,
+                                   description = "The standard output of the 
command as a stream, with the exit value and the"
+                                                 + " standard error in the 
CamelSshExitValue and CamelSshStderr headers; an"
+                                                 + " exit value that is not 0 
does not fail the exchange. Convert it to text to"
+                                                 + " read it more than once"))
 public class SshEndpoint extends ScheduledPollEndpoint implements 
EndpointServiceLocation {
 
     @UriParam
diff --git 
a/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
 
b/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
index a26ad023217c..2c99fd226779 100644
--- 
a/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
+++ 
b/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
@@ -41,6 +41,11 @@
     "CamelTelegramParseMode": { "index": 4, "kind": "header", "displayName": 
"", "group": "common", "label": "", "required": false, "javaType": 
"org.apache.camel.component.telegram.TelegramParseMode", "enum": [ "HTML", 
"MARKDOWN" ], "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "This header is used to format text messages 
using HTML or Markdown", "constantName": 
"org.apache.camel.component.telegram.TelegramConstants#TELEGRAM_PARSE_MODE" },
     "CamelMessageTimestamp": { "index": 5, "kind": "header", "displayName": 
"", "group": "common", "label": "", "required": false, "javaType": "long", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The message timestamp.", "constantName": 
"org.apache.camel.component.telegram.TelegramConstants#MESSAGE_TIMESTAMP" }
   },
+  "bodyTypes": {
+    "consumerOut": { "carriers": [ "pojo", "null" ], "description": "One 
message per update: an IncomingMessage for a message or a channel post, with 
the chat id in the CamelTelegramChatId header, and the IncomingCallbackQuery, 
IncomingInlineQuery, PreCheckoutQuery, ShippingQuery or PaidMediaPurchased of 
the other updates; no body for an update of another kind, such as an edited 
message" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo", 
"null" ], "description": "A message of 
org.apache.camel.component.telegram.model, such as an OutgoingTextMessage, or 
text to send as a text message: a stream or a file is read as text, and a Map 
or another POJO is sent as its toString(), so marshal it first. A byte array is 
sent as the photo, audio, video or document the CamelTelegramMediaType header 
names, and fails without it. No body sends nothing" },
+    "producerOut": { "carriers": [ "pojo", "unchanged" ], "description": "The 
MessageResult of the call, with the message sent, or a subclass of it such as 
MessageResultString for createInvoiceLink; an answer with ok=false is a 
MessageResult too. No body is left as it is" }
+  },
   "properties": {
     "type": { "index": 0, "kind": "path", "displayName": "Type", "group": 
"common", "label": "", "required": true, "type": "enum", "javaType": 
"java.lang.String", "enum": [ "bots" ], "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The endpoint type. 
Currently, only the 'bots' type is supported." },
     "limit": { "index": 1, "kind": "parameter", "displayName": "Limit", 
"group": "consumer", "label": "consumer", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "optionalPrefix": "consumer.", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": 100, 
"configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "Limit on the number of 
updates that can be received in a si [...]
diff --git a/components/camel-telegram/src/main/docs/telegram-component.adoc 
b/components/camel-telegram/src/main/docs/telegram-component.adoc
index 1c01a0531f03..decdf4ee0d9e 100644
--- a/components/camel-telegram/src/main/docs/telegram-component.adoc
+++ b/components/camel-telegram/src/main/docs/telegram-component.adoc
@@ -123,7 +123,7 @@ The following message bodies are allowed for a producer 
endpoint (messages of ty
 | `EditMessageMediaMessage` | To edit animation, audio, document, photo, or 
video messages. (editMessageMedia)
 | `EditMessageReplyMarkupMessage` | To edit only the reply markup of a 
message. (editMessageReplyMarkup)
 | `EditMessageDelete` | To delete a message, including service messages. 
(deleteMessage)
-| `SendLocationMessage` | To send a location (setSendLocation)
+| `SendLocationMessage` | To send a location (sendLocation)
 | `EditMessageLiveLocationMessage` | To send changes to a live location 
(editMessageLiveLocation)
 | `StopMessageLiveLocationMessage` | To stop updating a live location message 
sent by the bot or via the bot (for inline bots) before live_period expires 
(stopMessageLiveLocation)
 | `SendVenueMessage` | To send information about a venue (sendVenue)
@@ -136,11 +136,15 @@ The following message bodies are allowed for a producer 
endpoint (messages of ty
 | `GetStarTransactionsMessage` | To get the list of bot's Telegram Star 
transactions (getStarTransactions)
 | `RefundStarPaymentMessage` | To refund a successful payment in Telegram 
Stars (refundStarPayment)
 | `EditUserStarSubscriptionMessage` | To cancel or re-enable extension of a 
subscription paid in Telegram Stars (editUserStarSubscription)
-| `byte[]` | To send any media type supported. It requires the 
`CamelTelegramMediaType` header to be set to the appropriate media type
+| `byte[]` | To send a photo, an audio, a video or a document, as the 
`CamelTelegramMediaType` header names (`PHOTO_PNG`, `PHOTO_JPG`, `AUDIO`, 
`VIDEO` or `DOCUMENT`). It fails without the header. A stream or a file is sent 
as text, so convert it to `byte[]` to send it as media
 | `String` | To send a text message to a chat. It gets converted automatically 
into a `OutgoingTextMessage`
 
 |===================================================
 
+Any other body is sent as text, so marshal a Map or another object first, as 
it is otherwise sent as its `toString()`.
+After the call the message body is the `MessageResult` of the call, with the 
message sent, or a subclass of it, such as
+`MessageResultString` for `CreateInvoiceLinkMessage`. A message body that is 
null sends nothing, and stays null.
+
 
 === Consumer
 
@@ -204,11 +208,16 @@ Supported types for incoming messages are
 |===================================================
 | Java Type | Description
 
-| `IncomingMessage` | The full object representation of an incoming message
+| `IncomingMessage` | The full object representation of an incoming message or 
channel post, with the chat id in the `CamelTelegramChatId` header
 | `String` | The content of the message, for text messages only
+| `IncomingCallbackQuery` | A callback query, from a button of an inline 
keyboard
+| `IncomingInlineQuery` | An inline query
+| `PreCheckoutQuery`, `ShippingQuery`, `PaidMediaPurchased` | The payment 
updates, of the package `org.apache.camel.component.telegram.model.payments`
 
 |===================================================
 
+An update of another kind, such as an edited message, gives a message with no 
body.
+
 
 === Reactive Chat-Bot Example
 
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
index f1f248bb383e..d13603036b7b 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
@@ -38,6 +38,9 @@ import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.telegram.service.TelegramServiceRestBotAPIAdapter;
 import org.apache.camel.component.webhook.WebhookCapableEndpoint;
 import org.apache.camel.component.webhook.WebhookConfiguration;
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
 import org.apache.camel.spi.EndpointServiceLocation;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -51,6 +54,25 @@ import org.slf4j.LoggerFactory;
  */
 @UriEndpoint(firstVersion = "2.18.0", scheme = "telegram", title = "Telegram", 
syntax = "telegram:type",
              category = { Category.CLOUD, Category.API, Category.CHAT }, 
headersClass = TelegramConstants.class)
+@BodyTypes(consumerOut = @BodyType(value = { Carrier.POJO, Carrier.NULL },
+                                   description = "One message per update: an 
IncomingMessage for a message or a channel post,"
+                                                 + " with the chat id in the 
CamelTelegramChatId header, and the"
+                                                 + " IncomingCallbackQuery, 
IncomingInlineQuery, PreCheckoutQuery,"
+                                                 + " ShippingQuery or 
PaidMediaPurchased of the other updates; no body for an"
+                                                 + " update of another kind, 
such as an edited message"),
+           producerIn = @BodyType(value = {
+                   Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, 
Carrier.POJO,
+                   Carrier.NULL },
+                                  description = "A message of 
org.apache.camel.component.telegram.model, such as an"
+                                                + " OutgoingTextMessage, or 
text to send as a text message: a stream or a file"
+                                                + " is read as text, and a Map 
or another POJO is sent as its toString(), so"
+                                                + " marshal it first. A byte 
array is sent as the photo, audio, video or"
+                                                + " document the 
CamelTelegramMediaType header names, and fails without it."
+                                                + " No body sends nothing"),
+           producerOut = @BodyType(value = { Carrier.POJO, Carrier.UNCHANGED },
+                                   description = "The MessageResult of the 
call, with the message sent, or a subclass of it"
+                                                 + " such as 
MessageResultString for createInvoiceLink; an answer with ok=false"
+                                                 + " is a MessageResult too. 
No body is left as it is"))
 public class TelegramEndpoint extends ScheduledPollEndpoint implements 
WebhookCapableEndpoint, EndpointServiceLocation {
     private static final Logger LOG = 
LoggerFactory.getLogger(TelegramEndpoint.class);
     private static final Pattern WEBHOOK_SECRET_TOKEN_PATTERN = 
Pattern.compile("[A-Za-z0-9_-]{1,256}");
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 def082d4063b..16dad51ecaa9 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
@@ -35,6 +35,7 @@ import 
org.apache.camel.tooling.model.BodyTypeModel.OptionBodyTypeModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.DataFormatModel;
 import org.apache.camel.tooling.model.EipModel;
+import org.apache.camel.util.StringHelper;
 
 import static org.apache.camel.dsl.yaml.validator.RouteGraph.scheme;
 
@@ -252,7 +253,7 @@ final class CatalogBodyTypes {
         for (String option : side.getDependsOn()) {
             String value = set.get(option);
             if (value != null && side.getByOption().stream()
-                    .noneMatch(w -> w.getOption().equals(option) && 
value.equalsIgnoreCase(w.getValue()))) {
+                    .noneMatch(w -> w.getOption().equals(option) && 
isSameValue(value, w.getValue()))) {
                 // the body depends on this option, and no option value of the 
side says the body for its value: the
                 // value another option names does not hold then, such as 
output=bytes with a resultHandlerFactory
                 return null;
@@ -261,7 +262,7 @@ final class CatalogBodyTypes {
         Answer matched = null;
         for (OptionBodyTypeModel when : side.getByOption()) {
             String value = set.get(when.getOption());
-            if (value != null && value.equalsIgnoreCase(when.getValue())) {
+            if (value != null && isSameValue(value, when.getValue())) {
                 if (matched != null && 
!matched.carriers().equals(when.getCarriers())) {
                     // two options the step sets say different bodies
                     return null;
@@ -278,6 +279,15 @@ final class CatalogBodyTypes {
         return new Answer(side.getCarriers(), side.getJavaType(), 
side.getFormat());
     }
 
+    /**
+     * Whether the value a step sets is the value of an option body: in any 
case, and in any style of an enum constant,
+     * as the catalog and the enum type converter take it, so 
operation=convert-to-json is CONVERT_TO_JSON.
+     */
+    private static boolean isSameValue(String value, String other) {
+        return value.equalsIgnoreCase(other)
+                || 
StringHelper.asEnumConstantValue(value).equals(StringHelper.asEnumConstantValue(other));
+    }
+
     /** The options of a node, by name in camel case: allowNullBody as well 
for allow-null-body. */
     private static Map<String, String> values(JsonNode node) {
         Map<String, String> answer = new HashMap<>();
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 24da4237401c..10be33f10128 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
@@ -1491,6 +1491,156 @@ public class TextBodyFlowTest {
                         .contains("route mail: to: smtp needs the body as 
text, bytes, a stream or a file"));
     }
 
+    @Test
+    public void testTheBodyOfMoreComponents() {
+        // ssh runs the body as a command and gives its output as a stream, 
and so does its consumer; spring-redis keeps
+        // the body for SET, its default command, gives text for PING and is 
not known for GET; a couchbase row read with
+        // fullDocument=false is JSON text, and so is the result of CCB_DEL, 
while CCB_GET gives the GetResult of the SDK;
+        // the response of solr is a Map, and slack also sends an object as 
its text
+        String yaml = """
+                - route:
+                    id: shell
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: '{"id": 1}'
+                        - unmarshal:
+                            json: {}
+                        - to: ssh:localhost:22
+                - route:
+                    id: output
+                    from:
+                      uri: ssh:localhost:22?pollCommand=date
+                      steps:
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: ping
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: spring-redis:localhost:6379?command=PING
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: cache
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: '{"id": 1}'
+                        - unmarshal:
+                            json: {}
+                        - to: spring-redis:localhost:6379
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: lookup
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: spring-redis:localhost:6379?command=GET
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: rows
+                    from:
+                      uri: 
couchbase:http://localhost?bucket=orders&fullDocument=false
+                      steps:
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: remove
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: 
couchbase:http://localhost?bucket=orders&operation=CCB_DEL
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: fetch
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: 
couchbase:http://localhost?bucket=orders&operation=CCB_GET
+                        - setHeader:
+                            name: id
+                            simple: ${body[id]}
+                - route:
+                    id: search
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: "id:1"
+                        - to: 
solr:localhost:8983/solr?collection=orders&operation=SEARCH
+                        - setHeader:
+                            name: hits
+                            simple: ${body[response]}
+                - route:
+                    id: chat
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - setBody:
+                            constant: '{"id": 1}'
+                        - unmarshal:
+                            json: {}
+                        - to: slack:#orders
+                """;
+        assertThat(lines(yaml)).hasSize(5)
+                .anySatisfy(
+                        m -> assertThat(m).contains("route shell: to: ssh 
needs the body as text, bytes, a stream or a file"))
+                .anySatisfy(m -> assertThat(m).contains("route remove: simple 
reads a field of the body (${body[id]})"))
+                .anySatisfy(m -> assertThat(m).contains("route output: simple 
reads a field of the body (${body[id]})"))
+                .anySatisfy(m -> assertThat(m).contains("route ping: simple 
reads a field of the body (${body[id]})"))
+                .anySatisfy(m -> assertThat(m).contains("route rows: simple 
reads a field of the body (${body[id]})"));
+    }
+
+    @Test
+    public void testAnOptionValueInAnotherStyleOfTheEnum() {
+        // the catalog and the enum type converter take convert-to-json and 
convertToJson as CONVERT_TO_JSON, so after
+        // docling the body is the document of that operation, and not the 
text of the default operation
+        String yaml = """
+                - route:
+                    id: dashes
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: docling:convert?operation=convert-to-json
+                        - setHeader:
+                            name: title
+                            simple: ${body[title]}
+                - route:
+                    id: camelCase
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: docling:convert?operation=convertToJson
+                        - setHeader:
+                            name: title
+                            simple: ${body[title]}
+                - route:
+                    id: markdown
+                    from:
+                      uri: timer:tick
+                      steps:
+                        - to: docling:convert?operation=convert-to-markdown
+                        - setHeader:
+                            name: title
+                            simple: ${body[title]}
+                """;
+        assertThat(lines(yaml)).singleElement().asString()
+                .contains("route markdown: simple reads a field of the body 
(${body[title]})");
+    }
+
     @Test
     public void testTheBodyOfCoreComponents() {
         // the stream consumer gives each line as text, so a field read needs 
an unmarshal, and stream:out writes text,
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 fdde7315fe11..9cab8c34ffc9 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
@@ -21,6 +21,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -31,6 +32,7 @@ import org.apache.camel.tooling.model.BaseModel;
 import org.apache.camel.tooling.model.BaseOptionModel;
 import org.apache.camel.tooling.model.BodyTypeModel;
 import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.util.Strings;
 
 /**
  * Carries the {@link BodyTypes} of a component, data format or language into 
its model, which the catalog JSON is
@@ -140,19 +142,32 @@ final class BodyTypesHelper {
 
     /**
      * A body for a value the option cannot have would never hold: the value 
is one of an enum, or true or false for a
-     * boolean, in any case, as tooling compares it with what the endpoint 
sets.
+     * boolean, in any case and in any style of an enum constant, as tooling 
compares it with what the endpoint sets.
      */
     private static void checkValue(Class<?> owner, String side, BodyType.When 
when, BaseOptionModel option) {
         List<String> values = option.getEnums() != null && 
!option.getEnums().isEmpty()
                 ? option.getEnums()
                 : "boolean".equals(option.getType()) ? List.of("true", 
"false") : List.of();
-        if (!values.isEmpty() && values.stream().noneMatch(v -> 
v.equalsIgnoreCase(when.value()))) {
+        if (!values.isEmpty() && values.stream().noneMatch(v -> isSameValue(v, 
when.value()))) {
             throw new IllegalArgumentException(
                     owner.getName() + ": the @BodyTypes " + side + " has " + 
when.option() + "=" + when.value()
                                                + ", which is not a value of 
the option: " + String.join(", ", values));
         }
     }
 
+    /**
+     * Whether two values of an option are one value as Camel takes it: in any 
case, and in any style of an enum
+     * constant, so convert-to-json and convertToJson are CONVERT_TO_JSON, as 
for the catalog and the enum type
+     * converter (StringHelper.asEnumConstantValue in camel-util, which this 
plugin does not depend on).
+     */
+    private static boolean isSameValue(String value, String other) {
+        return value.equalsIgnoreCase(other) || 
asEnumConstant(value).equals(asEnumConstant(other));
+    }
+
+    private static String asEnumConstant(String value) {
+        return Strings.camelCaseToDash(value).replaceAll("-+", 
"-").replace('-', '_').toUpperCase(Locale.ENGLISH);
+    }
+
     /**
      * 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/EndpointSchemaGeneratorMojoTest.java
 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
index fc5e64ec4ade..e135f7a465b6 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
@@ -28,6 +28,7 @@ import 
org.apache.camel.maven.packaging.endpoint.SomeEndpointUsingInterfaceConst
 import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBadBodyTypes;
 import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBadHeaders;
 import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBodyTypes;
+import 
org.apache.camel.maven.packaging.endpoint.SomeEndpointWithEnumStyleBodyTypes;
 import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithFilter;
 import 
org.apache.camel.maven.packaging.endpoint.SomeEndpointWithHeaderClassHierarchy;
 import 
org.apache.camel.maven.packaging.endpoint.SomeEndpointWithHeaderInterfaceHierarchy;
@@ -261,6 +262,17 @@ class EndpointSchemaGeneratorMojoTest {
         assertEquals("byte[]", bytes.getJavaType());
     }
 
+    @Test
+    void testEndpointWithBodyTypesForEnumValuesInOtherStyles() throws 
Exception {
+        // the catalog and the enum type converter take convert-to-json and 
chunkHybrid as CONVERT_TO_JSON and
+        // CHUNK_HYBRID, so the build takes them too
+        addEnumOption("operation", "CONVERT_TO_MARKDOWN", "CONVERT_TO_JSON", 
"CHUNK_HYBRID");
+        mojo.addBodyTypes(model, SomeEndpointWithEnumStyleBodyTypes.class);
+        List<BodyTypeModel.OptionBodyTypeModel> values = 
model.getBodyType(BodyTypeModel.PRODUCER_OUT).getByOption();
+        assertEquals(List.of("convert-to-json", "chunkHybrid"),
+                
values.stream().map(BodyTypeModel.OptionBodyTypeModel::getValue).toList());
+    }
+
     @Test
     void testEndpointDoesNotInheritBodyTypes() throws Exception {
         // a subclass may change the body, so it declares its own
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithEnumStyleBodyTypes.java
 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithEnumStyleBodyTypes.java
new file mode 100644
index 000000000000..c55cd38e1a97
--- /dev/null
+++ 
b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithEnumStyleBodyTypes.java
@@ -0,0 +1,35 @@
+/*
+ * 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.maven.packaging.endpoint;
+
+import org.apache.camel.spi.BodyType;
+import org.apache.camel.spi.BodyType.Carrier;
+import org.apache.camel.spi.BodyTypes;
+import org.apache.camel.spi.UriEndpoint;
+
+/**
+ * Body types for option values written in other styles of the enum constants 
of the option.
+ */
+@UriEndpoint(scheme = "some", syntax = "some", title = "some")
+@BodyTypes(producerOut = @BodyType(value = Carrier.TEXT,
+                                   byOption = {
+                                           @BodyType.When(option = 
"operation", value = "convert-to-json",
+                                                          carriers = 
Carrier.POJO),
+                                           @BodyType.When(option = 
"operation", value = "chunkHybrid",
+                                                          carriers = 
Carrier.LIST) }))
+public class SomeEndpointWithEnumStyleBodyTypes {
+}

Reply via email to