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); + } +}
