You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2024/05/02 12:15:03 UTC
(camel) 08/09: CAMEL-20514: camel-model - Add support for bean constructors for beans in route templates or kamelets
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch kamelet-ctr
in repository https://gitbox.apache.org/repos/asf/camel.git
commit bb4faac348eab61a30dea95f2158fad0d1ac2c63
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu May 2 14:11:13 2024 +0200
CAMEL-20514: camel-model - Add support for bean constructors for beans in route templates or kamelets
---
.../kamelet/KameletLocalBeanInitDestroyTest.java | 86 ++++++++++++++++++++++
.../src/main/java/org/apache/camel/Route.java | 8 ++
.../org/apache/camel/RouteTemplateContext.java | 8 ++
.../org/apache/camel/impl/engine/DefaultRoute.java | 15 ++++
.../org/apache/camel/model/BeanModelHelper.java | 42 +++++++++--
.../camel/model/DefaultRouteTemplateContext.java | 5 ++
.../org/apache/camel/reifier/RouteReifier.java | 26 +++++++
.../apache/camel/support/LocalBeanRegistry.java | 25 +++++++
8 files changed, 209 insertions(+), 6 deletions(-)
diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanInitDestroyTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanInitDestroyTest.java
new file mode 100644
index 00000000000..cd042bf1794
--- /dev/null
+++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanInitDestroyTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.kamelet;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class KameletLocalBeanInitDestroyTest extends CamelTestSupport {
+
+ private static final AtomicBoolean CLOSED = new AtomicBoolean();
+
+ @Test
+ public void testOne() throws Exception {
+ getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Joes");
+
+ template.sendBody("direct:start", "John");
+
+ MockEndpoint.assertIsSatisfied(context);
+
+ context.stop();
+
+ Assertions.assertTrue(CLOSED.get(), "Should call destroy method on local bean");
+ }
+
+ // **********************************************
+ //
+ // test set-up
+ //
+ // **********************************************
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ routeTemplate("whereTo")
+ .templateBean("myBar").typeClass(MyBar.class).initMethod("startupMe").destroyMethod("destroyMe").end()
+ .from("kamelet:source")
+ // must use {{myBar}} to refer to the local bean
+ .to("bean:{{myBar}}");
+
+ from("direct:start")
+ .kamelet("whereTo")
+ .to("mock:result");
+ }
+ };
+ }
+
+ public static class MyBar {
+
+ private String bar;
+
+ public void startupMe() {
+ this.bar = "Joes";
+ }
+
+ public void destroyMe() {
+ this.bar = "closed";
+ CLOSED.set(true);
+ }
+
+ public String where(String name) {
+ return "Hi " + name + " we are going to " + bar;
+ }
+ }
+}
diff --git a/core/camel-api/src/main/java/org/apache/camel/Route.java b/core/camel-api/src/main/java/org/apache/camel/Route.java
index d247d02240a..6c7f46c4c8b 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Route.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Route.java
@@ -211,6 +211,14 @@ public interface Route extends RuntimeConfiguration {
*/
void addService(Service service);
+ /**
+ * Adds a service to this route
+ *
+ * @param service the service
+ * @param forceStop whether to force stopping the service when the route stops
+ */
+ void addService(Service service, boolean forceStop);
+
/**
* Returns a navigator to navigate this route by navigating all the {@link Processor}s.
*
diff --git a/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java b/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
index 855353d44b9..35cba9e23c8 100644
--- a/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
@@ -92,6 +92,14 @@ public interface RouteTemplateContext extends HasCamelContext {
*/
void bindAsPrototype(String id, Class<?> type, Supplier<Object> bean);
+ /**
+ * Registers an optional destroy method to invoke on the bean when stopping Camel.
+ *
+ * @param id the id of the bean
+ * @param method the destroy method name
+ */
+ void registerDestroyMethod(String id, String method);
+
/**
* Gets the property with the given name
*
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
index be9e8836abb..003e83bef5b 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java
@@ -107,6 +107,7 @@ public class DefaultRoute extends ServiceSupport implements Route {
private final Endpoint endpoint;
private final Map<String, Object> properties = new HashMap<>();
private final List<Service> services = new ArrayList<>();
+ private final List<Service> servicesToStop = new ArrayList<>();
private final StopWatch stopWatch = new StopWatch(false);
private RouteError routeError;
private Integer startupOrder;
@@ -236,6 +237,17 @@ public class DefaultRoute extends ServiceSupport implements Route {
}
}
+ @Override
+ public void addService(Service service, boolean forceStop) {
+ if (forceStop) {
+ if (!servicesToStop.contains(service)) {
+ servicesToStop.add(service);
+ }
+ } else {
+ addService(service);
+ }
+ }
+
@Override
public void warmUp() {
// noop
@@ -274,6 +286,9 @@ public class DefaultRoute extends ServiceSupport implements Route {
protected void doShutdown() throws Exception {
// clear services when shutting down
services.clear();
+ // shutdown forced services
+ ServiceHelper.stopAndShutdownService(servicesToStop);
+ servicesToStop.clear();
}
@Override
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
index afdaddc6c9e..c949f1961a8 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/BeanModelHelper.java
@@ -26,6 +26,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.NoSuchBeanException;
import org.apache.camel.RouteTemplateContext;
+import org.apache.camel.RuntimeCamelException;
import org.apache.camel.model.app.RegistryBeanDefinition;
import org.apache.camel.spi.ExchangeFactory;
import org.apache.camel.spi.Language;
@@ -144,8 +145,6 @@ public final class BeanModelHelper {
return target;
}
- // TODO: init and destroy method
-
/**
* Creates and binds the bean to the route-template repository (local beans for kamelets).
*
@@ -191,12 +190,24 @@ public final class BeanModelHelper {
// and memorize so the script is only evaluated once and the local bean is the same
// if a route template refers to the local bean multiple times
routeTemplateContext.bind(def.getName(), clazz, Suppliers.memorize(() -> {
+ Object local;
Map<String, Object> bindings = new HashMap<>();
// use rtx as the short-hand name, as context would imply its CamelContext
bindings.put("rtc", routeTemplateContext);
- Object local = slan.evaluate(script, bindings, Object.class);
- if (!props.isEmpty()) {
- PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
+ try {
+ local = slan.evaluate(script, bindings, Object.class);
+ if (!props.isEmpty()) {
+ PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
+ }
+ if (def.getInitMethod() != null) {
+ org.apache.camel.support.ObjectHelper.invokeMethodSafe(def.getInitMethod(), local);
+ }
+ if (def.getDestroyMethod() != null) {
+ routeTemplateContext.registerDestroyMethod(def.getName(), def.getDestroyMethod());
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Cannot create bean: " + def.getType(), e);
}
return local;
}));
@@ -215,10 +226,23 @@ public final class BeanModelHelper {
if (!props.isEmpty()) {
PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
}
+ if (def.getInitMethod() != null) {
+ try {
+ org.apache.camel.support.ObjectHelper.invokeMethodSafe(def.getInitMethod(), local);
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeException(e);
+ }
+ }
+ if (def.getDestroyMethod() != null) {
+ routeTemplateContext.registerDestroyMethod(def.getName(), def.getDestroyMethod());
+ }
return local;
} else {
return null;
}
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Cannot create bean: " + def.getType(), e);
} finally {
ef.release(dummy);
}
@@ -267,10 +291,16 @@ public final class BeanModelHelper {
PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, def.getProperties());
}
}
+ if (def.getInitMethod() != null) {
+ org.apache.camel.support.ObjectHelper.invokeMethodSafe(def.getInitMethod(), local);
+ }
+ if (def.getDestroyMethod() != null) {
+ routeTemplateContext.registerDestroyMethod(def.getName(), def.getDestroyMethod());
+ }
return local;
} catch (Exception e) {
throw new IllegalStateException(
- "Cannot create bean: " + def.getType());
+ "Cannot create bean: " + def.getType(), e);
}
}));
} else {
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java b/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
index 07649447ae7..68f85c0395c 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
@@ -85,6 +85,11 @@ public final class DefaultRouteTemplateContext implements RouteTemplateContext {
registry.bindAsPrototype(id, type, bean);
}
+ @Override
+ public void registerDestroyMethod(String id, String method) {
+ registry.registerDestroyMethod(id, method);
+ }
+
@Override
public Object getProperty(String name) {
return parameters.get(name);
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
index a41d8cbf34c..614084798e9 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.reifier;
+import java.io.Closeable;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -31,6 +33,7 @@ import org.apache.camel.FailedToCreateRouteException;
import org.apache.camel.Processor;
import org.apache.camel.Route;
import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.Service;
import org.apache.camel.ServiceStatus;
import org.apache.camel.ShutdownRoute;
import org.apache.camel.ShutdownRunningTask;
@@ -42,6 +45,7 @@ import org.apache.camel.model.RouteDefinition;
import org.apache.camel.processor.ContractAdvice;
import org.apache.camel.processor.RoutePipeline;
import org.apache.camel.reifier.rest.RestBindingReifier;
+import org.apache.camel.spi.BeanRepository;
import org.apache.camel.spi.CamelInternalProcessorAdvice;
import org.apache.camel.spi.Contract;
import org.apache.camel.spi.ErrorHandlerAware;
@@ -53,6 +57,8 @@ import org.apache.camel.spi.RoutePolicy;
import org.apache.camel.spi.RoutePolicyFactory;
import org.apache.camel.support.ExchangeHelper;
import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.IOHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -367,6 +373,26 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
camelContext.getCamelContextExtension().addBootstrap(route::clearRouteModel);
}
+ if (definition.getRouteTemplateContext() != null) {
+ // make route stop beans from the local repository (route templates / kamelets)
+ Service wrapper = new ServiceSupport() {
+ @Override
+ protected void doStop() throws Exception {
+ close();
+ }
+
+ @Override
+ public void close() throws IOException {
+ BeanRepository repo = definition.getRouteTemplateContext().getLocalBeanRepository();
+ if (repo instanceof Closeable obj) {
+ IOHelper.close(obj);
+ }
+ super.close();
+ }
+ };
+ route.addService(wrapper, true);
+ }
+
return route;
}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java b/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java
index d43814d56c9..42d364542a5 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java
@@ -16,7 +16,10 @@
*/
package org.apache.camel.support;
+import java.io.IOException;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
/**
@@ -26,9 +29,15 @@ import java.util.Set;
*/
public final class LocalBeanRegistry extends SupplierRegistry {
+ private final Map<String, String> destroyMethods = new HashMap<>();
+
public LocalBeanRegistry() {
}
+ public void registerDestroyMethod(String id, String method) {
+ destroyMethods.put(id, method);
+ }
+
/**
* Makes a copy of this registry
*/
@@ -42,4 +51,20 @@ public final class LocalBeanRegistry extends SupplierRegistry {
return Collections.unmodifiableSet(keySet());
}
+ @Override
+ public void close() throws IOException {
+ // stop all beans that has destroy method
+ destroyMethods.forEach((id, method) -> {
+ Object bean = lookupByName(id);
+ if (bean != null) {
+ try {
+ org.apache.camel.support.ObjectHelper.invokeMethodSafe(method, bean);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ });
+ destroyMethods.clear();
+ super.close();
+ }
}