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 f3f949af1e94 CAMEL-26159: camel-salesforce - send a Map or POJO body
as JSON, fail raw on a body it cannot send (#28942)
f3f949af1e94 is described below
commit f3f949af1e94b742b2077744b346754d460eb6a9
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 21:47:46 2026 +0100
CAMEL-26159: camel-salesforce - send a Map or POJO body as JSON, fail raw
on a body it cannot send (#28942)
* CAMEL-26159: camel-salesforce - send a Map or POJO body as JSON, fail…
… raw on a body it cannot send
The REST operations sent a Map or another non-DTO object as its toString
(for example {Name=x}); they now marshal it with the object mapper, as for
DTOs. Text and simple values are still sent as is.
The raw operation sent an empty request when the body could not be
converted to an InputStream (such as a Map); it now fails with an
InvalidPayloadException.
The SObject Collections operations completed the exchange with no
exception and no body when the response could not be parsed; they now set
a SalesforceException.
The rawPayload option description now says the REST producer returns an
InputStream, not a String.
* CAMEL-26159: camel-salesforce - fail the composite operations on an u…
…nparseable response
The composite, composite-batch and composite-tree operations set a null
exception when the response was empty or could not be parsed, so the
exchange completed with no exception and no response. They now set a
SalesforceException, as the SObject Collections operations do.
* Merge remote-tracking branch 'origin/main' into CAMEL-26159-salesforc…
…e-map-body
# Conflicts:
# docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/catalog/components/salesforce.json | 6 +-
.../camel/catalog/docs/salesforce-component.adoc | 2 +-
.../camel/catalog/docs/salesforce-rest-api.adoc | 3 +
.../camel/component/salesforce/salesforce.json | 6 +-
.../src/main/docs/salesforce-component.adoc | 2 +-
.../src/main/docs/salesforce-rest-api.adoc | 3 +
.../component/salesforce/SalesforceEndpoint.java | 9 +-
.../salesforce/SalesforceEndpointConfig.java | 5 +-
.../internal/processor/CompositeApiProcessor.java | 20 +-
.../CompositeSObjectCollectionsProcessor.java | 3 +
.../internal/processor/JsonRestProcessor.java | 13 ++
.../internal/processor/RawProcessor.java | 4 +-
.../salesforce/ProducerRequestBodyTest.java | 205 +++++++++++++++++++++
.../internal/processor/JsonRestProcessorTest.java | 49 +++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 9 +
.../dsl/SalesforceComponentBuilderFactory.java | 6 +-
.../dsl/SalesforceEndpointBuilderFactory.java | 36 ++--
17 files changed, 348 insertions(+), 33 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
index 14820edd6d69..250fccb2073c 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/salesforce.json
@@ -62,7 +62,7 @@
"pkChunkingParent": { "index": 34, "kind": "property", "displayName": "Pk
Chunking Parent", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Specifies the parent object
when you're enabling PK chunking for queries on sharing objects. The chun [...]
"pkChunkingStartRow": { "index": 35, "kind": "property", "displayName":
"Pk Chunking Start Row", "group": "common", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Specifies the 15-character or
18-character record ID to be used as the lower boundary for the fi [...]
"queryLocator": { "index": 36, "kind": "property", "displayName": "Query
Locator", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Query Locator provided by
salesforce for use when a query results in more records than can be retrieved
in a s [...]
- "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Use raw payload String for
request and response (either JSON or XML depending on format), instead o [...]
+ "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Use raw payload for request and
response, instead of DTOs, false by default. The REST, apexCall and [...]
"reportId": { "index": 38, "kind": "property", "displayName": "Report Id",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Salesforce1 Analytics report
Id" },
"reportMetadata": { "index": 39, "kind": "property", "displayName":
"Report Metadata", "group": "common", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
"deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Salesforce1 Analytics report
metadata for fi [...]
"resultId": { "index": 40, "kind": "property", "displayName": "Result Id",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Identifies one result set of a
completed Bulk API v1 query batch, as returned by the getQueryResultIds
operation. Used [...]
@@ -162,7 +162,7 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "map", "pojo" ], "byOption": [ { "option":
"operationName", "value": "pubSubSubscribe", "carriers": [ "pojo" ] }, {
"option": "rawPayload", "value": "true", "carriers": [ "text", "pojo" ] }, {
"option": "pubSubDeserializeType", "value": "JSON", "carriers": [ "text",
"map", "pojo" ] } ], "description": "The subscribe operation gives a push topic
record as a Map, or an object of sObjectClass (or the DTO of sObjectName), a
platform event (\/event\/) as a P [...]
- "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo",
"list", "map", "null" ], "description": "The REST operations send a generated
DTO (an AbstractSObjectBase) as JSON, or JSON text, bytes, a stream or a file
as is; a Map or another object is sent as its toString, not as JSON: marshal it
first. Text in the body is the id, SOQL or SOSL when the option or header is
not set. apexCall takes a Map as query parameters; bulk operations take a
JobInfo, BatchInfo or Job, a [...]
+ "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo",
"list", "map", "null" ], "description": "The REST operations send a generated
DTO (an AbstractSObjectBase), a Map or another object as JSON, or JSON text,
bytes, a stream or a file as is. Text in the body is the id, SOQL or SOSL when
the option or header is not set. apexCall takes a Map as query parameters; bulk
operations take a JobInfo, BatchInfo or Job, and createBatch the CSV or XML
records as a stream; comp [...]
"producerOut": { "carriers": [ "pojo", "list", "map", "stream",
"unchanged", "null" ], "dependsOn": [ "streamQueryResult", "notFoundBehaviour"
], "byOption": [ { "option": "operationName", "value": "createSObject",
"carriers": [ "pojo" ], "javaType":
"org.apache.camel.component.salesforce.api.dto.CreateSObjectResult" }, {
"option": "operationName", "value": "upsertSObject", "carriers": [ "pojo",
"unchanged" ] }, { "option": "operationName", "value": "updateSObject",
"carriers": [ "un [...]
},
"properties": {
@@ -199,7 +199,7 @@
"pkChunkingParent": { "index": 30, "kind": "parameter", "displayName": "Pk
Chunking Parent", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Specifies the parent
object when you're enabling PK chunking for queries on sharing objects. [...]
"pkChunkingStartRow": { "index": 31, "kind": "parameter", "displayName":
"Pk Chunking Start Row", "group": "common", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Specifies the
15-character or 18-character record ID to be used as the lower boundary fo [...]
"queryLocator": { "index": 32, "kind": "parameter", "displayName": "Query
Locator", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Query Locator provided
by salesforce for use when a query results in more records than can be retrieve
[...]
- "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Use raw payload String
for request and response (either JSON or XML depending on format), i [...]
+ "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Use raw payload for
request and response, instead of DTOs, false by default. The REST, apex [...]
"reportId": { "index": 34, "kind": "parameter", "displayName": "Report
Id", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Salesforce1 Analytics
report Id" },
"reportMetadata": { "index": 35, "kind": "parameter", "displayName":
"Report Metadata", "group": "common", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
"deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Salesforce1 Analytics
report metadat [...]
"resultId": { "index": 36, "kind": "parameter", "displayName": "Result
Id", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Identifies one result
set of a completed Bulk API v1 query batch, as returned by the
getQueryResultIds operati [...]
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
index a40ad7f27145..6e636d2f7688 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-component.adoc
@@ -267,7 +267,7 @@ header will be automatically set based on the `format`
option, but this can be o
|===
| Parameter | Type | Description| Default| Required
-| Body | `String` or `InputStream` | Body of the HTTP request | |
+| Body | `String` or `InputStream` | Body of the HTTP request. A body that
cannot be converted to an `InputStream`, such as a `Map`, fails with an
`InvalidPayloadException`. | |
| `rawPath` | `String` | The portion of the endpoint URL after the domain
name, e.g., '/services/data/v51.0/sobjects/Account/' | | x
| `rawMethod` | `String` | The HTTP method | | x
| `rawQueryParameters` | `String` | Comma separated list of message headers to
include as query parameters. Do not url-encode values as this will be done
automatically. | |
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
index 4f7dcb91491b..7d446454ca6d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/salesforce-rest-api.adoc
@@ -41,6 +41,9 @@ request as the input to a subsequent request.
Unless otherwise specified, DTO types for the following options are from
`org.apache.camel.component.salesforce.api.dto` or one if its sub-packages.
+The operations that send a request body (such as `createSObject`,
`updateSObject` and `upsertSObject`) send an SObject DTO,
+a `Map` or another object as JSON, and JSON text, bytes, a stream or a file as
is.
+
[[getVersions]]
== Versions
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
b/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
index 14820edd6d69..250fccb2073c 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
+++
b/components/camel-salesforce/camel-salesforce-component/src/generated/resources/META-INF/org/apache/camel/component/salesforce/salesforce.json
@@ -62,7 +62,7 @@
"pkChunkingParent": { "index": 34, "kind": "property", "displayName": "Pk
Chunking Parent", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Specifies the parent object
when you're enabling PK chunking for queries on sharing objects. The chun [...]
"pkChunkingStartRow": { "index": 35, "kind": "property", "displayName":
"Pk Chunking Start Row", "group": "common", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Specifies the 15-character or
18-character record ID to be used as the lower boundary for the fi [...]
"queryLocator": { "index": 36, "kind": "property", "displayName": "Query
Locator", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Query Locator provided by
salesforce for use when a query results in more records than can be retrieved
in a s [...]
- "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Use raw payload String for
request and response (either JSON or XML depending on format), instead o [...]
+ "rawPayload": { "index": 37, "kind": "property", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Use raw payload for request and
response, instead of DTOs, false by default. The REST, apexCall and [...]
"reportId": { "index": 38, "kind": "property", "displayName": "Report Id",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Salesforce1 Analytics report
Id" },
"reportMetadata": { "index": 39, "kind": "property", "displayName":
"Report Metadata", "group": "common", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
"deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Salesforce1 Analytics report
metadata for fi [...]
"resultId": { "index": 40, "kind": "property", "displayName": "Result Id",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "config", "description": "Identifies one result set of a
completed Bulk API v1 query batch, as returned by the getQueryResultIds
operation. Used [...]
@@ -162,7 +162,7 @@
},
"bodyTypes": {
"consumerOut": { "carriers": [ "map", "pojo" ], "byOption": [ { "option":
"operationName", "value": "pubSubSubscribe", "carriers": [ "pojo" ] }, {
"option": "rawPayload", "value": "true", "carriers": [ "text", "pojo" ] }, {
"option": "pubSubDeserializeType", "value": "JSON", "carriers": [ "text",
"map", "pojo" ] } ], "description": "The subscribe operation gives a push topic
record as a Map, or an object of sObjectClass (or the DTO of sObjectName), a
platform event (\/event\/) as a P [...]
- "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo",
"list", "map", "null" ], "description": "The REST operations send a generated
DTO (an AbstractSObjectBase) as JSON, or JSON text, bytes, a stream or a file
as is; a Map or another object is sent as its toString, not as JSON: marshal it
first. Text in the body is the id, SOQL or SOSL when the option or header is
not set. apexCall takes a Map as query parameters; bulk operations take a
JobInfo, BatchInfo or Job, a [...]
+ "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo",
"list", "map", "null" ], "description": "The REST operations send a generated
DTO (an AbstractSObjectBase), a Map or another object as JSON, or JSON text,
bytes, a stream or a file as is. Text in the body is the id, SOQL or SOSL when
the option or header is not set. apexCall takes a Map as query parameters; bulk
operations take a JobInfo, BatchInfo or Job, and createBatch the CSV or XML
records as a stream; comp [...]
"producerOut": { "carriers": [ "pojo", "list", "map", "stream",
"unchanged", "null" ], "dependsOn": [ "streamQueryResult", "notFoundBehaviour"
], "byOption": [ { "option": "operationName", "value": "createSObject",
"carriers": [ "pojo" ], "javaType":
"org.apache.camel.component.salesforce.api.dto.CreateSObjectResult" }, {
"option": "operationName", "value": "upsertSObject", "carriers": [ "pojo",
"unchanged" ] }, { "option": "operationName", "value": "updateSObject",
"carriers": [ "un [...]
},
"properties": {
@@ -199,7 +199,7 @@
"pkChunkingParent": { "index": 30, "kind": "parameter", "displayName": "Pk
Chunking Parent", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Specifies the parent
object when you're enabling PK chunking for queries on sharing objects. [...]
"pkChunkingStartRow": { "index": 31, "kind": "parameter", "displayName":
"Pk Chunking Start Row", "group": "common", "label": "", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Specifies the
15-character or 18-character record ID to be used as the lower boundary fo [...]
"queryLocator": { "index": 32, "kind": "parameter", "displayName": "Query
Locator", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Query Locator provided
by salesforce for use when a query results in more records than can be retrieve
[...]
- "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Use raw payload String
for request and response (either JSON or XML depending on format), i [...]
+ "rawPayload": { "index": 33, "kind": "parameter", "displayName": "Raw
Payload", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Use raw payload for
request and response, instead of DTOs, false by default. The REST, apex [...]
"reportId": { "index": 34, "kind": "parameter", "displayName": "Report
Id", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Salesforce1 Analytics
report Id" },
"reportMetadata": { "index": 35, "kind": "parameter", "displayName":
"Report Metadata", "group": "common", "label": "", "required": false, "type":
"object", "javaType":
"org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata",
"deprecated": false, "autowired": false, "secret": false,
"configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Salesforce1 Analytics
report metadat [...]
"resultId": { "index": 36, "kind": "parameter", "displayName": "Result
Id", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.salesforce.SalesforceEndpointConfig",
"configurationField": "configuration", "description": "Identifies one result
set of a completed Bulk API v1 query batch, as returned by the
getQueryResultIds operati [...]
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
index a40ad7f27145..6e636d2f7688 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
@@ -267,7 +267,7 @@ header will be automatically set based on the `format`
option, but this can be o
|===
| Parameter | Type | Description| Default| Required
-| Body | `String` or `InputStream` | Body of the HTTP request | |
+| Body | `String` or `InputStream` | Body of the HTTP request. A body that
cannot be converted to an `InputStream`, such as a `Map`, fails with an
`InvalidPayloadException`. | |
| `rawPath` | `String` | The portion of the endpoint URL after the domain
name, e.g., '/services/data/v51.0/sobjects/Account/' | | x
| `rawMethod` | `String` | The HTTP method | | x
| `rawQueryParameters` | `String` | Comma separated list of message headers to
include as query parameters. Do not url-encode values as this will be done
automatically. | |
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
index 4f7dcb91491b..7d446454ca6d 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-rest-api.adoc
@@ -41,6 +41,9 @@ request as the input to a subsequent request.
Unless otherwise specified, DTO types for the following options are from
`org.apache.camel.component.salesforce.api.dto` or one if its sub-packages.
+The operations that send a request body (such as `createSObject`,
`updateSObject` and `upsertSObject`) send an SObject DTO,
+a `Map` or another object as JSON, and JSON text, bytes, a stream or a file as
is.
+
[[getVersions]]
== Versions
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
index ca427742ab57..4acf416e97b8 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java
@@ -60,15 +60,16 @@ import org.slf4j.LoggerFactory;
producerIn = @BodyType(value = {
Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE,
Carrier.POJO, Carrier.LIST, Carrier.MAP,
Carrier.NULL },
- description = "The REST operations send a
generated DTO (an AbstractSObjectBase) as"
- + " JSON, or JSON text, bytes,
a stream or a file as is; a Map or another"
- + " object is sent as its
toString, not as JSON: marshal it first. Text in"
+ description = "The REST operations send a
generated DTO (an AbstractSObjectBase), a"
+ + " Map or another object as
JSON, or JSON text, bytes, a stream or a file"
+ + " as is. Text in"
+ " the body is the id, SOQL
or SOSL when the option or header is not set."
+ " apexCall takes a Map as
query parameters; bulk operations take a"
+ " JobInfo, BatchInfo or Job,
and createBatch the CSV or XML records as a"
+ " stream; composite
operations an SObjectComposite, SObjectBatch or"
+ " SObjectTree, the SObject
collections a List of DTOs; pubSubPublish a"
- + " List of events; raw the
request as a stream"),
+ + " List of events; raw the
request as a stream (a body that cannot be"
+ + " converted to a stream,
such as a Map, fails)"),
producerOut = @BodyType(value = {
Carrier.POJO, Carrier.LIST, Carrier.MAP, Carrier.STREAM,
Carrier.UNCHANGED, Carrier.NULL },
dependsOn = { "streamQueryResult",
"notFoundBehaviour" },
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
index 4ab658b1f139..07ae447d4030 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
@@ -327,8 +327,9 @@ public class SalesforceEndpointConfig implements Cloneable {
}
/**
- * Use raw payload {@link String} for request and response (either JSON or
XML depending on {@code format}), instead
- * of DTOs, false by default
+ * Use raw payload for request and response, instead of DTOs, false by
default. The REST, apexCall and composite
+ * operations return the response as is, as an {@code InputStream}, and
the streaming subscribe consumer gives the
+ * message as JSON text.
*/
public void setRawPayload(boolean rawPayload) {
this.rawPayload = rawPayload;
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
index 584448170db6..2037e6e01a7f 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeApiProcessor.java
@@ -106,7 +106,7 @@ public final class CompositeApiProcessor extends
AbstractSalesforceProcessor {
final SalesforceException exception, final AsyncCallback callback)
{
try {
if (!responseBody.isPresent()) {
- exchange.setException(exception);
+ exchange.setException(exceptionForEmptyResponse(exception));
} else {
final Message in = exchange.getIn();
final Message out = exchange.getOut();
@@ -130,7 +130,7 @@ public final class CompositeApiProcessor extends
AbstractSalesforceProcessor {
final SalesforceException exception, final AsyncCallback callback)
{
try {
if (!responseBody.isPresent()) {
- exchange.setException(exception);
+ exchange.setException(exceptionForEmptyResponse(exception));
} else {
final Message in = exchange.getIn();
final Message out = exchange.getOut();
@@ -154,7 +154,7 @@ public final class CompositeApiProcessor extends
AbstractSalesforceProcessor {
final SalesforceException exception, final AsyncCallback callback)
{
try {
if (!responseBody.isPresent()) {
- exchange.setException(exception);
+ exchange.setException(exceptionForEmptyResponse(exception));
} else {
final Message in = exchange.getIn();
final Message out = exchange.getOut();
@@ -179,7 +179,7 @@ public final class CompositeApiProcessor extends
AbstractSalesforceProcessor {
try {
if (!responseBody.isPresent()) {
- exchange.setException(exception);
+ exchange.setException(exceptionForEmptyResponse(exception));
} else {
final Message in = exchange.getIn();
@@ -214,6 +214,18 @@ public final class CompositeApiProcessor extends
AbstractSalesforceProcessor {
}
}
+ /**
+ * The exception to set when the response is empty or could not be parsed:
the given one, or one that says so, as
+ * the exchange must not complete without a response and without an
exception.
+ */
+ private static SalesforceException exceptionForEmptyResponse(final
SalesforceException exception) {
+ if (exception != null) {
+ return exception;
+ }
+ return new SalesforceException(
+ "Unable to read the response of the Composite API: the
response is empty or not valid JSON");
+ }
+
<T, R> boolean processInternal(
final Class<T> bodyType, final Exchange exchange, final
CompositeApiClient.Operation<T, R> clientOperation,
final ResponseHandler<R> responseHandler, final AsyncCallback
callback)
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
index 4627a02d830e..308dcecd99d5 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/CompositeSObjectCollectionsProcessor.java
@@ -174,6 +174,9 @@ public class CompositeSObjectCollectionsProcessor extends
AbstractSalesforceProc
try {
if (exception != null) {
exchange.setException(exception);
+ } else if (responseBody.isEmpty()) {
+ exchange.setException(new SalesforceException(
+ "Unable to read the response of the SObject
Collections API: the response is empty or not valid JSON"));
} else {
Message in = exchange.getIn();
Message out = exchange.getOut();
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
index 5803a05fbd03..894d6127cb79 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
@@ -145,9 +145,13 @@ public class JsonRestProcessor extends
AbstractRestProcessor {
request = in.getBody(InputStream.class);
if (request == null) {
AbstractDTOBase dto = in.getBody(AbstractDTOBase.class);
+ final Object payload = in.getBody();
if (dto != null) {
// marshall the DTO
request = getRequestStream(in, dto);
+ } else if (payload != null && !isTextOrSimpleValue(payload)) {
+ // marshall a Map or another POJO, as its toString is not JSON
+ request = getRequestStream(in, payload);
} else {
// if all else fails, get body as String
final String body = in.getBody(String.class);
@@ -163,6 +167,15 @@ public class JsonRestProcessor extends
AbstractRestProcessor {
return request;
}
+ /**
+ * Whether the body is text or a simple value (number, boolean, enum),
which is sent as its String value. A Map or
+ * another POJO is marshalled as JSON instead, as its toString is not JSON.
+ */
+ private static boolean isTextOrSimpleValue(Object body) {
+ return body instanceof CharSequence || body instanceof Number || body
instanceof Boolean
+ || body instanceof Character || body instanceof Enum;
+ }
+
@Override
protected InputStream getRequestStream(final Message in, final Object
object) throws SalesforceException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
index 126064450516..15519d7579b1 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/RawProcessor.java
@@ -86,7 +86,9 @@ public class RawProcessor extends AbstractSalesforceProcessor
{
}
}
- InputStream body = exchange.getIn().getBody(InputStream.class);
+ // a body that cannot be converted to a stream (such as a Map)
fails instead of sending an empty request
+ InputStream body = exchange.getIn().getBody() != null
+ ? exchange.getIn().getMandatoryBody(InputStream.class) :
null;
rawClient.makeRequest(method, path.toString(), format, body,
determineHeaders(exchange),
(response, headers, exception) -> {
Message in = exchange.getIn();
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/ProducerRequestBodyTest.java
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/ProducerRequestBodyTest.java
new file mode 100644
index 000000000000..da72574033ad
--- /dev/null
+++
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/ProducerRequestBodyTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.component.salesforce;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import mockwebserver3.Dispatcher;
+import mockwebserver3.MockResponse;
+import mockwebserver3.MockWebServer;
+import mockwebserver3.RecordedRequest;
+import okhttp3.HttpUrl;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.dto.CreateSObjectResult;
+import org.apache.camel.component.salesforce.api.dto.composite.SObjectBatch;
+import
org.apache.camel.component.salesforce.api.dto.composite.SObjectComposite;
+import org.apache.camel.component.salesforce.api.dto.composite.SObjectTree;
+import org.apache.camel.component.salesforce.dto.generated.Merchandise__c;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Checks the request bodies the producer sends, and the errors it reports,
against a mock Salesforce server.
+ */
+public class ProducerRequestBodyTest extends AbstractSalesforceTestBase {
+
+ private static final String OAUTH2_TOKEN_PATH = "/services/oauth2/token";
+
+ private static final LinkedBlockingQueue<RecordedRequest> REQUESTS = new
LinkedBlockingQueue<>();
+
+ private static MockWebServer server;
+ private static HttpUrl loginUrl;
+
+ @BeforeAll
+ public static void startServer() throws IOException {
+ server = new MockWebServer();
+ server.setDispatcher(new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest recordedRequest) {
+ final String path = recordedRequest.getUrl().encodedPath();
+ if (path.equals(OAUTH2_TOKEN_PATH)) {
+ return new MockResponse.Builder()
+ .code(200)
+ .body("{ \"access_token\": \"mock_token\", \"id\":
\"https://login.salesforce.com/id/00D4100000xxxxxxxx/0054100000xxxxxxxx\",
\"instance_url\": \""
+ + loginUrl + "\"}")
+ .build();
+ }
+ if (path.startsWith("/services/data/")) {
+ REQUESTS.add(recordedRequest);
+ }
+ if (path.contains("/composite")) {
+ // not the JSON of a composite response
+ return new MockResponse.Builder().code(200).body("not
json").build();
+ }
+ return new MockResponse.Builder()
+ .code(201)
+ .addHeader("Content-Type", "application/json")
+ .body("{ \"id\": \"001D000000IqhSLIAZ\", \"errors\":
[], \"success\": true }")
+ .build();
+ }
+ });
+ server.start();
+ loginUrl = server.url("");
+ }
+
+ @AfterAll
+ public static void shutDownServer() throws IOException {
+ if (server != null) {
+ server.close();
+ }
+ }
+
+ @BeforeEach
+ public void clearRequests() {
+ REQUESTS.clear();
+ }
+
+ @Override
+ protected void createComponent() throws Exception {
+ final SalesforceComponent component = new SalesforceComponent();
+ final SalesforceEndpointConfig config = new SalesforceEndpointConfig();
+ config.setApiVersion(SalesforceEndpointConfig.DEFAULT_VERSION);
+ component.setConfig(config);
+
+ final SalesforceLoginConfig loginConfig = new SalesforceLoginConfig();
+ loginConfig.setClientId("ignored");
+ loginConfig.setClientSecret("ignored");
+ loginConfig.setRefreshToken("ignored");
+ loginConfig.setLoginUrl(loginUrl.toString());
+ component.setLoginConfig(loginConfig);
+ component.setPackages(Merchandise__c.class.getPackage().getName());
+
+ context().addComponent("salesforce", component);
+ }
+
+ @Test
+ public void createSObjectShouldSendMapBodyAsJson() throws Exception {
+ final CreateSObjectResult result =
template().requestBody("direct:createSObject", Map.of("Name", "x"),
+ CreateSObjectResult.class);
+
+ assertThat(result.getId()).isEqualTo("001D000000IqhSLIAZ");
+ final RecordedRequest request = REQUESTS.poll(10, TimeUnit.SECONDS);
+ assertThat(request).isNotNull();
+ assertThat(request.getMethod()).isEqualTo("POST");
+ assertThat(request.getBody().utf8()).isEqualTo("{\"Name\":\"x\"}");
+ }
+
+ @Test
+ public void rawWithMapBodyShouldFail() {
+ final Exchange exchange = template().request("direct:raw", e ->
e.getIn().setBody(Map.of("Name", "x")));
+
+
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class);
+ assertThat(REQUESTS).isEmpty();
+ }
+
+ @Test
+ public void compositeCollectionsWithUnparseableResponseShouldFail() {
+ final Merchandise__c merchandise = new Merchandise__c();
+ merchandise.setName("x");
+
+ final Exchange exchange =
template().request("direct:compositeCreateSObjectCollections",
+ e -> e.getIn().setBody(List.of(merchandise)));
+
+
assertThat(exchange.getException()).isInstanceOf(SalesforceException.class)
+ .hasMessageContaining("SObject Collections API");
+ }
+
+ @Test
+ public void compositeWithUnparseableResponseShouldFail() {
+ final SObjectComposite composite = new
SObjectComposite(SalesforceEndpointConfig.DEFAULT_VERSION, true);
+ composite.addGet("Account", "001D000000IqhSLIAZ", "ref1");
+
+ assertCompositeFails("direct:composite", composite);
+ }
+
+ @Test
+ public void compositeBatchWithUnparseableResponseShouldFail() {
+ final SObjectBatch batch = new
SObjectBatch(SalesforceEndpointConfig.DEFAULT_VERSION);
+ batch.addGet("Account", "001D000000IqhSLIAZ");
+
+ assertCompositeFails("direct:compositeBatch", batch);
+ }
+
+ @Test
+ public void compositeTreeWithUnparseableResponseShouldFail() {
+ final Merchandise__c merchandise = new Merchandise__c();
+ merchandise.setName("x");
+ final SObjectTree tree = new SObjectTree();
+ tree.addObject(merchandise);
+
+ assertCompositeFails("direct:compositeTree", tree);
+ }
+
+ private void assertCompositeFails(String uri, Object body) {
+ final Exchange exchange = template().request(uri, e ->
e.getIn().setBody(body));
+
+
assertThat(exchange.getException()).isInstanceOf(SalesforceException.class)
+ .hasMessageContaining("Composite API");
+ }
+
+ @Override
+ protected RouteBuilder doCreateRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:createSObject").to("salesforce:createSObject?sObjectName=Account");
+
+ from("direct:raw")
+
.to("salesforce:raw?format=JSON&rawMethod=POST&rawPath=/services/data/v51.0/sobjects/Account");
+
+
from("direct:compositeCreateSObjectCollections").to("salesforce:compositeCreateSObjectCollections");
+
+ from("direct:composite").to("salesforce:composite");
+
+ from("direct:compositeBatch").to("salesforce:composite-batch");
+
+ from("direct:compositeTree").to("salesforce:composite-tree");
+ }
+ };
+ }
+}
diff --git
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
index bc47000a7d16..22f2fe612ce6 100644
---
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
+++
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessorTest.java
@@ -19,9 +19,12 @@ package
org.apache.camel.component.salesforce.internal.processor;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.util.LinkedHashMap;
+import java.util.Map;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
@@ -112,4 +115,50 @@ public class JsonRestProcessorTest {
}
}
}
+
+ public static class Contact {
+
+ private final String lastName;
+
+ public Contact(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+ }
+
+ @Test
+ public void getRequestStreamShouldMarshalMapAsJson() throws Exception {
+ final Map<String, Object> body = new LinkedHashMap<>();
+ body.put("Name", "x");
+ body.put("NumberOfEmployees", 5);
+
+
assertThat(requestOf(body)).isEqualTo("{\"Name\":\"x\",\"NumberOfEmployees\":5}");
+ }
+
+ @Test
+ public void getRequestStreamShouldMarshalPojoAsJson() throws Exception {
+ assertThat(requestOf(new
Contact("Doe"))).isEqualTo("{\"lastName\":\"Doe\"}");
+ }
+
+ @Test
+ public void getRequestStreamShouldSendTextAsIs() throws Exception {
+ assertThat(requestOf("{ \"Name\" : \"x\" }")).isEqualTo("{ \"Name\" :
\"x\" }");
+ assertThat(requestOf(new StringReader("{ \"Name\" : \"x\"
}"))).isEqualTo("{ \"Name\" : \"x\" }");
+ }
+
+ private static String requestOf(Object body) throws Exception {
+ final SalesforceComponent comp = new SalesforceComponent();
+ final SalesforceEndpointConfig conf = new SalesforceEndpointConfig();
+ final SalesforceEndpoint endpoint = new SalesforceEndpoint("", comp,
conf, OperationName.CREATE_SOBJECT, "");
+ try (final JsonRestProcessor jsonRestProcessor = new
JsonRestProcessor(endpoint)) {
+ final Exchange exchange = new DefaultExchange(new
DefaultCamelContext(), ExchangePattern.InOut);
+ exchange.getIn().setBody(body);
+ try (InputStream stream =
jsonRestProcessor.getRequestStream(exchange)) {
+ return IOUtils.toString(stream, StandardCharsets.UTF_8);
+ }
+ }
+ }
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 051f557f6290..a7befadbf7d3 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -771,6 +771,15 @@ the header.
A message header with a `null` value is no longer turned into a job parameter.
Before, such a header failed the job
launch, because Spring Batch does not accept a job parameter without a value.
+=== camel-salesforce - a Map or POJO body is sent as JSON, and raw fails on a
body it cannot send
+
+The REST operations (such as `createSObject`, `updateSObject` and
`upsertSObject`) now marshal a `Map` or another
+object that is not an SObject DTO as JSON. Before, they sent its `toString()`,
for example `{Name=x}`, which is not
+JSON. Text, bytes, a stream or a file in the body are sent as is, as before.
+
+The `raw` operation now fails with an `InvalidPayloadException` when the body
cannot be converted to an `InputStream`,
+for example a `Map`. Before, it sent the request with an empty body.
+
=== camel-spring-rabbitmq - confirm=auto waits for simple publisher confirms
With the default `confirm=auto`, an InOnly producer now also waits for the
publisher confirm when the connection
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
index e97ce090673b..992602bddc59 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SalesforceComponentBuilderFactory.java
@@ -700,8 +700,10 @@ public interface SalesforceComponentBuilderFactory {
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option is a: <code>boolean</code> type.
*
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
index c6d98088403b..99ed8c303c99 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SalesforceEndpointBuilderFactory.java
@@ -906,8 +906,10 @@ public interface SalesforceEndpointBuilderFactory {
return this;
}
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option is a: <code>boolean</code> type.
*
@@ -922,8 +924,10 @@ public interface SalesforceEndpointBuilderFactory {
return this;
}
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option will be converted to a <code>boolean</code> type.
*
@@ -2508,8 +2512,10 @@ public interface SalesforceEndpointBuilderFactory {
return this;
}
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option is a: <code>boolean</code> type.
*
@@ -2524,8 +2530,10 @@ public interface SalesforceEndpointBuilderFactory {
return this;
}
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option will be converted to a <code>boolean</code> type.
*
@@ -3952,8 +3960,10 @@ public interface SalesforceEndpointBuilderFactory {
return this;
}
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option is a: <code>boolean</code> type.
*
@@ -3968,8 +3978,10 @@ public interface SalesforceEndpointBuilderFactory {
return this;
}
/**
- * Use raw payload String for request and response (either JSON or XML
- * depending on format), instead of DTOs, false by default.
+ * Use raw payload for request and response, instead of DTOs, false by
+ * default. The REST, apexCall and composite operations return the
+ * response as is, as an InputStream, and the streaming subscribe
+ * consumer gives the message as JSON text.
*
* The option will be converted to a <code>boolean</code> type.
*