diff --git a/README.adoc b/README.adoc
index d42734a0e..8c7faa90a 100644
--- a/README.adoc
+++ b/README.adoc
@@ -27,7 +27,7 @@ readme's instructions.
=== Examples
// examples: START
-Number of Examples: 61 (0 deprecated)
+Number of Examples: 62 (0 deprecated)
[width="100%",cols="4,2,4",options="header"]
|===
@@ -98,7 +98,7 @@ Number of Examples: 61 (0 deprecated)
| link:fhir/readme.adoc[Fhir] (fhir) | Health Care | An example showing how to work with Camel, FHIR and Spring Boot
| link:fhir-auth-tx/readme.adoc[Fhir Auth Tx] (fhir-auth-tx) | Health Care | An example showing how to work with Camel, FHIR Authorization, FHIR Transaction and Spring Boot
-
+
| link:validator/readme.adoc[Validator Spring Boot] (validator) | Input/Output Type Contract | An example showing how to work with declarative validation and Spring Boot
@@ -114,7 +114,7 @@ Number of Examples: 61 (0 deprecated)
| link:metrics/README.adoc[Metrics] (metrics) | Management and Monitoring | An example showing how to work with Camel and Spring Boot and report metrics to Graphite
| link:observation/README.adoc[Micrometer Observation] (observation) | Management and Monitoring | An example showing how to trace incoming and outgoing messages from Camel with Micrometer Observation
-
+
| link:opentelemetry/README.adoc[OpenTelemetry] (opentelemetry) | Management and Monitoring | An example showing how to use Camel with OpenTelemetry
@@ -128,7 +128,7 @@ Number of Examples: 61 (0 deprecated)
| link:kafka-avro/README.adoc[Kafka Avro] (kafka-avro) | Messaging | An example for Kafka avro
-| link:kafka-oauth/README.adoc[Kafka OAuth] (kafka-oauth) | Messaging | An example for Kafka authentication using OAuth
+| link:kafka-oauth/README.adoc[Kafka Oauth] (kafka-oauth) | Messaging | An example of Kafka authentication using OAuth.
| link:kafka-offsetrepository/README.adoc[Kafka Offsetrepository] (kafka-offsetrepository) | Messaging | An example for Kafka offsetrepository
@@ -141,7 +141,7 @@ Number of Examples: 61 (0 deprecated)
| link:widget-gadget/README.adoc[Widget Gadget] (widget-gadget) | Messaging | The widget and gadget example from EIP book, running on Spring Boot
| link:reactive-streams/readme.adoc[Reactive Streams] (reactive-streams) | Reactive | An example that shows how Camel can exchange data using reactive streams with Spring Boot reactor
-
+
| link:http-ssl/README.adoc[Http Ssl] (http-ssl) | Rest | An example showing the Camel HTTP component with Spring Boot and SSL
@@ -160,6 +160,9 @@ Number of Examples: 61 (0 deprecated)
| link:jira/README.adoc[Jira] (jira) | SaaS | An example that uses Jira Camel API
| link:twitter-salesforce/README.adoc[Twitter Salesforce] (twitter-salesforce) | SaaS | Twitter mentions is created as contacts in Salesforce
+
+| link:salesforce/README.adoc[Salesforce] (salesforce) | SaaS | How to work with Salesforce contacts using REST endpoints and Streaming API
+
|===
// examples: END
diff --git a/pom.xml b/pom.xml
index 530dd4009..cacb117ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,7 @@
routetemplate-xml
route-reload
routes-configuration
+ salesforce
saga
soap-cxf
supervising-route-controller
diff --git a/salesforce/.gitignore b/salesforce/.gitignore
new file mode 100644
index 000000000..e75f8ade9
--- /dev/null
+++ b/salesforce/.gitignore
@@ -0,0 +1 @@
+src/main/resources/application.properties
\ No newline at end of file
diff --git a/salesforce/README.adoc b/salesforce/README.adoc
new file mode 100644
index 000000000..1181cd9ab
--- /dev/null
+++ b/salesforce/README.adoc
@@ -0,0 +1,101 @@
+= Camel Salesforce Example
+
+The example provides REST API endpoints for managing Salesforce contacts (list all, get by ID, update) and implements real-time monitoring of Contact changes through Change Data Capture (CDC) events.
+
+== Features
+
+* REST API endpoints to fetch all Salesforce contacts, get contact by ID and update a contact by ID
+* Listens continuously for Change Data Capture events (CDC)
+* Salesforce authentication using client credentials flow
+
+== Prerequisites
+
+* Java 17 or higher
+* Maven 3.6+
+* Salesforce developer account
+* Salesforce Connected App credentials
+
+== Configuration
+
+1. Create a Connected App in your Salesforce org:
+ * Go to Setup > Apps > App Manager > New Connected App
+ * Enable OAuth Settings
+ * Set Callback URL (can be http://localhost:8080)
+ * Add 'Perform requests at any time' to Selected OAuth Scopes
+ * Save and wait for activation
+
+2. Enable CDC events for Contact object:
+ * Go to Setup > Integrations > Change Data Capture
+ * Add `Contact (Contact)` to Selected Entities
+ * Save
+
+3. Copy `src/main/resources/application.properties.example` to `src/main/resources/application.properties`
+
+4. Update the properties with your Connected App credentials:
+[source,properties]
+----
+camel.component.salesforce.client-id= # Consumer Key from Connected App
+camel.component.salesforce.client-secret= # Consumer Secret from Connected App
+camel.component.salesforce.instance-url= # e.g. https://your-org.my.salesforce.com
+camel.component.salesforce.login-url= # Same as instance-url
+----
+
+== Building
+
+[source,bash]
+----
+mvn clean install
+----
+
+== Running
+
+[source,bash]
+----
+mvn spring-boot:run
+----
+
+The application will start on port 8080.
+
+== Testing
+
+=== REST Endpoints
+
+1. Fetch all contacts:
+[source,bash]
+----
+curl -X GET http://localhost:8080/camel/contacts | jq
+----
+
+2. Fetch a specific contact:
+[source,bash]
+----
+curl -X GET http://localhost:8080/camel/contacts/003XXXXXXXXXXXXXXX | jq
+----
+Replace `003XXXXXXXXXXXXXXX` with an actual Salesforce Contact ID.
+
+3. Update a specific contact:
+[source,bash]
+----
+curl --location --request PUT 'http://localhost:8080/camel/contacts/003XXXXXXXXXXXXXXX' \
+--header 'Content-Type: application/json' \
+--data-raw '{
+ "LastName": "Smith",
+ "FirstName": "John",
+ "Salutation": "Mr.",
+ "Email": "jsmith@gmail.com",
+ "Description": "Test description"
+}'
+----
+Replace `003XXXXXXXXXXXXXXX` with an actual Salesforce Contact ID.
+
+== Monitor CDC events
+Listens continuously for Contact Change Events (CDC):
+
+ * Make changes to contacts in Salesforce or update a specific contact
+ * Watch the application logs for real-time change events
+
+== Project Structure
+
+* `SalesforceRouter.java`: Contains Camel route definitions
+* `SalesforceApp.java`: Spring Boot application entry point
+* `application.properties`: Configuration properties
diff --git a/salesforce/pom.xml b/salesforce/pom.xml
new file mode 100644
index 000000000..e3f1d3525
--- /dev/null
+++ b/salesforce/pom.xml
@@ -0,0 +1,115 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache.camel.springboot.example
+ examples
+ 4.11.0-SNAPSHOT
+
+
+ camel-example-spring-boot-salesforce
+ jar
+ Camel SB Examples :: Salesforce
+ How to work with Salesforce contacts using REST endpoints and Streaming API
+
+
+ SaaS
+
+
+
+ TLSv1.3
+ https://login.salesforce.com
+
+
+
+
+
+ org.apache.camel.springboot
+ camel-spring-boot-bom
+ ${project.version}
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot-version}
+ pom
+ import
+
+
+
+
+
+
+
+
+ org.apache.camel.springboot
+ camel-spring-boot-starter
+
+
+ org.apache.camel.springboot
+ camel-salesforce-starter
+
+
+ org.apache.camel.springboot
+ camel-rest-starter
+
+
+ org.apache.camel.springboot
+ camel-servlet-starter
+
+
+ org.apache.camel.springboot
+ camel-jackson-starter
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
diff --git a/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceApp.java b/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceApp.java
new file mode 100644
index 000000000..3fd034276
--- /dev/null
+++ b/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceApp.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.example.salesforce;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SalesforceApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SalesforceApp.class, args);
+ }
+
+}
diff --git a/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java b/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java
new file mode 100644
index 000000000..02e96eea9
--- /dev/null
+++ b/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.example.salesforce;
+
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.dataformat.JsonLibrary;
+import org.apache.camel.model.rest.RestBindingMode;
+import org.springframework.stereotype.Component;
+
+/**
+ * A Camel router class that integrates with Salesforce to manage contact information.
+ * This router implements five main routes:
+ * 1. REST GET endpoint to fetch all contacts
+ * 2. REST GET endpoint to retrieve a specific contact by ID
+ * 3. REST PUT endpoint to update a specific contact by ID
+ * 4. Salesforce CDC event listener for Contact changes
+ *
+ * Key features:
+ * - REST API: Servlet-based REST endpoints with JSON binding
+ * - CRUD Operations: Support for reading and updating Salesforce contacts
+ * - Real-time Updates: CDC (Change Data Capture) event monitoring
+ *
+ * Endpoints:
+ * - GET /contacts: Retrieves all contacts
+ * - GET /contacts/{id}: Retrieves a specific contact
+ * - PUT /contacts/{id}: Updates a specific contact
+ *
+ * Technical details:
+ * - Uses Spring @Component for dependency injection
+ * - Implements RestBindingMode.json for automatic JSON serialization
+ * - Leverages direct endpoints for synchronous route execution
+ * - Integrates with Salesforce using SOQL queries and CDC events
+ * - Employs Jackson for JSON data transformation
+ *
+ * @see org.apache.camel.builder.RouteBuilder
+ * @see org.springframework.stereotype.Component
+ */
+@Component
+public class SalesforceRouter extends RouteBuilder {
+
+ @Override
+ public void configure() throws Exception {
+ // Configure REST endpoint using servlet component and JSON binding
+ restConfiguration()
+ .component("servlet") // Use servlet as the HTTP server
+ .bindingMode(RestBindingMode.json); // Enable automatic JSON data binding
+
+ // Define REST endpoint that responds to GET requests
+ rest("/contacts")
+ .get()
+ .id("Rest-based route: all contacts") // Create GET endpoint at /contacts path
+ .to("direct:getContacts?synchronous=true") // Route requests to direct:getContacts endpoint
+ .get("/{id}")
+ .id("Rest-based route: contact by id") // Create GET endpoint with path parameter
+ .to("direct:getContactById?synchronous=true") // Route requests to direct:getContactById endpoint
+ .put("/{id}")
+ .id("Rest-based route: update contact by id") // Create PUT endpoint with path parameter
+ .to("direct:updateContactById?synchronous=true"); // Route requests to direct:updateContactById endpoint
+
+ // Define route that queries Salesforce contacts
+ from("direct:getContacts")
+ .id("getContacts")
+ // Execute SOQL query to get Contact objects from Salesforce
+ .to("salesforce:queryAll?sObjectQuery=SELECT Id, Name, Email FROM Contact")
+ // Uncommented debug logging line
+ // .to("log:debug?showAll=true&multiline=true")
+ // Convert Salesforce response to JSON using Jackson library
+ .unmarshal().json(JsonLibrary.Jackson);
+
+ // Define route that queries Salesforce contacts
+ from("direct:getContactById")
+ .id("getContactById")
+ // Execute SOQL query to get Contact objects from Salesforce
+ .toD("salesforce:getSObject?sObjectName=Contact&sObjectId=${header.id}")
+ // Uncommented debug logging line
+ // .to("log:debug?showAll=true&multiline=true");
+ // Convert Salesforce response to JSON using Jackson library
+ .unmarshal().json(JsonLibrary.Jackson);
+
+ // Define route that updates a Salesforce contact by ID
+ from("direct:updateContactById")
+ .id("updateContactById")
+ // Convert the input body to JSON format using Jackson library
+ .marshal().json(JsonLibrary.Jackson)
+ // Convert the JSON to String format for Salesforce update
+ .convertBodyTo(String.class)
+ // Uncommented debug logging line for troubleshooting
+ // .to("log:debug?showAll=true&multiline=true")
+ // Update the Contact object in Salesforce using the ID from the header
+ .toD("salesforce:updateSObject?sObjectName=Contact&sObjectId=${header.id}");
+
+ // Define route that listens for Salesforce CDC events for Contact objects
+ from("salesforce:subscribe:data/ContactChangeEvent")
+ .id("Listener Salesforce CDC events") // Set route ID for monitoring
+ // Uncommented debug logging line
+ // .to("log:debug?showAll=true&multiline=true");
+ // Convert Salesforce response to JSON using Jackson library
+ .unmarshal().json(JsonLibrary.Jackson)
+ // Log the CDC event at INFO level
+ .log(LoggingLevel.INFO, "A new event: ${body}");
+ }
+}
diff --git a/salesforce/src/main/resources/application.properties.example b/salesforce/src/main/resources/application.properties.example
new file mode 100644
index 000000000..9c4492d34
--- /dev/null
+++ b/salesforce/src/main/resources/application.properties.example
@@ -0,0 +1,8 @@
+spring.application.name=Salesforce Example
+camel.component.salesforce.config.raw-payload=true
+camel.component.salesforce.authentication-type=CLIENT_CREDENTIALS
+camel.component.salesforce.client-id=
+camel.component.salesforce.client-secret=
+camel.component.salesforce.instance-url=
+camel.component.salesforce.login-url=
+