From cbf896aec7128bc4f8598f1de3fd3ac60f5bc154 Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Fri, 19 Jun 2020 19:31:35 +0200 Subject: [PATCH] Create a CompositeClassloader and set is as Camel's ApplicationClassloader #364 --- .../camel/k/loader/java/JavaSourceLoader.java | 9 ++++ .../camel/k/loader/java/RoutesLoaderTest.java | 16 ++++++ .../resources/MyRoutesWithNestedTypes.java | 28 ++++++++++ .../camel/k/main/ApplicationRuntime.java | 2 + .../deployment/DeploymentProcessor.java | 7 +++ .../camel/k/core/quarkus/RuntimeRecorder.java | 15 ++++++ .../core/quarkus/deployment/Application.java | 12 +++++ .../quarkus/deployment/ExtensionTest.java | 14 +++++ .../apache/camel/k/CompositeClassloader.java | 52 +++++++++++++++++++ 9 files changed, 155 insertions(+) create mode 100644 camel-k-loader-java/src/test/resources/MyRoutesWithNestedTypes.java create mode 100644 camel-k-runtime-core/src/main/java/org/apache/camel/k/CompositeClassloader.java diff --git a/camel-k-loader-java/src/main/java/org/apache/camel/k/loader/java/JavaSourceLoader.java b/camel-k-loader-java/src/main/java/org/apache/camel/k/loader/java/JavaSourceLoader.java index 42009ae97..7d18fe260 100644 --- a/camel-k-loader-java/src/main/java/org/apache/camel/k/loader/java/JavaSourceLoader.java +++ b/camel-k-loader-java/src/main/java/org/apache/camel/k/loader/java/JavaSourceLoader.java @@ -22,6 +22,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.Runtime; import org.apache.camel.k.Source; import org.apache.camel.k.SourceLoader; @@ -47,6 +48,14 @@ public Result load(Runtime runtime, Source source) throws Exception { final Reflect compiled = Reflect.compile(name, content); final Object instance = compiled.create().get(); + // The given source may contains additional nested classes which are unknown to Camel + // as they are associated to the ClassLoader used to compile the source thus we need + // to add it to the ApplicationContextClassLoader. + final ClassLoader loader = runtime.getCamelContext().getApplicationContextClassLoader(); + if (loader instanceof CompositeClassloader) { + ((CompositeClassloader) loader).addClassLoader(instance.getClass().getClassLoader()); + } + return Result.on(instance); } } diff --git a/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java b/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java index a576933f4..2519babc0 100644 --- a/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java +++ b/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java @@ -25,6 +25,7 @@ import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.Runtime; import org.apache.camel.k.Source; import org.apache.camel.k.SourceLoader; @@ -109,6 +110,20 @@ public void testLoadJavaWithModel() throws Exception { }); } + @Test + public void testLoadJavaWithNestedType() throws Exception { + TestRuntime runtime = new TestRuntime(); + Source source = Sources.fromURI("classpath:MyRoutesWithNestedTypes.java"); + SourceLoader loader = RoutesConfigurer.load(runtime, source); + + assertThat(loader).isInstanceOf(JavaSourceLoader.class); + assertThat(runtime.builders).hasSize(1); + assertThat(runtime.builders).first().isInstanceOf(RouteBuilder.class); + + runtime.getCamelContext().addRoutes(runtime.builders.get(0)); + runtime.getCamelContext().getApplicationContextClassLoader().loadClass("MyRoutesWithNestedTypes$MyModel"); + } + @ParameterizedTest @MethodSource("parameters") public void testLoaders(String location, Class type) throws Exception { @@ -148,6 +163,7 @@ static class TestRuntime implements Runtime { public TestRuntime() { this.camelContext = new DefaultCamelContext(); + this.camelContext.setApplicationContextClassLoader(new CompositeClassloader()); this.builders = new ArrayList<>(); this.configurations = new ArrayList<>(); } diff --git a/camel-k-loader-java/src/test/resources/MyRoutesWithNestedTypes.java b/camel-k-loader-java/src/test/resources/MyRoutesWithNestedTypes.java new file mode 100644 index 000000000..1e0d16896 --- /dev/null +++ b/camel-k-loader-java/src/test/resources/MyRoutesWithNestedTypes.java @@ -0,0 +1,28 @@ +/* + * 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. + */ +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; + +public class MyRoutesWithNestedTypes extends RouteBuilder { + @Override + public void configure() throws Exception { + } + + public static class MyModel { + } +} \ No newline at end of file diff --git a/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java b/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java index ab58fb97b..fc673770c 100644 --- a/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java +++ b/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java @@ -28,6 +28,7 @@ import org.apache.camel.RoutesBuilder; import org.apache.camel.RuntimeCamelException; import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.Runtime; import org.apache.camel.k.support.PropertiesSupport; import org.apache.camel.main.BaseMainSupport; @@ -52,6 +53,7 @@ public ApplicationRuntime() { this.context = new DefaultCamelContext(); this.context.setName("camel-k"); + this.context.setApplicationContextClassLoader(new CompositeClassloader()); this.main = new MainAdapter(); this.main.configure().setXmlRoutes("false"); diff --git a/camel-k-quarkus/camel-k-quarkus-core/deployment/src/main/java/org/apache/camel/k/core/quarkus/deployment/DeploymentProcessor.java b/camel-k-quarkus/camel-k-quarkus-core/deployment/src/main/java/org/apache/camel/k/core/quarkus/deployment/DeploymentProcessor.java index 63f9f6ff8..8d8ce8f27 100644 --- a/camel-k-quarkus/camel-k-quarkus-core/deployment/src/main/java/org/apache/camel/k/core/quarkus/deployment/DeploymentProcessor.java +++ b/camel-k-quarkus/camel-k-quarkus-core/deployment/src/main/java/org/apache/camel/k/core/quarkus/deployment/DeploymentProcessor.java @@ -30,6 +30,7 @@ import org.apache.camel.k.Constants; import org.apache.camel.k.Runtime; import org.apache.camel.k.core.quarkus.RuntimeRecorder; +import org.apache.camel.quarkus.core.deployment.spi.CamelContextCustomizerBuildItem; import org.apache.camel.quarkus.core.deployment.spi.CamelMainListenerBuildItem; import org.apache.camel.quarkus.core.deployment.spi.CamelServicePatternBuildItem; import org.apache.camel.spi.HasId; @@ -133,4 +134,10 @@ CamelMainListenerBuildItem registerListener(RuntimeRecorder recorder) { return new CamelMainListenerBuildItem(recorder.createMainListener(listeners)); } + + @Record(ExecutionTime.STATIC_INIT) + @BuildStep + void customizeContext(RuntimeRecorder recorder, BuildProducer customizers) { + customizers.produce(new CamelContextCustomizerBuildItem(recorder.registerCompositeClassLoader())); + } } diff --git a/camel-k-quarkus/camel-k-quarkus-core/runtime/src/main/java/org/apache/camel/k/core/quarkus/RuntimeRecorder.java b/camel-k-quarkus/camel-k-quarkus-core/runtime/src/main/java/org/apache/camel/k/core/quarkus/RuntimeRecorder.java index 01ab849fa..c02441b38 100644 --- a/camel-k-quarkus/camel-k-quarkus-core/runtime/src/main/java/org/apache/camel/k/core/quarkus/RuntimeRecorder.java +++ b/camel-k-quarkus/camel-k-quarkus-core/runtime/src/main/java/org/apache/camel/k/core/quarkus/RuntimeRecorder.java @@ -20,12 +20,27 @@ import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; +import org.apache.camel.CamelContext; +import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.Runtime; import org.apache.camel.main.MainListener; +import org.apache.camel.quarkus.core.CamelContextCustomizer; @Recorder public class RuntimeRecorder { public RuntimeValue createMainListener(List listeners) { return new RuntimeValue<>(new RuntimeListenerAdapter(listeners)); } + + public RuntimeValue registerCompositeClassLoader() { + return new RuntimeValue<>(new CamelContextCustomizer() { + @Override + public void customize(CamelContext context) { + final ClassLoader oldLoader = context.getApplicationContextClassLoader(); + final ClassLoader newLoader = CompositeClassloader.wrap(oldLoader); + + context.setApplicationContextClassLoader(newLoader); + } + }); + } } diff --git a/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/main/java/org/apache/camel/k/core/quarkus/deployment/Application.java b/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/main/java/org/apache/camel/k/core/quarkus/deployment/Application.java index daa0504c3..cba95a252 100644 --- a/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/main/java/org/apache/camel/k/core/quarkus/deployment/Application.java +++ b/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/main/java/org/apache/camel/k/core/quarkus/deployment/Application.java @@ -19,6 +19,7 @@ import java.util.ServiceLoader; import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; @@ -27,11 +28,15 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.apache.camel.CamelContext; import org.apache.camel.k.Runtime; @Path("/test") @ApplicationScoped public class Application { + @Inject + CamelContext camelContext; + @GET @Path("/services") @Produces(MediaType.APPLICATION_JSON) @@ -46,4 +51,11 @@ public JsonObject getServices() { .add("services", builder) .build(); } + + @GET + @Path("/application-classloader") + @Produces(MediaType.TEXT_PLAIN) + public String getApplicationClassloader() { + return camelContext.getApplicationContextClassLoader().getClass().getName(); + } } diff --git a/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java b/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java index 4bfda9e3e..bbf417c85 100644 --- a/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java +++ b/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java @@ -18,14 +18,17 @@ import javax.ws.rs.core.MediaType; +import io.quarkus.test.junit.DisabledOnNativeImage; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.path.json.JsonPath; +import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.listener.ContextConfigurer; import org.apache.camel.k.listener.RoutesConfigurer; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; @QuarkusTest public class ExtensionTest { @@ -45,4 +48,15 @@ public void testServices() { RoutesConfigurer.class.getName() ); } + + @DisabledOnNativeImage + @Test + public void testClassLoader() { + RestAssured.given() + .accept(MediaType.TEXT_PLAIN) + .get("/test/application-classloader") + .then() + .statusCode(200) + .body(is(CompositeClassloader.class.getName())); + } } \ No newline at end of file diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/CompositeClassloader.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/CompositeClassloader.java new file mode 100644 index 000000000..07192755c --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/CompositeClassloader.java @@ -0,0 +1,52 @@ +/* + * 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.k; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class CompositeClassloader extends ClassLoader { + private final List loaders = new CopyOnWriteArrayList<>(); + + public CompositeClassloader() { + } + + public CompositeClassloader(ClassLoader parent) { + super(parent); + } + + public void addClassLoader(ClassLoader loader) { + loaders.add(loader); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + for (ClassLoader loader: loaders) { + try { + return loader.loadClass(name); + } catch (ClassNotFoundException e) { + // ignore + } + } + + return super.loadClass(name); + } + + public static CompositeClassloader wrap(ClassLoader parent) { + return parent != null ? new CompositeClassloader(parent) : new CompositeClassloader(); + } +}