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 2023/09/27 07:34:20 UTC
[camel] 02/04: CAMEL-19808: camel-core-model - Add init and destroy method to
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch init-bean
in repository https://gitbox.apache.org/repos/asf/camel.git
commit a81f7cf409537155e90ffd30f3a5237507276f12
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Sep 27 08:15:08 2023 +0200
CAMEL-19808: camel-core-model - Add init and destroy method to <bean>
---
.../org/apache/camel/support/ObjectHelper.java | 32 ++++++++++
.../camel/dsl/xml/io/XmlRoutesBuilderLoader.java | 71 ++++++++++++++++++----
.../apache/camel/dsl/xml/io/XmlLoadAppTest.java | 37 +++++++++++
.../camel/dsl/xml/io/beans/MyDestroyBean.java | 63 +++++++++++++++++++
.../org/apache/camel/dsl/xml/io/camel-app8.xml | 39 ++++++++++++
5 files changed, 229 insertions(+), 13 deletions(-)
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java
index 1add25b7d88..decca85f8b6 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java
@@ -39,6 +39,7 @@ import org.apache.camel.Message;
import org.apache.camel.Ordered;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.TypeConverter;
+import org.apache.camel.util.ReflectionHelper;
import org.apache.camel.util.Scanner;
import org.apache.camel.util.StringHelper;
@@ -386,6 +387,37 @@ public final class ObjectHelper {
return answer;
}
+ /**
+ * A helper method to invoke a method via reflection in a safe way by allowing to invoke methods that are not
+ * accessible by default and wrap any exceptions as {@link RuntimeCamelException} instances
+ *
+ * @param name the method name
+ * @param instance the object instance (or null for static methods)
+ * @param parameters the parameters to the method
+ * @return the result of the method invocation
+ */
+ public static Object invokeMethodSafe(String name, Object instance, Object... parameters)
+ throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+
+ // find method first
+ Class<?>[] arr = null;
+ if (parameters != null) {
+ arr = new Class[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ Object p = parameters[i];
+ if (p != null) {
+ arr[i] = p.getClass();
+ }
+ }
+ }
+ Method m = ReflectionHelper.findMethod(instance.getClass(), name, arr);
+ if (m != null) {
+ return invokeMethodSafe(m, instance, parameters);
+ } else {
+ throw new NoSuchMethodException(name);
+ }
+ }
+
/**
* A helper method to create a new instance of a type using the default constructor arguments.
*/
diff --git a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
index 604378a65ba..d44a6941260 100644
--- a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
+++ b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.xml.io;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -50,8 +51,10 @@ import org.apache.camel.model.rest.RestsDefinition;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.annotations.RoutesLoader;
import org.apache.camel.support.CachedResource;
+import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.PropertyBindingSupport;
import org.apache.camel.support.scan.PackageScanHelper;
+import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.StringHelper;
import org.apache.camel.xml.io.util.XmlStreamDetector;
import org.apache.camel.xml.io.util.XmlStreamInfo;
@@ -71,6 +74,7 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
private final Map<String, XmlStreamInfo> xmlInfoCache = new ConcurrentHashMap<>();
private final Map<String, BeansDefinition> camelAppCache = new ConcurrentHashMap<>();
private final List<RegistryBeanDefinition> delayedRegistrations = new ArrayList<>();
+ private final Map<String, KeyValueHolder<Object, String>> beansToDestroy = new LinkedHashMap<>();
private final AtomicInteger counter = new AtomicInteger(0);
@@ -137,14 +141,12 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
.parseTemplatedRoutesDefinition()
.ifPresent(this::setTemplatedRouteCollection);
- case "rests", "rest" ->
- new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
- .parseRestsDefinition()
- .ifPresent(this::setRestCollection);
- case "routes", "route" ->
- new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
- .parseRoutesDefinition()
- .ifPresent(this::addRoutes);
+ case "rests", "rest" -> new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
+ .parseRestsDefinition()
+ .ifPresent(this::setRestCollection);
+ case "routes", "route" -> new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
+ .parseRoutesDefinition()
+ .ifPresent(this::addRoutes);
default -> {
}
}
@@ -353,12 +355,8 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
if (def.getProperties() != null && !def.getProperties().isEmpty()) {
PropertyBindingSupport.setPropertiesOnTarget(getCamelContext(), target, def.getProperties());
}
- getCamelContext().getRegistry().unbind(name);
- getCamelContext().getRegistry().bind(name, target);
- // register bean in model
- Model model = getCamelContext().getCamelContextExtension().getContextPlugin(Model.class);
- model.addRegistryBean(def);
+ bindBean(def, name, target);
} catch (Exception e) {
if (delayIfFailed) {
@@ -370,4 +368,51 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
}
}
+ protected void bindBean(RegistryBeanDefinition def, String name, Object target) throws Exception {
+ // destroy and unbind any existing bean
+ destroyBean(name, true);
+ getCamelContext().getRegistry().unbind(name);
+
+ // invoke init method and register bean
+ String initMethod = def.getInitMethod();
+ if (initMethod != null) {
+ ObjectHelper.invokeMethodSafe(initMethod, target);
+ }
+ getCamelContext().getRegistry().bind(name, target);
+
+ // remember to destroy bean on shutdown
+ if (def.getDestroyMethod() != null) {
+ beansToDestroy.put(name, new KeyValueHolder<>(target, def.getDestroyMethod()));
+ }
+
+ // register bean in model
+ Model model = getCamelContext().getCamelContextExtension().getContextPlugin(Model.class);
+ model.addRegistryBean(def);
+ }
+
+ protected void destroyBean(String name, boolean remove) {
+ var holder = remove ? beansToDestroy.remove(name) : beansToDestroy.get(name);
+ if (holder != null) {
+ String destroyMethod = holder.getValue();
+ Object target = holder.getKey();
+ try {
+ ObjectHelper.invokeMethodSafe(destroyMethod, target);
+ } catch (Exception e) {
+ LOG.warn("Error invoking destroy method: {} on bean: {} due to: {}. This exception is ignored.",
+ destroyMethod, target, e.getMessage(), e);
+ }
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ super.doStop();
+
+ // beans should trigger destroy methods on shutdown
+ for (String name : beansToDestroy.keySet()) {
+ destroyBean(name, false);
+ }
+ beansToDestroy.clear();
+ }
+
}
diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
index 3f031dccd43..d5984129b8d 100644
--- a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
+++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlLoadAppTest.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.xml.io;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.dsl.xml.io.beans.GreeterMessage;
+import org.apache.camel.dsl.xml.io.beans.MyDestroyBean;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.RoutesLoader;
@@ -25,7 +26,9 @@ import org.apache.camel.support.PluginHelper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class XmlLoadAppTest {
@@ -190,4 +193,38 @@ public class XmlLoadAppTest {
}
}
+ @Test
+ public void testLoadCamelAppWithBeanInitDestroy() throws Exception {
+ try (DefaultCamelContext context = new DefaultCamelContext()) {
+ context.start();
+
+ assertFalse(MyDestroyBean.initCalled.get());
+ assertFalse(MyDestroyBean.destroyCalled.get());
+
+ Resource resource = PluginHelper.getResourceLoader(context).resolveResource(
+ "/org/apache/camel/dsl/xml/io/camel-app8.xml");
+
+ RoutesLoader routesLoader = PluginHelper.getRoutesLoader(context);
+ routesLoader.preParseRoute(resource, false);
+ routesLoader.loadRoutes(resource);
+
+ assertNotNull(context.getRoute("r8"), "Loaded r8 route should be there");
+ assertEquals(1, context.getRoutes().size());
+
+ // test that loaded route works
+ MockEndpoint y8 = context.getEndpoint("mock:y8", MockEndpoint.class);
+ y8.expectedBodiesReceived("Hello Neptun. I am Camel and 45 years old!");
+ context.createProducerTemplate().sendBody("direct:x8", "Neptun");
+ y8.assertIsSatisfied();
+
+ assertTrue(MyDestroyBean.initCalled.get());
+ assertFalse(MyDestroyBean.destroyCalled.get());
+
+ context.stop();
+
+ assertTrue(MyDestroyBean.initCalled.get());
+ assertTrue(MyDestroyBean.destroyCalled.get());
+ }
+ }
+
}
diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyDestroyBean.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyDestroyBean.java
new file mode 100644
index 00000000000..e92c8bf6207
--- /dev/null
+++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/beans/MyDestroyBean.java
@@ -0,0 +1,63 @@
+/*
+ * 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.dsl.xml.io.beans;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class MyDestroyBean {
+
+ public static final AtomicBoolean initCalled = new AtomicBoolean();
+ public static final AtomicBoolean destroyCalled = new AtomicBoolean();
+
+ private String field1;
+ private String field2;
+ private int age;
+
+ public MyDestroyBean(String field1, String field2) {
+ this.field1 = field1;
+ this.field2 = field2;
+ }
+
+ public String getField1() {
+ return field1;
+ }
+
+ public String getField2() {
+ return field2;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String hello(String body) {
+ return field1 + " " + body + ". I am " + field2 + " and " + age + " years old!";
+ }
+
+ public void initMe() {
+ initCalled.set(true);
+ }
+
+ public void destroyMe() {
+ destroyCalled.set(true);
+ }
+
+}
diff --git a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app8.xml b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app8.xml
new file mode 100644
index 00000000000..35de6096a77
--- /dev/null
+++ b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/camel-app8.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<camel xmlns="http://camel.apache.org/schema/spring" xmlns:s="http://www.springframework.org/schema/beans">
+
+ <bean name="xml-bean-from-registry" type="org.apache.camel.dsl.xml.io.beans.MyDestroyBean"
+ initMethod="initMe" destroyMethod="destroyMe">
+ <constructors>
+ <constructor value="Hello"/>
+ <constructor value="Camel"/>
+ </constructors>
+ <properties>
+ <property key="age" value="45"/>
+ </properties>
+ </bean>
+
+ <route id="r8">
+ <from uri="direct:x8"/>
+ <bean ref="xml-bean-from-registry" method="hello"/>
+ <to uri="mock:y8"/>
+ </route>
+
+</camel>