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 22:07:41 UTC

[1/9] camel git commit: CAMEL-8267: Allow to configure Salesforce URL

Repository: camel
Updated Branches:
  refs/heads/camel-2.13.x a42d6fa3a -> 6a541ef83


CAMEL-8267: Allow to configure Salesforce URL


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

Branch: refs/heads/camel-2.13.x
Commit: f5484888f7fd32f08c56e07fe504d677a03756ba
Parents: a42d6fa
Author: Cristiano Nicolai <cr...@gmail.com>
Authored: Fri Jan 23 12:43:45 2015 +1000
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:40 2015 -0700

----------------------------------------------------------------------
 .../java/org/apache/camel/maven/CamelSalesforceMojo.java    | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/f5484888/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
index fdb0f8e..dab9c70 100644
--- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
+++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
@@ -114,6 +114,12 @@ public class CamelSalesforceMojo extends AbstractMojo {
     protected File outputDirectory;
 
     /**
+     * Salesforce URL.
+     */
+    @Parameter(property = "camelSalesforce.loginUrl", defaultValue = SalesforceLoginConfig.DEFAULT_LOGIN_URL)
+    protected String loginUrl;
+
+    /**
      * Names of Salesforce SObject for which POJOs must be generated
      */
     @Parameter
@@ -178,8 +184,7 @@ public class CamelSalesforceMojo extends AbstractMojo {
         }
 
         final SalesforceSession session = new SalesforceSession(httpClient,
-                new SalesforceLoginConfig(SalesforceLoginConfig.DEFAULT_LOGIN_URL,
-                        clientId, clientSecret, userName, password, false));
+                new SalesforceLoginConfig(loginUrl, clientId, clientSecret, userName, password, false));
 
         getLog().info("Salesforce login...");
         try {


[3/9] camel git commit: CAMEL-8269: added support for APEX REST calls, also updated Salesforce API version to 33.0

Posted by dh...@apache.org.
http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/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 9f51bbd..52a95f6 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
@@ -24,7 +24,12 @@ import java.nio.channels.ReadableByteChannel;
 import java.util.HashMap;
 import java.util.List;
 
+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.dto.AbstractDTOBase;
 import org.apache.camel.component.salesforce.api.dto.CreateSObjectResult;
 import org.apache.camel.component.salesforce.api.dto.GlobalObjects;
 import org.apache.camel.component.salesforce.api.dto.RestResources;
@@ -38,6 +43,7 @@ import org.apache.camel.component.salesforce.dto.generated.Document;
 import org.apache.camel.component.salesforce.dto.generated.Line_Item__c;
 import org.apache.camel.component.salesforce.dto.generated.Merchandise__c;
 import org.apache.camel.component.salesforce.dto.generated.QueryRecordsLine_Item__c;
+import org.eclipse.jetty.http.HttpStatus;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -297,6 +303,94 @@ public class RestApiIntegrationTest extends AbstractSalesforceTestBase {
         LOG.debug("ExecuteSearch: {}", searchResults);
     }
 
+    @Test
+    public void testApexCall() throws Exception {
+        try {
+            doTestApexCall("");
+            doTestApexCall("Xml");
+        } catch (CamelExecutionException e) {
+            if (e.getCause() instanceof SalesforceException) {
+                SalesforceException cause = (SalesforceException) e.getCause();
+                if (cause.getStatusCode() == HttpStatus.NOT_FOUND_404) {
+                    LOG.error("Make sure test REST resource MerchandiseRestResource.apxc has been loaded: "
+                        + e.getMessage());
+                }
+            }
+            throw e;
+        }
+    }
+
+    private void doTestApexCall(String suffix) throws Exception {
+
+        if (testId == null) {
+            // execute getBasicInfo to get test id from recent items
+            doTestGetBasicInfo("");
+        }
+
+        // request merchandise with id in URI template
+        Merchandise__c merchandise = template().requestBodyAndHeader("direct:apexCallGet" + suffix, null,
+            "id", testId, Merchandise__c.class);
+        assertNotNull(merchandise);
+        LOG.debug("ApexCallGet: {}", merchandise);
+
+        // request merchandise with id as query param
+        merchandise = template().requestBodyAndHeader("direct:apexCallGetWithId" + suffix, null,
+            SalesforceEndpointConfig.APEX_QUERY_PARAM_PREFIX + "id", testId, Merchandise__c.class);
+        assertNotNull(merchandise);
+        LOG.debug("ApexCallGetWithId: {}", merchandise);
+
+        // patch merchandise
+        // clear fields that won't be modified
+        merchandise.clearBaseFields();
+        merchandise.setId(testId);
+        merchandise.setPrice__c(null);
+        merchandise.setTotal_Inventory__c(null);
+
+        merchandise = template().requestBody("direct:apexCallPatch" + suffix,
+            new MerchandiseRequest(merchandise), Merchandise__c.class);
+        assertNotNull(merchandise);
+        LOG.debug("ApexCallPatch: {}", merchandise);
+    }
+
+    /**
+     * Request DTO for Salesforce APEX REST calls.
+     * See https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_rest_methods.htm.
+     */
+    @XStreamAlias("request")
+    public static class MerchandiseRequest extends AbstractDTOBase {
+        private Merchandise__c merchandise;
+
+        public MerchandiseRequest(Merchandise__c merchandise) {
+            this.merchandise = merchandise;
+        }
+
+        public Merchandise__c getMerchandise() {
+            return merchandise;
+        }
+
+        public void setMerchandise(Merchandise__c merchandise) {
+            this.merchandise = merchandise;
+        }
+    }
+
+    /**
+     * Response DTO for Salesforce APEX REST calls.
+     * See https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_rest_methods.htm.
+     */
+    @XStreamAlias("response")
+    public static class MerchandiseXmlResponse extends Merchandise__c {
+        // XML response contains a type string with the SObject type name
+        private String type;
+
+        public String getType() {
+            return type;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+    }
+
     @Override
     protected RouteBuilder doCreateRouteBuilder() throws Exception {
 
@@ -409,6 +503,25 @@ public class RestApiIntegrationTest extends AbstractSalesforceTestBase {
 
                 from("direct:searchXml")
                     .to("salesforce:search?format=XML&sObjectSearch=FIND {Wee}");
+
+                // testApexCall
+                from("direct:apexCallGet")
+                    .to("salesforce:apexCall?apexMethod=GET&apexUrl=Merchandise/{id}&sObjectName=Merchandise__c");
+
+                from("direct:apexCallGetXml")
+                    .to("salesforce:apexCall/Merchandise/{id}?format=XML&apexMethod=GET&sObjectClass=" + MerchandiseXmlResponse.class.getName());
+
+                from("direct:apexCallGetWithId")
+                    .to("salesforce:apexCall/Merchandise/?apexMethod=GET&id=dummyId&sObjectClass=" + Merchandise__c.class.getName());
+
+                from("direct:apexCallGetWithIdXml")
+                    .to("salesforce:apexCall?format=XML&apexMethod=GET&apexUrl=Merchandise/&id=dummyId&sObjectClass=" + MerchandiseXmlResponse.class.getName());
+
+                from("direct:apexCallPatch")
+                    .to("salesforce:apexCall?apexMethod=PATCH&apexUrl=Merchandise/&sObjectName=Merchandise__c");
+
+                from("direct:apexCallPatchXml")
+                    .to("salesforce:apexCall/Merchandise/?format=XML&apexMethod=PATCH&sObjectClass=" + MerchandiseXmlResponse.class.getName());
             }
         };
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/StreamingApiIntegrationTest.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/StreamingApiIntegrationTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/StreamingApiIntegrationTest.java
index 66628eb..0123b05 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/StreamingApiIntegrationTest.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/StreamingApiIntegrationTest.java
@@ -81,7 +81,9 @@ public class StreamingApiIntegrationTest extends AbstractSalesforceTestBase {
             public void configure() throws Exception {
 
                 // test topic subscription
-                from("salesforce:CamelTestTopic?notifyForFields=ALL&notifyForOperations=ALL&"
+                //from("salesforce:CamelTestTopic?notifyForFields=ALL&notifyForOperations=ALL&"
+                from("salesforce:CamelTestTopic?notifyForFields=ALL&"
+                    + "notifyForOperationCreate=true&notifyForOperationDelete=true&notifyForOperationUpdate=true&"
                     + "sObjectName=Merchandise__c&"
                     + "updateTopic=true&sObjectQuery=SELECT Id, Name FROM Merchandise__c").
                     to("mock:CamelTestTopic");

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
index 794b752..67e4b30 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/dto/generated/Document.java
@@ -1,18 +1,3 @@
-//CHECKSTYLE:OFF
-/*
- * Salesforce DTO generated by camel-salesforce-maven-plugin
- * Generated on: Tue May 14 21:15:54 PDT 2013
- */
-package org.apache.camel.component.salesforce.dto.generated;
-
-import com.thoughtworks.xstream.annotations.XStreamAlias;
-import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
-import org.codehaus.jackson.annotate.JsonProperty;
-
-/**
- * Salesforce DTO for SObject Document
- */
-@XStreamAlias("Document")
 /**
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -29,6 +14,17 @@ import org.codehaus.jackson.annotate.JsonProperty;
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.camel.component.salesforce.dto.generated;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+//CHECKSTYLE:OFF
+/**
+ * Salesforce DTO for SObject Document
+ */
+@XStreamAlias("Document")
 public class Document extends AbstractSObjectBase {
 
     // FolderId

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/resources/log4j.properties b/components/camel-salesforce/camel-salesforce-component/src/test/resources/log4j.properties
index 7ddaf0f..d0a193a 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/test/resources/log4j.properties
+++ b/components/camel-salesforce/camel-salesforce-component/src/test/resources/log4j.properties
@@ -22,6 +22,7 @@ log4j.rootLogger=INFO, file
 
 # uncomment the following to enable camel debugging
 #log4j.logger.org.apache.camel.component.salesforce=DEBUG
+#log4j.logger.org.eclipse.jetty=DEBUG
 
 # CONSOLE appender not used by default
 log4j.appender.out=org.apache.log4j.ConsoleAppender


[8/9] camel git commit: CAMEL-8517: Updated SalesforceSession to use volatile shared fields

Posted by dh...@apache.org.
CAMEL-8517: Updated SalesforceSession to use volatile shared fields


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

Branch: refs/heads/camel-2.13.x
Commit: a1bf92075b4977de80eb8c91f74fb6e565985810
Parents: 9c1c551
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Fri Mar 20 09:57:03 2015 -0700
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:44 2015 -0700

----------------------------------------------------------------------
 .../component/salesforce/internal/SalesforceSession.java  | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/a1bf9207/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 5dcb68b..2364697 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
@@ -56,8 +56,8 @@ public class SalesforceSession implements Service {
     private final ObjectMapper objectMapper;
     private final Set<SalesforceSessionListener> listeners;
 
-    private String accessToken;
-    private String instanceUrl;
+    private volatile String accessToken;
+    private volatile String instanceUrl;
 
     public SalesforceSession(HttpClient httpClient, SalesforceLoginConfig config) {
         // validate parameters
@@ -198,7 +198,7 @@ public class SalesforceSession implements Service {
         return accessToken;
     }
 
-    public void logout() throws SalesforceException {
+    public synchronized void logout() throws SalesforceException {
         if (accessToken == null) {
             return;
         }
@@ -236,7 +236,7 @@ public class SalesforceSession implements Service {
                 throw new SalesforceException("Logout request TIMEOUT!", null);
 
             default:
-                throw new SalesforceException("Unknow status: " + done, null);
+                throw new SalesforceException("Unknown status: " + done, null);
             }
         } catch (SalesforceException e) {
             throw e;
@@ -247,7 +247,7 @@ public class SalesforceSession implements Service {
             // reset session
             accessToken = null;
             instanceUrl = null;
-            // notify all session listeners of the new access token and instance url
+            // notify all session listeners about logout
             for (SalesforceSessionListener listener : listeners) {
                 try {
                     listener.onLogout();


[2/9] camel git commit: CAMEL-8267: fixed mojo integration test to use system property or default login url

Posted by dh...@apache.org.
CAMEL-8267: fixed mojo integration test to use system property or default login url


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

Branch: refs/heads/camel-2.13.x
Commit: 2bad2892a2312acea2cfce711dc93c40fb39865d
Parents: f548488
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Mon Feb 23 12:34:41 2015 -0800
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:41 2015 -0700

----------------------------------------------------------------------
 .../camel/maven/CamelSalesforceMojoIntegrationTest.java      | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/2bad2892/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java
index 406ce5f..cf9bcb9 100644
--- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java
+++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java
@@ -16,10 +16,15 @@
  */
 package org.apache.camel.maven;
 
-import java.io.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Properties;
 
 import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
+import org.apache.camel.component.salesforce.SalesforceLoginConfig;
 import org.apache.maven.plugin.logging.SystemStreamLog;
 import org.junit.Assert;
 import org.junit.Test;
@@ -39,6 +44,7 @@ public class CamelSalesforceMojoIntegrationTest {
 
         // set defaults
         mojo.version = System.getProperty("apiVersion", SalesforceEndpointConfig.DEFAULT_VERSION);
+        mojo.loginUrl = System.getProperty("loginUrl", SalesforceLoginConfig.DEFAULT_LOGIN_URL);
         mojo.outputDirectory = new File("target/generated-sources/camel-salesforce");
         mojo.packageName = "org.apache.camel.salesforce.dto";
 


[7/9] camel git commit: CAMEL-8519: Fixed salesforce security listener to replace OAuth header

Posted by dh...@apache.org.
CAMEL-8519: Fixed salesforce security listener to replace OAuth header


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

Branch: refs/heads/camel-2.13.x
Commit: 9c1c5512e83e52f45b0e9ac81b88bc29adbdcd8d
Parents: 53893e5
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Fri Mar 20 09:54:57 2015 -0700
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:44 2015 -0700

----------------------------------------------------------------------
 .../salesforce/internal/client/SalesforceSecurityListener.java   | 4 ++--
 .../salesforce/internal/streaming/SubscriptionHelper.java        | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/9c1c5512/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/SalesforceSecurityListener.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/SalesforceSecurityListener.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/SalesforceSecurityListener.java
index 552401c..27d838a 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/SalesforceSecurityListener.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/SalesforceSecurityListener.java
@@ -103,8 +103,8 @@ public class SalesforceSecurityListener extends HttpEventListenerWrapper {
                     client.setInstanceUrl(session.getInstanceUrl());
                     client.setAccessToken(exchange);
                 } else {
-                    exchange.addRequestHeader(HttpHeaders.AUTHORIZATION,
-                            "OAuth " + currentToken);
+                    exchange.setRequestHeader(HttpHeaders.AUTHORIZATION,
+                        "OAuth " + currentToken);
                 }
 
                 // TODO handle a change in Salesforce instanceUrl, right now we retry with the same destination

http://git-wip-us.apache.org/repos/asf/camel/blob/9c1c5512/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
index 7aaa086..b0ed0d6 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/SubscriptionHelper.java
@@ -213,7 +213,7 @@ public class SubscriptionHelper extends ServiceSupport {
                 }
 
                 // add current security token obtained from session
-                exchange.addRequestHeader(HttpHeaders.AUTHORIZATION,
+                exchange.setRequestHeader(HttpHeaders.AUTHORIZATION,
                         "OAuth " + accessToken);
             }
         };


[5/9] camel git commit: CAMEL-8395: handles empty Salesforce picklists, also maps urn:address to String

Posted by dh...@apache.org.
CAMEL-8395: handles empty Salesforce picklists, also maps urn:address to String


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

Branch: refs/heads/camel-2.13.x
Commit: e04638a970dfae06fef7816da0a1553c79322582
Parents: 2bad289
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Mon Feb 23 12:42:21 2015 -0800
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:42 2015 -0700

----------------------------------------------------------------------
 .../apache/camel/maven/CamelSalesforceMojo.java | 63 +++++++++++++-------
 .../src/main/resources/sobject-picklist.vm      | 18 +++---
 2 files changed, 52 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/e04638a9/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
index dab9c70..d517f24 100644
--- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
+++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java
@@ -21,6 +21,9 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -28,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
@@ -212,7 +216,7 @@ public class CamelSalesforceMojo extends AbstractMojo {
             final ObjectMapper mapper = new ObjectMapper();
 
             // call getGlobalObjects to get all SObjects
-            final Set<String> objectNames = new HashSet<String>();
+            final Set<String> objectNames = new TreeSet<String>();
             final SyncResponseCallback callback = new SyncResponseCallback();
             try {
                 getLog().info("Getting Salesforce Objects...");
@@ -307,23 +311,23 @@ public class CamelSalesforceMojo extends AbstractMojo {
             // for every accepted name, get SObject description
             final Set<SObjectDescription> descriptions = new HashSet<SObjectDescription>();
 
-            try {
-                getLog().info("Retrieving Object descriptions...");
-                for (String name : objectNames) {
-                    callback.reset();
-                    restClient.getDescription(name, callback);
-                    if (!callback.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
-                        throw new MojoExecutionException("Timeout waiting for getDescription for sObject " + name);
-                    }
-                    final SalesforceException ex = callback.getException();
-                    if (ex != null) {
-                        throw ex;
-                    }
-                    descriptions.add(mapper.readValue(callback.getResponse(), SObjectDescription.class));
+            getLog().info("Retrieving Object descriptions...");
+            for (String name : objectNames) {
+                try {
+                        callback.reset();
+                        restClient.getDescription(name, callback);
+                        if (!callback.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                            throw new MojoExecutionException("Timeout waiting for getDescription for sObject " + name);
+                        }
+                        final SalesforceException ex = callback.getException();
+                        if (ex != null) {
+                            throw ex;
+                        }
+                        descriptions.add(mapper.readValue(callback.getResponse(), SObjectDescription.class));
+                } catch (Exception e) {
+                    String msg = "Error getting SObject description for '" + name + "': " + e.getMessage();
+                    throw new MojoExecutionException(msg, e);
                 }
-            } catch (Exception e) {
-                String msg = "Error getting SObject description " + e.getMessage();
-                throw new MojoExecutionException(msg, e);
             }
 
             // create package directory
@@ -494,6 +498,7 @@ public class CamelSalesforceMojo extends AbstractMojo {
                 {"duration", "javax.xml.datatype.Duration"},
                 {"NOTATION", "javax.xml.namespace.QName"}
 */
+                {"address", "String"}
             };
             LOOKUP_MAP = new HashMap<String, String>();
             for (String[] entry : typeMap) {
@@ -556,9 +561,27 @@ public class CamelSalesforceMojo extends AbstractMojo {
             return false;
         }
 
-        public PickListValue getLastEntry(SObjectField field) {
-            final List<PickListValue> values = field.getPicklistValues();
-            return values.get(values.size() - 1);
+        public List<PickListValue> getUniqueValues(SObjectField field) {
+            if (field.getPicklistValues().isEmpty()) {
+                return field.getPicklistValues();
+            }
+            final List<PickListValue> result = new ArrayList<PickListValue>();
+            final Set<String> literals = new HashSet<String>();
+            for (PickListValue listValue : field.getPicklistValues()) {
+                final String value = listValue.getValue();
+                if (!literals.contains(value)) {
+                    literals.add(value);
+                    result.add(listValue);
+                }
+            }
+            literals.clear();
+            Collections.sort(result, new Comparator<PickListValue>() {
+                @Override
+                public int compare(PickListValue o1, PickListValue o2) {
+                    return o1.getValue().compareTo(o2.getValue());
+                }
+            });
+            return result;
         }
 
         public boolean isPicklist(SObjectField field) {

http://git-wip-us.apache.org/repos/asf/camel/blob/e04638a9/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/resources/sobject-picklist.vm
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/resources/sobject-picklist.vm b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/resources/sobject-picklist.vm
index 8e23fdb..bb3a22f 100644
--- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/resources/sobject-picklist.vm
+++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/resources/sobject-picklist.vm
@@ -29,18 +29,18 @@ import org.codehaus.jackson.annotate.JsonValue;
  * Salesforce Enumeration DTO for picklist $field.Name
  */
 public enum $enumName {
-## find the last entry
-#set ( $lastEntry = $utility.getLastEntry($field) )
+#set ( $values = $utility.getUniqueValues($field) )
 
-#foreach ( $entry in $field.PicklistValues)
-#set ( $value = $entry.Value )
-#if ( $entry == $lastEntry )
-#set ( $delim = ";" )
+## handle empty picklists
+#if ( $values.isEmpty() )
+    ; // empty picklist!
 #else
-#set ( $delim = ",")
-#end
+#foreach ( $entry in $values)
+#set ( $value = $entry.Value )
     // $value
-    $utility.getEnumConstant($value)("$value")$delim
+    $utility.getEnumConstant($value)("$value")#if ( $foreach.hasNext ),#else;#end
+
+#end
 #end
 
     final String value;


[6/9] camel git commit: CAMEL-8269: replaced 2.15 URISupport.appendParametersToURI with 2.14.x URISupport.createRemainingURI

Posted by dh...@apache.org.
CAMEL-8269: replaced 2.15 URISupport.appendParametersToURI with 2.14.x URISupport.createRemainingURI


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

Branch: refs/heads/camel-2.13.x
Commit: 53893e5d77884073663d224ee4a565b64d799f0f
Parents: c89e21f
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Mon Feb 23 13:56:52 2015 -0800
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:43 2015 -0700

----------------------------------------------------------------------
 .../component/salesforce/internal/client/DefaultRestClient.java  | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/53893e5d/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..9a9f91b 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
@@ -19,12 +19,14 @@ package org.apache.camel.component.salesforce.internal.client;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
+import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 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.dto.RestError;
 import org.apache.camel.component.salesforce.internal.PayloadFormat;
@@ -344,7 +346,7 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
         throws UnsupportedEncodingException, URISyntaxException {
 
         if (queryParams != null && !queryParams.isEmpty()) {
-            apexUrl = URISupport.appendParametersToURI(apexUrl, queryParams);
+            apexUrl = URISupport.createRemainingURI(new URI(apexUrl), queryParams).toString();
         }
 
         return instanceUrl + SERVICES_APEXREST + apexUrl;


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

Posted by dh...@apache.org.
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/6a541ef8
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/6a541ef8
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/6a541ef8

Branch: refs/heads/camel-2.13.x
Commit: 6a541ef8346b1875459e85a97f8f4bd022ef7d6a
Parents: a1bf920
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:59:45 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      |  68 +++++++---
 .../salesforce/internal/dto/RestChoices.java    |  40 ++++++
 .../salesforce/RestApiIntegrationTest.java      | 131 ++++++++++++++++++-
 9 files changed, 296 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/6a541ef8/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/6a541ef8/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/6a541ef8/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/6a541ef8/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/6a541ef8/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/6a541ef8/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/6a541ef8/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 9a9f91b..7269984 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
@@ -28,10 +28,13 @@ 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;
@@ -40,6 +43,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 {
@@ -63,6 +67,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
@@ -77,32 +82,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
@@ -357,16 +391,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/6a541ef8/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/6a541ef8/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


[4/9] camel git commit: CAMEL-8269: added support for APEX REST calls, also updated Salesforce API version to 33.0

Posted by dh...@apache.org.
CAMEL-8269: added support for APEX REST calls, also updated Salesforce API version to 33.0


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

Branch: refs/heads/camel-2.13.x
Commit: c89e21f4f8830edf7bd0d23d4e078bb0370ce1d7
Parents: e04638a
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Mon Feb 23 13:38:37 2015 -0800
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Fri Mar 20 13:59:42 2015 -0700

----------------------------------------------------------------------
 .../MerchandiseRestResource.apxc                |  44 +++++
 .../salesforce/SalesforceComponent.java         |  21 ++
 .../salesforce/SalesforceEndpointConfig.java    |  80 +++++++-
 .../salesforce/api/dto/AbstractSObjectBase.java |  22 +++
 .../salesforce/api/dto/ActionOverride.java      |  75 ++++++++
 .../api/dto/ActionOverrideTypeEnum.java         |  57 ++++++
 .../salesforce/api/dto/ChildRelationShip.java   |  20 ++
 .../salesforce/api/dto/FilteredLookupInfo.java  |  48 +++++
 .../component/salesforce/api/dto/InfoUrls.java  |  30 +++
 .../salesforce/api/dto/NamedLayoutInfo.java     |  39 ++++
 .../salesforce/api/dto/RecordTypeInfo.java      |   9 +
 .../salesforce/api/dto/RestResources.java       |  99 ++++++++++
 .../component/salesforce/api/dto/SObject.java   |   8 +
 .../salesforce/api/dto/SObjectDescription.java  |  20 ++
 .../salesforce/api/dto/SObjectField.java        |  63 ++++++
 .../salesforce/api/dto/SObjectUrls.java         |  81 ++++++++
 .../salesforce/internal/OperationName.java      |   1 +
 .../internal/client/DefaultRestClient.java      |  60 +++++-
 .../salesforce/internal/client/RestClient.java  |  13 ++
 .../internal/dto/NotifyForFieldsEnum.java       |  10 +-
 .../internal/dto/NotifyForOperationsEnum.java   |  10 +-
 .../salesforce/internal/dto/PushTopic.java      |  49 ++++-
 .../processor/AbstractRestProcessor.java        | 190 +++++++++++++++----
 .../internal/processor/JsonRestProcessor.java   |  10 +-
 .../internal/processor/XmlRestProcessor.java    |  17 +-
 .../internal/streaming/PushTopicHelper.java     |  74 ++++++--
 .../salesforce/RestApiIntegrationTest.java      | 113 +++++++++++
 .../salesforce/StreamingApiIntegrationTest.java |   4 +-
 .../salesforce/dto/generated/Document.java      |  26 ++-
 .../src/test/resources/log4j.properties         |   1 +
 30 files changed, 1194 insertions(+), 100 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/MerchandiseRestResource.apxc
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/MerchandiseRestResource.apxc b/components/camel-salesforce/camel-salesforce-component/MerchandiseRestResource.apxc
new file mode 100644
index 0000000..91ce5ce
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/MerchandiseRestResource.apxc
@@ -0,0 +1,44 @@
+@RestResource(urlMapping='/Merchandise/*')
+global with sharing class MerchandiseRestResource {
+
+    @HttpGet
+    global static Merchandise__c doGet() {
+        RestRequest req = RestContext.request;
+        String merchandiseId = null;
+        if (!req.requestURI.endsWith('/')) {
+            merchandiseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
+        } else if (req.params.get('id') != null) {
+            merchandiseId = req.params.get('id');
+        }
+        if (merchandiseId != null) {
+	        Merchandise__c merchandise = [SELECT Id, Name, Description__c, Price__c, Total_Inventory__c FROM Merchandise__c WHERE Id = :merchandiseId];
+            return merchandise;
+        } else {
+            throw new InvalidParamException('Missing merchandise id in URL and query params');
+        }
+    }
+  
+	@HttpPatch
+	global static Merchandise__c doPatch(Merchandise__c merchandise) {
+        // lookup merchandise
+        Merchandise__c current = [SELECT Id, Name, Description__c, Price__c, Total_Inventory__c FROM Merchandise__c WHERE Id = :merchandise.Id];
+        if (current == null) {
+            throw new InvalidParamException('Missing merchandise for id ' + merchandise.Id);
+        }
+        if (merchandise.Description__c != null) {
+            current.Description__c = merchandise.Description__c;
+        }
+        if (merchandise.Price__c != null) {
+            current.Price__c = merchandise.Price__c;
+        }
+        if (merchandise.Total_Inventory__c != null) {
+            current.Total_Inventory__c = merchandise.Total_Inventory__c;
+        }
+
+        update current;
+        return current;
+    }
+
+	// Invalid Merchandise Id exception
+    public class InvalidParamException extends Exception {}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java
index 8a38bec..c12edc2 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java
@@ -59,6 +59,7 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin
     private static final int CONNECTION_TIMEOUT = 60000;
     private static final int RESPONSE_TIMEOUT = 60000;
     private static final Pattern SOBJECT_NAME_PATTERN = Pattern.compile("^.*[\\?&]sObjectName=([^&,]+).*$");
+    private static final String APEX_CALL_PREFIX = OperationName.APEX_CALL.value() + "/";
 
     private SalesforceLoginConfig loginConfig;
     private SalesforceEndpointConfig config;
@@ -86,8 +87,14 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin
         // get Operation from remaining URI
         OperationName operationName = null;
         String topicName = null;
+        String apexUrl = null;
         try {
             LOG.debug("Creating endpoint for: {}", remaining);
+            if (remaining.startsWith(APEX_CALL_PREFIX)) {
+                // extract APEX URL
+                apexUrl = remaining.substring(APEX_CALL_PREFIX.length());
+                remaining = OperationName.APEX_CALL.value();
+            }
             operationName = OperationName.fromValue(remaining);
         } catch (IllegalArgumentException ex) {
             // if its not an operation name, treat is as topic name for consumer endpoints
@@ -107,12 +114,26 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin
         final SalesforceEndpointConfig copy = config.copy();
         setProperties(copy, parameters);
 
+        // set apexUrl in endpoint config
+        if (apexUrl != null) {
+            copy.setApexUrl(apexUrl);
+        }
+
         final SalesforceEndpoint endpoint = new SalesforceEndpoint(uri, this, copy,
                 operationName, topicName);
 
         // map remaining parameters to endpoint (specifically, synchronous)
         setProperties(endpoint, parameters);
 
+        // if operation is APEX call, map remaining parameters to query params
+        if (operationName == OperationName.APEX_CALL && !parameters.isEmpty()) {
+            Map<String, Object> queryParams = new HashMap<String, Object>(parameters);
+            parameters.clear();
+
+            queryParams.putAll(copy.getApexQueryParams());
+            copy.setApexQueryParams(queryParams);
+        }
+
         return endpoint;
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/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 353ed30..9af05b9 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
@@ -36,7 +36,7 @@ import org.eclipse.jetty.client.HttpClient;
 public class SalesforceEndpointConfig implements Cloneable {
 
     // default API version
-    public static final String DEFAULT_VERSION = "27.0";
+    public static final String DEFAULT_VERSION = "33.0";
 
     // general parameter
     public static final String API_VERSION = "apiVersion";
@@ -52,6 +52,11 @@ public class SalesforceEndpointConfig implements Cloneable {
     public static final String SOBJECT_CLASS = "sObjectClass";
     public static final String SOBJECT_QUERY = "sObjectQuery";
     public static final String SOBJECT_SEARCH = "sObjectSearch";
+    public static final String APEX_METHOD = "apexMethod";
+    public static final String APEX_URL = "apexUrl";
+
+    // prefix for parameters in headers
+    public static final String APEX_QUERY_PARAM_PREFIX = "apexQueryParam.";
 
     // parameters for Bulk API
     public static final String CONTENT_TYPE = "contentType";
@@ -84,6 +89,12 @@ public class SalesforceEndpointConfig implements Cloneable {
     private String sObjectQuery;
     @UriParam
     private String sObjectSearch;
+    @UriParam
+    private String apexMethod;
+    @UriParam
+    private String apexUrl;
+    @UriParam
+    private Map<String, Object> apexQueryParams;
 
     // Bulk API properties
     @UriParam
@@ -102,6 +113,14 @@ public class SalesforceEndpointConfig implements Cloneable {
     private NotifyForFieldsEnum notifyForFields;
     @UriParam
     private NotifyForOperationsEnum notifyForOperations;
+    @UriParam
+    private Boolean notifyForOperationCreate;
+    @UriParam
+    private Boolean notifyForOperationUpdate;
+    @UriParam
+    private Boolean notifyForOperationDelete;
+    @UriParam
+    private Boolean notifyForOperationUndelete;
 
     // Jetty HttpClient, set using reference
     @UriParam
@@ -205,6 +224,30 @@ public class SalesforceEndpointConfig implements Cloneable {
         this.sObjectSearch = sObjectSearch;
     }
 
+    public String getApexMethod() {
+        return apexMethod;
+    }
+
+    public void setApexMethod(String apexMethod) {
+        this.apexMethod = apexMethod;
+    }
+
+    public String getApexUrl() {
+        return apexUrl;
+    }
+
+    public void setApexUrl(String apexUrl) {
+        this.apexUrl = apexUrl;
+    }
+
+    public Map<String, Object> getApexQueryParams() {
+        return apexQueryParams == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(apexQueryParams);
+    }
+
+    public void setApexQueryParams(Map<String, Object> apexQueryParams) {
+        this.apexQueryParams = apexQueryParams;
+    }
+
     public ContentType getContentType() {
         return contentType;
     }
@@ -261,6 +304,38 @@ public class SalesforceEndpointConfig implements Cloneable {
         this.notifyForOperations = notifyForOperations;
     }
 
+    public Boolean getNotifyForOperationCreate() {
+        return notifyForOperationCreate;
+    }
+
+    public void setNotifyForOperationCreate(Boolean notifyForOperationCreate) {
+        this.notifyForOperationCreate = notifyForOperationCreate;
+    }
+
+    public Boolean getNotifyForOperationUpdate() {
+        return notifyForOperationUpdate;
+    }
+
+    public void setNotifyForOperationUpdate(Boolean notifyForOperationUpdate) {
+        this.notifyForOperationUpdate = notifyForOperationUpdate;
+    }
+
+    public Boolean getNotifyForOperationDelete() {
+        return notifyForOperationDelete;
+    }
+
+    public void setNotifyForOperationDelete(Boolean notifyForOperationDelete) {
+        this.notifyForOperationDelete = notifyForOperationDelete;
+    }
+
+    public Boolean getNotifyForOperationUndelete() {
+        return notifyForOperationUndelete;
+    }
+
+    public void setNotifyForOperationUndelete(Boolean notifyForOperationUndelete) {
+        this.notifyForOperationUndelete = notifyForOperationUndelete;
+    }
+
     public void setHttpClient(HttpClient httpClient) {
         this.httpClient = httpClient;
     }
@@ -284,6 +359,9 @@ public class SalesforceEndpointConfig implements Cloneable {
         valueMap.put(SOBJECT_CLASS, sObjectClass);
         valueMap.put(SOBJECT_QUERY, sObjectQuery);
         valueMap.put(SOBJECT_SEARCH, sObjectSearch);
+        valueMap.put(APEX_METHOD, apexMethod);
+        valueMap.put(APEX_URL, apexUrl);
+        // apexQueryParams are handled explicitly in AbstractRestProcessor
 
         // add bulk API properties
         if (contentType != null) {

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/AbstractSObjectBase.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/AbstractSObjectBase.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/AbstractSObjectBase.java
index fa20827..7337a52 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/AbstractSObjectBase.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/AbstractSObjectBase.java
@@ -35,6 +35,8 @@ public class AbstractSObjectBase extends AbstractDTOBase {
     private String LastModifiedById;
     private DateTime SystemModstamp;
     private String LastActivityDate;
+    private DateTime LastViewedDate;
+    private DateTime LastReferencedDate;
 
     /**
      * Utility method to clear all system {@link AbstractSObjectBase} fields.
@@ -161,5 +163,25 @@ public class AbstractSObjectBase extends AbstractDTOBase {
     public void setLastActivityDate(String lastActivityDate) {
         this.LastActivityDate = lastActivityDate;
     }
+
+    @JsonProperty("LastViewedDate")
+    public DateTime getLastViewedDate() {
+        return LastViewedDate;
+    }
+
+    @JsonProperty("LastViewedDate")
+    public void setLastViewedDate(DateTime lastViewedDate) {
+        LastViewedDate = lastViewedDate;
+    }
+
+    @JsonProperty("LastReferencedDate")
+    public DateTime getLastReferencedDate() {
+        return LastReferencedDate;
+    }
+
+    @JsonProperty("LastReferencedDate")
+    public void setLastReferencedDate(DateTime lastReferencedDate) {
+        LastReferencedDate = lastReferencedDate;
+    }
 }
 //CHECKSTYLE:ON

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverride.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverride.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverride.java
new file mode 100644
index 0000000..eff26df
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverride.java
@@ -0,0 +1,75 @@
+/**
+ * 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.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+
+import org.apache.camel.component.salesforce.api.PicklistEnumConverter;
+
+public class ActionOverride extends AbstractDTOBase {
+
+    private String actionName;
+
+    private String comment;
+
+    private String content;
+
+    private Boolean skipRecordTypeSelect;
+
+    @XStreamConverter(PicklistEnumConverter.class)
+    private ActionOverrideTypeEnum type;
+
+    public String getActionName() {
+        return actionName;
+    }
+
+    public void setActionName(String actionName) {
+        this.actionName = actionName;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Boolean getSkipRecordTypeSelect() {
+        return skipRecordTypeSelect;
+    }
+
+    public void setSkipRecordTypeSelect(Boolean skipRecordTypeSelect) {
+        this.skipRecordTypeSelect = skipRecordTypeSelect;
+    }
+
+    public ActionOverrideTypeEnum getType() {
+        return type;
+    }
+
+    public void setType(ActionOverrideTypeEnum type) {
+        this.type = type;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverrideTypeEnum.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverrideTypeEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverrideTypeEnum.java
new file mode 100644
index 0000000..967b0a8
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ActionOverrideTypeEnum.java
@@ -0,0 +1,57 @@
+/**
+ * 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.dto;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonValue;
+import org.codehaus.jackson.map.annotate.JsonDeserialize;
+
+@JsonDeserialize
+public enum ActionOverrideTypeEnum {
+
+    // The override uses a custom override provided by an installed package.
+    // If there isn’t one available, the standard Salesforce behavior is used.
+    DEFAULT("default"),
+    // The override uses behavior from an s-control.
+    SCONTROL("scontrol"),
+    // The override uses regular Salesforce behavior.
+    STANDARD("standard"),
+    // The override uses behavior from a Visualforce page.
+    VISUALFORCE("visualforce");
+
+    final String value;
+
+    private ActionOverrideTypeEnum(String value) {
+        this.value = value;
+    }
+
+    @JsonValue
+    public String value() {
+        return this.value;
+    }
+
+    @JsonCreator
+    public static ActionOverrideTypeEnum fromValue(String value) {
+        for (ActionOverrideTypeEnum e : ActionOverrideTypeEnum.values()) {
+            if (e.value.equals(value)) {
+                return e;
+            }
+        }
+        throw new IllegalArgumentException(value);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ChildRelationShip.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ChildRelationShip.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ChildRelationShip.java
index 9b89923..7a674e7 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ChildRelationShip.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/ChildRelationShip.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.component.salesforce.api.dto;
 
+import java.util.List;
+
 public class ChildRelationShip extends AbstractDTOBase {
 
     private String field;
@@ -24,6 +26,8 @@ public class ChildRelationShip extends AbstractDTOBase {
     private Boolean cascadeDelete;
     private Boolean restrictedDelete;
     private String childSObject;
+    private String junctionIdListName;
+    private List<String> junctionReferenceTo;
 
     public String getField() {
         return field;
@@ -72,4 +76,20 @@ public class ChildRelationShip extends AbstractDTOBase {
     public void setChildSObject(String childSObject) {
         this.childSObject = childSObject;
     }
+
+    public String getJunctionIdListName() {
+        return junctionIdListName;
+    }
+
+    public void setJunctionIdListName(String junctionIdListName) {
+        this.junctionIdListName = junctionIdListName;
+    }
+
+    public List<String> getJunctionReferenceTo() {
+        return junctionReferenceTo;
+    }
+
+    public void setJunctionReferenceTo(List<String> junctionReferenceTo) {
+        this.junctionReferenceTo = junctionReferenceTo;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/FilteredLookupInfo.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/FilteredLookupInfo.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/FilteredLookupInfo.java
new file mode 100644
index 0000000..182efd6
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/FilteredLookupInfo.java
@@ -0,0 +1,48 @@
+/**
+ * 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.dto;
+
+public class FilteredLookupInfo extends AbstractDTOBase {
+
+    private String controllingFields;
+    private Boolean dependent;
+    private Boolean optionalFilter;
+
+    public String getControllingFields() {
+        return controllingFields;
+    }
+
+    public void setControllingFields(String controllingFields) {
+        this.controllingFields = controllingFields;
+    }
+
+    public Boolean getDependent() {
+        return dependent;
+    }
+
+    public void setDependent(Boolean dependent) {
+        this.dependent = dependent;
+    }
+
+    public Boolean getOptionalFilter() {
+        return optionalFilter;
+    }
+
+    public void setOptionalFilter(Boolean optionalFilter) {
+        this.optionalFilter = optionalFilter;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/InfoUrls.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/InfoUrls.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/InfoUrls.java
new file mode 100644
index 0000000..51da519
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/InfoUrls.java
@@ -0,0 +1,30 @@
+/**
+ * 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.dto;
+
+public class InfoUrls extends AbstractDTOBase {
+
+    private String layout;
+
+    public String getLayout() {
+        return layout;
+    }
+
+    public void setLayout(String layout) {
+        this.layout = layout;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/NamedLayoutInfo.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/NamedLayoutInfo.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/NamedLayoutInfo.java
new file mode 100644
index 0000000..7dc1d00
--- /dev/null
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/NamedLayoutInfo.java
@@ -0,0 +1,39 @@
+/**
+ * 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.dto;
+
+public class NamedLayoutInfo extends AbstractDTOBase {
+
+    private String name;
+    private InfoUrls urls;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public InfoUrls getUrls() {
+        return urls;
+    }
+
+    public void setUrls(InfoUrls urls) {
+        this.urls = urls;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RecordTypeInfo.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RecordTypeInfo.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RecordTypeInfo.java
index 392ddb4..92eda59 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RecordTypeInfo.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RecordTypeInfo.java
@@ -22,6 +22,7 @@ public class RecordTypeInfo extends AbstractDTOBase {
     private Boolean available;
     private String recordTypeId;
     private Boolean defaultRecordTypeMapping;
+    private InfoUrls urls;
 
     public String getName() {
         return name;
@@ -54,4 +55,12 @@ public class RecordTypeInfo extends AbstractDTOBase {
     public void setDefaultRecordTypeMapping(Boolean defaultRecordTypeMapping) {
         this.defaultRecordTypeMapping = defaultRecordTypeMapping;
     }
+
+    public InfoUrls getUrls() {
+        return urls;
+    }
+
+    public void setUrls(InfoUrls urls) {
+        this.urls = urls;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RestResources.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RestResources.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RestResources.java
index ce108b1..b37aa13 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RestResources.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/RestResources.java
@@ -34,6 +34,17 @@ public class RestResources extends AbstractDTOBase {
     private String tooling;
     private String licensing;
     private String analytics;
+    private String limits;
+    private String theme;
+    private String queryAll;
+    private String knowledgeManagement;
+    private String process;
+    private String flexiPage;
+    private String quickActions;
+    private String appMenu;
+    private String compactLayouts;
+    private String actions;
+    private String tabs;
 
     public String getSobjects() {
         return sobjects;
@@ -114,4 +125,92 @@ public class RestResources extends AbstractDTOBase {
     public void setAnalytics(String analytics) {
         this.analytics = analytics;
     }
+
+    public String getLimits() {
+        return limits;
+    }
+
+    public void setLimits(String limits) {
+        this.limits = limits;
+    }
+
+    public String getTheme() {
+        return theme;
+    }
+
+    public void setTheme(String theme) {
+        this.theme = theme;
+    }
+
+    public String getQueryAll() {
+        return queryAll;
+    }
+
+    public void setQueryAll(String queryAll) {
+        this.queryAll = queryAll;
+    }
+
+    public String getKnowledgeManagement() {
+        return knowledgeManagement;
+    }
+
+    public void setKnowledgeManagement(String knowledgeManagement) {
+        this.knowledgeManagement = knowledgeManagement;
+    }
+
+    public String getProcess() {
+        return process;
+    }
+
+    public void setProcess(String process) {
+        this.process = process;
+    }
+
+    public String getFlexiPage() {
+        return flexiPage;
+    }
+
+    public void setFlexiPage(String flexiPage) {
+        this.flexiPage = flexiPage;
+    }
+
+    public String getQuickActions() {
+        return quickActions;
+    }
+
+    public void setQuickActions(String quickActions) {
+        this.quickActions = quickActions;
+    }
+
+    public String getAppMenu() {
+        return appMenu;
+    }
+
+    public void setAppMenu(String appMenu) {
+        this.appMenu = appMenu;
+    }
+
+    public String getCompactLayouts() {
+        return compactLayouts;
+    }
+
+    public void setCompactLayouts(String compactLayouts) {
+        this.compactLayouts = compactLayouts;
+    }
+
+    public String getActions() {
+        return actions;
+    }
+
+    public void setActions(String actions) {
+        this.actions = actions;
+    }
+
+    public String getTabs() {
+        return tabs;
+    }
+
+    public void setTabs(String tabs) {
+        this.tabs = tabs;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObject.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObject.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObject.java
index 97fa39d..d390595 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObject.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObject.java
@@ -42,6 +42,7 @@ public class SObject extends AbstractDTOBase {
     private String searchLayoutable;
     private Boolean undeletable;
     private Boolean triggerable;
+    private Boolean compactLayoutable;
 
     public String getName() {
         return name;
@@ -235,4 +236,11 @@ public class SObject extends AbstractDTOBase {
         this.triggerable = triggerable;
     }
 
+    public Boolean getCompactLayoutable() {
+        return compactLayoutable;
+    }
+
+    public void setCompactLayoutable(Boolean compactLayoutable) {
+        this.compactLayoutable = compactLayoutable;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectDescription.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectDescription.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectDescription.java
index 9096c1e..4fb5a29 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectDescription.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectDescription.java
@@ -23,12 +23,24 @@ import com.thoughtworks.xstream.annotations.XStreamImplicit;
 public class SObjectDescription extends SObject {
 
     @XStreamImplicit
+    private List<ActionOverride> actionOverrides;
+    @XStreamImplicit
     private List<SObjectField> fields;
     private SObjectDescriptionUrls urls;
     @XStreamImplicit
     private List<ChildRelationShip> childRelationships;
     @XStreamImplicit
     private List<RecordTypeInfo> recordTypeInfos;
+    @XStreamImplicit
+    private List<NamedLayoutInfo> namedLayoutInfos;
+
+    public List<ActionOverride> getActionOverrides() {
+        return actionOverrides;
+    }
+
+    public void setActionOverrides(List<ActionOverride> actionOverrides) {
+        this.actionOverrides = actionOverrides;
+    }
 
     public List<SObjectField> getFields() {
         return fields;
@@ -61,4 +73,12 @@ public class SObjectDescription extends SObject {
     public void setRecordTypeInfos(List<RecordTypeInfo> recordTypeInfos) {
         this.recordTypeInfos = recordTypeInfos;
     }
+
+    public List<NamedLayoutInfo> getNamedLayoutInfos() {
+        return namedLayoutInfos;
+    }
+
+    public void setNamedLayoutInfos(List<NamedLayoutInfo> namedLayoutInfos) {
+        this.namedLayoutInfos = namedLayoutInfos;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectField.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectField.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectField.java
index 8664c19..312e6c3 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectField.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectField.java
@@ -67,6 +67,13 @@ public class SObjectField extends AbstractDTOBase {
     private Boolean groupable;
     private Boolean permissionable;
     private Boolean displayLocationInDecimal;
+    private String extraTypeInfo;
+    private FilteredLookupInfo filteredLookupInfo;
+    private Boolean highScaleNumber;
+    private String mask;
+    private String maskType;
+    private Boolean queryByDistance;
+    private String referenceTargetField;
 
     public Integer getLength() {
         return length;
@@ -411,4 +418,60 @@ public class SObjectField extends AbstractDTOBase {
     public void setDisplayLocationInDecimal(Boolean displayLocationInDecimal) {
         this.displayLocationInDecimal = displayLocationInDecimal;
     }
+
+    public String getExtraTypeInfo() {
+        return extraTypeInfo;
+    }
+
+    public void setExtraTypeInfo(String extraTypeInfo) {
+        this.extraTypeInfo = extraTypeInfo;
+    }
+
+    public FilteredLookupInfo getFilteredLookupInfo() {
+        return filteredLookupInfo;
+    }
+
+    public void setFilteredLookupInfo(FilteredLookupInfo filteredLookupInfo) {
+        this.filteredLookupInfo = filteredLookupInfo;
+    }
+
+    public Boolean getHighScaleNumber() {
+        return highScaleNumber;
+    }
+
+    public void setHighScaleNumber(Boolean highScaleNumber) {
+        this.highScaleNumber = highScaleNumber;
+    }
+
+    public String getMask() {
+        return mask;
+    }
+
+    public void setMask(String mask) {
+        this.mask = mask;
+    }
+
+    public String getMaskType() {
+        return maskType;
+    }
+
+    public void setMaskType(String maskType) {
+        this.maskType = maskType;
+    }
+
+    public Boolean getQueryByDistance() {
+        return queryByDistance;
+    }
+
+    public void setQueryByDistance(Boolean queryByDistance) {
+        this.queryByDistance = queryByDistance;
+    }
+
+    public String getReferenceTargetField() {
+        return referenceTargetField;
+    }
+
+    public void setReferenceTargetField(String referenceTargetField) {
+        this.referenceTargetField = referenceTargetField;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectUrls.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectUrls.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectUrls.java
index b1abd74..85115d1 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectUrls.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/api/dto/SObjectUrls.java
@@ -22,6 +22,15 @@ public class SObjectUrls extends AbstractDTOBase {
     private String describe;
     private String rowTemplate;
     private String passwordUtilities;
+    private String approvalLayouts;
+    private String quickActions;
+    private String caseArticleSuggestions;
+    private String listviews;
+    private String layouts;
+    private String namedLayouts;
+    private String compactLayouts;
+    private String caseRowArticleSuggestions;
+    private String push;
 
     public String getSobject() {
         return sobject;
@@ -54,4 +63,76 @@ public class SObjectUrls extends AbstractDTOBase {
     public void setPasswordUtilities(String passwordUtilities) {
         this.passwordUtilities = passwordUtilities;
     }
+
+    public String getApprovalLayouts() {
+        return approvalLayouts;
+    }
+
+    public void setApprovalLayouts(String approvalLayouts) {
+        this.approvalLayouts = approvalLayouts;
+    }
+
+    public String getQuickActions() {
+        return quickActions;
+    }
+
+    public void setQuickActions(String quickActions) {
+        this.quickActions = quickActions;
+    }
+
+    public String getCaseArticleSuggestions() {
+        return caseArticleSuggestions;
+    }
+
+    public void setCaseArticleSuggestions(String caseArticleSuggestions) {
+        this.caseArticleSuggestions = caseArticleSuggestions;
+    }
+
+    public String getListviews() {
+        return listviews;
+    }
+
+    public void setListviews(String listviews) {
+        this.listviews = listviews;
+    }
+
+    public String getLayouts() {
+        return layouts;
+    }
+
+    public void setLayouts(String layouts) {
+        this.layouts = layouts;
+    }
+
+    public String getNamedLayouts() {
+        return namedLayouts;
+    }
+
+    public void setNamedLayouts(String namedLayouts) {
+        this.namedLayouts = namedLayouts;
+    }
+
+    public String getCompactLayouts() {
+        return compactLayouts;
+    }
+
+    public void setCompactLayouts(String compactLayouts) {
+        this.compactLayouts = compactLayouts;
+    }
+
+    public String getCaseRowArticleSuggestions() {
+        return caseRowArticleSuggestions;
+    }
+
+    public void setCaseRowArticleSuggestions(String caseRowArticleSuggestions) {
+        this.caseRowArticleSuggestions = caseRowArticleSuggestions;
+    }
+
+    public String getPush() {
+        return push;
+    }
+
+    public void setPush(String push) {
+        this.push = push;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
index 50141f1..a5b7893 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/OperationName.java
@@ -35,6 +35,7 @@ public enum OperationName {
     QUERY("query"),
     QUERY_MORE("queryMore"),
     SEARCH("search"),
+    APEX_CALL("apexCall"),
 
     // bulk API
     CREATE_JOB("createJob"),

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/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 1edfed7..0b06289 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
@@ -19,8 +19,10 @@ package org.apache.camel.component.salesforce.internal.client;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.util.List;
+import java.util.Map;
 
 import com.thoughtworks.xstream.XStream;
 import org.apache.camel.component.salesforce.api.SalesforceException;
@@ -28,6 +30,7 @@ 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.apache.camel.util.URISupport;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.type.TypeReference;
 import org.eclipse.jetty.client.ContentExchange;
@@ -42,6 +45,7 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
     private static final String SERVICES_DATA = "/services/data/";
     private static final String TOKEN_HEADER = "Authorization";
     private static final String TOKEN_PREFIX = "Bearer ";
+    private static final String SERVICES_APEXREST = "/services/apexrest/";
 
     protected PayloadFormat format;
     private ObjectMapper objectMapper;
@@ -267,9 +271,7 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
     public void query(String soqlQuery, ResponseCallback callback) {
         try {
 
-            String encodedQuery = URLEncoder.encode(soqlQuery, StringUtil.__UTF8_CHARSET.toString());
-            // URLEncoder likes to use '+' for spaces
-            encodedQuery = encodedQuery.replace("+", "%20");
+            String encodedQuery = urlEncode(soqlQuery);
             final ContentExchange get = getContentExchange(HttpMethods.GET, versionUrl() + "query/?q=" + encodedQuery);
 
             // requires authorization token
@@ -297,9 +299,7 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
     public void search(String soslQuery, ResponseCallback callback) {
         try {
 
-            String encodedQuery = URLEncoder.encode(soslQuery, StringUtil.__UTF8_CHARSET.toString());
-            // URLEncoder likes to use '+' for spaces
-            encodedQuery = encodedQuery.replace("+", "%20");
+            String encodedQuery = urlEncode(soslQuery);
             final ContentExchange get = getContentExchange(HttpMethods.GET, versionUrl() + "search/?q=" + encodedQuery);
 
             // requires authorization token
@@ -313,6 +313,43 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
         }
     }
 
+    @Override
+    public void apexCall(String httpMethod, String apexUrl,
+                         Map<String, Object> queryParams, InputStream requestDto, ResponseCallback callback) {
+        // create APEX call exchange
+        final ContentExchange exchange;
+        try {
+            exchange = getContentExchange(httpMethod, apexCallUrl(apexUrl, queryParams));
+            // set request SObject and content type
+            if (requestDto != null) {
+                exchange.setRequestContentSource(requestDto);
+                exchange.setRequestContentType(
+                    PayloadFormat.JSON.equals(format) ? APPLICATION_JSON_UTF8 : APPLICATION_XML_UTF8);
+            }
+
+            // requires authorization token
+            setAccessToken(exchange);
+
+            doHttpRequest(exchange, new DelegatingClientCallback(callback));
+        } catch (UnsupportedEncodingException e) {
+            String msg = "Unexpected error: " + e.getMessage();
+            callback.onResponse(null, new SalesforceException(msg, e));
+        } catch (URISyntaxException e) {
+            String msg = "Unexpected error: " + e.getMessage();
+            callback.onResponse(null, new SalesforceException(msg, e));
+        }
+    }
+
+    private String apexCallUrl(String apexUrl, Map<String, Object> queryParams)
+        throws UnsupportedEncodingException, URISyntaxException {
+
+        if (queryParams != null && !queryParams.isEmpty()) {
+            apexUrl = URISupport.appendParametersToURI(apexUrl, queryParams);
+        }
+
+        return instanceUrl + SERVICES_APEXREST + apexUrl;
+    }
+
     private String servicesDataUrl() {
         return instanceUrl + SERVICES_DATA;
     }
@@ -336,9 +373,7 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
             throw new IllegalArgumentException("External field name and value cannot be NULL");
         }
         try {
-            String encodedValue = URLEncoder.encode(fieldValue, StringUtil.__UTF8_CHARSET.toString());
-            // URLEncoder likes to use '+' for spaces
-            encodedValue = encodedValue.replace("+", "%20");
+            String encodedValue = urlEncode(fieldValue);
             return sobjectsUrl(sObjectName + "/" + fieldName + "/" + encodedValue);
         } catch (UnsupportedEncodingException e) {
             String msg = "Unexpected error: " + e.getMessage();
@@ -350,6 +385,13 @@ public class DefaultRestClient extends AbstractClientBase implements RestClient
         httpExchange.setRequestHeader(TOKEN_HEADER, TOKEN_PREFIX + accessToken);
     }
 
+    private String urlEncode(String query) throws UnsupportedEncodingException {
+        String encodedQuery = URLEncoder.encode(query, StringUtil.__UTF8_CHARSET.toString());
+        // URLEncoder likes to use '+' for spaces
+        encodedQuery = encodedQuery.replace("+", "%20");
+        return encodedQuery;
+    }
+
     private static class DelegatingClientCallback implements ClientResponseCallback {
         private final ResponseCallback callback;
 

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
index a28f8b8..5747976 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/client/RestClient.java
@@ -17,6 +17,7 @@
 package org.apache.camel.component.salesforce.internal.client;
 
 import java.io.InputStream;
+import java.util.Map;
 
 import org.apache.camel.component.salesforce.api.SalesforceException;
 
@@ -173,4 +174,16 @@ public interface RestClient {
      */
     void search(String soslQuery, ResponseCallback callback);
 
+    /**
+     * Executes a user defined APEX REST API call.
+     *
+     * @param httpMethod    HTTP method to execute.
+     * @param apexUrl       APEX api url.
+     * @param queryParams   optional query parameters for GET methods, may be empty.
+     * @param requestDto    optional input DTO for POST, etc. may be null.
+     * @param callback      {@link ResponseCallback} to handle response or exception
+     */
+    void apexCall(String httpMethod, String apexUrl, Map<String, Object> queryParams, InputStream requestDto,
+                  ResponseCallback callback);
+
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForFieldsEnum.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForFieldsEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForFieldsEnum.java
index 970b9aa..0385be3 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForFieldsEnum.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForFieldsEnum.java
@@ -24,10 +24,14 @@ import org.codehaus.jackson.annotate.JsonValue;
  */
 public enum NotifyForFieldsEnum {
 
-    SELECT("Select"),
-    WHERE("Where"),
+    // All
+    ALL("All"),
+    // Referenced
     REFERENCED("Referenced"),
-    ALL("All");
+    // Select
+    SELECT("Select"),
+    // Where
+    WHERE("Where");
 
     final String value;
 

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForOperationsEnum.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForOperationsEnum.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForOperationsEnum.java
index f75839c..6ac2b87 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForOperationsEnum.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/NotifyForOperationsEnum.java
@@ -24,10 +24,14 @@ import org.codehaus.jackson.annotate.JsonValue;
  */
 public enum NotifyForOperationsEnum {
 
-    CREATE("Create"),
-    UPDATE("Update"),
+    // All
     ALL("All"),
-    EXTENDED("Extended");
+    // Create
+    CREATE("Create"),
+    // Extended
+    EXTENDED("Extended"),
+    // Update
+    UPDATE("Update");
 
     final String value;
 

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/PushTopic.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/PushTopic.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/PushTopic.java
index 17dbd98..4274dc1 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/PushTopic.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/dto/PushTopic.java
@@ -47,6 +47,14 @@ public class PushTopic extends AbstractSObjectBase {
 
     private String Description;
 
+    private Boolean NotifyForOperationCreate;
+
+    private Boolean NotifyForOperationUpdate;
+
+    private Boolean NotifyForOperationDelete;
+
+    private Boolean NotifyForOperationUndelete;
+
     @JsonProperty("Query")
     public String getQuery() {
         return this.Query;
@@ -106,6 +114,45 @@ public class PushTopic extends AbstractSObjectBase {
     public void setDescription(String description) {
         this.Description = description;
     }
+
+    @JsonProperty("NotifyForOperationCreate")
+    public Boolean getNotifyForOperationCreate() {
+        return this.NotifyForOperationCreate;
+    }
+
+    @JsonProperty("NotifyForOperationCreate")
+    public void setNotifyForOperationCreate(Boolean notifyForOperationCreate) {
+        this.NotifyForOperationCreate = notifyForOperationCreate;
+    }
+
+    @JsonProperty("NotifyForOperationUpdate")
+    public Boolean getNotifyForOperationUpdate() {
+        return this.NotifyForOperationUpdate;
+    }
+
+    @JsonProperty("NotifyForOperationUpdate")
+    public void setNotifyForOperationUpdate(Boolean notifyForOperationUpdate) {
+        this.NotifyForOperationUpdate = notifyForOperationUpdate;
+    }
+
+    @JsonProperty("NotifyForOperationDelete")
+    public Boolean getNotifyForOperationDelete() {
+        return this.NotifyForOperationDelete;
+    }
+
+    @JsonProperty("NotifyForOperationDelete")
+    public void setNotifyForOperationDelete(Boolean notifyForOperationDelete) {
+        this.NotifyForOperationDelete = notifyForOperationDelete;
+    }
+
+    @JsonProperty("NotifyForOperationUndelete")
+    public Boolean getNotifyForOperationUndelete() {
+        return this.NotifyForOperationUndelete;
+    }
+
+    @JsonProperty("NotifyForOperationUndelete")
+    public void setNotifyForOperationUndelete(Boolean notifyForOperationUndelete) {
+        this.NotifyForOperationUndelete = notifyForOperationUndelete;
+    }
 }
 //CHECKSTYLE:ON
-

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/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 661a896..834ea51 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
@@ -17,9 +17,14 @@
 package org.apache.camel.component.salesforce.internal.processor;
 
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.net.URLEncoder;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
@@ -30,12 +35,26 @@ import org.apache.camel.component.salesforce.internal.PayloadFormat;
 import org.apache.camel.component.salesforce.internal.client.DefaultRestClient;
 import org.apache.camel.component.salesforce.internal.client.RestClient;
 import org.apache.camel.util.ServiceHelper;
-
-import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.*;
+import org.eclipse.jetty.http.HttpMethods;
+
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.APEX_METHOD;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.APEX_QUERY_PARAM_PREFIX;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.APEX_URL;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.API_VERSION;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_BLOB_FIELD_NAME;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_CLASS;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_EXT_ID_NAME;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_EXT_ID_VALUE;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_FIELDS;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_ID;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_NAME;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_QUERY;
+import static org.apache.camel.component.salesforce.SalesforceEndpointConfig.SOBJECT_SEARCH;
 
 public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor {
 
     protected static final String RESPONSE_CLASS = AbstractRestProcessor.class.getName() + ".responseClass";
+    private static final Pattern URL_TEMPLATE = Pattern.compile("\\{([^\\{\\}]+)\\}");
 
     private RestClient restClient;
     private Map<String, Class<?>> classMap;
@@ -128,6 +147,9 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
             case SEARCH:
                 processSearch(exchange, callback);
                 break;
+            case APEX_CALL:
+                processApexCall(exchange, callback);
+                break;
             default:
                 throw new SalesforceException("Unknow operation name: " + operationName, null);
             }
@@ -244,12 +266,12 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
         }
 
         restClient.createSObject(sObjectName, getRequestStream(exchange),
-                new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, SalesforceException exception) {
-                        processResponse(exchange, response, exception, callback);
-                    }
-                });
+            new RestClient.ResponseCallback() {
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                }
+            });
     }
 
     private void processUpdateSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -270,13 +292,13 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
 
         final String finalsObjectId = sObjectId;
         restClient.updateSObject(sObjectName, sObjectId, getRequestStream(exchange),
-                new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, SalesforceException exception) {
-                        processResponse(exchange, response, exception, callback);
-                        restoreFields(exchange, sObjectBase, finalsObjectId, null, null);
-                    }
-                });
+            new RestClient.ResponseCallback() {
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                    restoreFields(exchange, sObjectBase, finalsObjectId, null, null);
+                }
+            });
     }
 
     private void processDeleteSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -325,13 +347,13 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
 
         final Object finalOldValue = oldValue;
         restClient.getSObjectWithId(sObjectName, sObjectExtIdName, sObjectExtIdValue,
-                new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, SalesforceException exception) {
-                        processResponse(exchange, response, exception, callback);
-                        restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
-                    }
-                });
+            new RestClient.ResponseCallback() {
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                    restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
+                }
+            });
     }
 
     private void processUpsertSobject(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -357,12 +379,12 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
         final Object finalOldValue = oldValue;
         restClient.upsertSObject(sObjectName, sObjectExtIdName, sObjectExtIdValue, getRequestStream(exchange),
             new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, SalesforceException exception) {
-                        processResponse(exchange, response, exception, callback);
-                        restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
-                    }
-                });
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                    restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
+                }
+            });
     }
 
     private void processDeleteSobjectWithId(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -384,13 +406,13 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
 
         final Object finalOldValue = oldValue;
         restClient.deleteSObjectWithId(sObjectName, sObjectExtIdName, sObjectExtIdValue,
-                new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, SalesforceException exception) {
-                        processResponse(exchange, response, exception, callback);
-                        restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
-                    }
-                });
+            new RestClient.ResponseCallback() {
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                    restoreFields(exchange, sObjectBase, null, sObjectExtIdName, finalOldValue);
+                }
+            });
     }
 
     private void processGetBlobField(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -412,13 +434,13 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
         final String sObjectId = sObjectIdValue;
 
         restClient.getBlobField(sObjectName, sObjectId, sObjectBlobFieldName,
-                new RestClient.ResponseCallback() {
-                    @Override
-                    public void onResponse(InputStream response, SalesforceException exception) {
-                        processResponse(exchange, response, exception, callback);
-                        restoreFields(exchange, sObjectBase, sObjectId, null, null);
-                    }
-                });
+            new RestClient.ResponseCallback() {
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                    restoreFields(exchange, sObjectBase, sObjectId, null, null);
+                }
+            });
     }
 
     private void processQuery(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
@@ -461,6 +483,90 @@ public abstract class AbstractRestProcessor extends AbstractSalesforceProcessor
         });
     }
 
+    private void processApexCall(final Exchange exchange, final AsyncCallback callback) throws SalesforceException {
+
+        // HTTP method, URL and query params for APEX call
+        final String apexUrl = getApexUrl(exchange);
+        String apexMethod = getParameter(APEX_METHOD, exchange, IGNORE_BODY, IS_OPTIONAL);
+        // default to GET
+        if (apexMethod == null) {
+            apexMethod = HttpMethods.GET;
+            log.debug("Using HTTP GET method by default for APEX REST call for {}", apexUrl);
+        }
+        final Map<String, Object> queryParams = getQueryParams(exchange);
+
+        // set response class
+        setResponseClass(exchange, getParameter(SOBJECT_NAME, exchange, IGNORE_BODY, IS_OPTIONAL));
+
+        // set request stream
+        final Object requestBody = exchange.getIn().getBody();
+        final InputStream requestDto =
+            (requestBody != null && !(requestBody instanceof Map)) ? getRequestStream(exchange) : null;
+
+        restClient.apexCall(apexMethod, apexUrl, queryParams, requestDto,
+            new RestClient.ResponseCallback() {
+                @Override
+                public void onResponse(InputStream response, SalesforceException exception) {
+                    processResponse(exchange, response, exception, callback);
+                }
+            });
+    }
+
+    private String getApexUrl(Exchange exchange) throws SalesforceException {
+        final String apexUrl = getParameter(APEX_URL, exchange, IGNORE_BODY, NOT_OPTIONAL);
+
+        final Matcher matcher = URL_TEMPLATE.matcher(apexUrl);
+        StringBuilder result = new StringBuilder();
+        int start = 0;
+        while (matcher.find()) {
+            // append part before parameter template
+            result.append(apexUrl.substring(start, matcher.start()));
+            start = matcher.end();
+
+            // append template value from exchange header
+            final String parameterName = matcher.group(1);
+            final Object value = exchange.getIn().getHeader(parameterName);
+            if (value == null) {
+                throw new IllegalArgumentException("Missing APEX URL template header " + parameterName);
+            }
+            try {
+                result.append(URLEncoder.encode(String.valueOf(value), "UTF-8").replaceAll("\\+", "%20"));
+            } catch (UnsupportedEncodingException e) {
+                throw new SalesforceException("Unexpected error: " + e.getMessage(), e);
+            }
+        }
+        if (start != 0) {
+            // append remaining URL
+            result.append(apexUrl.substring(start));
+            final String resolvedUrl = result.toString();
+            log.debug("Resolved APEX URL {} to {}", apexUrl, resolvedUrl);
+            return resolvedUrl;
+        }
+        return apexUrl;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> getQueryParams(Exchange exchange) {
+
+        // use endpoint map
+        Map<String, Object> queryParams = new HashMap<String, Object>(endpoint.getConfiguration().getApexQueryParams());
+
+        // look for individual properties, allowing endpoint properties to be overridden
+        for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
+            if (entry.getKey().startsWith(APEX_QUERY_PARAM_PREFIX)) {
+                queryParams.put(entry.getKey().substring(APEX_QUERY_PARAM_PREFIX.length()), entry.getValue());
+            }
+        }
+        // add params from body if it's a map
+        final Object body = exchange.getIn().getBody();
+        if (body instanceof Map) {
+            queryParams.putAll((Map<String, Object>) body);
+        }
+
+        log.debug("Using APEX query params {}", queryParams);
+        return queryParams;
+    }
+
     private void restoreFields(Exchange exchange, AbstractSObjectBase sObjectBase,
                                String sObjectId, String sObjectExtIdName, Object oldValue) {
         // restore fields

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/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 b29f36b..f3c8b4d 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,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.Message;
 import org.apache.camel.component.salesforce.SalesforceEndpoint;
 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.AbstractDTOBase;
 import org.apache.camel.component.salesforce.api.dto.CreateSObjectResult;
 import org.apache.camel.component.salesforce.api.dto.GlobalObjects;
 import org.apache.camel.component.salesforce.api.dto.RestResources;
@@ -113,11 +113,11 @@ public class JsonRestProcessor extends AbstractRestProcessor {
             Message in = exchange.getIn();
             request = in.getBody(InputStream.class);
             if (request == null) {
-                AbstractSObjectBase sObject = in.getBody(AbstractSObjectBase.class);
-                if (sObject != null) {
-                    // marshall the SObject
+                AbstractDTOBase dto = in.getBody(AbstractDTOBase.class);
+                if (dto != null) {
+                    // marshall the DTO
                     ByteArrayOutputStream out = new ByteArrayOutputStream();
-                    objectMapper.writeValue(out, sObject);
+                    objectMapper.writeValue(out, dto);
                     request = new ByteArrayInputStream(out.toByteArray());
                 } else {
                     // if all else fails, get body as String

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/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 a10de93..f11bc2f 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
@@ -37,7 +37,7 @@ import org.apache.camel.Message;
 import org.apache.camel.component.salesforce.SalesforceEndpoint;
 import org.apache.camel.component.salesforce.api.JodaTimeConverter;
 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.AbstractDTOBase;
 import org.apache.camel.component.salesforce.api.dto.CreateSObjectResult;
 import org.apache.camel.component.salesforce.api.dto.GlobalObjects;
 import org.apache.camel.component.salesforce.api.dto.RestResources;
@@ -144,6 +144,11 @@ public class XmlRestProcessor extends AbstractRestProcessor {
             exchange.setProperty(RESPONSE_CLASS, SearchResults.class);
             break;
 
+        case APEX_CALL:
+            // need to add alias for Salesforce XML that uses SObject name as root element
+            exchange.setProperty(RESPONSE_ALIAS, "response");
+            break;
+
         default:
             // ignore, some operations do not require alias or class exchange properties
         }
@@ -156,14 +161,14 @@ public class XmlRestProcessor extends AbstractRestProcessor {
             Message in = exchange.getIn();
             InputStream request = in.getBody(InputStream.class);
             if (request == null) {
-                AbstractSObjectBase sObject = in.getBody(AbstractSObjectBase.class);
-                if (sObject != null) {
-                    // marshall the SObject
+                AbstractDTOBase dto = in.getBody(AbstractDTOBase.class);
+                if (dto != null) {
+                    // marshall the DTO
                     // first process annotations on the class, for things like alias, etc.
-                    localXStream.processAnnotations(sObject.getClass());
+                    localXStream.processAnnotations(dto.getClass());
                     ByteArrayOutputStream out = new ByteArrayOutputStream();
                     // make sure we write the XML with the right encoding
-                    localXStream.toXML(sObject, new OutputStreamWriter(out, StringUtil.__UTF8_CHARSET));
+                    localXStream.toXML(dto, new OutputStreamWriter(out, StringUtil.__UTF8_CHARSET));
                     request = new ByteArrayInputStream(out.toByteArray());
                 } else {
                     // if all else fails, get body as String

http://git-wip-us.apache.org/repos/asf/camel/blob/c89e21f4/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/PushTopicHelper.java
----------------------------------------------------------------------
diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/PushTopicHelper.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/PushTopicHelper.java
index 4924aad..7423064 100644
--- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/PushTopicHelper.java
+++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/internal/streaming/PushTopicHelper.java
@@ -41,11 +41,28 @@ public class PushTopicHelper {
     private final SalesforceEndpointConfig config;
     private final String topicName;
     private final RestClient restClient;
+    private final boolean preApi29;
 
     public PushTopicHelper(SalesforceEndpointConfig config, String topicName, RestClient restClient) {
         this.config = config;
         this.topicName = topicName;
         this.restClient = restClient;
+        this.preApi29 = Double.valueOf(config.getApiVersion()) < 29.0;
+
+        // validate notify fields right away
+        if (preApi29 && (config.getNotifyForOperationCreate() != null
+                || config.getNotifyForOperationDelete() != null
+                || config.getNotifyForOperationUndelete() != null
+                || config.getNotifyForOperationUpdate() != null)) {
+            throw new IllegalArgumentException("NotifyForOperationCreate, NotifyForOperationDelete"
+                + ", NotifyForOperationUndelete, and NotifyForOperationUpdate"
+                + " are only supported since API version 29.0"
+                + ", instead use NotifyForOperations");
+        } else if (!preApi29 && config.getNotifyForOperations() != null) {
+            throw new IllegalArgumentException("NotifyForOperations is readonly since API version 29.0"
+                + ", instead use NotifyForOperationCreate, NotifyForOperationDelete"
+                + ", NotifyForOperationUndelete, and NotifyForOperationUpdate");
+        }
     }
 
     public void createOrUpdateTopic() throws CamelException {
@@ -63,8 +80,9 @@ public class PushTopicHelper {
             if (!callback.await(API_TIMEOUT, TimeUnit.SECONDS)) {
                 throw new SalesforceException("API call timeout!", null);
             }
-            if (callback.getException() != null) {
-                throw callback.getException();
+            final SalesforceException callbackException = callback.getException();
+            if (callbackException != null) {
+                throw callbackException;
             }
             QueryRecordsPushTopic records = OBJECT_MAPPER.readValue(callback.getResponse(),
                     QueryRecordsPushTopic.class);
@@ -73,13 +91,21 @@ public class PushTopicHelper {
                 PushTopic topic = records.getRecords().get(0);
                 LOG.info("Found existing topic {}: {}", topicName, topic);
 
-                // check if we need to update topic query, notifyForFields or notifyForOperations
+                // check if we need to update topic
+                final boolean notifyOperationsChanged;
+                if (preApi29) {
+                    notifyOperationsChanged =
+                        notEquals(config.getNotifyForOperations(), topic.getNotifyForOperations());
+                } else {
+                    notifyOperationsChanged =
+                        notEquals(config.getNotifyForOperationCreate(), topic.getNotifyForOperationCreate())
+                        || notEquals(config.getNotifyForOperationDelete(), topic.getNotifyForOperationDelete())
+                        || notEquals(config.getNotifyForOperationUndelete(), topic.getNotifyForOperationUndelete())
+                        || notEquals(config.getNotifyForOperationUpdate(), topic.getNotifyForOperationUpdate());
+                }
                 if (!query.equals(topic.getQuery())
-                        || (config.getNotifyForFields() != null
-                                && !config.getNotifyForFields().equals(topic.getNotifyForFields()))
-                        || (config.getNotifyForOperations() != null
-                                && !config.getNotifyForOperations().equals(topic.getNotifyForOperations()))
-                ) {
+                    || notEquals(config.getNotifyForFields(), topic.getNotifyForFields())
+                    || notifyOperationsChanged) {
 
                     if (!config.isUpdateTopic()) {
                         String msg = "Query doesn't match existing Topic and updateTopic is set to false";
@@ -126,7 +152,14 @@ public class PushTopicHelper {
         topic.setQuery(config.getSObjectQuery());
         topic.setDescription("Topic created by Camel Salesforce component");
         topic.setNotifyForFields(config.getNotifyForFields());
-        topic.setNotifyForOperations(config.getNotifyForOperations());
+        if (preApi29) {
+            topic.setNotifyForOperations(config.getNotifyForOperations());
+        } else {
+            topic.setNotifyForOperationCreate(config.getNotifyForOperationCreate());
+            topic.setNotifyForOperationDelete(config.getNotifyForOperationDelete());
+            topic.setNotifyForOperationUndelete(config.getNotifyForOperationUndelete());
+            topic.setNotifyForOperationUpdate(config.getNotifyForOperationUpdate());
+        }
 
         LOG.info("Creating Topic {}: {}", topicName, topic);
         final SyncResponseCallback callback = new SyncResponseCallback();
@@ -137,8 +170,9 @@ public class PushTopicHelper {
             if (!callback.await(API_TIMEOUT, TimeUnit.SECONDS)) {
                 throw new SalesforceException("API call timeout!", null);
             }
-            if (callback.getException() != null) {
-                throw callback.getException();
+            final SalesforceException callbackException = callback.getException();
+            if (callbackException != null) {
+                throw callbackException;
             }
 
             CreateSObjectResult result = OBJECT_MAPPER.readValue(callback.getResponse(), CreateSObjectResult.class);
@@ -182,7 +216,14 @@ public class PushTopicHelper {
             final PushTopic topic = new PushTopic();
             topic.setQuery(query);
             topic.setNotifyForFields(config.getNotifyForFields());
-            topic.setNotifyForOperations(config.getNotifyForOperations());
+            if (preApi29) {
+                topic.setNotifyForOperations(config.getNotifyForOperations());
+            } else {
+                topic.setNotifyForOperationCreate(config.getNotifyForOperationCreate());
+                topic.setNotifyForOperationDelete(config.getNotifyForOperationDelete());
+                topic.setNotifyForOperationUndelete(config.getNotifyForOperationUndelete());
+                topic.setNotifyForOperationUpdate(config.getNotifyForOperationUpdate());
+            }
 
             restClient.updateSObject("PushTopic", topicId,
                     new ByteArrayInputStream(OBJECT_MAPPER.writeValueAsBytes(topic)),
@@ -191,8 +232,9 @@ public class PushTopicHelper {
             if (!callback.await(API_TIMEOUT, TimeUnit.SECONDS)) {
                 throw new SalesforceException("API call timeout!", null);
             }
-            if (callback.getException() != null) {
-                throw callback.getException();
+            final SalesforceException callbackException = callback.getException();
+            if (callbackException != null) {
+                throw callbackException;
             }
 
         } catch (SalesforceException e) {
@@ -219,4 +261,8 @@ public class PushTopicHelper {
         }
     }
 
+    private static <T> boolean notEquals(T o1, T o2) {
+        return o1 != null && !o1.equals(o2);
+    }
+
 }
\ No newline at end of file