This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch custom-cl in repository https://gitbox.apache.org/repos/asf/camel.git
commit e97f9577aeecbecfcf686280a5782bb7328fdbaa Author: Claus Ibsen <[email protected]> AuthorDate: Wed Sep 27 16:30:53 2023 +0200 CAMEL-19924: camel-jbang - Java compiled classes should be available for classloading by components. There are some other fixes for loading OSGi blueprint <beans> and camel-mybatis as well. --- .../component/mybatis/BaseMyBatisEndpoint.java | 14 +++++- .../camel/component/mybatis/MyBatisComponent.java | 28 +++++------ .../component/mybatis/MyBatisTestSupport.java | 2 +- .../java/org/apache/camel/spi/ClassResolver.java | 9 +++- .../camel/impl/engine/DefaultClassResolver.java | 10 ++++ .../camel/dsl/java/joor/JavaJoorClassLoader.java | 8 +++- .../java/org/apache/camel/main/KameletMain.java | 16 +++---- .../main/download/CamelCustomClassLoader.java} | 33 +++++++------ .../xml/blueprint/BlueprintXmlBeansHandler.java | 56 +++++++++++++++++++--- .../main/xml/spring/SpringXmlBeansHandler.java | 4 ++ 10 files changed, 130 insertions(+), 50 deletions(-) diff --git a/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/BaseMyBatisEndpoint.java b/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/BaseMyBatisEndpoint.java index 7c65469eee8..5d521584ddd 100644 --- a/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/BaseMyBatisEndpoint.java +++ b/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/BaseMyBatisEndpoint.java @@ -23,6 +23,9 @@ import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionFactory; public abstract class BaseMyBatisEndpoint extends DefaultPollingEndpoint { + + private SqlSessionFactory sqlSessionFactory; + @UriParam(label = "producer", defaultValue = "SIMPLE") private ExecutorType executorType; @UriParam(label = "producer") @@ -42,8 +45,17 @@ public abstract class BaseMyBatisEndpoint extends DefaultPollingEndpoint { return (MyBatisComponent) super.getComponent(); } + @Override + protected void doStart() throws Exception { + super.doStart(); + + if (sqlSessionFactory == null) { + sqlSessionFactory = getComponent().createSqlSessionFactory(); + } + } + public SqlSessionFactory getSqlSessionFactory() { - return getComponent().getSqlSessionFactory(); + return sqlSessionFactory; } public ExecutorType getExecutorType() { diff --git a/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisComponent.java b/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisComponent.java index 0eab5409740..e8112e436a4 100644 --- a/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisComponent.java +++ b/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisComponent.java @@ -34,7 +34,7 @@ import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisComponent extends HealthCheckComponent { @Metadata(label = "advanced", autowired = true) - private SqlSessionFactory sqlSessionFactory; + private volatile SqlSessionFactory sqlSessionFactory; @Metadata(defaultValue = "SqlMapConfig.xml", supportFileReference = true) private String configurationUri = "SqlMapConfig.xml"; @@ -45,14 +45,17 @@ public class MyBatisComponent extends HealthCheckComponent { return answer; } - protected SqlSessionFactory createSqlSessionFactory() throws IOException { - ObjectHelper.notNull(configurationUri, "configurationUri", this); - InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), configurationUri); - try { - return new SqlSessionFactoryBuilder().build(is); - } finally { - IOHelper.close(is); + protected synchronized SqlSessionFactory createSqlSessionFactory() throws IOException { + if (sqlSessionFactory == null) { + ObjectHelper.notNull(configurationUri, "configurationUri", this); + InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), configurationUri); + try { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); + } finally { + IOHelper.close(is); + } } + return sqlSessionFactory; } public SqlSessionFactory getSqlSessionFactory() { @@ -79,13 +82,4 @@ public class MyBatisComponent extends HealthCheckComponent { this.configurationUri = configurationUri; } - @Override - protected void doStart() throws Exception { - super.doStart(); - - if (sqlSessionFactory == null) { - sqlSessionFactory = createSqlSessionFactory(); - } - } - } diff --git a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisTestSupport.java b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisTestSupport.java index 05bdc2cdbf5..d71a3ba87cd 100644 --- a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisTestSupport.java +++ b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisTestSupport.java @@ -95,7 +95,7 @@ public abstract class MyBatisTestSupport extends CamelTestSupport { protected Connection createConnection() throws Exception { MyBatisComponent component = context.getComponent("mybatis", MyBatisComponent.class); - return component.getSqlSessionFactory().getConfiguration().getEnvironment().getDataSource().getConnection(); + return component.createSqlSessionFactory().getConfiguration().getEnvironment().getDataSource().getConnection(); } } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ClassResolver.java b/core/camel-api/src/main/java/org/apache/camel/spi/ClassResolver.java index f7748d7eb3e..8c0f573bf2d 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/ClassResolver.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/ClassResolver.java @@ -18,11 +18,13 @@ package org.apache.camel.spi; import java.io.InputStream; import java.net.URL; +import java.util.Collection; import java.util.Enumeration; +import java.util.Set; /** * A class resolver for loading classes in a loosly coupled manner to cater for different platforms such as standalone, - * web container, j2ee container and OSGi platforms. + * Spring Boot, Quarkus, JBang etc. */ public interface ClassResolver { @@ -33,6 +35,11 @@ public interface ClassResolver { */ void addClassLoader(ClassLoader classLoader); + /** + * Gets the custom class loaders. + */ + Set<ClassLoader> getClassLoaders(); + /** * Resolves the given class by its name * diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultClassResolver.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultClassResolver.java index 14f4cc89ff5..a047df98724 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultClassResolver.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultClassResolver.java @@ -18,6 +18,7 @@ package org.apache.camel.impl.engine; import java.io.InputStream; import java.net.URL; +import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; @@ -61,6 +62,15 @@ public class DefaultClassResolver implements ClassResolver, CamelContextAware { classLoaders.add(classLoader); } + @Override + @SuppressWarnings("unchecked") + public Set<ClassLoader> getClassLoaders() { + if (classLoaders == null) { + return Collections.EMPTY_SET; + } + return Collections.unmodifiableSet(classLoaders); + } + @Override public Class<?> resolveClass(String name) { Class<?> answer; diff --git a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java index 95a5d6f3257..f83ba0953f7 100644 --- a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java +++ b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java @@ -28,8 +28,12 @@ public class JavaJoorClassLoader extends ClassLoader { } @Override - protected Class<?> findClass(String name) { - return classes.get(name); + public Class<?> loadClass(String name) throws ClassNotFoundException { + Class<?> clazz = classes.get(name); + if (clazz != null) { + return clazz; + } + throw new ClassNotFoundException(name); } public void addClass(String name, Class<?> clazz) { diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java index c436be5703f..39a4793b899 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java @@ -34,6 +34,7 @@ import org.apache.camel.dsl.support.SourceLoader; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.main.download.AutoConfigureDownloadListener; import org.apache.camel.main.download.BasePackageScanDownloadListener; +import org.apache.camel.main.download.CamelCustomClassLoader; import org.apache.camel.main.download.CircuitBreakerDownloader; import org.apache.camel.main.download.CommandLineDependencyDownloader; import org.apache.camel.main.download.DependencyDownloaderClassLoader; @@ -325,6 +326,8 @@ public class KameletMain extends MainCommandLineSupport { if (getCamelContext() != null) { getCamelContext().stop(); } + springXmlBeansHandler.stop(); + blueprintXmlBeansHandler.stop(); } @Override @@ -343,7 +346,7 @@ public class KameletMain extends MainCommandLineSupport { // do not build/init camel context yet DefaultCamelContext answer = new DefaultCamelContext(false); if (download) { - ClassLoader dynamicCL = createApplicationContextClassLoader(); + ClassLoader dynamicCL = createApplicationContextClassLoader(answer); answer.setApplicationContextClassLoader(dynamicCL); PluginHelper.getPackageScanClassResolver(answer).addClassLoader(dynamicCL); PluginHelper.getPackageScanResourceResolver(answer).addClassLoader(dynamicCL); @@ -566,8 +569,9 @@ public class KameletMain extends MainCommandLineSupport { @Override protected void autoconfigure(CamelContext camelContext) throws Exception { + ClassLoader cl = createApplicationContextClassLoader(camelContext); // create classloader that may include additional JARs - camelContext.setApplicationContextClassLoader(createApplicationContextClassLoader()); + camelContext.setApplicationContextClassLoader(cl); // auto configure camel afterwards super.autoconfigure(camelContext); } @@ -577,7 +581,7 @@ public class KameletMain extends MainCommandLineSupport { return new KameletAutowiredLifecycleStrategy(camelContext, stubPattern, silent); } - protected ClassLoader createApplicationContextClassLoader() { + protected ClassLoader createApplicationContextClassLoader(CamelContext camelContext) { if (classLoader == null) { // jars need to be added to dependency downloader classloader List<String> jars = new ArrayList<>(); @@ -600,6 +604,7 @@ public class KameletMain extends MainCommandLineSupport { LOG.info("Additional files added to classpath: {}", String.join(", ", files)); } } + parentCL = new CamelCustomClassLoader(parentCL, camelContext); DependencyDownloaderClassLoader cl = new DependencyDownloaderClassLoader(parentCL); if (!jars.isEmpty()) { for (String jar : jars) { @@ -688,11 +693,6 @@ public class KameletMain extends MainCommandLineSupport { blueprintXmlBeansHandler.createAndRegisterBeans(camelContext); } - @Override - protected void doShutdown() throws Exception { - // TODO: manage BeanFactory as a field and clear the beans here - } - private static String getPid() { return String.valueOf(ProcessHandle.current().pid()); } diff --git a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/CamelCustomClassLoader.java similarity index 50% copy from dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java copy to dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/CamelCustomClassLoader.java index 95a5d6f3257..5018b9818b3 100644 --- a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaJoorClassLoader.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/CamelCustomClassLoader.java @@ -14,28 +14,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.dsl.java.joor; +package org.apache.camel.main.download; -import java.util.HashMap; -import java.util.Map; +import org.apache.camel.CamelContext; -public class JavaJoorClassLoader extends ClassLoader { +/** + * ClassLoader loading from any custom class loaders that may + * have been added to Camel {@link org.apache.camel.spi.ClassResolver}. + */ +public class CamelCustomClassLoader extends ClassLoader { - private final Map<String, Class<?>> classes = new HashMap<>(); + private final CamelContext camelContext; - public JavaJoorClassLoader() { - super(JavaJoorClassLoader.class.getClassLoader()); + public CamelCustomClassLoader(ClassLoader parent, CamelContext camelContext) { + super(parent); + this.camelContext = camelContext; } @Override - protected Class<?> findClass(String name) { - return classes.get(name); - } - - public void addClass(String name, Class<?> clazz) { - if (name != null && clazz != null) { - classes.put(name, clazz); + protected Class<?> findClass(String name) throws ClassNotFoundException { + for (ClassLoader cl : camelContext.getClassResolver().getClassLoaders()) { + try { + return cl.loadClass(name); + } catch (ClassNotFoundException e) { + // ignore + } } + throw new ClassNotFoundException(name); } } diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java index c5037814ebb..4be053ce16e 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java @@ -36,7 +36,9 @@ import org.apache.camel.model.Model; import org.apache.camel.model.app.RegistryBeanDefinition; import org.apache.camel.spi.Resource; import org.apache.camel.spi.ResourceLoader; +import org.apache.camel.support.ObjectHelper; import org.apache.camel.support.PropertyBindingSupport; +import org.apache.camel.util.KeyValueHolder; import org.apache.camel.util.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +57,7 @@ public class BlueprintXmlBeansHandler { private final Map<String, Node> delayedBeans = new LinkedHashMap<>(); private final Map<String, Resource> resources = new LinkedHashMap<>(); private final List<RegistryBeanDefinition> delayedRegistrations = new ArrayList<>(); + private final Map<String, KeyValueHolder<Object, String>> beansToDestroy = new LinkedHashMap<>(); /** * Parses the XML documents and discovers blueprint beans, which will be created manually via Camel. @@ -118,7 +121,7 @@ public class BlueprintXmlBeansHandler { } String im = XmlHelper.getAttribute(node, "init-method"); if (im != null) { - rrd.setInitMethod(fm); + rrd.setInitMethod(im); } String dm = XmlHelper.getAttribute(node, "destroy-method"); if (dm != null) { @@ -248,12 +251,8 @@ public class BlueprintXmlBeansHandler { if (def.getProperties() != null && !def.getProperties().isEmpty()) { PropertyBindingSupport.setPropertiesOnTarget(camelContext, target, def.getProperties()); } - camelContext.getRegistry().unbind(name); - camelContext.getRegistry().bind(name, target); - // register bean in model - Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class); - model.addRegistryBean(def); + bindBean(camelContext, def, name, target); } catch (Exception e) { if (delayIfFailed) { @@ -265,4 +264,49 @@ public class BlueprintXmlBeansHandler { } } + 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) { + 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 { + 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() { + // beans should trigger destroy methods on shutdown + for (String name : beansToDestroy.keySet()) { + destroyBean(name, false); + } + beansToDestroy.clear(); + } + } diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java index 7fc231df481..22e1142975a 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java @@ -331,4 +331,8 @@ public class SpringXmlBeansHandler { return val; } + public void stop() { + // noop + } + }
