Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
Expand Down Expand Up @@ -64,6 +66,7 @@
import org.apache.camel.spi.RouteTemplateLoaderListener;
import org.apache.camel.spi.RouteTemplateParameterSource;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.LocalBeanRegistry;
import org.apache.camel.support.PatternHelper;
import org.apache.camel.support.RouteTemplateHelper;
import org.apache.camel.util.AntPathMatcher;
Expand Down Expand Up @@ -708,7 +711,16 @@ private static void addProperty(Map<String, Object> prop, String key, Object val

private static void addTemplateBeans(RouteTemplateContext routeTemplateContext, RouteTemplateDefinition target)
throws Exception {
// a bean the caller has bound (TemplatedRouteBuilder or templated route) takes precedence over the template
// bean with the same name, as a parameter takes precedence over the default value of the template
Set<String> callerBeans = Collections.emptySet();
if (routeTemplateContext.getLocalBeanRepository() instanceof LocalBeanRegistry local) {
callerBeans = new HashSet<>(local.keys());
}
for (BeanFactoryDefinition b : target.getTemplateBeans()) {
if (callerBeans.contains(b.getName())) {
continue;
}
// route template beans do not directly support property placeholders
// but need to use rtc.property API calls
b.setScriptPropertyPlaceholders("false");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public TemplatedRouteBuilder parameters(Map<String, Object> parameters) {
}

/**
* Binds the bean to the template local repository (takes precedence over global beans)
* Binds the bean to the template local repository (takes precedence over global beans, and over a template bean
* with the same name)
*
* @param id the id of the bean
* @param bean the bean
Expand All @@ -111,7 +112,8 @@ public TemplatedRouteBuilder bean(String id, Object bean) {
}

/**
* Binds the bean to the template local repository (takes precedence over global beans)
* Binds the bean to the template local repository (takes precedence over global beans, and over a template bean
* with the same name)
*
* @param id the id of the bean
* @param type the type of the bean to associate the binding
Expand All @@ -123,7 +125,8 @@ public TemplatedRouteBuilder bean(String id, Class<?> type, Object bean) {
}

/**
* Binds the bean (via a supplier) to the template local repository (takes precedence over global beans)
* Binds the bean (via a supplier) to the template local repository (takes precedence over global beans, and over a
* template bean with the same name)
*
* @param id the id of the bean
* @param type the type of the bean to associate the binding
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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.builder;

import java.util.concurrent.atomic.AtomicInteger;

import org.apache.camel.ContextTestSupport;
import org.apache.camel.Processor;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.TemplatedRouteDefinition;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* A bean that the caller binds with the same name as a template bean takes precedence over the template bean.
*/
class RouteTemplateCallerBeanTest extends ContextTestSupport {

private final AtomicInteger templateGreetings = new AtomicInteger();

@Override
public boolean isUseRouteBuilder() {
return false;
}

@BeforeEach
void addTemplate() throws Exception {
context.addRoutes(new RouteBuilder() {
@Override
public void configure() {
routeTemplate("myTemplate")
.templateParameter("foo")
.templateBean("greeting", Processor.class, rtc -> {
templateGreetings.incrementAndGet();
return (Processor) e -> e.getMessage().setBody("template");
})
.templateBean("suffix", Processor.class,
rtc -> (Processor) e -> e.getMessage().setBody(e.getMessage().getBody() + "!"))
.from("direct:{{foo}}")
.process("greeting")
.process("suffix");
}
});
context.start();
}

@Test
void builderBeanTakesPrecedence() {
TemplatedRouteBuilder.builder(context, "myTemplate")
.parameter("foo", "one")
.bean("greeting", mine())
.add();
TemplatedRouteBuilder.builder(context, "myTemplate")
.parameter("foo", "two")
.bean("greeting", Processor.class, mine())
.add();

assertThat(template.requestBody("direct:one", "World")).isEqualTo("caller!");
assertThat(template.requestBody("direct:two", "World")).isEqualTo("caller!");
// the template bean with the same name is not created
assertThat(templateGreetings.get()).isZero();
}

@Test
void templatedRouteBeanTakesPrecedence() throws Exception {
TemplatedRouteDefinition def = new TemplatedRouteDefinition();
def.setRouteTemplateRef("myTemplate");
def.parameter("foo", "one").bean("greeting", mine());
((ModelCamelContext) context).addRouteFromTemplatedRoute(def);

assertThat(template.requestBody("direct:one", "World")).isEqualTo("caller!");
assertThat(templateGreetings.get()).isZero();
}

@Test
void configurerBeanTakesPrecedence() {
TemplatedRouteBuilder.builder(context, "myTemplate")
.parameter("foo", "one")
.configure(rtc -> rtc.bind("greeting", Processor.class, mine()))
.add();

assertThat(template.requestBody("direct:one", "World")).isEqualTo("caller!");
}

private static Processor mine() {
return e -> e.getMessage().setBody("caller");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,12 @@ The `org.apache.camel.processor.InterceptSendToEndpointCallback` class is deprec
id. Prior to Camel 4.23 they were accepted, and both processors had the same id at runtime.
- A hardcoded node id in a route template no longer clashes with the same id in a regular route, as the ids of
the route created from the template are prefixed.
- A bean bound by `TemplatedRouteBuilder.bean` or in a `templatedRoute` is now used instead of a `templateBean` with
the same name, which is no longer created. Prior to Camel 4.23 the template bean replaced it when both had the same
type, and was used when the route looked the bean up by the type of the template bean (such as `process("myBean")`).
When the same `RouteTemplateContext` is reused for several `addRouteFromTemplate` calls, the template beans bound for
the first route are now kept as beans of the caller, so the later routes share these instances instead of each
getting new ones. Use a new `RouteTemplateContext` per route to get new template beans for each route.

=== camel-aws2-ddb

Expand Down
6 changes: 6 additions & 0 deletions docs/user-manual/modules/ROOT/pages/route-template.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,12 @@ This is an advanced feature.

See xref:route-template-bean-binding.adoc[Route Template Bean Binding] for details.

A bean that is bound when creating the route (`TemplatedRouteBuilder.bean` or the beans of a `templatedRoute`)
takes precedence over a `templateBean` with the same name, which is then not created. This allows replacing a
template bean, for example with a mock in a test. The template beans are bound to the `RouteTemplateContext` too, so
when the same `RouteTemplateContext` is reused for several `addRouteFromTemplate` calls, the routes after the first
share the template beans created for the first route; use a new `RouteTemplateContext` per route to get new ones.

== Configuring route templates when creating route (advanced)

There may be some special situations where you want to be able to do some custom configuration/code when
Expand Down
Loading