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 dd1b5087c99 CAMEL-21641: Java DSL: RouteBuilder to support inlined 
component conf… (#16888)
dd1b5087c99 is described below

commit dd1b5087c99dbbfa5dd30fc044bfe25d72fa9797
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jan 23 07:28:57 2025 +0100

    CAMEL-21641: Java DSL: RouteBuilder to support inlined component conf… 
(#16888)
    
    * CAMEL-21641: Java DSL: RouteBuilder to support inlined component 
configurers using lambda style for low-code usage in Java to be in a single 
soure file, that supports jbang exporting (stub)
---
 .../org/apache/camel/builder/RouteBuilder.java     | 99 ++++++++++++++++++++++
 .../component/log/LogComponentCustomizeTest.java   | 53 ++++++++++++
 .../log/LogComponentCustomizeThreeTest.java        | 74 ++++++++++++++++
 .../log/LogComponentCustomizeTwoTest.java          | 53 ++++++++++++
 .../camel/util/function/ThrowingFunction.java      |  2 +-
 .../{ThrowingFunction.java => VoidFunction.java}   | 15 ++--
 6 files changed, 286 insertions(+), 10 deletions(-)

diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
index e20d5b77289..0841e8465cc 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
+import org.apache.camel.Component;
 import org.apache.camel.Endpoint;
 import org.apache.camel.ErrorHandlerFactory;
 import org.apache.camel.Ordered;
@@ -51,6 +52,8 @@ import 
org.apache.camel.model.errorhandler.RefErrorHandlerDefinition;
 import org.apache.camel.model.rest.RestConfigurationDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.rest.RestsDefinition;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.Language;
 import org.apache.camel.spi.OnCamelContextEvent;
 import org.apache.camel.spi.PropertiesComponent;
 import org.apache.camel.spi.Resource;
@@ -62,6 +65,7 @@ import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.function.ThrowingBiConsumer;
 import org.apache.camel.util.function.ThrowingConsumer;
+import org.apache.camel.util.function.VoidFunction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -204,6 +208,101 @@ public abstract class RouteBuilder extends BuilderSupport 
implements RoutesBuild
         // noop
     }
 
+    /**
+     * To customize a given component / data format / language / service using 
Java lambda style. This is only intended
+     * for low-code integrations where you have a minimal set of code and 
files, to make it easy and quick to
+     * customize/configure components directly in a single {@link 
RouteBuilder} class.
+     *
+     * @param type the type such as a component FQN class name
+     * @param code callback for customizing the component instance (if present)
+     */
+    public <T> void customize(Class<T> type, VoidFunction<T> code) throws 
Exception {
+        ObjectHelper.notNull(type, "type", this);
+        ObjectHelper.notNull(code, "code", this);
+
+        // custom may be stored directly in registry so lookup first here
+        T obj = getContext().getRegistry().findSingleByType(type);
+        // try component / dataformat / language
+        if (obj == null && Component.class.isAssignableFrom(type)) {
+            org.apache.camel.spi.annotations.Component ann
+                    = 
type.getAnnotation(org.apache.camel.spi.annotations.Component.class);
+            if (ann != null) {
+                String name = ann.value();
+                // just grab first scheme name if the component has scheme 
alias (eg http,https)
+                if (name.contains(",")) {
+                    name = StringHelper.before(name, ",");
+                }
+                // do not auto-start component as we need to configure it first
+                obj = (T) getCamelContext().getComponent(name, true, false);
+            }
+            if (obj != null && 
!type.getName().equals("org.apache.camel.component.stub.StubComponent")
+                    && 
"org.apache.camel.component.stub.StubComponent".equals(obj.getClass().getName()))
 {
+                // if we run in stub mode then we can't apply the 
configuration as we stubbed the actual component
+                obj = null;
+            }
+        }
+        if (obj == null && DataFormat.class.isAssignableFrom(type)) {
+            // it's maybe a dataformat
+            org.apache.camel.spi.annotations.Dataformat ann
+                    = 
type.getAnnotation(org.apache.camel.spi.annotations.Dataformat.class);
+            if (ann != null) {
+                String name = ann.value();
+                obj = (T) getCamelContext().resolveDataFormat(name);
+            }
+        }
+        if (obj == null && Language.class.isAssignableFrom(type)) {
+            // it's maybe a dataformat
+            org.apache.camel.spi.annotations.Language ann
+                    = 
type.getAnnotation(org.apache.camel.spi.annotations.Language.class);
+            if (ann != null) {
+                String name = ann.value();
+                obj = (T) getCamelContext().resolveLanguage(name);
+            }
+        }
+        // it may be a special service
+        if (obj == null) {
+            obj = getContext().hasService(type);
+        }
+        if (obj != null) {
+            code.apply(obj);
+        }
+    }
+
+    /**
+     * To customize a given component / data format / language / service using 
Java lambda style. This is only intended
+     * for low-code integrations where you have a minimal set of code and 
files, to make it easy and quick to
+     * customize/configure components directly in a single {@link 
RouteBuilder} class.
+     *
+     * @param name the name of the component / service
+     * @param type the type such as a component FQN class name
+     * @param code callback for customizing the component instance (if present)
+     */
+    public <T> void customize(String name, Class<T> type, VoidFunction<T> 
code) throws Exception {
+        ObjectHelper.notNull(name, "name", this);
+        ObjectHelper.notNull(type, "type", this);
+        ObjectHelper.notNull(code, "code", this);
+
+        T obj = getContext().getRegistry().lookupByNameAndType(name, type);
+        if (obj == null && Component.class.isAssignableFrom(type)) {
+            // do not auto-start component as we need to configure it first
+            obj = (T) getCamelContext().getComponent(name, true, false);
+            if (obj != null && !"stub".equals(name)
+                    && 
"org.apache.camel.component.stub.StubComponent".equals(obj.getClass().getName()))
 {
+                // if we run in stub mode then we can't apply the 
configuration as we stubbed the actual component
+                obj = null;
+            }
+        }
+        if (obj == null && DataFormat.class.isAssignableFrom(type)) {
+            obj = (T) getCamelContext().resolveDataFormat(name);
+        }
+        if (obj == null && Language.class.isAssignableFrom(type)) {
+            obj = (T) getCamelContext().resolveLanguage(name);
+        }
+        if (obj != null) {
+            code.apply(obj);
+        }
+    }
+
     /**
      * Binds the bean to the repository (if possible).
      *
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeTest.java
new file mode 100644
index 00000000000..1d0bda20951
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeTest.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.component.log;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class LogComponentCustomizeTest extends ContextTestSupport {
+
+    private final LogCustomFormatterTest.TestExchangeFormatter formatter = new 
LogCustomFormatterTest.TestExchangeFormatter();
+
+    @Test
+    public void testCustomize() throws Exception {
+        Assertions.assertEquals(0, formatter.getCounter());
+
+        template.sendBody("direct:start", "Hello World");
+
+        Assertions.assertEquals(1, formatter.getCounter());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                // customize the log component using java lambda style (using 
the default name)
+                customize(LogComponent.class, l -> {
+                    l.setExchangeFormatter(formatter);
+                });
+
+                from("direct:start")
+                        .to("log:foo");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeThreeTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeThreeTest.java
new file mode 100644
index 00000000000..18b8361ae65
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeThreeTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.component.log;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Handler;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.service.ServiceSupport;
+import org.junit.jupiter.api.Test;
+
+public class LogComponentCustomizeThreeTest extends ContextTestSupport {
+
+    private final MyService myService = new MyService();
+
+    @Test
+    public void testCustomize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("Hello World");
+
+        template.sendBody("direct:start", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                context.addService(myService);
+
+                customize(MyService.class, s -> {
+                    s.setPrefix("Hello");
+                });
+
+                from("direct:start")
+                        .bean(myService)
+                        .to("mock:result");
+            }
+        };
+    }
+
+    private static final class MyService extends ServiceSupport {
+
+        private String prefix;
+
+        public String getPrefix() {
+            return prefix;
+        }
+
+        public void setPrefix(String prefix) {
+            this.prefix = prefix;
+        }
+
+        @Handler
+        public String hello(String body) {
+            return prefix + " " + body;
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeTwoTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeTwoTest.java
new file mode 100644
index 00000000000..e89495bf787
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/log/LogComponentCustomizeTwoTest.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.component.log;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class LogComponentCustomizeTwoTest extends ContextTestSupport {
+
+    private final LogCustomFormatterTest.TestExchangeFormatter formatter = new 
LogCustomFormatterTest.TestExchangeFormatter();
+
+    @Test
+    public void testCustomize() throws Exception {
+        Assertions.assertEquals(0, formatter.getCounter());
+
+        template.sendBody("direct:start", "Hello World");
+
+        Assertions.assertEquals(1, formatter.getCounter());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                // customize the log component using java lambda style
+                customize("log", LogComponent.class, l -> {
+                    l.setExchangeFormatter(formatter);
+                });
+
+                from("direct:start")
+                        .to("log:foo");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
 
b/core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
index eaf005ad08d..debb4cdede1 100644
--- 
a/core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
+++ 
b/core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
@@ -17,7 +17,7 @@
 package org.apache.camel.util.function;
 
 /**
- * Represents a function that accepts a single arguments, produces a result 
and may thrown an exception.
+ * Represents a function that accepts a single arguments, produces a result 
and may throw an exception.
  *
  * @param <I> the type of the input of the function
  * @param <R> the type of the result of the function
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
 
b/core/camel-util/src/main/java/org/apache/camel/util/function/VoidFunction.java
similarity index 65%
copy from 
core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
copy to 
core/camel-util/src/main/java/org/apache/camel/util/function/VoidFunction.java
index eaf005ad08d..c41b591c850 100644
--- 
a/core/camel-util/src/main/java/org/apache/camel/util/function/ThrowingFunction.java
+++ 
b/core/camel-util/src/main/java/org/apache/camel/util/function/VoidFunction.java
@@ -17,22 +17,19 @@
 package org.apache.camel.util.function;
 
 /**
- * Represents a function that accepts a single arguments, produces a result 
and may thrown an exception.
+ * Represents a void function that accepts a single arguments.
  *
  * @param <I> the type of the input of the function
- * @param <R> the type of the result of the function
- * @param <T> the type of the exception the accept method may throw
  *
  * @see       java.util.function.Function
  */
 @FunctionalInterface
-public interface ThrowingFunction<I, R, T extends Throwable> {
+public interface VoidFunction<I> {
+
     /**
-     * Applies this function to the given argument, potentially throwing an 
exception.
+     * Applies this function to the given argument.
      *
-     * @param  in the function argument
-     * @return    the function result
-     * @throws T  the exception that may be thrown
+     * @param in the function argument
      */
-    R apply(I in) throws T;
+    void apply(I in) throws Exception;
 }

Reply via email to