From 0580a0feebde06c259202998d5889ef8d263952a Mon Sep 17 00:00:00 2001 From: Stanislav Deviatov Date: Sat, 22 Mar 2025 13:07:58 +0100 Subject: [PATCH 1/3] Initial version --- salesforce/.gitignore | 1 + salesforce/pom.xml | 194 ++++++++++++++++++ .../example/salesforce/SalesforceApp.java | 29 +++ .../example/salesforce/SalesforceRouter.java | 114 ++++++++++ .../resources/application.properties.example | 8 + 5 files changed, 346 insertions(+) create mode 100644 salesforce/.gitignore create mode 100644 salesforce/pom.xml create mode 100644 salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceApp.java create mode 100644 salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java create mode 100644 salesforce/src/main/resources/application.properties.example 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/pom.xml b/salesforce/pom.xml new file mode 100644 index 000000000..9295f55da --- /dev/null +++ b/salesforce/pom.xml @@ -0,0 +1,194 @@ + + + + + 4.0.0 + + + org.apache.camel.springboot.example + examples + 4.11.0-SNAPSHOT + + + camel-example-spring-boot-salesforce + jar + Camel SB Examples :: Salesforce + It shows how to query Salesforce contacts using both 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 + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-version} + + + ${camelSalesforce.clientId} + ${camelSalesforce.clientSecret} + ${camelSalesforce.userName} + ${camelSalesforce.password} + ${camelSalesforce.loginUrl} + + + + + + repackage + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/camel-salesforce + + + + + + + + + + + generate-salesforce-dto + + + generate.dto + true + + + + + + org.apache.camel.maven + camel-salesforce-maven-plugin + ${camel-version} + + + + generate + + + ${camelSalesforce.clientId} + ${camelSalesforce.clientSecret} + ${camelSalesforce.userName} + ${camelSalesforce.password} + + ${camelSalesforce.sslContextParameters.secureSocketProtocol} + + + Contact + + + + + + + + + + + + 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..bfebb1036 --- /dev/null +++ b/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java @@ -0,0 +1,114 @@ +/* + * 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") + // 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") + // 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") + // 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= + From dae5cdba4c0dfba8c70a9eb3357259f328f6239e Mon Sep 17 00:00:00 2001 From: Stanislav Deviatov Date: Sat, 22 Mar 2025 13:39:18 +0100 Subject: [PATCH 2/3] Add Salesforce example with REST API and CDC monitoring --- README.adoc | 13 ++++-- pom.xml | 1 + salesforce/README.adoc | 101 +++++++++++++++++++++++++++++++++++++++++ salesforce/pom.xml | 81 +-------------------------------- 4 files changed, 111 insertions(+), 85 deletions(-) create mode 100644 salesforce/README.adoc 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/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 index 9295f55da..e3f1d3525 100644 --- a/salesforce/pom.xml +++ b/salesforce/pom.xml @@ -30,7 +30,7 @@ camel-example-spring-boot-salesforce jar Camel SB Examples :: Salesforce - It shows how to query Salesforce contacts using both REST endpoints and Streaming API. + How to work with Salesforce contacts using REST endpoints and Streaming API SaaS @@ -103,92 +103,13 @@ - - - ${project.artifactId} - org.springframework.boot spring-boot-maven-plugin - ${spring-boot-version} - - - ${camelSalesforce.clientId} - ${camelSalesforce.clientSecret} - ${camelSalesforce.userName} - ${camelSalesforce.password} - ${camelSalesforce.loginUrl} - - - - - - repackage - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-source - generate-sources - - add-source - - - - ${project.build.directory}/generated-sources/camel-salesforce - - - - - - - generate-salesforce-dto - - - generate.dto - true - - - - - - org.apache.camel.maven - camel-salesforce-maven-plugin - ${camel-version} - - - - generate - - - ${camelSalesforce.clientId} - ${camelSalesforce.clientSecret} - ${camelSalesforce.userName} - ${camelSalesforce.password} - - ${camelSalesforce.sslContextParameters.secureSocketProtocol} - - - Contact - - - - - - - - - - From daf34ba3eb40026fb5240cfc5e5fac5a78486c68 Mon Sep 17 00:00:00 2001 From: Stanislav Deviatov Date: Sat, 22 Mar 2025 13:39:21 +0100 Subject: [PATCH 3/3] CAMEL-21910: Add route IDs for Salesforce contact queries and updates --- .../org/apache/camel/example/salesforce/SalesforceRouter.java | 3 +++ 1 file changed, 3 insertions(+) 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 index bfebb1036..02e96eea9 100644 --- a/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java +++ b/salesforce/src/main/java/org/apache/camel/example/salesforce/SalesforceRouter.java @@ -74,6 +74,7 @@ public void configure() throws Exception { // 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 @@ -83,6 +84,7 @@ public void configure() throws Exception { // 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 @@ -92,6 +94,7 @@ public void configure() throws Exception { // 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