From bd5c711476ce93200e329223be21ed84f79053be Mon Sep 17 00:00:00 2001 From: croway Date: Fri, 4 Sep 2026 10:15:27 +0200 Subject: [PATCH 1/3] Fix rest-openapi example README: correct actuator URL and refresh sample log The actuator base URL in the README had a trailing slash which 404s; the working URL has none. The sample startup log was also stale, showing Spring Boot 1.5.10/Camel 2.22.0-SNAPSHOT output from 2017. Replaced it with output from a verified run against the current Spring Boot/Camel versions. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Wgr7MBBqUbNGG6xrH6ybWq --- rest-openapi/README.adoc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rest-openapi/README.adoc b/rest-openapi/README.adoc index bf74d5472..b35d97ba2 100644 --- a/rest-openapi/README.adoc +++ b/rest-openapi/README.adoc @@ -25,7 +25,7 @@ You should see the following output when the application is launched: [source,text] ---- ... -[INFO] --- spring-boot-maven-plugin:1.5.10.RELEASE:run (default-cli) @ camel-example-spring-boot-rest-openapi --- +[INFO] --- spring-boot:4.1.1:run (default-cli) @ camel-example-spring-boot-rest-openapi --- [...] . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ @@ -33,12 +33,14 @@ You should see the following output when the application is launched: \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v2.0.0.RELEASE) -[...] -2017-03-05 14:55:44.032 INFO 15312 --- [ main] o.a.camel.spring.SpringCamelContext : Total 4 routes, of which 4 are started. -2017-03-05 14:55:44.034 INFO 15312 --- [ main] o.a.camel.spring.SpringCamelContext : Apache Camel 2.22.0-SNAPSHOT (CamelContext: camel-1) started in 0.614 seconds -2017-03-05 14:55:44.131 INFO 15312 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) -2017-03-05 14:55:44.140 INFO 15312 --- [ main] o.a.c.example.springboot.Application : Started Application in 6.265 seconds (JVM running for 21.092) + + :: Spring Boot :: (v4.1.1) + +2026-09-04T10:13:59.681+02:00 INFO 26229 --- [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' +2026-09-04T10:13:59.872+02:00 INFO 26229 --- [ main] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 4.23.0-SNAPSHOT (MyCamel) is starting +2026-09-04T10:13:59.913+02:00 INFO 26229 --- [ main] o.a.c.impl.engine.AbstractCamelContext : Routes startup (total:4 rest-dsl:4) +2026-09-04T10:13:59.913+02:00 INFO 26229 --- [ main] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 4.23.0-SNAPSHOT (MyCamel) started in 41ms (build:0ms init:0ms start:41ms boot:586ms) +2026-09-04T10:13:59.915+02:00 INFO 26229 --- [ main] o.a.c.example.springboot.Application : Started Application in 1.476 seconds (process running for 1.63) ---- After the Spring Boot application is started, you can open the following URL in your web browser to access the REST endpoint and retrieve a list of users: http://localhost:8080/api/users @@ -77,7 +79,7 @@ The Spring Boot application can be stopped pressing `[CTRL] + [C]` in the shell. === Actuator -The actuator is available at: `http://localhost:8080/actuator/` such as metrics for Prometheus: `http://localhost:8080/actuator/prometheus`. +The actuator is available at: `http://localhost:8080/actuator` such as metrics for Prometheus: `http://localhost:8080/actuator/prometheus`. === Help and contributions From 562c6b257f72241a460d1d452cf801ff05d342b7 Mon Sep 17 00:00:00 2001 From: croway Date: Fri, 4 Sep 2026 10:30:41 +0200 Subject: [PATCH 2/3] Fix twitter-salesforce example: repair broken Contact DTO fallback The reflection in TwitterSalesforceRoute was meant to prefer the Contact DTO generated by camel-salesforce-maven-plugin (only present when built with -Dgenerate.dto against a live org) and otherwise fall back to a bundled DTO, so the module compiles without live Salesforce credentials. But Class.forName(x) != null never returns false - a missing class throws ClassNotFoundException immediately, so the fallback branch was dead code, and the bundled fallback DTO was itself an abstract class that didn't extend AbstractSObjectBase, so it could neither be instantiated nor be recognized by the Salesforce component. Without running the generate.dto profile, every tweet mention failed. - Replace the abstract draftdto.Contact with a concrete example.mention.dto.Contact extending AbstractSObjectBase, with @JsonProperty annotations preserving Salesforce's case-sensitive field API names (LastName, TwitterScreenName__c) - without them Jackson decapitalizes the leading letter of bean-derived property names and would silently send the wrong field names. - Fix the Class.forName check with a proper try/catch(ClassNotFoundException). - Pin build-helper-maven-plugin to 3.0.0, matching other examples in this repo. - Clarify in the README that the bundled Twitter/X and Salesforce credentials are illustrative placeholders (the Twitter ones predate the 2023 API overhaul and are very likely dead) that must be replaced with self-provisioned ones, and document the DTO fallback design. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Wgr7MBBqUbNGG6xrH6ybWq --- twitter-salesforce/README.adoc | 38 +++++++++--- twitter-salesforce/pom.xml | 1 + .../mention/TwitterSalesforceRoute.java | 20 +++--- .../camel/example/mention/dto/Contact.java | 61 +++++++++++++++++++ .../camel/salesforce/draftdto/Contact.java | 6 -- 5 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 twitter-salesforce/src/main/java/org/apache/camel/example/mention/dto/Contact.java delete mode 100644 twitter-salesforce/src/main/java/org/apache/camel/salesforce/draftdto/Contact.java diff --git a/twitter-salesforce/README.adoc b/twitter-salesforce/README.adoc index 0cc460652..e3209dd5b 100644 --- a/twitter-salesforce/README.adoc +++ b/twitter-salesforce/README.adoc @@ -8,20 +8,26 @@ as a contact in Salesforce. === Configuring Twitter -This example is already configured using a testing purpose twitter -account named `cameltweet'. And therefore the example is ready to run -out of the box. +This example ships with the keys and tokens of a long-standing testing +Twitter/X account named `cameltweet`, used only to let the example show +a complete, working configuration; they are not a working credential you +can rely on. They predate Twitter/X's 2023 API overhaul and are very +likely no longer valid, and even if they were, they are for a shared +testing account, not something to build on. -This account is only for testing purpose, and should not be used in your -custom applications. For that you need to setup and use your own twitter -account. +You must self-provision your own Twitter/X developer app and use its +consumer key/secret and access token/secret in place of the values in +`+application.properties+` before this example will actually run. === Configuring Salesforce -This example uses Camels own test Salesforce developer account, you -would most likely want to sign up with your own Developer account at -https://developer.salesforce.com/. After you have done that, you’ll need -to create a Connected Application for your integration. +This example ships with `+clientId+`/`+clientSecret+` values for a Camel +test Salesforce developer account, included only to show what a filled-in +configuration looks like. As with the Twitter/X keys above, self-provision +your own Salesforce Developer account at +https://developer.salesforce.com/ for anything beyond reading this +example, and follow the steps below to create your own Connected +Application for your integration. To do this after logging in to your Salesforce Developer account, navigate to _Apps_ located under _Build_ and then _Create_, there you @@ -89,6 +95,18 @@ to use the `+camel-salesforce-maven-plugin+` and specify the desired DTO in org.codehaus.mojo build-helper-maven-plugin + 3.0.0 add-source diff --git a/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java b/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java index 2f57362dd..af951f34b 100644 --- a/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java +++ b/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java @@ -16,9 +16,7 @@ */ package org.apache.camel.example.mention; -import java.lang.Class; import java.lang.reflect.Method; -import org.apache.camel.salesforce.draftdto.Contact; import org.apache.camel.builder.RouteBuilder; import org.springframework.stereotype.Component; import twitter4j.v1.Status; @@ -27,6 +25,13 @@ @Component public class TwitterSalesforceRoute extends RouteBuilder { + // Prefer the DTO generated by the generate-salesforce-dto profile (org.apache.camel.salesforce.dto.Contact) + // when it was built against a live org, since it carries the full field set for the org's Contact object. + // That class only exists on the classpath when that profile ran, so it can't be referenced statically - + // without it, the module wouldn't compile at all. Reflection lets us prefer it when present and fall back + // to the bundled org.apache.camel.example.mention.dto.Contact otherwise. + private static final String GENERATED_CONTACT_CLASS = "org.apache.camel.salesforce.dto.Contact"; + @Override public void configure() throws Exception { from("twitter-timeline:mentions") @@ -36,11 +41,12 @@ public void configure() throws Exception { User user = status.getUser(); String name = user.getName(); String screenName = user.getScreenName(); - Class contact = null; - if (Class.forName("org.apache.camel.salesforce.dto.Contact") != null) { - contact = Class.forName("org.apache.camel.salesforce.dto.Contact"); - } else { - contact = Contact.class; + + Class contact; + try { + contact = Class.forName(GENERATED_CONTACT_CLASS); + } catch (ClassNotFoundException e) { + contact = org.apache.camel.example.mention.dto.Contact.class; } Object contactObject = contact.getDeclaredConstructor().newInstance(); diff --git a/twitter-salesforce/src/main/java/org/apache/camel/example/mention/dto/Contact.java b/twitter-salesforce/src/main/java/org/apache/camel/example/mention/dto/Contact.java new file mode 100644 index 000000000..f795d3688 --- /dev/null +++ b/twitter-salesforce/src/main/java/org/apache/camel/example/mention/dto/Contact.java @@ -0,0 +1,61 @@ +/* + * 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.mention.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase; + +/** + * Minimal, hand-maintained stand-in for the Salesforce {@code Contact} DTO that + * {@code camel-salesforce-maven-plugin} would otherwise generate from a live org (see the + * {@code generate-salesforce-dto} Maven profile). It only carries the fields this example + * actually sets, so the module builds and runs without needing Salesforce credentials at + * build time. + */ +public class Contact extends AbstractSObjectBase { + + private String lastName; + private String twitterScreenNameC; + + public Contact() { + getAttributes().setType("Contact"); + } + + // Salesforce field API names are case-sensitive (LastName, TwitterScreenName__c); without + // @JsonProperty, Jackson would decapitalize the leading letter of the bean-derived property + // name and silently send the wrong field names to the Salesforce REST API. + @JsonProperty("LastName") + public String getLastName() { + return lastName; + } + + @JsonProperty("LastName") + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @JsonProperty("TwitterScreenName__c") + public String getTwitterScreenName__c() { + return twitterScreenNameC; + } + + @JsonProperty("TwitterScreenName__c") + public void setTwitterScreenName__c(String twitterScreenNameC) { + this.twitterScreenNameC = twitterScreenNameC; + } + +} diff --git a/twitter-salesforce/src/main/java/org/apache/camel/salesforce/draftdto/Contact.java b/twitter-salesforce/src/main/java/org/apache/camel/salesforce/draftdto/Contact.java deleted file mode 100644 index 4d46cf358..000000000 --- a/twitter-salesforce/src/main/java/org/apache/camel/salesforce/draftdto/Contact.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.apache.camel.salesforce.draftdto; - -public abstract class Contact { - public abstract void setLastName(String LastName); - public abstract void setTwitterScreenName__c(String TwitterScreenName__c); -} From ec75a26d400dd5df237c764c1ae0326f1611f687 Mon Sep 17 00:00:00 2001 From: croway Date: Fri, 4 Sep 2026 10:39:03 +0200 Subject: [PATCH 3/3] Drop unnecessary reflection from twitter-salesforce Contact DTO lookup The reflection preferred the DTO generated by camel-salesforce-maven-plugin (only present after building with -Dgenerate.dto against a live org) over the bundled Contact DTO, but the route only ever calls setLastName and setTwitterScreenName__c either way. AbstractSObjectBase already gives both classes the same standard fields, and upsertSObject's create/update path only casts the body to AbstractSObjectBase - it doesn't use the generated DTO's extra fields or its SObjectDescription metadata. So the reflection had no effect on behavior; it only added a failure mode. Reference the bundled Contact DTO directly instead. The generate-salesforce-dto profile and camel-salesforce-maven-plugin wiring stay as documented, optional tooling for extending the route to more fields - just no longer glued to it via reflection. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Wgr7MBBqUbNGG6xrH6ybWq --- twitter-salesforce/README.adoc | 14 ++++----- .../mention/TwitterSalesforceRoute.java | 29 ++++--------------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/twitter-salesforce/README.adoc b/twitter-salesforce/README.adoc index e3209dd5b..6ddf5d6c0 100644 --- a/twitter-salesforce/README.adoc +++ b/twitter-salesforce/README.adoc @@ -97,15 +97,15 @@ specified using regular expressions. Running that plugin needs a live login against a real org, so it only runs under the `+generate-salesforce-dto+` profile (`+-Dgenerate.dto+`), -never during a plain `+mvn compile+`. Because of that, the route can't -statically reference the generated `+org.apache.camel.salesforce.dto.Contact+` -class - it may not exist on the classpath. Instead -`+TwitterSalesforceRoute+` looks it up by name via reflection, using it -when present (e.g. after running with `+-Dgenerate.dto+` against your -own org) and otherwise falling back to the minimal, hand-maintained +never during a plain `+mvn compile+`. This example itself only sets +`+LastName+` and `+TwitterScreenName__c+`, so it doesn't need a +generated DTO - it uses the minimal, hand-maintained `+org.apache.camel.example.mention.dto.Contact+` bundled with the example, which is enough to compile and run without any Salesforce -credentials at build time. +credentials at build time. The plugin and profile are kept around as a +starting point if you want to extend the route to read or write +additional fields on `+Contact+` (or other SObjects) - generate a DTO +for them and use it in place of the bundled one. === Build diff --git a/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java b/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java index af951f34b..6a1be0d96 100644 --- a/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java +++ b/twitter-salesforce/src/main/java/org/apache/camel/example/mention/TwitterSalesforceRoute.java @@ -16,8 +16,8 @@ */ package org.apache.camel.example.mention; -import java.lang.reflect.Method; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.example.mention.dto.Contact; import org.springframework.stereotype.Component; import twitter4j.v1.Status; import twitter4j.v1.User; @@ -25,13 +25,6 @@ @Component public class TwitterSalesforceRoute extends RouteBuilder { - // Prefer the DTO generated by the generate-salesforce-dto profile (org.apache.camel.salesforce.dto.Contact) - // when it was built against a live org, since it carries the full field set for the org's Contact object. - // That class only exists on the classpath when that profile ran, so it can't be referenced statically - - // without it, the module wouldn't compile at all. Reflection lets us prefer it when present and fall back - // to the bundled org.apache.camel.example.mention.dto.Contact otherwise. - private static final String GENERATED_CONTACT_CLASS = "org.apache.camel.salesforce.dto.Contact"; - @Override public void configure() throws Exception { from("twitter-timeline:mentions") @@ -39,23 +32,11 @@ public void configure() throws Exception { .process(exchange -> { Status status = exchange.getIn().getBody(Status.class); User user = status.getUser(); - String name = user.getName(); - String screenName = user.getScreenName(); - - Class contact; - try { - contact = Class.forName(GENERATED_CONTACT_CLASS); - } catch (ClassNotFoundException e) { - contact = org.apache.camel.example.mention.dto.Contact.class; - } - - Object contactObject = contact.getDeclaredConstructor().newInstance(); - Method setLastName = contact.getMethod("setLastName", String.class); - Method setTwitterScreenName__c = contact.getMethod("setTwitterScreenName__c", String.class); - setLastName.invoke(contactObject, name); - setTwitterScreenName__c.invoke(contactObject, screenName); - exchange.getIn().setBody(contactObject); + Contact contact = new Contact(); + contact.setLastName(user.getName()); + contact.setTwitterScreenName__c(user.getScreenName()); + exchange.getIn().setBody(contact); }) .to("salesforce:upsertSObject?sObjectIdName=TwitterScreenName__c") .log("SObject ID: ${body?.id}");