You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by dh...@apache.org on 2015/03/20 21:37:07 UTC

camel git commit: CAMEL-8516: Fixed 300 status code exception handling, minor polish

Repository: camel
Updated Branches:
  refs/heads/master 781e2c1f2 -> cebd92468


CAMEL-8516: Fixed 300 status code exception handling, minor polish


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

Branch: refs/heads/master
Commit: cebd92468ca086c37810040b9276cc02448698e7
Parents: 781e2c1
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Fri Mar 20 13:36:48 2015 -0700
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:36:48 2015 -0700

----------------------------------------------------------------------
 .../salesforce/SalesforceConsumer.java          |   4 +-
 .../salesforce/api/SalesforceException.java     |  44 ++++---
 .../api/SalesforceMultipleChoicesException.java |  41 ++++++
 .../salesforce/internal/SalesforceSession.java  |  21 ++-
 .../internal/client/AbstractClientBase.java     |   4 +-
 .../internal/client/DefaultBulkApiClient.java   |   2 +-
 .../internal/client/DefaultRestClient.java      |  69 +++++++---
 .../salesforce/internal/dto/RestChoices.java    |  40 ++++++
 .../salesforce/RestApiIntegrationTest.java      | 131 ++++++++++++++++++-
 9 files changed, 297 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceConsumer.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceConsumer.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceConsumer.java
