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 82f10bc608b95f9395b43d21a2401e82e87ede02
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 27 09:16:47 2023 +0200

    CAMEL-19808: camel-core-model - Add init and destroy method to <bean>
---
 .../camel/dsl/xml/io/XmlRoutesBuilderLoader.java   | 81 ++++++++++++----------
 .../dsl/yaml/deserializers/BeansDeserializer.java  | 74 +++++++++++++++++---
 .../dsl/yaml/YamlRoutesBuilderLoaderSupport.java   |  4 ++
 .../org/apache/camel/dsl/yaml/BeansTest.groovy     | 32 +++++++++
 .../dsl/yaml/support/model/MyDestroyBean.groovy    | 62 +++++++++++++++++
 5 files changed, 205 insertions(+), 48 deletions(-)

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 d44a6941260..9fa17215c3a 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
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.w3c.dom.Document;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
 import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.builder.RouteBuilder;
@@ -321,51 +322,57 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
      * {@link #doLoadRouteBuilder}), a failure may lead to delayed 
registration.
      */
     private void registerBeanDefinition(RegistryBeanDefinition def, boolean 
delayIfFailed) {
-        String type = def.getType();
         String name = def.getName();
-        if (name == null || name.isBlank()) {
-            name = type;
+        String type = def.getType();
+        try {
+            Object target = newInstance(def, getCamelContext());
+            bindBean(def, name, target);
+        } catch (Exception e) {
+            if (delayIfFailed) {
+                delayedRegistrations.add(def);
+            } else {
+                String msg
+                        = name != null ? "Error creating bean: " + name + " of 
type: " + type : "Error creating bean: " + type;
+                throw new RuntimeException(msg, e);
+            }
         }
-        if (type != null && !type.startsWith("#")) {
-            type = "#class:" + type;
-            try {
-                // factory bean/method
-                if (def.getFactoryBean() != null && def.getFactoryMethod() != 
null) {
-                    type = type + "#" + def.getFactoryBean() + ":" + 
def.getFactoryMethod();
-                } else if (def.getFactoryMethod() != null) {
-                    type = type + "#" + def.getFactoryMethod();
-                }
-                // property binding support has constructor arguments as part 
of the type
-                StringJoiner ctr = new StringJoiner(", ");
-                if (def.getConstructors() != null && 
!def.getConstructors().isEmpty()) {
-                    // need to sort constructor args based on index position
-                    Map<Integer, Object> sorted = new 
TreeMap<>(def.getConstructors());
-                    for (Object val : sorted.values()) {
-                        String text = val.toString();
-                        if (!StringHelper.isQuoted(text)) {
-                            text = "\"" + text + "\"";
-                        }
-                        ctr.add(text);
-                    }
-                    type = type + "(" + ctr + ")";
-                }
+    }
 
-                final Object target = 
PropertyBindingSupport.resolveBean(getCamelContext(), type);
+    public Object newInstance(RegistryBeanDefinition def, CamelContext 
context) throws Exception {
 
-                if (def.getProperties() != null && 
!def.getProperties().isEmpty()) {
-                    
PropertyBindingSupport.setPropertiesOnTarget(getCamelContext(), target, 
def.getProperties());
-                }
-
-                bindBean(def, name, target);
+        String type = def.getType();
+        if (!type.startsWith("#")) {
+            type = "#class:" + type;
+        }
 
-            } catch (Exception e) {
-                if (delayIfFailed) {
-                    delayedRegistrations.add(def);
-                } else {
-                    LOG.warn("Error creating bean: {} due to: {}. This 
exception is ignored.", type, e.getMessage(), e);
+        // factory bean/method
+        if (def.getFactoryBean() != null && def.getFactoryMethod() != null) {
+            type = type + "#" + def.getFactoryBean() + ":" + 
def.getFactoryMethod();
+        } else if (def.getFactoryMethod() != null) {
+            type = type + "#" + def.getFactoryMethod();
+        }
+        // property binding support has constructor arguments as part of the 
type
+        StringJoiner ctr = new StringJoiner(", ");
+        if (def.getConstructors() != null && !def.getConstructors().isEmpty()) 
{
+            // need to sort constructor args based on index position
+            Map<Integer, Object> sorted = new TreeMap<>(def.getConstructors());
+            for (Object val : sorted.values()) {
+                String text = val.toString();
+                if (!StringHelper.isQuoted(text)) {
+                    text = "\"" + text + "\"";
                 }
+                ctr.add(text);
             }
+            type = type + "(" + ctr + ")";
         }
+
+        final Object target = PropertyBindingSupport.resolveBean(context, 
type);
+
+        if (def.getProperties() != null && !def.getProperties().isEmpty()) {
+            PropertyBindingSupport.setPropertiesOnTarget(context, target, 
def.getProperties());
+        }
+
+        return target;
     }
 
     protected void bindBean(RegistryBeanDefinition def, String name, Object 
target) throws Exception {
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
index 905d5a4ce21..4224881e130 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/BeansDeserializer.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml.deserializers;
 
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -35,8 +36,11 @@ import org.apache.camel.spi.annotations.YamlIn;
 import org.apache.camel.spi.annotations.YamlProperty;
 import org.apache.camel.spi.annotations.YamlType;
 import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.util.KeyValueHolder;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.snakeyaml.engine.v2.api.ConstructNode;
 import org.snakeyaml.engine.v2.nodes.Node;
 import org.snakeyaml.engine.v2.nodes.SequenceNode;
@@ -51,7 +55,10 @@ import org.snakeyaml.engine.v2.nodes.SequenceNode;
           })
 public class BeansDeserializer extends YamlDeserializerSupport implements 
ConstructNode {
 
+    public static final Logger LOG = 
LoggerFactory.getLogger(BeansDeserializer.class);
+
     private final Set<String> beanCache = new HashSet<>();
+    private final Map<String, KeyValueHolder<Object, String>> beansToDestroy = 
new LinkedHashMap<>();
 
     @Override
     public Object construct(Node node) {
@@ -129,22 +136,19 @@ public class BeansDeserializer extends 
YamlDeserializerSupport implements Constr
             CamelContext camelContext,
             List<RegistryBeanDefinition> delayedRegistrations,
             RegistryBeanDefinition def, boolean delayIfFailed) {
-        try {
-            // to support hot reloading of beans then we need to unbind old 
existing first
-            String name = def.getName();
-            Object bean = newInstance(def, camelContext);
-            camelContext.getRegistry().unbind(name);
-            camelContext.getRegistry().bind(name, bean);
-
-            // register bean in model
-            Model model = 
camelContext.getCamelContextExtension().getContextPlugin(Model.class);
-            model.addRegistryBean(def);
 
+        String name = def.getName();
+        String type = def.getType();
+        try {
+            Object target = newInstance(def, camelContext);
+            bindBean(camelContext, def, name, target);
         } catch (Exception e) {
             if (delayIfFailed) {
                 delayedRegistrations.add(def);
             } else {
-                throw new RuntimeException(e);
+                String msg
+                        = name != null ? "Error creating bean: " + name + " of 
type: " + type : "Error creating bean: " + type;
+                throw new RuntimeException(msg, e);
             }
         }
     }
@@ -173,4 +177,52 @@ public class BeansDeserializer extends 
YamlDeserializerSupport implements Constr
         }
     }
 
+    protected void bindBean(
+            CamelContext camelContext, RegistryBeanDefinition def,
+            String name, Object target)
+            throws Exception {
+
+        // destroy and unbind any existing bean
+        destroyBean(name, true);
+        camelContext.getRegistry().unbind(name);
+
+        // invoke init method and register bean
+        String initMethod = def.getInitMethod();
+        if (initMethod != null) {
+            org.apache.camel.support.ObjectHelper.invokeMethodSafe(initMethod, 
target);
+        }
+        camelContext.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 = 
camelContext.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 {
+                
org.apache.camel.support.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);
+            }
+        }
+    }
+
+    public void stop() throws Exception {
+        // beans should trigger destroy methods on shutdown
+        for (String name : beansToDestroy.keySet()) {
+            destroyBean(name, false);
+        }
+        beansToDestroy.clear();
+    }
+
 }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
index bfd11e5212f..d7e77b54cb9 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
@@ -113,4 +113,8 @@ public abstract class YamlRoutesBuilderLoaderSupport 
extends RouteBuilderLoaderS
         return null;
     }
 
+    @Override
+    protected void doStop() throws Exception {
+        beansDeserializer.stop();
+    }
 }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
