This is an automated email from the ASF dual-hosted git repository.

jeremyross 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 c2b6f494f6a CAMEL-16642: camel-salesforce: Detect SObject type (#8275)
c2b6f494f6a is described below

commit c2b6f494f6a89ec7ab85925ea735f570a12a64b2
Author: Jeremy Ross <jeremy.g.r...@gmail.com>
AuthorDate: Mon Sep 5 14:25:07 2022 -0500

    CAMEL-16642: camel-salesforce: Detect SObject type (#8275)
    
    CAMEL-16642: camel-salesforce: Detect SObject type
    
    Sniff the response to detect SObject type.
    Implemented for query* and apexcall operations.
---
 .../src/main/docs/salesforce-component.adoc        | 32 +++++++++++++--------
 .../salesforce/api/SalesforceException.java        |  4 +++
 .../internal/processor/AbstractRestProcessor.java  | 32 +++++++++++++--------
 .../processor/AbstractSalesforceProcessor.java     | 27 ++++++++++++++----
 .../CompositeSObjectCollectionsProcessor.java      |  2 +-
 .../internal/processor/JsonRestProcessor.java      | 33 +++++++++++++++++++++-
 .../salesforce/RestApiIntegrationTest.java         | 32 +++++++++++++++++++--
 7 files changed, 129 insertions(+), 33 deletions(-)

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 fbb608cb786..7b7e6961839 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
@@ -522,15 +522,16 @@ Deletes a record in salesforce by External ID.
 
 `query`
 
-Runs a Salesforce SOQL query.
+Runs a Salesforce SOQL query. If neither `sObjectClass` nor `sObjectName` are 
set, Camel will attempt to determine
+the correct `AbstractQueryRecordsBase` sublcass based on the response.
 
 |===
 | Parameter | Type | Description | Default | Required
 
 | Body or `sObjectQuery` | `String`  | SOQL query | | x
 | streamQueryResult | `Boolean` | If true, returns a streaming `Iterator` and 
transparently retrieves all pages as needed. The `sObjectClass` option must 
reference an `AbstractQueryRecordsBase` subclass. | false |
-| `sObjectClass` | `String` | Fully qualified name of class to deserialize 
response to. Usually a subclass of `AbstractQueryRecordsBase`, e.g. 
`org.my.dto.QueryRecordsAccount`| | One of sObjectClass or sObjectName is 
required
-| `sObjectName` | `String` | Simple name of class to deserialize response to. 
Usually a subclass of `AbstractQueryRecordsBase`, e.g. `QueryRecordsAccount`. 
Requires the `package` option be set.| |One of sObjectClass or sObjectName is 
required
+| `sObjectClass` | `String` | Fully qualified name of class to deserialize 
response to. Usually a subclass of `AbstractQueryRecordsBase`, e.g. 
`org.my.dto.QueryRecordsAccount`| |
+| `sObjectName` | `String` | Simple name of class to deserialize response to. 
Usually a subclass of `AbstractQueryRecordsBase`, e.g. `QueryRecordsAccount`. 
Requires the `package` option be set.| |
 |===
 
 *Output*
@@ -542,14 +543,16 @@ Type: Instance of class supplied in `sObjectClass`, or 
`Iterator<SomeSObject>` i
 
 `queryMore`
 
-Retrieves more results (in case of large number of results) using result link 
returned from the `query` and `queryAll` operations.
+Retrieves more results (in case of large number of results) using result link 
returned from the `query` and `queryAll`
+operations. If neither `sObjectClass` nor `sObjectName` are set, Camel will 
attempt to determine the correct
+`AbstractQueryRecordsBase` sublcass based on the response.
 
 |===
 | Parameter | Type | Description | Default | Required
 
 | Body or `sObjectQuery` | `String`  | `nextRecords` value. Can be found in a 
prior query result in the `AbstractQueryRecordsBase.nextRecordsUrl` property | 
| X
-| `sObjectClass` | `String` | Fully qualified name of class to deserialize 
response to. Usually a subclass of `AbstractQueryRecordsBase`, e.g. 
`org.my.dto.QueryRecordsAccount`| |One of sObjectClass or sObjectName is 
required
-| `sObjectName` | `String` | Simple name of class to deserialize response to. 
Usually a subclass of `AbstractQueryRecordsBase`, e.g. `QueryRecordsAccount`. 
Requires the `package` option be set. | |One of sObjectClass or sObjectName is 
required
+| `sObjectClass` | `String` | Fully qualified name of class to deserialize 
response to. Usually a subclass of `AbstractQueryRecordsBase`, e.g. 
`org.my.dto.QueryRecordsAccount`| |
+| `sObjectName` | `String` | Simple name of class to deserialize response to. 
Usually a subclass of `AbstractQueryRecordsBase`, e.g. `QueryRecordsAccount`. 
Requires the `package` option be set. | |
 
 |===
 
@@ -562,15 +565,18 @@ Type: Instance of class supplied in `sObjectClass`
 
 `queryAll`
 
-Executes the specified SOQL query. Unlike the `query` operation , `queryAll` 
returns records that are deleted because of a merge or delete. It also returns 
information about archived task and event records.
+Executes the specified SOQL query. Unlike the `query` operation , `queryAll` 
returns records that are deleted because
+of a merge or delete. It also returns information about archived task and 
event records. If neither `sObjectClass` nor
+`sObjectName` are set, Camel will attempt to determine the correct 
`AbstractQueryRecordsBase` sublcass based on the
+response.
 
 |===
 | Parameter | Type | Description | Default | Required
 
 | Body or `sObjectQuery` | `String`  | SOQL query | | x
 | streamQueryResult | `Boolean` | If true, returns a streaming `Iterable` and 
transparently retrieves all pages as needed. The `sObjectClass` option must 
reference an `AbstractQueryRecordsBase` subclass. | false |
-| `sObjectClass` | `String` | Fully qualified name of class to deserialize 
response to. Usually a subclass of `AbstractQueryRecordsBase`, e.g. 
`org.my.dto.QueryRecordsAccount`| |One of sObjectClass or sObjectName is 
required
-| `sObjectName` | `String` | Simple name of class to deserialize response to. 
Usually a subclass of `AbstractQueryRecordsBase`, e.g. `QueryRecordsAccount`. 
Requires the `package` option be set.| |One of sObjectClass or sObjectName is 
required
+| `sObjectClass` | `String` | Fully qualified name of class to deserialize 
response to. Usually a subclass of `AbstractQueryRecordsBase`, e.g. 
`org.my.dto.QueryRecordsAccount`| |
+| `sObjectName` | `String` | Simple name of class to deserialize response to. 
Usually a subclass of `AbstractQueryRecordsBase`, e.g. `QueryRecordsAccount`. 
Requires the `package` option be set.| |
 |===
 
 *Output*
@@ -1008,7 +1014,9 @@ salesforce:apexCall[/yourApexRestUrl][?options]
 
 You can supply the apexUrl either in the endpoint (see above), or as the 
`apexUrl` option as listed in the table below.
 In either case the Apex URL can contain placeholders in the format of 
`\{headerName}`. E.g., for the Apex URL `MyApexClass/\{id}`,
-the value of the header named `id` will be used to replace the placeholder.
+the value of the header named `id` will be used to replace the placeholder. If 
`rawPayload` is false and neither
+`sObjectClass` nor `sObjectName` are set, Camel will attempt to determine the 
correct `AbstractQueryRecordsBase`
+sublcass based on the response.
 
 |===
 | Parameter | Type | Description| Default| Required
@@ -1019,8 +1027,8 @@ is transformed into query parameters. For other HTTP 
methods, the body is used f
 | `apexMethod` | `String` | The HTTP method (e.g. `GET`, `POST`) to use. | 
`GET` |
 | `rawPayload` | `Boolean` | If true, Camel will not serialize the request or 
response bodies. | false |
 | Header: `apexQueryParam.[paramName]` | Object | Headers that override apex 
parameters passed in the endpoint. |  |
-| `sObjectName` | `String` | Name of sObject (e.g. `Merchandise__c`) used to 
deserialize the response |  | If `rawPayload` is false and `sObjectClass` not 
supplied
-| `sObjectClass` | `String` | Fully qualified class name used to deserialize 
the response |  | If `rawPayload` is false and `sObjectName` not supplied
+| `sObjectName` | `String` | Name of sObject (e.g. `Merchandise__c`) used to 
deserialize the response |  |
+| `sObjectClass` | `String` | Fully qualified class name used to deserialize 
the response |  |
 |===
 
 *Output*
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceException.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceException.java
index 529c094fe9b..356ee7eee7f 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceException.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceException.java
@@ -31,6 +31,10 @@ public class SalesforceException extends CamelException {
     private final int statusCode;
     private final InputStream responseContent;
 
+    public SalesforceException(String message) {
+        this(message, null);
+    }
+
     public SalesforceException(Throwable cause) {
         this(null, 0, null, cause);
     }
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
index df4b4efd642..cc6d3699c9f 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
@@ -63,6 +63,10 @@ import static 
org.apache.camel.component.salesforce.SalesforceEndpointConfig.STR
 public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor {
 
     protected static final String RESPONSE_CLASS = 
AbstractRestProcessor.class.getName() + ".responseClass";
+    protected static final String RESPONSE_CLASS_DEFERRED = 
AbstractRestProcessor.class.getName()
+                                                            + 
".responseClassDeferred";
+    protected static final String RESPONSE_CLASS_PREFIX = 
AbstractRestProcessor.class.getName()
+                                                          + 
".responseClassPrefix";
     protected static final String RESPONSE_TYPE = 
JsonRestProcessor.class.getName() + ".responseType";
 
     private static final Pattern URL_TEMPLATE = 
Pattern.compile("\\{([^\\{\\}]+)\\}");
@@ -332,7 +336,7 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
         final String sObjectId = determineSObjectId(exchange);
 
         // use sObject name to load class
-        setResponseClass(exchange, sObjectName);
+        setResponseClass(exchange);
 
         // get optional field list
         String fieldsValue = getParameter(SOBJECT_FIELDS, exchange, 
IGNORE_BODY, IS_OPTIONAL);
@@ -403,7 +407,7 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
         }
 
         // use sObject name to load class
-        setResponseClass(exchange, sObjectName);
+        setResponseClass(exchange);
 
         final Object finalOldValue = oldValue;
         restClient.getSObjectWithId(sObjectName, sObjectExtIdName, 
sObjectExtIdValue, determineHeaders(exchange),
@@ -492,10 +496,10 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
     private void processQuery(final Exchange exchange, final AsyncCallback 
callback) throws SalesforceException {
         final String sObjectQuery = getParameter(SOBJECT_QUERY, exchange, 
USE_BODY, NOT_OPTIONAL);
         final boolean streamQueryResults = getParameter(STREAM_QUERY_RESULT, 
exchange, IGNORE_BODY, IS_OPTIONAL, Boolean.class);
-        final String sObjectName = getParameter(SOBJECT_NAME, exchange, 
IGNORE_BODY, IS_OPTIONAL);
 
         // use custom response class property
-        setResponseClass(exchange, sObjectName);
+        setResponseClass(exchange);
+        exchange.setProperty(RESPONSE_CLASS_PREFIX, "QueryRecords");
 
         if (streamQueryResults) {
             restClient.query(sObjectQuery, determineHeaders(exchange), 
processWithStreamResultCallback(exchange, callback));
@@ -507,10 +511,10 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
     private void processQueryMore(final Exchange exchange, final AsyncCallback 
callback) throws SalesforceException {
         // reuse SOBJECT_QUERY parameter name for nextRecordsUrl
         final String nextRecordsUrl = getParameter(SOBJECT_QUERY, exchange, 
USE_BODY, NOT_OPTIONAL);
-        final String sObjectName = getParameter(SOBJECT_NAME, exchange, 
IGNORE_BODY, IS_OPTIONAL);
 
         // use custom response class property
-        setResponseClass(exchange, sObjectName);
+        setResponseClass(exchange);
+        exchange.setProperty(RESPONSE_CLASS_PREFIX, "QueryRecords");
 
         restClient.queryMore(nextRecordsUrl, determineHeaders(exchange), 
processWithResponseCallback(exchange, callback));
     }
@@ -518,10 +522,10 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
     private void processQueryAll(final Exchange exchange, final AsyncCallback 
callback) throws SalesforceException {
         final String sObjectQuery = getParameter(SOBJECT_QUERY, exchange, 
USE_BODY, NOT_OPTIONAL);
         final boolean streamQueryResults = getParameter(STREAM_QUERY_RESULT, 
exchange, IGNORE_BODY, IS_OPTIONAL, Boolean.class);
-        final String sObjectName = getParameter(SOBJECT_NAME, exchange, 
IGNORE_BODY, IS_OPTIONAL);
 
         // use custom response class property
-        setResponseClass(exchange, sObjectName);
+        setResponseClass(exchange);
+        exchange.setProperty(RESPONSE_CLASS_PREFIX, "QueryRecords");
 
         if (streamQueryResults) {
             restClient.queryAll(sObjectQuery, determineHeaders(exchange), 
processWithStreamResultCallback(exchange, callback));
@@ -549,7 +553,7 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
         final Map<String, Object> queryParams = getQueryParams(exchange);
 
         // set response class
-        setResponseClass(exchange, getParameter(SOBJECT_NAME, exchange, 
IGNORE_BODY, IS_OPTIONAL));
+        setResponseClass(exchange);
 
         // set request stream
         final Object requestBody = exchange.getIn().getBody();
@@ -731,15 +735,19 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
      */
     protected abstract InputStream getRequestStream(Message in, Object object) 
throws SalesforceException;
 
-    private void setResponseClass(Exchange exchange, String sObjectName) 
throws SalesforceException {
+    protected void setResponseClass(Exchange exchange) throws 
SalesforceException {
 
         // nothing to do if using rawPayload
         if (rawPayload) {
             return;
         }
 
-        Class<?> sObjectClass = getSObjectClass(sObjectName, exchange);
-        exchange.setProperty(RESPONSE_CLASS, sObjectClass);
+        Class<?> sObjectClass = getSObjectClass(exchange);
+        if (sObjectClass != null) {
+            exchange.setProperty(RESPONSE_CLASS, sObjectClass);
+        } else {
+            exchange.setProperty(RESPONSE_CLASS_DEFERRED, true);
+        }
     }
 
     final ResponseCallback processWithResponseCallback(final Exchange 
exchange, final AsyncCallback callback) {
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractSalesforceProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractSalesforceProcessor.java
index d028ed62c91..773733222ec 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractSalesforceProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractSalesforceProcessor.java
@@ -175,18 +175,35 @@ public abstract class AbstractSalesforceProcessor extends 
ServiceSupport impleme
         }
     }
 
-    protected Class<?> getSObjectClass(String sObjectName, Exchange exchange) 
throws SalesforceException {
+    protected Class<?> getSObjectClass(Exchange exchange) throws 
SalesforceException {
+        final String sObjectName = 
getParameter(SalesforceEndpointConfig.SOBJECT_NAME, exchange, IGNORE_BODY, 
IS_OPTIONAL);
+        final String className = 
getParameter(SalesforceEndpointConfig.SOBJECT_CLASS, exchange, IGNORE_BODY, 
IS_OPTIONAL);
+        return getSObjectClass(sObjectName, className);
+    }
+
+    /**
+     *
+     * @param  sObjectName         if provided, will attempt to look up class 
by simple name
+     * @param  className           if provided, will attempt to look up class 
by fully qualified name
+     * @return                     Class, if found.
+     * @throws SalesforceException if unable to find class by whichever 
parameter was non-null
+     */
+    protected Class<?> getSObjectClass(String sObjectName, String className) 
throws SalesforceException {
         Class<?> sObjectClass = null;
         if (sObjectName != null) {
             sObjectClass = classMap.get(sObjectName);
+            if (sObjectClass == null) {
+                throw new SalesforceException(
+                        String.format("SObject class not found for 
sObjectName: %s", sObjectName));
+            }
         }
-        if (sObjectClass == null) {
-            final String className = 
getParameter(SalesforceEndpointConfig.SOBJECT_CLASS, exchange, IGNORE_BODY, 
NOT_OPTIONAL);
+        if (className != null) {
             try {
-                sObjectClass = 
endpoint.getComponent().getCamelContext().getClassResolver().resolveMandatoryClass(className);
+                sObjectClass
+                        = 
endpoint.getComponent().getCamelContext().getClassResolver().resolveMandatoryClass(className);
             } catch (ClassNotFoundException e) {
                 throw new SalesforceException(
-                        String.format("SObject class not found %s or by 
sObjectName %s", className, sObjectName), e);
+                        String.format("SObject class not found %s", 
className), e);
             }
         }
         return sObjectClass;
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 03d6a20d111..979c2d4708b 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
@@ -98,7 +98,7 @@ public class CompositeSObjectCollectionsProcessor extends 
AbstractSalesforceProc
         String sObjectName = 
getParameter(SalesforceEndpointConfig.SOBJECT_NAME, exchange, IGNORE_BODY, 
IS_OPTIONAL);
 
         // gets class by sObjectName if not null, otherwise tries the 
SOBJECT_CLASS option
-        Class<?> sObjectClass = getSObjectClass(sObjectName, exchange);
+        Class<?> sObjectClass = getSObjectClass(exchange);
 
         RetrieveSObjectCollectionsDto request = new 
RetrieveSObjectCollectionsDto(ids, fields);
         compositeClient.submitRetrieveCompositeCollections(request, 
determineHeaders(exchange),
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 fd8bf7094f1..b3e4d02c1bc 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
@@ -23,6 +23,9 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.camel.AsyncCallback;
@@ -196,6 +199,9 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
                 // do we need to un-marshal a response
                 final Object response;
                 Class<?> responseClass = exchange.getProperty(RESPONSE_CLASS, 
Class.class);
+                if (responseClass == null && 
exchange.getProperty(RESPONSE_CLASS_DEFERRED, false, Boolean.class)) {
+                    responseClass = detectResponseClass(exchange, 
responseEntity);
+                }
                 if (!rawPayload && responseClass != null) {
                     response = objectMapper.readValue(responseEntity, 
responseClass);
                 } else {
@@ -203,7 +209,7 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
                     if (!rawPayload && responseType != null) {
                         response = objectMapper.readValue(responseEntity, 
responseType);
                     } else {
-                        // return the response as a stream, for getBlobField
+                        // return the response as a stream, for getBlobField 
and rawPayload
                         response = responseEntity;
                     }
                 }
@@ -231,6 +237,29 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
 
     }
 
+    private Class<?> detectResponseClass(Exchange exchange, InputStream 
responseEntity) throws IOException {
+        Class<?> responseClass;
+        try {
+            final JsonParser parser = new 
JsonFactory().createParser(responseEntity);
+            String type = null;
+            while (parser.nextToken() != JsonToken.END_OBJECT) {
+                String propName = parser.getCurrentName();
+                if ("type".equals(propName)) {
+                    parser.nextToken();
+                    type = parser.getText();
+                    break;
+                }
+            }
+            String prefix = exchange.getProperty(RESPONSE_CLASS_PREFIX, "", 
String.class);
+            responseClass = getSObjectClass(prefix + type, null);
+        } catch (IOException | SalesforceException exc) {
+            throw new RuntimeException(exc);
+        } finally {
+            responseEntity.reset();
+        }
+        return responseClass;
+    }
+
     @Override
     protected void processStreamResultResponse(
             Exchange exchange, InputStream responseEntity, Map<String, String> 
headers, SalesforceException ex,
@@ -265,6 +294,8 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
             exchange.setException(new SalesforceException(msg, e));
         } finally {
             exchange.removeProperty(RESPONSE_CLASS);
+            exchange.removeProperty(RESPONSE_CLASS_DEFERRED);
+            exchange.removeProperty(RESPONSE_CLASS_PREFIX);
             exchange.removeProperty(RESPONSE_TYPE);
 
             try {
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiIntegrationTest.java
 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiIntegrationTest.java
index 6ea82df1142..0966e5ee092 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiIntegrationTest.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiIntegrationTest.java
@@ -227,6 +227,15 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
         assertEquals("test response", 
IOUtils.toString(exception.getResponseContent(), StandardCharsets.UTF_8));
     }
 
+    @Test
+    public void testApexCallDetectResponseType() throws Exception {
+        // request merchandise with id in URI template
+        Merchandise__c merchandise
+                = 
template().requestBodyAndHeader("direct:apexCallGetDetectResponseType", null, 
"id", merchandiseId,
+                        Merchandise__c.class);
+        assertNotNull(merchandise);
+    }
+
     @Test
     public void returnsHttpResponseStatusAndText() {
         Exchange exchange = new DefaultExchange(context);
@@ -512,6 +521,14 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
         assertNotNull(lineItem.getRecordType());
     }
 
+    @Test
+    public void testQueryDetectResponseClass() throws Exception {
+        createLineItem();
+        final QueryRecordsLine_Item__c queryRecords
+                = template().requestBody("direct:queryDetectResponseClass", 
null, QueryRecordsLine_Item__c.class);
+        assertNotNull(queryRecords);
+    }
+
     @Test
     public void testQueryWithSObjectName() throws Exception {
         createLineItem();
@@ -823,6 +840,13 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
                 from("direct:getBlobField")
                         
.to("salesforce:getBlobField?sObjectName=Document&sObjectBlobFieldName=Body");
 
+                // testQuery
+                from("direct:queryDetectResponseClass")
+                        .to("salesforce:query?sObjectQuery=SELECT Id, name, 
Typeof Owner WHEN User Then Username End, recordTypeId, RecordType.Name "
+                            + "from Line_Item__c "
+                            + "ORDER BY CreatedDate DESC "
+                            + "LIMIT 1");
+
                 // testQuery
                 from("direct:query")
                         .to("salesforce:query?sObjectQuery=SELECT Id, name, 
Typeof Owner WHEN User Then Username End, recordTypeId, RecordType.Name "
@@ -833,8 +857,8 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
 
                 // testQuery
                 from("direct:queryWithSObjectName")
-                        .to("salesforce:query?sObjectQuery=SELECT Id, name, 
Typeof Owner WHEN User Then Username End, recordTypeId, RecordType.Name from 
Line_Item__c&sObjectName="
-                            + "QueryRecordsLine_Item__c");
+                        .to("salesforce:query?sObjectQuery=SELECT Id, name, 
Typeof Owner WHEN User Then Username End, recordTypeId, RecordType.Name from 
Line_Item__c"
+                            + "&sObjectName=QueryRecordsLine_Item__c");
 
                 // testQuery
                 from("direct:queryStreamResult")
@@ -885,6 +909,10 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
                 from("direct:apexCallGet")
                         
.to("salesforce:apexCall?apexMethod=GET&apexUrl=Merchandise/{id}&sObjectName=Merchandise__c");
 
+                // testApexCall
+                from("direct:apexCallGetDetectResponseType")
+                        
.to("salesforce:apexCall?apexMethod=GET&apexUrl=Merchandise/{id}");
+
                 from("direct:apexCallGetWithId")
                         
.to("salesforce:apexCall/Merchandise/?apexMethod=GET&id=dummyId" + 
"&sObjectClass="
                             + Merchandise__c.class.getName());

Reply via email to