CAMEL-10846 Handle 404 situations more gracefully

Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/87dc6fc2
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/87dc6fc2
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/87dc6fc2

Branch: refs/heads/master
Commit: 87dc6fc295c697d3d0c35dd37aa9742264863017
Parents: 285bea2
Author: Zoran Regvart <zregv...@apache.org>
Authored: Mon Mar 27 19:02:38 2017 +0200
Committer: Zoran Regvart <zregv...@apache.org>
Committed: Mon Mar 27 19:25:03 2017 +0200

----------------------------------------------------------------------
 .../src/main/docs/salesforce-component.adoc     |  3 +-
 .../component/salesforce/NotFoundBehaviour.java | 21 +++++++
 .../salesforce/SalesforceEndpointConfig.java    | 21 +++++++
 .../salesforce/api/NoSuchSObjectException.java  | 29 +++++++++
 .../api/SalesforceMultipleChoicesException.java |  6 --
 .../internal/client/AbstractClientBase.java     | 33 ++++++++---
 .../client/DefaultAnalyticsApiClient.java       | 10 +++-
 .../client/DefaultCompositeApiClient.java       | 31 +++++++---
 .../internal/client/DefaultRestClient.java      | 19 +++---
 .../processor/AbstractRestProcessor.java        | 13 +++-
 .../internal/processor/JsonRestProcessor.java   | 23 +++++---
 .../internal/processor/XmlRestProcessor.java    | 23 ++++----
 .../salesforce/NotFoundIntegrationTest.java     | 62 ++++++++++++++++++++
 .../salesforce/RestApiIntegrationTest.java      | 34 +++++------
 .../SalesforceComponentConfiguration.java       | 16 +++++
 15 files changed, 274 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/docs/salesforce-component.adoc
----------------------------------------------------------------------
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 322950f..5c856ee 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
@@ -519,7 +519,7 @@ with the following path and query parameters:
 | **topicName** | The name of the topic to use |  | String
 |=======================================================================
 
-#### Query Parameters (41 parameters):
+#### Query Parameters (42 parameters):
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |=======================================================================
@@ -540,6 +540,7 @@ with the following path and query parameters:
 | **jobId** (common) | Bulk API Job ID |  | String
 | **limit** (common) | Limit on number of returned records. Applicable to some 
of the API check the Salesforce documentation. |  | Integer
 | **maxBackoff** (common) | Maximum backoff interval for Streaming connection 
restart attempts for failures beyond CometD auto-reconnect. |  | long
+| **notFoundBehaviour** (common) | Sets the behaviour of 404 not found status 
received from Salesforce API. Should the body be set to NULL link 
NotFoundBehaviourNULL or should a exception be signaled on the exchange link 
NotFoundBehaviourEXCEPTION - the default. |  | NotFoundBehaviour
 | **notifyForFields** (common) | Notify for fields options are ALL REFERENCED 
SELECT WHERE |  | NotifyForFieldsEnum
 | **notifyForOperationCreate** (common) | Notify for create operation defaults 
to false (API version = 29.0) |  | Boolean
 | **notifyForOperationDelete** (common) | Notify for delete operation defaults 
