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 c03ad2c71e59 CAMEL-24879: camel-groovy - Bind @BindToRegistry and
@Converter classes from Groovy sources in every runtime (#26709)
c03ad2c71e59 is described below
commit c03ad2c71e59b2a653898897c45cc49d435f88c8
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 22 08:30:23 2026 +0200
CAMEL-24879: camel-groovy - Bind @BindToRegistry and @Converter classes
from Groovy sources in every runtime (#26709)
* CAMEL-24879: camel-groovy - Bind @BindToRegistry and @Converter classes
from Groovy sources in every runtime
The Camel-annotation compile post-processors move from camel-kamelet-main to
camel-support (org.apache.camel.support.compile).
DefaultGroovyScriptCompiler
uses the processors in the registry when there are any (camel-jbang), and
otherwise these built-in ones, so a .groovy file with @BindToRegistry or
@Converter also works after camel export, in camel-main, Spring Boot and
Quarkus. camel-main is not changed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* CAMEL-24879: Address review feedback
Initialise the built-in compile post-processors eagerly as a final field
instead of lazily in postCompile.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* CAMEL-24879: Address review feedback
- BindToRegistryCompilePostProcessor: a lazy bean is bound (and unbound on
reload) with the annotation value, else the simple class name, as an eager
bean is; before it was bound with the FQCN and the value was ignored
- camel-groovy: test the EventNotifier built-in and the lazy bean names
- upgrade guide: note the lazy bean naming
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../apache/camel/catalog/docs/groovy-language.adoc | 12 +-
.../apache/camel/catalog/docs/simple-advanced.adoc | 7 +-
.../src/main/docs/groovy-language.adoc | 12 +-
.../groovy/DefaultGroovyScriptCompiler.java | 25 ++--
.../GroovyDefaultCompilePostProcessorTest.java | 131 +++++++++++++++++++++
.../camel-groovy-default/MaskEmailFunction.groovy | 34 ++++++
.../camel-groovy-default/NamedLazyBean.groovy | 24 ++++
.../camel-groovy-default/OrderConverter.groovy | 27 +++++
.../camel-groovy-default/OrderEventNotifier.groovy | 37 ++++++
.../camel-groovy-default/UnnamedLazyBean.groovy | 24 ++++
.../modules/languages/pages/simple-advanced.adoc | 7 +-
.../BindToRegistryCompilePostProcessor.java | 107 +++++++++++++++++
.../compile/EventNotifierCompilePostProcessor.java | 53 +++++++++
.../compile/TypeConverterCompilePostProcessor.java | 50 ++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 22 +++-
.../modules/ROOT/pages/camel-jbang-beans.adoc | 7 +-
.../injection/AnnotationDependencyInjection.java | 123 +------------------
17 files changed, 551 insertions(+), 151 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
index 6806dcf3ad4a..9d51d0d55a13 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
@@ -192,11 +192,13 @@ with Camel features that would support this such as in
the route DSL and elsewhe
However, there may be some features in Camel where this may not work (yet).
-A compiled class with class-level annotations is handed to the registered
`org.apache.camel.spi.CompilePostProcessor`s
-with a new instance, as the Java DSL does for `.java` sources. With Camel CLI
(`camel run`) that binds a
-`@BindToRegistry` class in the Registry and registers a `@Converter` class as
type converters (the Spring
-and Quarkus annotations are supported as well), so a Groovy source can provide
a bean such as a custom
-xref:languages:simple-advanced.adoc[simple function]. Plain classes without
annotations are not instantiated.
+A compiled class with class-level annotations is handed to the
`org.apache.camel.spi.CompilePostProcessor`s with a
+new instance, as the Java DSL does for `.java` sources. The processors in the
Registry are used when there are any
+(Camel CLI registers processors that also handle the Spring and Quarkus
annotations); otherwise the built-in
+processors for the Camel annotations are used. So in every runtime a
`@BindToRegistry` class is bound in the
+Registry, a `@Converter` class is registered as type converters, and an
`EventNotifier` class is added, and a Groovy
+source can provide a bean such as a custom
xref:languages:simple-advanced.adoc[simple function]. Plain classes
+without annotations are not instantiated.
IMPORTANT: This feature is only intended to include smaller groovy sources as
small functions, DTOs
that makes it easier to use together with Camel for low-code integrations. It
is not
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
index 626317f7bac2..daed623fcfef 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
@@ -1054,9 +1054,10 @@ In dev mode (`--dev`) an edit of the function, in the
`.groovy` file or in the Y
message: the bean is created again on reload and the simple expression
resolves the function again per evaluation
when the `dev` profile is active.
-NOTE: The `@BindToRegistry` binding of a `.groovy` file is done by the Camel
CLI. In a project created with
-`camel export` the file is compiled, but the class is not bound as a bean, so
the function is unknown. For a project
-that is exported, use the inline YAML bean shown above, which works in every
runtime, or write the function in Java.
+NOTE: The `.groovy` file keeps working in a project created with `camel
export`: the Groovy compiler in camel-groovy
+binds a `@BindToRegistry` class itself, so the function is available to Camel
in camel-main, Spring Boot and Quarkus
+alike. The bean lives in Camel's registry, not in the Spring or CDI container,
so it is not a candidate for
+`@Autowired` or `@Inject`; for that, write the class in Java.
== JavaScript Validator
diff --git a/components/camel-groovy/src/main/docs/groovy-language.adoc
b/components/camel-groovy/src/main/docs/groovy-language.adoc
index 6806dcf3ad4a..9d51d0d55a13 100644
--- a/components/camel-groovy/src/main/docs/groovy-language.adoc
+++ b/components/camel-groovy/src/main/docs/groovy-language.adoc
@@ -192,11 +192,13 @@ with Camel features that would support this such as in
the route DSL and elsewhe
However, there may be some features in Camel where this may not work (yet).
-A compiled class with class-level annotations is handed to the registered
`org.apache.camel.spi.CompilePostProcessor`s
-with a new instance, as the Java DSL does for `.java` sources. With Camel CLI
(`camel run`) that binds a
-`@BindToRegistry` class in the Registry and registers a `@Converter` class as
type converters (the Spring
-and Quarkus annotations are supported as well), so a Groovy source can provide
a bean such as a custom
-xref:languages:simple-advanced.adoc[simple function]. Plain classes without
annotations are not instantiated.
+A compiled class with class-level annotations is handed to the
`org.apache.camel.spi.CompilePostProcessor`s with a
+new instance, as the Java DSL does for `.java` sources. The processors in the
Registry are used when there are any
+(Camel CLI registers processors that also handle the Spring and Quarkus
annotations); otherwise the built-in
+processors for the Camel annotations are used. So in every runtime a
`@BindToRegistry` class is bound in the
+Registry, a `@Converter` class is registered as type converters, and an
`EventNotifier` class is added, and a Groovy
+source can provide a bean such as a custom
xref:languages:simple-advanced.adoc[simple function]. Plain classes
+without annotations are not instantiated.
IMPORTANT: This feature is only intended to include smaller groovy sources as
small functions, DTOs
that makes it easier to use together with Camel for low-code integrations. It
is not
diff --git
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
index 6d493c05ec1d..de92be2c30f0 100644
---
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
+++
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/DefaultGroovyScriptCompiler.java
@@ -47,6 +47,9 @@ import org.apache.camel.spi.Resource;
import org.apache.camel.spi.annotations.JdkService;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.SimpleEventNotifierSupport;
+import org.apache.camel.support.compile.BindToRegistryCompilePostProcessor;
+import org.apache.camel.support.compile.EventNotifierCompilePostProcessor;
+import org.apache.camel.support.compile.TypeConverterCompilePostProcessor;
import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
@@ -68,6 +71,10 @@ public class DefaultGroovyScriptCompiler extends
ServiceSupport
private static final Logger LOG =
LoggerFactory.getLogger(DefaultGroovyScriptCompiler.class);
private GroovyPreCompiledClassLoader groovyPreCompiledClassLoader;
+ private final List<CompilePostProcessor> defaultPostProcessors = List.of(
+ new TypeConverterCompilePostProcessor(),
+ new EventNotifierCompilePostProcessor(),
+ new BindToRegistryCompilePostProcessor());
private GroovyScriptClassLoader classLoader;
private CamelContext camelContext;
private EventNotifier notifier;
@@ -374,21 +381,23 @@ public class DefaultGroovyScriptCompiler extends
ServiceSupport
}
/**
- * Runs the registered {@link CompilePostProcessor}s on a compiled class,
as the Java DSL loader does for
- * {@code .java} sources, so annotations such as {@link BindToRegistry}
and {@link org.apache.camel.Converter} (and
- * the Spring and Quarkus equivalents camel-jbang registers) work in
Groovy sources as well. On a recompile (live
- * reload) the bean is created and bound again, replacing the previous one.
+ * Runs the {@link CompilePostProcessor}s on a compiled class, as the Java
DSL loader does for {@code .java}
+ * sources, so annotations such as {@link BindToRegistry} and {@link
org.apache.camel.Converter} work in Groovy
+ * sources as well. The processors in the registry are used when there are
any (camel-jbang registers processors
+ * that also handle the Spring and Quarkus annotations); otherwise the
built-in processors for the Camel annotations
+ * are used, so a Groovy source works the same in every runtime. On a
recompile (live reload) the bean is created
+ * and bound again, replacing the previous one.
*/
private void postCompile(Class<?> clazz, byte[] byteCode) throws Exception
{
- Set<CompilePostProcessor> posts =
camelContext.getRegistry().findByType(CompilePostProcessor.class);
- if (posts == null || posts.isEmpty()) {
- return;
- }
// only annotated classes are instantiated: a plain groovy class or
script is a DTO or a
// function library, and creating it here would only run its
constructor for nothing
if (clazz.getAnnotations().length == 0 ||
Script.class.isAssignableFrom(clazz)) {
return;
}
+ Collection<CompilePostProcessor> posts =
camelContext.getRegistry().findByType(CompilePostProcessor.class);
+ if (posts == null || posts.isEmpty()) {
+ posts = defaultPostProcessors;
+ }
Object instance = null;
BindToRegistry bir = clazz.getAnnotation(BindToRegistry.class);
boolean skip = clazz.isInterface() ||
Modifier.isAbstract(clazz.getModifiers())
diff --git
a/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyDefaultCompilePostProcessorTest.java
b/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyDefaultCompilePostProcessorTest.java
new file mode 100644
index 000000000000..e2f10321747c
--- /dev/null
+++
b/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyDefaultCompilePostProcessorTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.language.groovy;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.EventNotifier;
+import org.apache.camel.spi.SimpleFunction;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Without any {@link CompilePostProcessor} in the registry (as in a
camel-main, Spring Boot or Quarkus application,
+ * unlike camel-jbang) the compiler falls back to the built-in processors for
the Camel annotations.
+ */
+public class GroovyDefaultCompilePostProcessorTest extends CamelTestSupport {
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+
+ DefaultGroovyScriptCompiler compiler = new
DefaultGroovyScriptCompiler();
+ compiler.setCamelContext(context);
+
compiler.setScriptPattern("file:src/test/resources/camel-groovy-default/*");
+ context.addService(compiler);
+
+ return context;
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:mask")
+ .setBody().simple("${maskEmail(${body})}")
+ .to("mock:result");
+
+ from("direct:order")
+ .convertBodyTo(Order.class)
+ .setBody().simple("${body.id}")
+ .to("mock:result");
+
+ // the Groovy sources are compiled when the compiler service
starts, after the routes are created,
+ // so the beans are resolved at runtime
+ from("direct:namedLazy")
+ .toD("bean:named-lazy?method=hello")
+ .to("mock:result");
+
+ from("direct:unnamedLazy")
+ .toD("bean:UnnamedLazyBean?method=hello")
+ .to("mock:result");
+ }
+ };
+ }
+
+ @Test
+ public void testBindToRegistryWithoutRegisteredPostProcessor() throws
Exception {
+
assertTrue(context.getRegistry().findByType(CompilePostProcessor.class).isEmpty());
+ assertInstanceOf(SimpleFunction.class,
context.getRegistry().lookupByName("mask-email-function"));
+
+
getMockEndpoint("mock:result").expectedBodiesReceived("j***@example.com");
+ template.sendBody("direct:mask", "[email protected]");
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testConverterWithoutRegisteredPostProcessor() throws Exception
{
+ assertNotNull(context.getTypeConverterRegistry().lookup(Order.class,
String.class));
+
+ getMockEndpoint("mock:result").expectedBodiesReceived("123");
+ template.sendBody("direct:order", " 123 ");
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testEventNotifierWithoutRegisteredPostProcessor() throws
Exception {
+ Object bean = context.getRegistry().lookupByName("OrderEventNotifier");
+ assertInstanceOf(EventNotifier.class, bean);
+
assertTrue(context.getManagementStrategy().getEventNotifiers().contains(bean),
+ "EventNotifier from the Groovy source should be added to the
management strategy");
+
+ getMockEndpoint("mock:result").expectedMessageCount(1);
+ template.sendBody("direct:order", "123");
+ MockEndpoint.assertIsSatisfied(context);
+
+ // the notifier counts completed exchanges (the event is fired before
sendBody returns)
+ AtomicInteger completed = (AtomicInteger)
bean.getClass().getMethod("getCompleted").invoke(bean);
+ assertEquals(1, completed.get());
+ }
+
+ @Test
+ public void testLazyBeanWithoutRegisteredPostProcessor() throws Exception {
+ // a lazy bean is bound by the annotation value, else by the simple
class name, as an eager bean is
+ assertNotNull(context.getRegistry().lookupByName("named-lazy"));
+ assertNull(context.getRegistry().lookupByName("NamedLazyBean"));
+ assertNotNull(context.getRegistry().lookupByName("UnnamedLazyBean"));
+
+ getMockEndpoint("mock:result").expectedBodiesReceived("named",
"unnamed");
+ template.sendBody("direct:namedLazy", "");
+ template.sendBody("direct:unnamedLazy", "");
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ public record Order(String id) {
+ }
+}
diff --git
a/components/camel-groovy/src/test/resources/camel-groovy-default/MaskEmailFunction.groovy
b/components/camel-groovy/src/test/resources/camel-groovy-default/MaskEmailFunction.groovy
new file mode 100644
index 000000000000..c9e578c534fa
--- /dev/null
+++
b/components/camel-groovy/src/test/resources/camel-groovy-default/MaskEmailFunction.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+import org.apache.camel.BindToRegistry
+import org.apache.camel.Exchange
+import org.apache.camel.spi.SimpleFunction
+
+@BindToRegistry("mask-email-function")
+class MaskEmailFunction implements SimpleFunction {
+
+ String getName() { 'maskEmail' }
+
+ Object apply(Exchange exchange, Object input) {
+ def email = input.toString().trim()
+ int at = email.indexOf('@')
+ if (at <= 0 || at == email.length() - 1) return '***'
+ return email[0] + '***' + email.substring(at)
+ }
+
+}
diff --git
a/components/camel-groovy/src/test/resources/camel-groovy-default/NamedLazyBean.groovy
b/components/camel-groovy/src/test/resources/camel-groovy-default/NamedLazyBean.groovy
new file mode 100644
index 000000000000..f7ffb2d3e067
--- /dev/null
+++
b/components/camel-groovy/src/test/resources/camel-groovy-default/NamedLazyBean.groovy
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+import org.apache.camel.BindToRegistry
+
+@BindToRegistry(value = "named-lazy", lazy = true)
+class NamedLazyBean {
+
+ String hello() { 'named' }
+
+}
diff --git
a/components/camel-groovy/src/test/resources/camel-groovy-default/OrderConverter.groovy
b/components/camel-groovy/src/test/resources/camel-groovy-default/OrderConverter.groovy
new file mode 100644
index 000000000000..de8338abc6ad
--- /dev/null
+++
b/components/camel-groovy/src/test/resources/camel-groovy-default/OrderConverter.groovy
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+import org.apache.camel.Converter
+import
org.apache.camel.language.groovy.GroovyDefaultCompilePostProcessorTest.Order
+
+@Converter
+class OrderConverter {
+
+ @Converter
+ static Order toOrder(String body) {
+ new Order(body.trim())
+ }
+}
diff --git
a/components/camel-groovy/src/test/resources/camel-groovy-default/OrderEventNotifier.groovy
b/components/camel-groovy/src/test/resources/camel-groovy-default/OrderEventNotifier.groovy
new file mode 100644
index 000000000000..48ba4c3ffb40
--- /dev/null
+++
b/components/camel-groovy/src/test/resources/camel-groovy-default/OrderEventNotifier.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+import org.apache.camel.BindToRegistry
+import org.apache.camel.spi.CamelEvent
+import org.apache.camel.support.EventNotifierSupport
+
+import java.util.concurrent.atomic.AtomicInteger
+
+@BindToRegistry
+class OrderEventNotifier extends EventNotifierSupport {
+
+ final AtomicInteger completed = new AtomicInteger()
+
+ @Override
+ boolean isEnabled(CamelEvent event) {
+ event instanceof CamelEvent.ExchangeCompletedEvent
+ }
+
+ @Override
+ void notify(CamelEvent event) {
+ completed.incrementAndGet()
+ }
+}
diff --git
a/components/camel-groovy/src/test/resources/camel-groovy-default/UnnamedLazyBean.groovy
b/components/camel-groovy/src/test/resources/camel-groovy-default/UnnamedLazyBean.groovy
new file mode 100644
index 000000000000..798aa86f2ea6
--- /dev/null
+++
b/components/camel-groovy/src/test/resources/camel-groovy-default/UnnamedLazyBean.groovy
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+import org.apache.camel.BindToRegistry
+
+@BindToRegistry(lazy = true)
+class UnnamedLazyBean {
+
+ String hello() { 'unnamed' }
+
+}
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
index 626317f7bac2..daed623fcfef 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
@@ -1054,9 +1054,10 @@ In dev mode (`--dev`) an edit of the function, in the
`.groovy` file or in the Y
message: the bean is created again on reload and the simple expression
resolves the function again per evaluation
when the `dev` profile is active.
-NOTE: The `@BindToRegistry` binding of a `.groovy` file is done by the Camel
CLI. In a project created with
-`camel export` the file is compiled, but the class is not bound as a bean, so
the function is unknown. For a project
-that is exported, use the inline YAML bean shown above, which works in every
runtime, or write the function in Java.
+NOTE: The `.groovy` file keeps working in a project created with `camel
export`: the Groovy compiler in camel-groovy
+binds a `@BindToRegistry` class itself, so the function is available to Camel
in camel-main, Spring Boot and Quarkus
+alike. The bean lives in Camel's registry, not in the Spring or CDI container,
so it is not a candidate for
+`@Autowired` or `@Inject`; for that, write the class in Java.
== JavaScript Validator
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/compile/BindToRegistryCompilePostProcessor.java
b/core/camel-support/src/main/java/org/apache/camel/support/compile/BindToRegistryCompilePostProcessor.java
new file mode 100644
index 000000000000..2530aebc18a7
--- /dev/null
+++
b/core/camel-support/src/main/java/org/apache/camel/support/compile/BindToRegistryCompilePostProcessor.java
@@ -0,0 +1,107 @@
+/*
+ * 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.support.compile;
+
+import java.util.function.Supplier;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelConfiguration;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Configuration;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.CamelBeanPostProcessor;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * Binds a compiled class annotated with {@link BindToRegistry} or {@link
Configuration} (or that is a
+ * {@link CamelConfiguration}) to the registry, using the {@link
CamelBeanPostProcessor} so the Camel dependency
+ * injection annotations on the class are processed as well. As the class may
be compiled again (live reload) the
+ * previous bean is unbound first.
+ */
+public class BindToRegistryCompilePostProcessor implements
CompilePostProcessor {
+
+ private final boolean lazyBean;
+
+ public BindToRegistryCompilePostProcessor() {
+ this(false);
+ }
+
+ /**
+ * @param lazyBean whether to bind every {@link BindToRegistry} bean
lazily (created on first use), as if
+ * {@link BindToRegistry#lazy()} was set
+ */
+ public BindToRegistryCompilePostProcessor(boolean lazyBean) {
+ this.lazyBean = lazyBean;
+ }
+
+ @Override
+ public void postCompile(CamelContext camelContext, String name, Class<?>
clazz, byte[] byteCode, Object instance)
+ throws Exception {
+
+ BindToRegistry bir = clazz.getAnnotation(BindToRegistry.class);
+ Configuration cfg = clazz.getAnnotation(Configuration.class);
+
+ // special for lazy beans which we must create on-demand
+ if (instance == null && bir != null && (lazyBean || bir.lazy())) {
+ // the bean id is the annotation value, else the simple class name
(as for an eager bean)
+ final String id = ObjectHelper.isNotEmpty(bir.value()) ?
bir.value() : clazz.getSimpleName();
+ final String beanName = name;
+ instance = (Supplier<Object>) () -> {
+ Object answer = camelContext.getInjector().newInstance(clazz);
+ CamelBeanPostProcessor bpp =
PluginHelper.getBeanPostProcessor(camelContext);
+ try {
+ bpp.postProcessBeforeInitialization(answer, beanName);
+ bpp.postProcessAfterInitialization(answer, beanName);
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeException(e);
+ }
+ return answer;
+ };
+ // unbind old bean and register lazy bean
+ camelContext.getRegistry().unbind(id);
+ // use dependency injection factory to perform the task of binding
the bean to registry
+ Runnable task =
PluginHelper.getDependencyInjectionAnnotationFactory(camelContext)
+ .createBindToRegistryFactory(id, instance, clazz,
beanName, false, bir.initMethod(),
+ bir.destroyMethod());
+ task.run();
+ } else {
+ if (bir != null || cfg != null || instance instanceof
CamelConfiguration) {
+ CamelBeanPostProcessor bpp =
PluginHelper.getBeanPostProcessor(camelContext);
+ if (bir != null && ObjectHelper.isNotEmpty(bir.value())) {
+ name = bir.value();
+ } else if (cfg != null &&
ObjectHelper.isNotEmpty(cfg.value())) {
+ name = cfg.value();
+ }
+ // to support hot reloading of beans then we need to enable
unbind mode in bean post processor
+ bpp.setUnbindEnabled(true);
+ try {
+ // this class uses camels own annotations so the bind to
registry happens
+ // automatic by the bean post processor
+ bpp.postProcessBeforeInitialization(instance, name);
+ bpp.postProcessAfterInitialization(instance, name);
+ } finally {
+ bpp.setUnbindEnabled(false);
+ }
+ if (instance instanceof CamelConfiguration cc) {
+ cc.configure(camelContext);
+ }
+ }
+ }
+ }
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/compile/EventNotifierCompilePostProcessor.java
b/core/camel-support/src/main/java/org/apache/camel/support/compile/EventNotifierCompilePostProcessor.java
new file mode 100644
index 000000000000..393678b8e32f
--- /dev/null
+++
b/core/camel-support/src/main/java/org/apache/camel/support/compile/EventNotifierCompilePostProcessor.java
@@ -0,0 +1,53 @@
+/*
+ * 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.support.compile;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.EventNotifier;
+import org.apache.camel.spi.ManagementStrategy;
+
+/**
+ * Adds a compiled class that is an {@link EventNotifier} to the {@link
ManagementStrategy}. As the class may be
+ * compiled again (live reload) the previous notifier of the same name is
removed first.
+ */
+public class EventNotifierCompilePostProcessor implements CompilePostProcessor
{
+
+ private final Map<String, EventNotifier> notifiers = new HashMap<>();
+
+ @Override
+ public void postCompile(CamelContext camelContext, String name, Class<?>
clazz, byte[] byteCode, Object instance)
+ throws Exception {
+ if (instance instanceof EventNotifier en) {
+ ManagementStrategy ms = camelContext.getManagementStrategy();
+ if (ms != null) {
+ notifiers.compute(name, (key, old) -> {
+ // remove previous instance
+ if (old != null) {
+ ms.removeEventNotifier(old);
+ }
+ // and new notifier
+ ms.addEventNotifier(en);
+ return en;
+ });
+ }
+ }
+ }
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/compile/TypeConverterCompilePostProcessor.java
b/core/camel-support/src/main/java/org/apache/camel/support/compile/TypeConverterCompilePostProcessor.java
new file mode 100644
index 000000000000..c25ba37508fd
--- /dev/null
+++
b/core/camel-support/src/main/java/org/apache/camel/support/compile/TypeConverterCompilePostProcessor.java
@@ -0,0 +1,50 @@
+/*
+ * 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.support.compile;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Converter;
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.TypeConverterExists;
+import org.apache.camel.spi.CompilePostProcessor;
+import org.apache.camel.spi.TypeConverterRegistry;
+
+/**
+ * Registers a compiled class annotated with {@link Converter} as type
converters. As the class may be compiled again
+ * (live reload) the converters override any existing ones.
+ */
+public class TypeConverterCompilePostProcessor implements CompilePostProcessor
{
+
+ @Override
+ public void postCompile(CamelContext camelContext, String name, Class<?>
clazz, byte[] byteCode, Object instance)
+ throws Exception {
+ if (clazz.isAnnotationPresent(Converter.class)) {
+ TypeConverterRegistry tcr =
camelContext.getTypeConverterRegistry();
+ TypeConverterExists exists = tcr.getTypeConverterExists();
+ LoggingLevel level = tcr.getTypeConverterExistsLoggingLevel();
+ // force type converter to override as we could be re-loading
+ tcr.setTypeConverterExists(TypeConverterExists.Override);
+ tcr.setTypeConverterExistsLoggingLevel(LoggingLevel.OFF);
+ try {
+ tcr.addTypeConverters(clazz);
+ } finally {
+ tcr.setTypeConverterExists(exists);
+ tcr.setTypeConverterExistsLoggingLevel(level);
+ }
+ }
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index af6fe2ebd35f..cd41d1f7fb46 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -144,11 +144,23 @@ script languages and the Camel 4 API use; `request` and
`in` stay as its older n
provided its own global variable named `message` is now hidden by the exchange
variable, like the other exchange
variable names.
-The Groovy script compiler now runs the registered
`org.apache.camel.spi.CompilePostProcessor`s on a compiled
-class that has class-level annotations, with a new instance of the class, as
the Java DSL loader does for `.java`
-sources. In the Camel CLI this binds a `@BindToRegistry` class and registers a
`@Converter` class (and handles the
-Spring and Quarkus annotations) from a `.groovy` file, and binds it again on a
reload in dev mode. A Groovy class
-without annotations, and a Groovy script, is not instantiated, as before.
+The Groovy script compiler now runs the
`org.apache.camel.spi.CompilePostProcessor`s on a compiled class that has
+class-level annotations, with a new instance of the class, as the Java DSL
loader does for `.java` sources. The
+processors in the registry are used when there are any (the Camel CLI
registers processors that also handle the
+Spring and Quarkus annotations); otherwise built-in processors for the Camel
annotations are used. So in every runtime
+(camel-main, Spring Boot, Quarkus) a `@BindToRegistry` or `@Configuration`
class from a `.groovy` file is bound in
+Camel's registry, a `@Converter` class is registered as type converters, and
an `EventNotifier` class is added to
+the management strategy; in the Camel CLI the bean is bound again on a reload
in dev mode. A Groovy class without
+annotations, and a Groovy script, is not instantiated, as before.
+
+The Camel-annotation compile post-processors that camel-kamelet-main used
internally are now public classes in
+camel-support:
`org.apache.camel.support.compile.BindToRegistryCompilePostProcessor`,
+`TypeConverterCompilePostProcessor` and `EventNotifierCompilePostProcessor`.
+
+A lazy `@BindToRegistry` class from a compiled `.java` or `.groovy` source
(`lazy = true`, or `camel run --lazy-bean`
+in the Camel CLI) is now bound with the name from the annotation, or else the
simple class name, the same as an eager
+`@BindToRegistry` class. Before, the bean was bound with the fully qualified
class name and the annotation value was
+ignored, so a class in a package, or with a name in the annotation, was not
found by the expected name.
=== camel-djl (Breaking change)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
index 8bbfa4e7edf5..b68f14438609 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-beans.adoc
@@ -79,9 +79,10 @@ NOTE: A `.groovy` file is compiled after the beans of the
YAML and XML files hav
`- beans:` entry cannot refer to a Groovy class by its `type`; use
`@BindToRegistry` on the class instead, or
create the bean with an inline Groovy `script` in the `- beans:` entry.
-NOTE: The annotations on a `.groovy` file are handled by the Camel CLI only.
In a project created with
-`camel export` the file is compiled, but the class is not bound as a bean. For
a project that is exported, create
-the bean with an inline Groovy `script` in a `- beans:` entry, which works in
every runtime, or write it in Java.
+NOTE: The Camel annotations (`@BindToRegistry`, `@Configuration`,
`@Converter`) on a `.groovy` file keep working in
+a project created with `camel export`, as the Groovy compiler in camel-groovy
handles them itself in every runtime;
+the Spring and Quarkus annotations are handled by the Camel CLI only. The bean
lives in Camel's registry, not in the
+Spring or CDI container.
== Defining beans in XML DSL
diff --git
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
index 0fcedcdd1f54..cc7f280015da 100644
---
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
+++
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/injection/AnnotationDependencyInjection.java
@@ -18,28 +18,20 @@ package org.apache.camel.main.injection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
import java.util.function.Supplier;
-import org.apache.camel.BindToRegistry;
-import org.apache.camel.CamelConfiguration;
import org.apache.camel.CamelContext;
-import org.apache.camel.Configuration;
-import org.apache.camel.Converter;
-import org.apache.camel.LoggingLevel;
import org.apache.camel.NoSuchBeanException;
import org.apache.camel.RuntimeCamelException;
-import org.apache.camel.TypeConverterExists;
import org.apache.camel.impl.engine.CamelPostProcessorHelper;
import org.apache.camel.spi.CamelBeanPostProcessor;
import org.apache.camel.spi.CamelBeanPostProcessorInjector;
import org.apache.camel.spi.CompilePostProcessor;
-import org.apache.camel.spi.EventNotifier;
-import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.spi.Registry;
-import org.apache.camel.spi.TypeConverterRegistry;
import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.compile.BindToRegistryCompilePostProcessor;
+import org.apache.camel.support.compile.EventNotifierCompilePostProcessor;
+import org.apache.camel.support.compile.TypeConverterCompilePostProcessor;
import org.apache.camel.util.AnnotationHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ReflectionHelper;
@@ -79,7 +71,7 @@ public final class AnnotationDependencyInjection {
// camel / common
registry.bind("CamelTypeConverterCompilePostProcessor", new
TypeConverterCompilePostProcessor());
registry.bind("CamelEventNotifierCompilePostProcessor", new
EventNotifierCompilePostProcessor());
- registry.bind("CamelBindToRegistryCompilePostProcessor", new
BindToRegistryCompilePostProcessor());
+ registry.bind("CamelBindToRegistryCompilePostProcessor", new
BindToRegistryCompilePostProcessor(lazyBean));
// spring
registry.bind("SpringAnnotationCompilePostProcessor", new
SpringAnnotationCompilePostProcessor());
cbbp.addCamelBeanPostProjectInjector(new
SpringBeanPostProcessorInjector(context));
@@ -88,113 +80,6 @@ public final class AnnotationDependencyInjection {
cbbp.addCamelBeanPostProjectInjector(new
QuarkusBeanPostProcessorInjector(context));
}
- private static class TypeConverterCompilePostProcessor implements
CompilePostProcessor {
-
- @Override
- public void postCompile(CamelContext camelContext, String name,
Class<?> clazz, byte[] byteCode, Object instance)
- throws Exception {
- if (clazz.isAnnotationPresent(Converter.class)) {
- TypeConverterRegistry tcr =
camelContext.getTypeConverterRegistry();
- TypeConverterExists exists = tcr.getTypeConverterExists();
- LoggingLevel level = tcr.getTypeConverterExistsLoggingLevel();
- // force type converter to override as we could be re-loading
- tcr.setTypeConverterExists(TypeConverterExists.Override);
- tcr.setTypeConverterExistsLoggingLevel(LoggingLevel.OFF);
- try {
- tcr.addTypeConverters(clazz);
- } finally {
- tcr.setTypeConverterExists(exists);
- tcr.setTypeConverterExistsLoggingLevel(level);
- }
- }
- }
- }
-
- private static class EventNotifierCompilePostProcessor implements
CompilePostProcessor {
-
- private final Map<String, EventNotifier> notifiers = new HashMap<>();
-
- @Override
- public void postCompile(CamelContext camelContext, String name,
Class<?> clazz, byte[] byteCode, Object instance)
- throws Exception {
- if (instance == null) {
- return;
- }
-
- if (instance instanceof EventNotifier) {
- ManagementStrategy ms = camelContext.getManagementStrategy();
- if (ms != null) {
- notifiers.compute(name, (key, old) -> {
- // remove previous instance
- if (old != null) {
- ms.removeEventNotifier(old);
- }
- // and new notifier
- EventNotifier en = (EventNotifier) instance;
- ms.addEventNotifier(en);
- return en;
- });
- }
- }
- }
- }
-
- private class BindToRegistryCompilePostProcessor implements
CompilePostProcessor {
-
- @Override
- public void postCompile(CamelContext camelContext, String name,
Class<?> clazz, byte[] byteCode, Object instance)
- throws Exception {
-
- BindToRegistry bir = clazz.getAnnotation(BindToRegistry.class);
- Configuration cfg = clazz.getAnnotation(Configuration.class);
-
- // special for lazy beans which we must create on-demand
- if (instance == null && bir != null && (lazyBean || bir.lazy())) {
- final String beanName = bir.value();
- instance = (Supplier<Object>) () -> {
- Object answer =
camelContext.getInjector().newInstance(clazz);
- CamelBeanPostProcessor bpp =
PluginHelper.getBeanPostProcessor(camelContext);
- try {
- bpp.postProcessBeforeInitialization(answer, beanName);
- bpp.postProcessAfterInitialization(answer, beanName);
- } catch (Exception e) {
- throw RuntimeCamelException.wrapRuntimeException(e);
- }
- return answer;
- };
- // unbind old bean and register lazy bean
- camelContext.getRegistry().unbind(beanName);
- // use dependency injection factory to perform the task of
binding the bean to registry
- Runnable task =
PluginHelper.getDependencyInjectionAnnotationFactory(camelContext)
- .createBindToRegistryFactory(name, instance, clazz,
beanName, false, bir.initMethod(),
- bir.destroyMethod());
- task.run();
- } else {
- if (bir != null || cfg != null || instance instanceof
CamelConfiguration) {
- CamelBeanPostProcessor bpp =
PluginHelper.getBeanPostProcessor(camelContext);
- if (bir != null && ObjectHelper.isNotEmpty(bir.value())) {
- name = bir.value();
- } else if (cfg != null &&
ObjectHelper.isNotEmpty(cfg.value())) {
- name = cfg.value();
- }
- // to support hot reloading of beans then we need to
enable unbind mode in bean post processor
- bpp.setUnbindEnabled(true);
- try {
- // this class uses camels own annotations so the bind
to registry happens
- // automatic by the bean post processor
- bpp.postProcessBeforeInitialization(instance, name);
- bpp.postProcessAfterInitialization(instance, name);
- } finally {
- bpp.setUnbindEnabled(false);
- }
- if (instance instanceof CamelConfiguration) {
- ((CamelConfiguration)
instance).configure(camelContext);
- }
- }
- }
- }
- }
-
private class SpringAnnotationCompilePostProcessor implements
CompilePostProcessor {
@Override