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:21 UTC
[camel] 03/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 82f10bc608b95f9395b43d21a2401e82e87ede02
Author: Claus Ibsen <cl...@gmail.com>
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);
+ }
+}