to false (API version = 29.0) |  | Boolean

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/NotFoundBehaviour.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/NotFoundBehaviour.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/NotFoundBehaviour.java
new file mode 100644
index 0000000..8f7feec
--- /dev/null
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/NotFoundBehaviour.java
@@ -0,0 +1,21 @@
+/**
+ * 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;
+
+public enum NotFoundBehaviour {
+    EXCEPTION, NULL
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java
----------------------------------------------------------------------
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 f035449..5363c0c 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
@@ -91,6 +91,8 @@ public class SalesforceEndpointConfig implements Cloneable {
     public static final long DEFAULT_BACKOFF_INCREMENT = 1000L;
     public static final long DEFAULT_MAX_BACKOFF = 30000L;
 
+    public static final String NOT_FOUND_BEHAVIOUR = "notFoundBehaviour";
+
     // general properties
     @UriParam
     private String apiVersion = DEFAULT_VERSION;
@@ -187,6 +189,9 @@ public class SalesforceEndpointConfig implements Cloneable {
     @UriParam
     private Integer limit;
 
+    @UriParam
+    private NotFoundBehaviour notFoundBehaviour = NotFoundBehaviour.EXCEPTION;
+
     public SalesforceEndpointConfig copy() {
         try {
             final SalesforceEndpointConfig copy = (SalesforceEndpointConfig) 
super.clone();
@@ -614,6 +619,8 @@ public class SalesforceEndpointConfig implements Cloneable {
         valueMap.put(DEFAULT_REPLAY_ID, defaultReplayId);
         valueMap.put(INITIAL_REPLAY_ID_MAP, initialReplayIdMap);
 
+        valueMap.put(NOT_FOUND_BEHAVIOUR, notFoundBehaviour);
+
         return Collections.unmodifiableMap(valueMap);
     }
 
@@ -816,4 +823,18 @@ public class SalesforceEndpointConfig implements Cloneable 
{
 
         approval.setSkipEntryCriteria(skipEntryCriteria);
     }
+
+    public NotFoundBehaviour getNotFoundBehaviour() {
+        return notFoundBehaviour;
+    }
+
+    /**
+     * Sets the behaviour of 404 not found status received from Salesforce API.
+     * Should the body be set to NULL {@link NotFoundBehaviour#NULL} or should 
a
+     * exception be signaled on the exchange {@link 
NotFoundBehaviour#EXCEPTION}
+     * - the default.
+     */
+    public void setNotFoundBehaviour(final NotFoundBehaviour 
notFoundBehaviour) {
+        this.notFoundBehaviour = notFoundBehaviour;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/NoSuchSObjectException.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/NoSuchSObjectException.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/NoSuchSObjectException.java
new file mode 100644
index 0000000..2096569
--- /dev/null
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/NoSuchSObjectException.java
@@ -0,0 +1,29 @@
+/**
+ * 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.api;
+
+import java.util.List;
+
+import org.apache.camel.component.salesforce.api.dto.RestError;
+
+public final class NoSuchSObjectException extends SalesforceException {
+
+    public NoSuchSObjectException(final List<RestError> restErrors) {
+        super(restErrors, 404);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceMultipleChoicesException.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceMultipleChoicesException.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceMultipleChoicesException.java
index 8330e5e..b6614ef 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceMultipleChoicesException.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceMultipleChoicesException.java
@@ -32,10 +32,4 @@ public class SalesforceMultipleChoicesException extends 
SalesforceException {
         return choices;
     }
 
-    @Override
-    public void appendFields(StringBuilder builder) {
-        super.appendFields(builder);
-        builder.append(",choices=");
-        builder.append(choices.toString());
-    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/AbstractClientBase.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/AbstractClientBase.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/AbstractClientBase.java
index 10799f6..af96dcd 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/AbstractClientBase.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/AbstractClientBase.java
@@ -16,16 +16,26 @@
  */
 package org.apache.camel.component.salesforce.internal.client;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.thoughtworks.xstream.XStream;
+
 import org.apache.camel.Service;
 import org.apache.camel.component.salesforce.SalesforceHttpClient;
 import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.TypeReferences;
+import org.apache.camel.component.salesforce.api.dto.RestError;
+import org.apache.camel.component.salesforce.internal.PayloadFormat;
 import org.apache.camel.component.salesforce.internal.SalesforceSession;
+import org.apache.camel.component.salesforce.internal.dto.RestErrors;
 import org.eclipse.jetty.client.HttpContentResponse;
 import org.eclipse.jetty.client.api.ContentProvider;
 import org.eclipse.jetty.client.api.ContentResponse;
@@ -167,16 +177,11 @@ public abstract class AbstractClientBase implements 
SalesforceSession.Salesforce
 
                         }
                     } else if (status < HttpStatus.OK_200 || status >= 
HttpStatus.MULTIPLE_CHOICES_300) {
-
                         // Salesforce HTTP failure!
-                        request = (SalesforceHttpRequest) result.getRequest();
-                        final String msg = String.format("Error {%s:%s} 
executing {%s:%s}",
-                            status, response.getReason(), request.getMethod(), 
request.getURI());
-                        final SalesforceException cause = 
createRestException(response, getContentAsInputStream());
+                        final SalesforceException exception = 
createRestException(response, getContentAsInputStream());
 
                         // for APIs that return body on status 400, such as 
Composite API we need content as well
-                        callback.onResponse(getContentAsInputStream(), new 
SalesforceException(msg, response.getStatus(), cause));
-
+                        callback.onResponse(getContentAsInputStream(), 
exception);
                     } else {
 
                         // Success!!!
@@ -203,6 +208,20 @@ public abstract class AbstractClientBase implements 
SalesforceSession.Salesforce
         this.instanceUrl = instanceUrl;
     }
 
