This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 11a0917b1698 CAMEL-25045: camel-core - Java DSL builders: fix bugs
found in a deep review (#26926)
11a0917b1698 is described below
commit 11a0917b16982692014585754e5fa68a78c9b4bd
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 28 09:08:22 2026 +0200
CAMEL-25045: camel-core - Java DSL builders: fix bugs found in a deep
review (#26926)
- choice().note() no longer recurses into itself (StackOverflowError)
- throttle(Expression, long correlationExpressionKey) uses the key
- route configuration onException(e1, e2, e3) keeps the third exception
- simple(text, resultType, pretty, trimResult) sets trimResult
- endDoTry() from a block inside doCatch/doFinally returns the try
- enrichWith(EndpointProducerBuilder) is dynamic as its sibling overloads
- the type: prefix works in all bean(...) overloads
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/builder/ExpressionClauseSupport.java | 1 +
.../apache/camel/model/ProcessorDefinition.java | 55 ++++---
.../ProcessorDefinitionBuilderEdgeCasesTest.java | 174 +++++++++++++++++++++
3 files changed, 202 insertions(+), 28 deletions(-)
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
index 3ae3f8278f9c..9e95800abb37 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
@@ -849,6 +849,7 @@ public class ExpressionClauseSupport<T> implements
ExpressionFactoryAware, Predi
SimpleExpression expression = new SimpleExpression(text);
expression.setResultType(resultType);
expression.setPretty(Boolean.toString(pretty));
+ expression.setTrimResult(Boolean.toString(trimResult));
expression(expression);
return result;
}
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
index 5acd659582cc..d3c8cb78b296 100644
---
a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
+++
b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
@@ -854,7 +854,7 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
p.note(note);
}
} else {
- cbr.note(note);
+ cbr.setNote(note);
}
return asType();
}
@@ -1220,6 +1220,10 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
// okay end this and get back to the try
def = end();
+ if (def instanceof CatchDefinition || def instanceof
FinallyDefinition) {
+ // we ended a block inside doCatch or doFinally, so go back to the
try that owns it
+ def = def.getParent();
+ }
return (TryDefinition) def;
}
@@ -2101,7 +2105,8 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
* @return the builder
*/
public ThrottleDefinition throttle(Expression maximumConcurrentRequests,
long correlationExpressionKey) {
- ThrottleDefinition answer = new
ThrottleDefinition(maximumConcurrentRequests, maximumConcurrentRequests);
+ ThrottleDefinition answer = new ThrottleDefinition(
+ maximumConcurrentRequests, new
ConstantExpression(Long.toString(correlationExpressionKey)));
addOutput(answer);
return answer;
}
@@ -2454,7 +2459,7 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
Class<? extends Throwable> exceptionType3) {
if (this.getRouteConfiguration() != null) {
// this is part of route configuration
- return this.getRouteConfiguration().onException(exceptionType1,
exceptionType2, exceptionType2);
+ return this.getRouteConfiguration().onException(exceptionType1,
exceptionType2, exceptionType3);
}
OnExceptionDefinition answer = new OnExceptionDefinition(
Arrays.asList(exceptionType1, exceptionType2, exceptionType3));
@@ -2598,11 +2603,7 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
*/
public Type bean(Object bean) {
BeanDefinition answer = new BeanDefinition();
- if (bean instanceof String str) {
- answer.setRef(str);
- } else {
- answer.setBean(bean);
- }
+ setBeanOrRef(answer, bean);
addOutput(answer);
return asType();
}
@@ -2614,6 +2615,19 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
* @param bean the bean to invoke, or a reference to a bean if the type
is a String
* @return the builder
*/
+ private static void setBeanOrRef(BeanDefinition answer, Object bean) {
+ if (bean instanceof String str) {
+ // type:com.foo.MyBean refers to the class of the bean
+ if (str.startsWith("type:")) {
+ answer.setBeanType(str.substring(5));
+ } else {
+ answer.setRef(str);
+ }
+ } else {
+ answer.setBean(bean);
+ }
+ }
+
public Type bean(Supplier<Object> bean) {
return bean(bean.get());
}
@@ -2629,15 +2643,7 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
*/
public Type bean(Object bean, String method) {
BeanDefinition answer = new BeanDefinition();
- if (bean instanceof String str) {
- if (str.startsWith("type:")) {
- answer.setBeanType(str.substring(5));
- } else {
- answer.setRef(str);
- }
- } else {
- answer.setBean(bean);
- }
+ setBeanOrRef(answer, bean);
answer.setMethod(method);
addOutput(answer);
return asType();
@@ -2678,11 +2684,7 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
*/
public Type bean(Object bean, BeanScope scope) {
BeanDefinition answer = new BeanDefinition();
- if (bean instanceof String str) {
- answer.setRef(str);
- } else {
- answer.setBean(bean);
- }
+ setBeanOrRef(answer, bean);
answer.setScope(scope);
addOutput(answer);
return asType();
@@ -2699,11 +2701,7 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
*/
public Type bean(Object bean, String method, BeanScope scope) {
BeanDefinition answer = new BeanDefinition();
- if (bean instanceof String str) {
- answer.setRef(str);
- } else {
- answer.setBean(bean);
- }
+ setBeanOrRef(answer, bean);
answer.setMethod(method);
answer.setScope(scope);
addOutput(answer);
@@ -3506,7 +3504,8 @@ public abstract class ProcessorDefinition<Type extends
ProcessorDefinition<Type>
* @see org.apache.camel.processor.Enricher
*/
public EnrichClause<ProcessorDefinition<Type>> enrichWith(@AsEndpointUri
EndpointProducerBuilder resourceUri) {
- return enrichWith(resourceUri.getRawUri());
+ // same as the other endpoint builder variants (the uri may be dynamic)
+ return enrichWith(resourceUri, false, false);
}
/**
diff --git
a/core/camel-core/src/test/java/org/apache/camel/model/ProcessorDefinitionBuilderEdgeCasesTest.java
b/core/camel-core/src/test/java/org/apache/camel/model/ProcessorDefinitionBuilderEdgeCasesTest.java
new file mode 100644
index 000000000000..1386706cd623
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/model/ProcessorDefinitionBuilderEdgeCasesTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.model;
+
+import java.io.IOException;
+import java.lang.reflect.Proxy;
+import java.util.List;
+
+import org.apache.camel.BeanScope;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.EndpointProducerBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.RouteConfigurationBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProcessorDefinitionBuilderEdgeCasesTest extends
ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ private RouteDefinition route(RouteBuilder builder) throws Exception {
+ context.addRoutes(builder);
+ context.start();
+ return
context.getRouteDefinitions().get(context.getRouteDefinitions().size() - 1);
+ }
+
+ @Test
+ public void testNoteOnChoice() throws Exception {
+ RouteDefinition route = route(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").choice().note("my
note").when(header("a")).to("mock:a").end();
+ }
+ });
+ assertThat(route.getOutputs().get(0).getNote()).isEqualTo("my note");
+ }
+
+ @Test
+ public void testThrottleWithCorrelationKey() throws Exception {
+ RouteDefinition route = route(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").throttle(header("max"), 7L).to("mock:a");
+ }
+ });
+ ThrottleDefinition throttle = (ThrottleDefinition)
route.getOutputs().get(0);
+ assertThat(throttle.getExpression().getExpression()).isEqualTo("max");
+
assertThat(throttle.getCorrelationExpression().getExpressionType().getExpression()).isEqualTo("7");
+ }
+
+ @Test
+ public void testRouteConfigurationOnExceptionWithThreeExceptions() throws
Exception {
+ context.addRoutes(new RouteConfigurationBuilder() {
+ @Override
+ public void configuration() {
+
routeConfiguration().onException(IOException.class).handled(true).to("mock:io")
+ .onException(IllegalArgumentException.class,
IllegalStateException.class,
+ UnsupportedOperationException.class)
+ .handled(true).to("mock:other");
+ }
+ });
+ List<String> exceptions =
context.getRouteConfigurationDefinitions().get(0).getOnExceptions().get(1).getExceptions();
+
assertThat(exceptions).containsExactly(IllegalArgumentException.class.getName(),
+ IllegalStateException.class.getName(),
UnsupportedOperationException.class.getName());
+ }
+
+ @Test
+ public void testSimpleWithResultTypePrettyAndTrim() throws Exception {
+ route(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").setBody().simple("${body}", String.class,
false, true).to("mock:result");
+ }
+ });
+ getMockEndpoint("mock:result").expectedBodiesReceived("x");
+ template.sendBody("direct:start", " x ");
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testEndDoTryFromBlockInsideCatchAndFinally() throws Exception {
+ RouteDefinition route = route(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .doTry()
+ .to("mock:try")
+ .doCatch(Exception.class)
+ .filter(header("x"))
+ .to("mock:filtered")
+ .endDoTry()
+ .doFinally()
+ .filter(header("y"))
+ .to("mock:finally")
+ .endDoTry()
+ .end()
+ .to("mock:result");
+ }
+ });
+ TryDefinition doTry = (TryDefinition) route.getOutputs().get(0);
+ assertThat(doTry.getCatchClauses()).hasSize(1);
+ assertThat(doTry.getFinallyClause()).isNotNull();
+ assertThat(route.getOutputs().get(1)).isInstanceOf(ToDefinition.class);
+ }
+
+ @Test
+ public void testEnrichWithEndpointBuilderIsDynamic() throws Exception {
+ RouteDefinition route = route(new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:start").enrichWith(producerBuilder("direct:${header.target}")).body((a,
b) -> b).to("mock:result");
+ }
+ });
+ EnrichDefinition enrich = (EnrichDefinition) route.getOutputs().get(0);
+ assertThat(enrich.getExpression().getLanguage()).isEqualTo("simple");
+ }
+
+ @Test
+ public void testBeanTypePrefix() throws Exception {
+ RouteDefinition route = route(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .bean("type:" + MyBean.class.getName())
+ .bean("type:" + MyBean.class.getName(),
BeanScope.Prototype)
+ .bean("type:" + MyBean.class.getName(), "hello",
BeanScope.Prototype)
+ .to("mock:result");
+ }
+ });
+ for (int i = 0; i < 3; i++) {
+ BeanDefinition bean = (BeanDefinition) route.getOutputs().get(i);
+ assertThat(bean.getBeanType()).isEqualTo(MyBean.class.getName());
+ assertThat(bean.getRef()).isNull();
+ }
+
+ getMockEndpoint("mock:result").expectedBodiesReceived("Hello Hello
Hello World");
+ template.sendBody("direct:start", "World");
+ assertMockEndpointsSatisfied();
+ }
+
+ private static EndpointProducerBuilder producerBuilder(String uri) {
+ return (EndpointProducerBuilder)
Proxy.newProxyInstance(EndpointProducerBuilder.class.getClassLoader(),
+ new Class<?>[] { EndpointProducerBuilder.class }, (proxy,
method, args) -> {
+ if ("getRawUri".equals(method.getName()) ||
"getUri".equals(method.getName())) {
+ return uri;
+ }
+ throw new UnsupportedOperationException(method.getName());
+ });
+ }
+
+ public static class MyBean {
+ public String hello(String body) {
+ return "Hello " + body;
+ }
+ }
+}