index 1d3209c6627..e7e2185ddef 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/BeansTest.groovy
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.yaml
 import org.apache.camel.dsl.yaml.support.YamlTestSupport
 import org.apache.camel.dsl.yaml.support.model.MyBean
 import org.apache.camel.dsl.yaml.support.model.MyCtrBean
+import org.apache.camel.dsl.yaml.support.model.MyDestroyBean
 import org.apache.camel.dsl.yaml.support.model.MyFacBean
 import org.apache.camel.dsl.yaml.support.model.MyFacHelper
 
@@ -178,5 +179,36 @@ class BeansTest extends YamlTestSupport {
         }
     }
 
+    def "beans with init destroy"() {
+        when:
+        loadRoutes """
+                - beans:
+                  - name: myBean
+                    type: ${MyDestroyBean.class.name}
+                    initMethod: initMe
+                    destroyMethod: destroyMe
+                    constructors:
+                      0: 'fac1'
+                      1: 'fac2'
+                    properties:
+                      age: 43 
+            """
+
+        then:
+
+        MyDestroyBean.initCalled.get() == true
+        MyDestroyBean.destroyCalled.get() == false
+
+        with(context.registry.lookupByName('myBean'), MyDestroyBean) {
+            it.field1 == 'fac1'
+            it.field2 == 'fac2'
+            it.age == 43
+        }
+
+        context.stop()
+
+        MyDestroyBean.initCalled.get() == true
+        MyDestroyBean.destroyCalled.get() == true
+    }
 
 }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/model/MyDestroyBean.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/model/MyDestroyBean.groovy
new file mode 100644
index 00000000000..f1a563c058f
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/model/MyDestroyBean.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.yaml.support.model
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+class MyDestroyBean {
+
+    public static final AtomicBoolean initCalled = new AtomicBoolean();
+    public static final AtomicBoolean destroyCalled = new AtomicBoolean();
+
+    String field1
+    String field2
+    int age;
+
+    public MyDestroyBean(String field1, String field2) {
+        this.field1 = field1
+        this.field2 = field2
+    }
+
+    String getField1() {
+        return field1
+    }
+
+    String getField2() {
+        return field2
+    }
+
+    int getAge() {
+        return age
+    }
+
+    void setAge(int age) {
+        this.age = age
+    }
+
+    String hello(String body) {
+        return field1 + " " + body + ". I am " + field2 + " and " + age + " 
years old!";
+    }
+
+    void initMe() {
+        initCalled.set(true);
+    }
+
+    void destroyMe() {
+        destroyCalled.set(true);
+    }
+}

Reply via email to