+    final List<RestError> readErrorsFrom(final InputStream responseContent, 
final PayloadFormat format,
+        final ObjectMapper objectMapper, final XStream xStream)
+        throws IOException, JsonParseException, JsonMappingException {
+        final List<RestError> restErrors;
+        if (PayloadFormat.JSON.equals(format)) {
+            restErrors = objectMapper.readValue(responseContent, 
TypeReferences.REST_ERROR_LIST_TYPE);
+        } else {
+            RestErrors errors = new RestErrors();
+            xStream.fromXML(responseContent, errors);
+            restErrors = errors.getErrors();
+        }
+        return restErrors;
+    }
+
     protected abstract void setAccessToken(Request request);
 
     protected abstract SalesforceException createRestException(Response 
response, InputStream responseContent);

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultAnalyticsApiClient.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultAnalyticsApiClient.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultAnalyticsApiClient.java
index 96396f8..daf624b 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultAnalyticsApiClient.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultAnalyticsApiClient.java
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import org.apache.camel.component.salesforce.SalesforceHttpClient;
+import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
 import org.apache.camel.component.salesforce.api.SalesforceException;
 import org.apache.camel.component.salesforce.api.TypeReferences;
 import org.apache.camel.component.salesforce.api.dto.RestError;
@@ -36,12 +37,14 @@ import 
org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportIns
 import 
org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata;
 import 
org.apache.camel.component.salesforce.api.dto.analytics.reports.SyncReportResults;
 import org.apache.camel.component.salesforce.api.utils.JsonUtils;
