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 <[email protected]>
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>

Reply via email to