This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 345abf4 CAMEL-12833: Do CDI CamelContext creation with an apporpriate TCCL 345abf4 is described below commit 345abf449e615c49e9affbb2761ff0763ffd7828 Author: James Netherton <jamesnether...@gmail.com> AuthorDate: Wed Sep 26 15:47:32 2018 +0100 CAMEL-12833: Do CDI CamelContext creation with an apporpriate TCCL --- .../org/apache/camel/cdi/CamelContextProducer.java | 3 +- .../org/apache/camel/cdi/CdiCamelExtension.java | 32 +++++++---- .../java/org/apache/camel/cdi/CdiSpiHelper.java | 50 ++++++++++++++++ .../org/apache/camel/cdi/SyntheticAnnotated.java | 17 +++++- .../org/apache/camel/cdi/XmlCdiBeanFactory.java | 11 ++-- .../org/apache/camel/cdi/test/NoTCCLSetTest.java | 66 ++++++++++++++++++++++ 6 files changed, 162 insertions(+), 17 deletions(-) diff --git a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CamelContextProducer.java b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CamelContextProducer.java index 4dbe62efe4..e51d856 100644 --- a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CamelContextProducer.java +++ b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CamelContextProducer.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.camel.cdi.AnyLiteral.ANY; +import static org.apache.camel.cdi.CdiSpiHelper.createCamelContextWithTCCL; import static org.apache.camel.cdi.CdiSpiHelper.getRawType; import static org.apache.camel.cdi.CdiSpiHelper.isAnnotationType; import static org.apache.camel.cdi.DefaultLiteral.DEFAULT; @@ -64,7 +65,7 @@ final class CamelContextProducer<T extends CamelContext> extends DelegateProduce @Override public T produce(CreationalContext<T> ctx) { - T context = super.produce(ctx); + T context = createCamelContextWithTCCL(() -> super.produce(ctx), annotated); // Do not override the name if it's been already set (in the bean constructor for example) if (context.getNameStrategy() instanceof DefaultCamelContextNameStrategy) { diff --git a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelExtension.java b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelExtension.java index 6535aa9..d01acb1 100644 --- a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelExtension.java +++ b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelExtension.java @@ -24,9 +24,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.EventObject; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.newSetFromMap; @@ -271,15 +274,22 @@ public class CdiCamelExtension implements Extension { .forEach(contextQualifiers::addAll); // From the @ContextName qualifiers on RoutesBuilder and RouteContainer beans - cdiBeans.stream() + List<Bean<?>> routeBeans = cdiBeans.stream() .filter(hasType(RoutesBuilder.class).or(hasType(RouteContainer.class))) - .map(Bean::getQualifiers) - .flatMap(Set::stream) - .filter(isAnnotationType(ContextName.class)) - .filter(name -> !contextQualifiers.contains(name)) - .peek(contextQualifiers::add) - .map(name -> camelContextBean(manager, ANY, name, APPLICATION_SCOPED)) - .forEach(extraBeans::add); + .filter(bean -> bean.getQualifiers() + .stream() + .filter(isAnnotationType(ContextName.class).and(name -> !contextQualifiers.contains(name))) + .peek(contextQualifiers::add) + .count() > 0 + ) + .collect(Collectors.toList()); + + for (Bean<?> bean : routeBeans) { + Optional<Annotation> annotation = bean.getQualifiers() + .stream() + .filter(isAnnotationType(ContextName.class)).findFirst(); + extraBeans.add(camelContextBean(manager, bean.getBeanClass(), ANY, annotation.get(), APPLICATION_SCOPED)); + } Set<Bean<?>> allBeans = concat(cdiBeans.stream(), extraBeans.stream()) .collect(toSet()); @@ -289,7 +299,7 @@ public class CdiCamelExtension implements Extension { if (contexts.size() == 0 && shouldDeployDefaultCamelContext(allBeans)) { // Add @Default Camel context bean if any - extraBeans.add(camelContextBean(manager, ANY, DEFAULT, APPLICATION_SCOPED)); + extraBeans.add(camelContextBean(manager, null, ANY, DEFAULT, APPLICATION_SCOPED)); } else if (contexts.size() == 1) { // Add the @Default qualifier if there is only one Camel context bean Bean<?> context = contexts.iterator().next(); @@ -360,9 +370,9 @@ public class CdiCamelExtension implements Extension { .anyMatch(isAnnotationType(Uri.class).or(isAnnotationType(Mock.class)).or(isEqual(DEFAULT))); } - private SyntheticBean<?> camelContextBean(BeanManager manager, Annotation... qualifiers) { + private SyntheticBean<?> camelContextBean(BeanManager manager, Class<?> beanClass, Annotation... qualifiers) { SyntheticAnnotated annotated = new SyntheticAnnotated(DefaultCamelContext.class, - manager.createAnnotatedType(DefaultCamelContext.class).getTypeClosure(), qualifiers); + manager.createAnnotatedType(DefaultCamelContext.class).getTypeClosure(), beanClass, qualifiers); return new SyntheticBean<>(manager, annotated, DefaultCamelContext.class, environment.camelContextInjectionTarget( new SyntheticInjectionTarget<>(DefaultCamelContext::new), annotated, manager, this), bean -> diff --git a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiSpiHelper.java b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiSpiHelper.java index a69a1d8..023ed6b 100644 --- a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiSpiHelper.java +++ b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiSpiHelper.java @@ -31,6 +31,7 @@ import java.util.Objects; import java.util.Set; import java.util.StringJoiner; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Stream; import static java.security.AccessController.doPrivileged; @@ -43,12 +44,14 @@ import static java.util.stream.Collectors.toSet; import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.AnnotatedConstructor; import javax.enterprise.inject.spi.AnnotatedField; +import javax.enterprise.inject.spi.AnnotatedMember; import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.util.Nonbinding; +import org.apache.camel.CamelContext; import static org.apache.camel.cdi.AnyLiteral.ANY; import static org.apache.camel.cdi.DefaultLiteral.DEFAULT; @@ -221,4 +224,51 @@ final class CdiSpiHelper { StringJoiner::merge) .toString(); } + + /** + * Wraps creation of a {@link CamelContext} with the current thread context ClassLoader + * set with the ClassLoader associated to the {@link Annotated} java class. + */ + static <T extends CamelContext> T createCamelContextWithTCCL(Supplier<T> supplier, Annotated annotated) { + ClassLoader oldTccl = Thread.currentThread().getContextClassLoader(); + try { + ClassLoader classLoader = getClassLoader(annotated); + Thread.currentThread().setContextClassLoader(classLoader); + return supplier.get(); + } finally { + Thread.currentThread().setContextClassLoader(oldTccl); + } + } + + private static ClassLoader getClassLoader(Annotated annotated) { + // Try to find a ClassLoader associated with the class containing AnnotatedMember + if (annotated instanceof AnnotatedMember) { + AnnotatedMember annotatedMember = (AnnotatedMember) annotated; + AnnotatedType type = annotatedMember.getDeclaringType(); + return type.getJavaClass().getClassLoader(); + } + + // Try to find a ClassLoader associated with the annotated class + if (annotated instanceof AnnotatedType) { + AnnotatedType type = (AnnotatedType) annotated; + return type.getJavaClass().getClassLoader(); + } + + if (annotated instanceof SyntheticAnnotated) { + SyntheticAnnotated syntheticAnnotated = (SyntheticAnnotated) annotated; + Class<?> javaClass = syntheticAnnotated.getJavaClass(); + if (javaClass != null) { + return javaClass.getClassLoader(); + } + } + + // Fallback to TCCL if available + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + if (tccl != null) { + return tccl; + } + + // If no TCCL is available, use the ClassLoader of this class + return CdiSpiHelper.class.getClassLoader(); + } } \ No newline at end of file diff --git a/components/camel-cdi/src/main/java/org/apache/camel/cdi/SyntheticAnnotated.java b/components/camel-cdi/src/main/java/org/apache/camel/cdi/SyntheticAnnotated.java index 9afa412..398f841 100644 --- a/components/camel-cdi/src/main/java/org/apache/camel/cdi/SyntheticAnnotated.java +++ b/components/camel-cdi/src/main/java/org/apache/camel/cdi/SyntheticAnnotated.java @@ -39,13 +39,24 @@ final class SyntheticAnnotated implements Annotated { private final Set<Annotation> annotations; + private final Class<?> javaClass; + SyntheticAnnotated(Class<?> type, Set<Type> types, Annotation... annotations) { - this(type, types, asList(annotations)); + this(type, types, null, asList(annotations)); } SyntheticAnnotated(Class<?> type, Set<Type> types, Collection<Annotation> annotations) { + this(type, types, null, annotations); + } + + SyntheticAnnotated(Class<?> type, Set<Type> types, Class<?> javaClass, Annotation... annotations) { + this(type, types, javaClass, asList(annotations)); + } + + SyntheticAnnotated(Class<?> type, Set<Type> types, Class<?> javaClass, Collection<Annotation> annotations) { this.type = type; this.types = types; + this.javaClass = javaClass; this.annotations = new HashSet<>(annotations); } @@ -83,4 +94,8 @@ final class SyntheticAnnotated implements Annotated { public boolean isAnnotationPresent(Class<? extends Annotation> type) { return annotations.stream().anyMatch(isAnnotationType(type)); } + + public Class<?> getJavaClass() { + return javaClass; + } } diff --git a/components/camel-cdi/src/main/java/org/apache/camel/cdi/XmlCdiBeanFactory.java b/components/camel-cdi/src/main/java/org/apache/camel/cdi/XmlCdiBeanFactory.java index 3ef81e2..549a322 100644 --- a/components/camel-cdi/src/main/java/org/apache/camel/cdi/XmlCdiBeanFactory.java +++ b/components/camel-cdi/src/main/java/org/apache/camel/cdi/XmlCdiBeanFactory.java @@ -62,6 +62,7 @@ import org.slf4j.LoggerFactory; import static org.apache.camel.cdi.AnyLiteral.ANY; import static org.apache.camel.cdi.ApplicationScopedLiteral.APPLICATION_SCOPED; +import static org.apache.camel.cdi.CdiSpiHelper.createCamelContextWithTCCL; import static org.apache.camel.cdi.DefaultLiteral.DEFAULT; import static org.apache.camel.cdi.ResourceHelper.getResource; import static org.apache.camel.cdi.Startup.Literal.STARTUP; @@ -109,7 +110,7 @@ final class XmlCdiBeanFactory { ApplicationContextFactoryBean app = (ApplicationContextFactoryBean) node; Set<SyntheticBean<?>> beans = new HashSet<>(); for (CamelContextFactoryBean factory : app.getContexts()) { - SyntheticBean<?> bean = camelContextBean(factory, url); + SyntheticBean<?> bean = camelContextBean(factory, url, annotatedType); beans.add(bean); beans.addAll(camelContextBeans(factory, bean, url)); } @@ -137,7 +138,7 @@ final class XmlCdiBeanFactory { } else if (node instanceof CamelContextFactoryBean) { CamelContextFactoryBean factory = (CamelContextFactoryBean) node; Set<SyntheticBean<?>> beans = new HashSet<>(); - SyntheticBean<?> bean = camelContextBean(factory, url); + SyntheticBean<?> bean = camelContextBean(factory, url, annotatedType); beans.add(bean); beans.addAll(camelContextBeans(factory, bean, url)); return beans; @@ -152,7 +153,7 @@ final class XmlCdiBeanFactory { return emptySet(); } - private SyntheticBean<?> camelContextBean(CamelContextFactoryBean factory, URL url) { + private SyntheticBean<?> camelContextBean(CamelContextFactoryBean factory, URL url, AnnotatedType annotatedType) { Set<Annotation> annotations = new HashSet<>(); annotations.add(ANY); if (hasId(factory)) { @@ -167,11 +168,13 @@ final class XmlCdiBeanFactory { annotations.add(APPLICATION_SCOPED); SyntheticAnnotated annotated = new SyntheticAnnotated(DefaultCamelContext.class, manager.createAnnotatedType(DefaultCamelContext.class).getTypeClosure(), + annotatedType.getJavaClass(), annotations); + return new SyntheticBean<>(manager, annotated, DefaultCamelContext.class, environment.camelContextInjectionTarget( new SyntheticInjectionTarget<>(() -> { - DefaultCamelContext context = new DefaultCamelContext(); + DefaultCamelContext context = createCamelContextWithTCCL(DefaultCamelContext::new, annotated); factory.setContext(context); factory.setBeanManager(manager); return context; diff --git a/components/camel-cdi/src/test/java/org/apache/camel/cdi/test/NoTCCLSetTest.java b/components/camel-cdi/src/test/java/org/apache/camel/cdi/test/NoTCCLSetTest.java new file mode 100644 index 0000000..a290362 --- /dev/null +++ b/components/camel-cdi/src/test/java/org/apache/camel/cdi/test/NoTCCLSetTest.java @@ -0,0 +1,66 @@ +/** + * 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.cdi.test; + +import java.util.Set; + +import javax.enterprise.event.Observes; +import javax.inject.Inject; + +import org.apache.camel.CamelContext; +import org.apache.camel.cdi.CdiCamelConfiguration; +import org.apache.camel.cdi.CdiCamelExtension; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +@RunWith(Arquillian.class) +public class NoTCCLSetTest { + + @Inject + CamelContext camelContext; + + @Deployment + public static Archive<?> deployment() { + return ShrinkWrap.create(JavaArchive.class) + // Camel CDI + .addPackage(CdiCamelExtension.class.getPackage()) + // Bean archive deployment descriptor + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); + } + + @Test + public void verifyNoTCCLFallbackClassLoader() { + assertThat(camelContext, is(notNullValue())); + Set<ClassLoader> classLoaders = camelContext.getPackageScanClassResolver().getClassLoaders(); + assertThat(classLoaders.size(), is(1)); + assertThat(classLoaders.iterator().next(), is(CamelContext.class.getClassLoader())); + } + + private void configuration(@Observes CdiCamelConfiguration configuration) { + // Nullify TCCL before CamelContext creation + Thread.currentThread().setContextClassLoader(null); + } +}