+import org.apache.camel.component.salesforce.internal.PayloadFormat;
 import org.apache.camel.component.salesforce.internal.SalesforceSession;
 import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.client.api.Response;
 import org.eclipse.jetty.client.util.BytesContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.util.StringUtil;
 
 /**
@@ -254,7 +257,12 @@ public class DefaultAnalyticsApiClient extends 
AbstractClientBase implements Ana
         try {
             if (responseContent != null) {
                 // unmarshal RestError
-                final List<RestError> errors = 
objectMapper.readValue(responseContent, TypeReferences.REST_ERROR_LIST_TYPE);
+                final List<RestError> errors = readErrorsFrom(responseContent, 
PayloadFormat.JSON, objectMapper, null);
+
+                if (statusCode == HttpStatus.NOT_FOUND_404) {
+                    return new NoSuchSObjectException(errors);
+                }
+
                 return new SalesforceException(errors, statusCode);
             }
         } catch (UnsupportedEncodingException e) {

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultCompositeApiClient.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultCompositeApiClient.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultCompositeApiClient.java
index cd85671..1945064 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultCompositeApiClient.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultCompositeApiClient.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.Writer;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
@@ -41,8 +42,10 @@ import com.thoughtworks.xstream.io.xml.XppDriver;
 
 import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
 import org.apache.camel.component.salesforce.SalesforceHttpClient;
+import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
 import org.apache.camel.component.salesforce.api.SalesforceException;
 import org.apache.camel.component.salesforce.api.dto.AnnotationFieldKeySorter;
+import org.apache.camel.component.salesforce.api.dto.RestError;
 import org.apache.camel.component.salesforce.api.dto.composite.SObjectBatch;
 import 
org.apache.camel.component.salesforce.api.dto.composite.SObjectBatchResponse;
 import org.apache.camel.component.salesforce.api.dto.composite.SObjectTree;
@@ -59,6 +62,7 @@ import org.eclipse.jetty.client.api.Response;
 import org.eclipse.jetty.client.util.InputStreamContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.util.StringUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -81,8 +85,8 @@ public class DefaultCompositeApiClient extends 
AbstractClientBase implements Com
     private final XStream xStream;
 
     public DefaultCompositeApiClient(final SalesforceEndpointConfig 
configuration, final PayloadFormat format,
-            final String version, final SalesforceSession session, final 
SalesforceHttpClient httpClient)
-            throws SalesforceException {
+        final String version, final SalesforceSession session, final 
SalesforceHttpClient httpClient)
+        throws SalesforceException {
         super(version, session, httpClient);
         this.format = format;
 
@@ -120,7 +124,7 @@ public class DefaultCompositeApiClient extends 
AbstractClientBase implements Com
 
     @Override
     public void submitCompositeBatch(final SObjectBatch batch, final 
ResponseCallback<SObjectBatchResponse> callback)
-            throws SalesforceException {
+        throws SalesforceException {
         checkCompositeBatchVersion(version, batch.getVersion());
 
         final String url = versionUrl() + "composite/batch";
@@ -136,7 +140,7 @@ public class DefaultCompositeApiClient extends 
AbstractClientBase implements Com
 
     @Override
     public void submitCompositeTree(final SObjectTree tree, final 
ResponseCallback<SObjectTreeResponse> callback)
-            throws SalesforceException {
+        throws SalesforceException {
         final String url = versionUrl() + "composite/tree/" + 
tree.getObjectType();
 
         final Request post = createRequest(HttpMethod.POST, url);
@@ -151,10 +155,8 @@ public class DefaultCompositeApiClient extends 
AbstractClientBase implements Com
     static void checkCompositeBatchVersion(final String configuredVersion, 
final Version batchVersion)
         throws SalesforceException {
         if (Version.create(configuredVersion).compareTo(batchVersion) < 0) {
-            throw new SalesforceException("Component is configured with 
Salesforce API version "
-                                          + configuredVersion
-                                          + ", but the payload of the 
Composite API batch operation requires at least "
-                                          + batchVersion, 0);
+            throw new SalesforceException("Component is configured with 
Salesforce API version " + configuredVersion
+                + ", but the payload of the Composite API batch operation 
requires at least " + batchVersion, 0);
         }
     }
 
@@ -263,8 +265,19 @@ public class DefaultCompositeApiClient extends 
AbstractClientBase implements Com
 
     @Override
     protected SalesforceException createRestException(final Response response, 
final InputStream responseContent) {
-        final String reason = response.getReason();
+        final List<RestError> errors;
+        try {
+            errors = readErrorsFrom(responseContent, format, mapper, xStream);
+        } catch (IOException e) {
+            return new SalesforceException("Unable to read error response", e);
+        }
+
         final int status = response.getStatus();
+        if (status == HttpStatus.NOT_FOUND_404) {
+            return new NoSuchSObjectException(errors);
+        }
+
+        final String reason = response.getReason();
 
         return new SalesforceException("Unexpected error: " + reason, status);
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
index 9e190be..e8e38e0 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultRestClient.java
@@ -25,10 +25,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.thoughtworks.xstream.XStream;
 
+import org.apache.camel.component.salesforce.NotFoundBehaviour;
 import org.apache.camel.component.salesforce.SalesforceHttpClient;
+import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
 import org.apache.camel.component.salesforce.api.SalesforceException;
 import 
org.apache.camel.component.salesforce.api.SalesforceMultipleChoicesException;
 import org.apache.camel.component.salesforce.api.TypeReferences;
@@ -59,8 +63,8 @@ public class DefaultRestClient extends AbstractClientBase 
implements RestClient
     private ObjectMapper objectMapper;
     private XStream xStream;
 
-    public DefaultRestClient(SalesforceHttpClient httpClient, String version, 
PayloadFormat format, SalesforceSession session)
-            throws SalesforceException {
+    public DefaultRestClient(final SalesforceHttpClient httpClient, final 
String version, final PayloadFormat format,
+        final SalesforceSession session) throws SalesforceException {
         super(version, session, httpClient);
 
         this.format = format;
@@ -109,14 +113,11 @@ public class DefaultRestClient extends AbstractClientBase 
implements RestClient
                     }
                     return new SalesforceMultipleChoicesException(reason, 
statusCode, choices);
                 } else {
-                    final List<RestError> restErrors;
-                    if (PayloadFormat.JSON.equals(format)) {
-                        restErrors = objectMapper.readValue(responseContent, 
TypeReferences.REST_ERROR_LIST_TYPE);
-                    } else {
-                        RestErrors errors = new RestErrors();
-                        xStream.fromXML(responseContent, errors);
-                        restErrors = errors.getErrors();
+                    final List<RestError> restErrors = 
readErrorsFrom(responseContent, format, objectMapper, xStream);
+                    if (statusCode == HttpStatus.NOT_FOUND_404) {
+                        return new NoSuchSObjectException(restErrors);
                     }
+
                     return new SalesforceException(restErrors, statusCode);
                 }
             }

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/AbstractRestProcessor.java
----------------------------------------------------------------------
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 7f4ccea..758cc57 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
@@ -34,8 +34,10 @@ import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.component.salesforce.NotFoundBehaviour;
 import org.apache.camel.component.salesforce.SalesforceEndpoint;
 import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
+import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
 import org.apache.camel.component.salesforce.api.SalesforceException;
 import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
 import org.apache.camel.component.salesforce.api.dto.approval.ApprovalRequest;
@@ -67,10 +69,14 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
     private RestClient restClient;
     private Map<String, Class<?>> classMap;
 
+    private final NotFoundBehaviour notFoundBehaviour;
+
     public AbstractRestProcessor(SalesforceEndpoint endpoint) throws 
SalesforceException {
         super(endpoint);
 
-        final PayloadFormat payloadFormat = 
endpoint.getConfiguration().getFormat();
+        final SalesforceEndpointConfig configuration = 
endpoint.getConfiguration();
+        final PayloadFormat payloadFormat = configuration.getFormat();
+        notFoundBehaviour = configuration.getNotFoundBehaviour();
 
         this.restClient = new DefaultRestClient(httpClient, (String) 
endpointConfigMap.get(API_VERSION),
                 payloadFormat, session);
@@ -84,6 +90,8 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
         super(endpoint);
         this.restClient = restClient;
         this.classMap = classMap;
+        final SalesforceEndpointConfig configuration = 
endpoint.getConfiguration();
+        notFoundBehaviour = configuration.getNotFoundBehaviour();
     }
 
     @Override
@@ -831,4 +839,7 @@ public abstract class AbstractRestProcessor extends 
AbstractSalesforceProcessor
     // process response entity and set out message in exchange
     protected abstract void processResponse(Exchange exchange, InputStream 
responseEntity, SalesforceException ex, AsyncCallback callback);
 
+    final boolean shouldReport(SalesforceException ex) {
+        return !(ex instanceof NoSuchSObjectException && notFoundBehaviour == 
NotFoundBehaviour.NULL);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/JsonRestProcessor.java
----------------------------------------------------------------------
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 67a973c..d52601f 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
@@ -27,7 +27,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.component.salesforce.NotFoundBehaviour;
 import org.apache.camel.component.salesforce.SalesforceEndpoint;
+import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
 import org.apache.camel.component.salesforce.api.SalesforceException;
 import org.apache.camel.component.salesforce.api.TypeReferences;
 import org.apache.camel.component.salesforce.api.dto.AbstractDTOBase;
@@ -171,8 +173,17 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
 
         // process JSON response for TypeReference
         try {
-            // do we need to un-marshal a response
-            if (responseEntity != null) {
+            final Message out = exchange.getOut();
+            final Message in = exchange.getIn();
+            out.copyFromWithNewBody(in, null);
+
+            if (ex != null) {
+                // if an exception is reported we should not loose it
+                if (shouldReport(ex)) {
+                    exchange.setException(ex);
+                }
+            } else if (responseEntity != null) {
+                // do we need to un-marshal a response
                 Object response = null;
                 Class<?> responseClass = exchange.getProperty(RESPONSE_CLASS, 
Class.class);
                 if (responseClass != null) {
@@ -186,13 +197,8 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
                         response = responseEntity;
                     }
                 }
-                exchange.getOut().setBody(response);
-            } else {
-                exchange.setException(ex);
+                out.setBody(response);
             }
-            // copy headers and attachments
-            
exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
-            
exchange.getOut().getAttachmentObjects().putAll(exchange.getIn().getAttachmentObjects());
         } catch (IOException e) {
             String msg = "Error parsing JSON response: " + e.getMessage();
             exchange.setException(new SalesforceException(msg, e));
@@ -214,5 +220,4 @@ public class JsonRestProcessor extends 
AbstractRestProcessor {
         }
 
     }
-
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/XmlRestProcessor.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/XmlRestProcessor.java
 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/XmlRestProcessor.java
index 2bbc190..6650ff0 100644
--- 
a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/XmlRestProcessor.java
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/processor/XmlRestProcessor.java
@@ -220,12 +220,20 @@ public class XmlRestProcessor extends 
AbstractRestProcessor {
     }
 
     @Override
-    protected void processResponse(Exchange exchange, InputStream 
responseEntity,
-                                   SalesforceException exception, 
AsyncCallback callback) {
+    protected void processResponse(final Exchange exchange, final InputStream 
responseEntity,
+        final SalesforceException exception, final AsyncCallback callback) {
         final XStream localXStream = xStream.get();
         try {
-            // do we need to un-marshal a response
-            if (responseEntity != null) {
+            final Message out = exchange.getOut();
+            final Message in = exchange.getIn();
+            out.copyFromWithNewBody(in, null);
+
+            if (exception != null) {
+                if (shouldReport(exception)) {
+                    exchange.setException(exception);
+                }
+            } else if (responseEntity != null) {
+                // do we need to un-marshal a response
                 final Class<?> responseClass = 
exchange.getProperty(RESPONSE_CLASS, Class.class);
                 Object response;
                 if (responseClass != null) {
@@ -251,13 +259,8 @@ public class XmlRestProcessor extends 
AbstractRestProcessor {
                     // return the response as a stream, for getBlobField
                     response = responseEntity;
                 }
-                exchange.getOut().setBody(response);
-            } else {
-                exchange.setException(exception);
+                out.setBody(response);
             }
-            // copy headers and attachments
-            
exchange.getOut().getHeaders().putAll(exchange.getIn().getHeaders());
-            
exchange.getOut().getAttachmentObjects().putAll(exchange.getIn().getAttachmentObjects());
         } catch (XStreamException e) {
             String msg = "Error parsing XML response: " + e.getMessage();
             exchange.setException(new SalesforceException(msg, e));

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/NotFoundIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/NotFoundIntegrationTest.java
 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/NotFoundIntegrationTest.java
new file mode 100644
index 0000000..8ffd8b4
--- /dev/null
+++ 
b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/NotFoundIntegrationTest.java
@@ -0,0 +1,62 @@
+/**
+ * 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.util.Arrays;
+
+import com.googlecode.junittoolbox.ParallelParameterized;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
+import org.apache.camel.component.salesforce.dto.generated.Account;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(ParallelParameterized.class)
+public class NotFoundIntegrationTest extends AbstractSalesforceTestBase {
+
+    @Parameter
+    public String format;
+
+    @Test
+    public void shouldNotReportNotFoundExceptionFromRestApiIfConfiguredNotTo() 
{
+        final Account got = template
+            
.requestBody("salesforce:getSObjectWithId?sObjectName=Account&sObjectIdName=Name&format="
 + format
+                + "&notFoundBehaviour=NULL", "NonExistant", Account.class);
+
+        assertNull("Expecting null when `notFoundBehaviour` is set to NULL", 
got);
+    }
+
+    @Test
+    public void shouldReportNotFoundExceptionFromRestApi() {
+        try {
+            
template.requestBody("salesforce:getSObjectWithId?sObjectName=Account&sObjectIdName=Name&format="
 + format,
+                "NonExistant", Account.class);
+            fail("Expecting CamelExecutionException");
+        } catch (final CamelExecutionException e) {
+            assertTrue("Expecting the cause of CamelExecutionException to be 
NoSuchSObjectException",
+                e.getCause() instanceof NoSuchSObjectException);
+        }
+    }
+
+    @Parameters(name = "{0}")
+    public static Iterable<String> formats() {
+        return Arrays.asList("XML", "JSON");
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/RestApiIntegrationTest.java
----------------------------------------------------------------------
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 716fda1..ccd09d9 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
@@ -30,6 +30,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
 import org.apache.camel.CamelExecutionException;
 import org.apache.camel.Processor;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.salesforce.api.NoSuchSObjectException;
 import org.apache.camel.component.salesforce.api.SalesforceException;
 import 
org.apache.camel.component.salesforce.api.SalesforceMultipleChoicesException;
 import org.apache.camel.component.salesforce.api.dto.AbstractDTOBase;
@@ -442,12 +443,11 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
                 template().requestBody("direct:getSObjectWithId", merchandise, 
Merchandise__c.class);
                 fail("Expected SalesforceException with statusCode 300");
             } catch (final CamelExecutionException e) {
-                assertTrue(e.getCause() instanceof SalesforceException);
-                assertTrue(e.getCause().getCause() instanceof 
SalesforceMultipleChoicesException);
-                final SalesforceMultipleChoicesException cause = 
(SalesforceMultipleChoicesException) e.getCause()
-                    .getCause();
-                assertEquals(300, cause.getStatusCode());
-                final List<String> choices = cause.getChoices();
+                final Throwable cause = e.getCause();
+                assertTrue(cause instanceof 
SalesforceMultipleChoicesException);
+                final SalesforceMultipleChoicesException multipleChoices = 
(SalesforceMultipleChoicesException) cause;
+                assertEquals(300, multipleChoices.getStatusCode());
+                final List<String> choices = multipleChoices.getChoices();
                 assertNotNull(choices);
                 assertFalse(choices.isEmpty());
             }
@@ -476,12 +476,12 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
             result = template().requestBody("direct:createSObject", 
merchandise, CreateSObjectResult.class);
             fail("Expected SalesforceException with statusCode 400");
         } catch (final CamelExecutionException e) {
-            assertTrue(e.getCause() instanceof SalesforceException);
-            assertTrue(e.getCause().getCause() instanceof SalesforceException);
-            final SalesforceException cause = (SalesforceException) 
e.getCause().getCause();
-            assertEquals(400, cause.getStatusCode());
-            assertEquals(1, cause.getErrors().size());
-            assertEquals("[Total_Inventory__c]", 
cause.getErrors().get(0).getFields().toString());
+            final Throwable cause = e.getCause();
+            assertTrue(cause instanceof SalesforceException);
+            final SalesforceException badRequest = (SalesforceException) cause;
+            assertEquals(400, badRequest.getStatusCode());
+            assertEquals(1, badRequest.getErrors().size());
+            assertEquals("[Total_Inventory__c]", 
badRequest.getErrors().get(0).getFields().toString());
         } finally {
             // delete the clone if created
             if (result != null) {
@@ -497,11 +497,11 @@ public class RestApiIntegrationTest extends 
AbstractSalesforceTestBase {
             template().requestBody("direct:getSObject", "ILLEGAL_ID", 
Merchandise__c.class);
             fail("Expected SalesforceException");
         } catch (final CamelExecutionException e) {
-            assertTrue(e.getCause() instanceof SalesforceException);
-            assertTrue(e.getCause().getCause() instanceof SalesforceException);
-            final SalesforceException cause = (SalesforceException) 
e.getCause().getCause();
-            assertEquals(404, cause.getStatusCode());
-            assertEquals(1, cause.getErrors().size());
+            final Throwable cause = e.getCause();
+            assertTrue(cause instanceof NoSuchSObjectException);
+            final NoSuchSObjectException noSuchObject = 
(NoSuchSObjectException) cause;
+            assertEquals(404, noSuchObject.getStatusCode());
+            assertEquals(1, noSuchObject.getErrors().size());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/87dc6fc2/platforms/spring-boot/components-starter/camel-salesforce-starter/src/main/java/org/apache/camel/component/salesforce/springboot/SalesforceComponentConfiguration.java
----------------------------------------------------------------------
diff --git 
a/platforms/spring-boot/components-starter/camel-salesforce-starter/src/main/java/org/apache/camel/component/salesforce/springboot/SalesforceComponentConfiguration.java
 
b/platforms/spring-boot/components-starter/camel-salesforce-starter/src/main/java/org/apache/camel/component/salesforce/springboot/SalesforceComponentConfiguration.java
index 5ae2470..5d6ee52 100644
--- 
a/platforms/spring-boot/components-starter/camel-salesforce-starter/src/main/java/org/apache/camel/component/salesforce/springboot/SalesforceComponentConfiguration.java
+++ 
b/platforms/spring-boot/components-starter/camel-salesforce-starter/src/main/java/org/apache/camel/component/salesforce/springboot/SalesforceComponentConfiguration.java
@@ -21,6 +21,7 @@ import java.util.Map;
 import java.util.Set;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.camel.component.salesforce.AuthenticationType;
+import org.apache.camel.component.salesforce.NotFoundBehaviour;
 import org.apache.camel.component.salesforce.SalesforceHttpClient;
 import 
org.apache.camel.component.salesforce.api.dto.analytics.reports.ReportMetadata;
 import org.apache.camel.component.salesforce.api.dto.approval.ApprovalRequest;
@@ -622,6 +623,13 @@ public class SalesforceComponentConfiguration {
          * @param skipEntryCriteria
          */
         private Boolean approvalSkipEntryCriteria;
+        /**
+         * Sets the behaviour of 404 not found status received from Salesforce
+         * API. Should the body be set to NULL {@link NotFoundBehaviour#NULL} 
or
+         * should a exception be signaled on the exchange
+         * {@link NotFoundBehaviour#EXCEPTION} - the default.
+         */
+        private NotFoundBehaviour notFoundBehaviour;
 
         public PayloadFormat getFormat() {
             return format;
@@ -978,6 +986,14 @@ public class SalesforceComponentConfiguration {
                 Boolean approvalSkipEntryCriteria) {
             this.approvalSkipEntryCriteria = approvalSkipEntryCriteria;
         }
+
+        public NotFoundBehaviour getNotFoundBehaviour() {
+            return notFoundBehaviour;
+        }
+
+        public void setNotFoundBehaviour(NotFoundBehaviour notFoundBehaviour) {
+            this.notFoundBehaviour = notFoundBehaviour;
+        }
     }
 
     public static class SalesforceLoginConfigNestedConfiguration {

Reply via email to