index b47f658..ac44d5a 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceConsumer.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceConsumer.java
@@ -141,8 +141,8 @@ public class SalesforceConsumer extends DefaultConsumer {
         // TODO do we need to add NPE checks for message/data.get***???
         Map<String, Object> data = message.getDataAsMap();
 
-        @SuppressWarnings("unchecked") final
-        Map<String, Object> event = (Map<String, Object>) data.get(EVENT_PROPERTY);
+        @SuppressWarnings("unchecked")
+        final Map<String, Object> event = (Map<String, Object>) data.get(EVENT_PROPERTY);
         final Object eventType = event.get(TYPE_PROPERTY);
         Object createdDate = event.get(CREATED_DATE_PROPERTY);
         if (log.isDebugEnabled()) {

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceException.java
----------------------------------------------------------------------
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 4005ec4..dd25f0a 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
@@ -58,13 +58,13 @@ public class SalesforceException extends CamelException {
     }
 
     public SalesforceException(List<RestError> errors, int statusCode, String message, Throwable cause) {
-        super(toErrorMessage(errors, statusCode), cause);
+        super(message == null ? toErrorMessage(errors, statusCode) : message, cause);
         this.errors = errors;
         this.statusCode = statusCode;
     }
 
     public List<RestError> getErrors() {
-        return Collections.unmodifiableList(errors);
+        return errors != null ? Collections.unmodifiableList(errors) : null;
     }
 
     public int getStatusCode() {
@@ -73,30 +73,42 @@ public class SalesforceException extends CamelException {
 
     @Override
     public String toString() {
+        final StringBuilder builder = new StringBuilder("{");
+        appendFields(builder);
+        builder.append("}");
+        return builder.toString();
+    }
+
+    protected void appendFields(StringBuilder builder) {
+        // append message
+        builder.append("message:'");
+        builder.append(getMessage());
+        builder.append("',");
+
+        // check for error
         if (errors != null) {
-            return toErrorMessage(errors, statusCode);
-        } else {
-            // make sure we include the custom message
-            final StringBuilder builder = new StringBuilder("{ ");
-            builder.append(getMessage());
-            builder.append(", statusCode: ");
-            builder.append(statusCode);
-            builder.append("}");
-
-            return builder.toString();
+            builder.append("errors:[");
+            for (RestError error : errors) {
+                builder.append(error.toString());
+            }
+            builder.append("],");
+
         }
+        // append statusCode
+        builder.append("statusCode:");
+        builder.append(statusCode);
     }
 
     private static String toErrorMessage(List<RestError> errors, int statusCode) {
-        StringBuilder builder = new StringBuilder("{ ");
+        StringBuilder builder = new StringBuilder("{");
         if (errors != null) {
-            builder.append(" errors: [");
+            builder.append("errors:[");
             for (RestError error : errors) {
                 builder.append(error.toString());
             }
-            builder.append("], ");
+            builder.append("],");
         }
-        builder.append("statusCode: ");
+        builder.append("statusCode:");
         builder.append(statusCode);
         builder.append("}");
 

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/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
new file mode 100644
index 0000000..8330e5e
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/SalesforceMultipleChoicesException.java
@@ -0,0 +1,41 @@
+/**
+ * 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;
+
+public class SalesforceMultipleChoicesException extends SalesforceException {
+
+    private static final long serialVersionUID = 1L;
+    private final List<String> choices;
+
+    public SalesforceMultipleChoicesException(String message, int statusCode, List<String> choices) {
+        super(message, statusCode);
+        this.choices = choices;
+    }
+
+    public List<String> getChoices() {
+        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/cebd9246/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/SalesforceSession.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/SalesforceSession.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/SalesforceSession.java
index 2364697..bf3a395 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/SalesforceSession.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/SalesforceSession.java
@@ -28,6 +28,7 @@ import org.apache.camel.component.salesforce.api.SalesforceException;
 import org.apache.camel.component.salesforce.api.dto.RestError;
 import org.apache.camel.component.salesforce.internal.dto.LoginError;
 import org.apache.camel.component.salesforce.internal.dto.LoginToken;
+import org.apache.camel.util.ObjectHelper;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.eclipse.jetty.client.ContentExchange;
 import org.eclipse.jetty.client.HttpClient;
@@ -61,13 +62,13 @@ public class SalesforceSession implements Service {
 
     public SalesforceSession(HttpClient httpClient, SalesforceLoginConfig config) {
         // validate parameters
-        assertNotNull("Null httpClient", httpClient);
-        assertNotNull("Null SalesforceLoginConfig", config);
-        assertNotNull("Null loginUrl", config.getLoginUrl());
-        assertNotNull("Null clientId", config.getClientId());
-        assertNotNull("Null clientSecret", config.getClientSecret());
-        assertNotNull("Null userName", config.getUserName());
-        assertNotNull("Null password", config.getPassword());
+        ObjectHelper.notNull(httpClient, "httpClient");
+        ObjectHelper.notNull(config, "SalesforceLoginConfig");
+        ObjectHelper.notNull(config.getLoginUrl(), "loginUrl");
+        ObjectHelper.notNull(config.getClientId(), "clientId");
+        ObjectHelper.notNull(config.getClientSecret(), "clientSecret");
+        ObjectHelper.notNull(config.getUserName(), "userName");
+        ObjectHelper.notNull(config.getPassword(), "password");
 
         this.httpClient = httpClient;
         this.config = config;
@@ -80,12 +81,6 @@ public class SalesforceSession implements Service {
         this.listeners = new CopyOnWriteArraySet<SalesforceSessionListener>();
     }
 
-    private void assertNotNull(String s, Object o) {
-        if (o == null) {
-            throw new IllegalArgumentException(s);
-        }
-    }
-
     @SuppressWarnings("unchecked")
     public synchronized String login(String oldToken) throws SalesforceException {
 

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/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 a115a31..a00d289 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
@@ -150,7 +150,7 @@ public abstract class AbstractClientBase implements SalesforceSession.Salesforce
                 if (responseStatus < HttpStatus.OK_200 || responseStatus >= HttpStatus.MULTIPLE_CHOICES_300) {
                     final String msg = String.format("Error {%s:%s} executing {%s:%s}",
                             responseStatus, reason, request.getMethod(), request.getRequestURI());
-                    final SalesforceException exception = new SalesforceException(msg, responseStatus, createRestException(request));
+                    final SalesforceException exception = new SalesforceException(msg, responseStatus, createRestException(request, reason));
                     callback.onResponse(null, exception);
                 } else {
                     // TODO not memory efficient for large response messages,
@@ -190,6 +190,6 @@ public abstract class AbstractClientBase implements SalesforceSession.Salesforce
 
     protected abstract void setAccessToken(HttpExchange httpExchange);
 
-    protected abstract SalesforceException createRestException(ContentExchange httpExchange);
+    protected abstract SalesforceException createRestException(ContentExchange httpExchange, String reason);
 
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiClient.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiClient.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiClient.java
index a72aee7..3ab4227 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiClient.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/DefaultBulkApiClient.java
@@ -386,7 +386,7 @@ public class DefaultBulkApiClient extends AbstractClientBase implements BulkApiC
     }
 
     @Override
-    protected SalesforceException createRestException(ContentExchange request) {
+    protected SalesforceException createRestException(ContentExchange request, String reason) {
         // this must be of type Error
         try {
             final Error error = unmarshalResponse(new ByteArrayInputStream(request.getResponseContentBytes()),

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/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 0b06289..86ee39b 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,11 +25,15 @@ import java.util.List;
 import java.util.Map;
 
 import com.thoughtworks.xstream.XStream;
+
 import org.apache.camel.component.salesforce.api.SalesforceException;
+import org.apache.camel.component.salesforce.api.SalesforceMultipleChoicesException;
 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.RestChoices;
 import org.apache.camel.component.salesforce.internal.dto.RestErrors;
+import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.URISupport;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.type.TypeReference;
@@ -38,6 +42,7 @@ import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpExchange;
 import org.eclipse.jetty.http.HttpHeaders;
 import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.util.StringUtil;
 
 public class DefaultRestClient extends AbstractClientBase implements RestClient {
@@ -61,6 +66,7 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
         this.objectMapper = new ObjectMapper();
         this.xStream = new XStream();
         xStream.processAnnotations(RestErrors.class);
+        xStream.processAnnotations(RestChoices.class);
     }
 
     @Override
@@ -75,32 +81,61 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
     }
 
     @Override
-    protected SalesforceException createRestException(ContentExchange httpExchange) {
+    protected SalesforceException createRestException(ContentExchange httpExchange, String reason) {
+        // get status code and reason phrase
+        final int statusCode = httpExchange.getResponseStatus();
+        if (reason == null || reason.isEmpty()) {
+            reason = HttpStatus.getMessage(statusCode);
+        }
         // try parsing response according to format
+        String responseContent = null;
         try {
-            if (PayloadFormat.JSON.equals(format)) {
-                List<RestError> restErrors = objectMapper.readValue(
-                    httpExchange.getResponseContent(), new TypeReference<List<RestError>>() {
+            responseContent = httpExchange.getResponseContent();
+            if (responseContent != null && !responseContent.isEmpty()) {
+                final List<String> choices;
+                // return list of choices as error message for 300
+                if (statusCode == HttpStatus.MULTIPLE_CHOICES_300) {
+                    if (PayloadFormat.JSON.equals(format)) {
+                        choices = objectMapper.readValue(
+                            responseContent, new TypeReference<List<String>>() {
+                            }
+                        );
+                    } else {
+                        RestChoices restChoices = new RestChoices();
+                        xStream.fromXML(responseContent, restChoices);
+                        choices = restChoices.getUrls();
+                    }
+                    return new SalesforceMultipleChoicesException(reason, statusCode, choices);
+                } else {
+                    final List<RestError> restErrors;
+                    if (PayloadFormat.JSON.equals(format)) {
+                        restErrors = objectMapper.readValue(
+                            responseContent, new TypeReference<List<RestError>>() {
+                            }
+                        );
+                    } else {
+                        RestErrors errors = new RestErrors();
+                        xStream.fromXML(responseContent, errors);
+                        restErrors = errors.getErrors();
                     }
-                );
-                return new SalesforceException(restErrors, httpExchange.getResponseStatus());
-            } else {
-                RestErrors errors = new RestErrors();
-                xStream.fromXML(httpExchange.getResponseContent(), errors);
-                return new SalesforceException(errors.getErrors(), httpExchange.getResponseStatus());
+                    return new SalesforceException(restErrors, statusCode);
+                }
             }
         } catch (IOException e) {
             // log and ignore
-            String msg = "Unexpected Error parsing " + format + " error response: " + e.getMessage();
+            String msg = "Unexpected Error parsing " + format
+                    + " error response body + [" + responseContent + "] : " + e.getMessage();
             log.warn(msg, e);
         } catch (RuntimeException e) {
             // log and ignore
-            String msg = "Unexpected Error parsing " + format + " error response: " + e.getMessage();
+            String msg = "Unexpected Error parsing " + format
+                    + " error response body + [" + responseContent + "] : " + e.getMessage();
             log.warn(msg, e);
         }
 
         // just report HTTP status info
-        return new SalesforceException("Unexpected error", httpExchange.getResponseStatus());
+        return new SalesforceException("Unexpected error: " + reason + ", with content: " + responseContent,
+                statusCode);
     }
 
     @Override
@@ -355,16 +390,12 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
     }
 
     private String versionUrl() {
-        if (version == null) {
-            throw new IllegalArgumentException("NULL API version", new NullPointerException("version"));
-        }
+        ObjectHelper.notNull(version, "version");
         return servicesDataUrl() + "v" + version + "/";
     }
 
     private String sobjectsUrl(String sObjectName) {
-        if (sObjectName == null) {
-            throw new IllegalArgumentException("Null SObject name", new NullPointerException("sObjectName"));
-        }
+        ObjectHelper.notNull(sObjectName, "sObjectName");
         return versionUrl() + "sobjects/" + sObjectName;
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/RestChoices.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/RestChoices.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/RestChoices.java
new file mode 100644
index 0000000..cd639ff
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/RestChoices.java
@@ -0,0 +1,40 @@
+/**
+ * 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.internal.dto;
+
+import java.util.List;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamImplicit;
+
+/**
+ * DTO for Salesforce REST choices XML response for status code 300
+ */
+@XStreamAlias("Choices")
+public class RestChoices {
+
+    @XStreamImplicit(itemFieldName = "Url")
+    private List<String> urls;
+
+    public List<String> getUrls() {
+        return urls;
+    }
+
+    public void setUrls(List<String> urls) {
+        this.urls = urls;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/cebd9246/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 52a95f6..9627cf3 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
@@ -29,6 +29,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
 import org.apache.camel.CamelExecutionException;
 import org.apache.camel.builder.RouteBuilder;
 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;
 import org.apache.camel.component.salesforce.api.dto.CreateSObjectResult;
 import org.apache.camel.component.salesforce.api.dto.GlobalObjects;
@@ -172,7 +173,7 @@ public class RestApiIntegrationTest extends AbstractSalesforceTestBase {
         merchandise.setDescription__c("Microlite plane");
         merchandise.setPrice__c(2000.0);
         merchandise.setTotal_Inventory__c(50.0);
-        CreateSObjectResult result = template().requestBody("direct:CreateSObject" + suffix,
+        CreateSObjectResult result = template().requestBody("direct:createSObject" + suffix,
             merchandise, CreateSObjectResult.class);
         assertNotNull(result);
         assertTrue("Create success", result.getSuccess());
@@ -186,7 +187,7 @@ public class RestApiIntegrationTest extends AbstractSalesforceTestBase {
         // also need to set the Id
         merchandise.setId(result.getId());
 
-        assertNull(template().requestBodyAndHeader("direct:UpdateSObject" + suffix,
+        assertNull(template().requestBodyAndHeader("direct:updateSObject" + suffix,
             merchandise, SalesforceEndpointConfig.SOBJECT_ID, result.getId()));
         LOG.debug("Update successful");
 
@@ -391,6 +392,124 @@ public class RestApiIntegrationTest extends AbstractSalesforceTestBase {
         }
     }
 
+    @Test
+    public void testStatus300() throws Exception {
+        doTestStatus300("");
+        doTestStatus300("Xml");
+    }
+
+    private void doTestStatus300(String suffix) throws Exception {
+        // clone test merchandise with same external Id
+        if (testId == null) {
+            // execute getBasicInfo to get test id from recent items
+            doTestGetBasicInfo("");
+        }
+
+        // get test merchandise
+        // note that the header value overrides sObjectFields in endpoint
+        Merchandise__c merchandise = template().requestBodyAndHeader("direct:getSObject" + suffix, testId,
+            "sObjectFields", "Name,Description__c,Price__c,Total_Inventory__c", Merchandise__c.class);
+        assertNotNull(merchandise);
+        assertNotNull(merchandise.getName());
+        assertNotNull(merchandise.getPrice__c());
+        assertNotNull(merchandise.getTotal_Inventory__c());
+
+        CreateSObjectResult result = null;
+        try {
+            merchandise.clearBaseFields();
+            result = template().requestBody("direct:createSObject" + suffix,
+                merchandise, CreateSObjectResult.class);
+            assertNotNull(result);
+            assertNotNull(result.getId());
+            LOG.debug("Clone SObject: {}", result);
+
+            // look by external Id to cause 300 error
+            // note that the request SObject overrides settings on the endpoint for LineItem__c
+            try {
+                template().requestBody("direct:getSObjectWithId" + suffix, merchandise, Merchandise__c.class);
+                fail("Expected SalesforceException with statusCode 300");
+            } catch (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();
+                assertNotNull(choices);
+                assertFalse(choices.isEmpty());
+                LOG.debug("Multiple choices: {}", choices);
+            }
+        } finally {
+            // delete the test clone
+            if (result != null) {
+                template().requestBody("direct:deleteSObject" + suffix, result.getId());
+            }
+        }
+    }
+
+    @Test
+    public void testStatus400() throws Exception {
+        doTestStatus400("");
+        doTestStatus400("Xml");
+    }
+
+    private void doTestStatus400(String suffix) throws Exception {
+        // clone test merchandise with same external Id
+        if (testId == null) {
+            // execute getBasicInfo to get test id from recent items
+            doTestGetBasicInfo("");
+        }
+
+        // get test merchandise
+        // note that the header value overrides sObjectFields in endpoint
+        Merchandise__c merchandise = template().requestBodyAndHeader("direct:getSObject" + suffix, testId,
+            "sObjectFields", "Description__c,Price__c", Merchandise__c.class);
+        assertNotNull(merchandise);
+        assertNotNull(merchandise.getPrice__c());
+        assertNull(merchandise.getTotal_Inventory__c());
+
+        merchandise.clearBaseFields();
+        // required field Total_Inventory__c is missing
+        CreateSObjectResult result = null;
+        try {
+            result = template().requestBody("direct:createSObject" + suffix,
+                merchandise, CreateSObjectResult.class);
+            fail("Expected SalesforceException with statusCode 400");
+        } catch (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());
+        } finally {
+            // delete the clone if created
+            if (result != null) {
+                template().requestBody("direct:deleteSObject" + suffix, result.getId());
+            }
+        }
+    }
+
+    @Test
+    public void testStatus404() {
+        doTestStatus404("");
+        doTestStatus404("Xml");
+    }
+
+    private void doTestStatus404(String suffix) {
+        // try to get a non existent SObject
+        try {
+            template().requestBody("direct:getSObject" + suffix, "ILLEGAL_ID", Merchandise__c.class);
+            fail("Expected SalesforceException");
+        } catch (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());
+            LOG.debug("Errors for 404: {}", cause.getErrors());
+        }
+    }
+
     @Override
     protected RouteBuilder doCreateRouteBuilder() throws Exception {
 
@@ -442,17 +561,17 @@ public class RestApiIntegrationTest extends AbstractSalesforceTestBase {
                     .to("salesforce:getSObject?format=XML&sObjectName=Merchandise__c&sObjectFields=Description__c,Total_Inventory__c");
 
                 // testCreateSObject
-                from("direct:CreateSObject")
+                from("direct:createSObject")
                     .to("salesforce:createSObject?sObjectName=Merchandise__c");
 
-                from("direct:CreateSObjectXml")
+                from("direct:createSObjectXml")
                     .to("salesforce:createSObject?format=XML&sObjectName=Merchandise__c");
 
                 // testUpdateSObject
-                from("direct:UpdateSObject")
+                from("direct:updateSObject")
                     .to("salesforce:updateSObject?sObjectName=Merchandise__c");
 
-                from("direct:UpdateSObjectXml")
+                from("direct:updateSObjectXml")
                     .to("salesforce:updateSObject?format=XML&sObjectName=Merchandise__c");
 
                 // testDeleteSObject