This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-spring-boot-examples.git
The following commit(s) were added to refs/heads/main by this push: new 53eba73 CAMEL-21910: Add Salesforce example (#155) 53eba73 is described below commit 53eba73afcfbeb813a306dcdc205b3b294bad2c7 Author: Stanislav Deviatov <devya...@gmail.com> AuthorDate: Fri Mar 28 20:15:20 2025 +0100 CAMEL-21910: Add Salesforce example (#155) * Initial version * Add Salesforce example with REST API and CDC monitoring * CAMEL-21910: Add route IDs for Salesforce contact queries and updates --- README.adoc | 13 ++- pom.xml | 1 + salesforce/.gitignore | 1 + salesforce/README.adoc | 101 ++++++++++++++++++ salesforce/pom.xml | 115 ++++++++++++++++++++ .../camel/example/salesforce/SalesforceApp.java | 29 +++++ .../camel/example/salesforce/SalesforceRouter.java | 117 +++++++++++++++++++++ .../main/resources/application.properties.example | 8 ++ 8 files changed, 380 insertions(+), 5 deletions(-) diff --git a/README.adoc b/README.adoc index d42734a..8c7faa9 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 530dd40..cacb117 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ <module>routetemplate-xml</module> <module>route-reload</module> <module>routes-configuration</module> + <module>salesforce</module> <module>saga</module> <module>soap-cxf</module> <module>supervising-route-controller</module> diff --git a/salesforce/.gitignore b/salesforce/.gitignore new file mode 100644 index 0000000..e75f8ad --- /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 0000000..1181cd9 --- /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=<YOUR_CLIENT_ID> # Consumer Key from Connected App +camel.component.salesforce.client-secret=<YOUR_CLIENT_SECRET> # Consumer Secret from Connected App +camel.component.salesforce.instance-url=<YOUR_DOMAIN> # e.g. https://your-org.my.salesforce.com +camel.component.salesforce.login-url=<YOUR_DOMAIN> # 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": "jsm...@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 0000000..e3f1d35 --- /dev/null +++ b/salesforce/pom.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel.springboot.example</groupId> + <artifactId>examples</artifactId> + <version>4.11.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-example-spring-boot-salesforce</artifactId> + <packaging>jar</packaging> + <name>Camel SB Examples :: Salesforce</name> + <description>How to work with Salesforce contacts using REST endpoints and Streaming API</description> + + <properties> + <category>SaaS</category> + + <camelSalesforce.clientId></camelSalesforce.clientId> + <camelSalesforce.clientSecret></camelSalesforce.clientSecret> + <camelSalesforce.sslContextParameters.secureSocketProtocol>TLSv1.3</camelSalesforce.sslContextParameters.secureSocketProtocol> + <camelSalesforce.loginUrl>https://login.salesforce.com</camelSalesforce.loginUrl> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.apache.camel.springboot</groupId> + <artifactId>camel-spring-boot-bom</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring-boot-version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + + <!-- Camel --> + <dependency> + <groupId>org.apache.camel.springboot</groupId> + <artifactId>camel-spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.springboot</groupId> + <artifactId>camel-salesforce-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.springboot</groupId> + <artifactId>camel-rest-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.springboot</groupId> + <artifactId>camel-servlet-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.springboot</groupId> + <artifactId>camel-jackson-starter</artifactId> + </dependency> + + <!-- Spring Boot --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + + +</project> 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 0000000..3fd0342 --- /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 0000000..02e96ee --- /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 0000000..9c4492d --- /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=<YOUR_CLIENT_ID> +camel.component.salesforce.client-secret=<YOUR_CLIENT_SECRET> +camel.component.salesforce.instance-url=<YOUR_DOMAIN> +camel.component.salesforce.login-url=<YOUR_DOMAIN> +