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:30:43 UTC

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

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/65cd95c5
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/65cd95c5
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/65cd95c5

Branch: refs/heads/camel-2.12.x
Commit: 65cd95c5ec6c531a58621dd206d69a35532045c9
Parents: 2dc15fa
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 14:24:45 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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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/65cd95c5/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