From 052390d73633ac910ef7d71086126c629d282855 Mon Sep 17 00:00:00 2001 From: James Netherton Date: Thu, 3 Oct 2024 10:01:47 +0100 Subject: [PATCH] Fix build time processing of Kamelets with bean definitions Fixes #6581 --- .../kamelet/deployment/KameletProcessor.java | 30 ++++++++++++- .../component/kamelet/KameletRecorder.java | 7 ++++ integration-tests/kamelet/pom.xml | 17 ++++++++ .../component/kamelet/it/KameletBean.java | 29 +++++++++++++ .../component/kamelet/it/KameletResource.java | 6 +++ .../component/kamelet/it/KameletRoutes.java | 3 ++ .../src/main/resources/application.properties | 2 +- .../resources/kamelets/greeting.kamelet.yaml | 42 +++++++++++++++++++ .../component/kamelet/it/KameletTest.java | 12 +++++- 9 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletBean.java create mode 100644 integration-tests/kamelet/src/main/resources/kamelets/greeting.kamelet.yaml diff --git a/extensions/kamelet/deployment/src/main/java/org/apache/camel/quarkus/component/kamelet/deployment/KameletProcessor.java b/extensions/kamelet/deployment/src/main/java/org/apache/camel/quarkus/component/kamelet/deployment/KameletProcessor.java index 1bd217b289f8..8588bdb1b174 100644 --- a/extensions/kamelet/deployment/src/main/java/org/apache/camel/quarkus/component/kamelet/deployment/KameletProcessor.java +++ b/extensions/kamelet/deployment/src/main/java/org/apache/camel/quarkus/component/kamelet/deployment/KameletProcessor.java @@ -20,14 +20,17 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import org.apache.camel.CamelContext; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.Ordered; @@ -89,6 +92,7 @@ void loadResources( @BuildStep CamelContextCustomizerBuildItem configureTemplates( List resources, + BuildProducer reflectiveClass, KameletRecorder recorder) throws Exception { List definitions = new ArrayList<>(); @@ -97,9 +101,13 @@ CamelContextCustomizerBuildItem configureTemplates( ExtendedCamelContext ecc = context.getCamelContextExtension(); for (KameletResourceBuildItem item : resources) { - LOGGER.debugf("Loading kamelet from: %s)", item.getResource()); + Resource resource = item.getResource(); + if (!resource.exists()) { + throw new IllegalStateException("Unable to load kamelet from: " + resource.getLocation()); + } - Collection rbs = PluginHelper.getRoutesLoader(ecc).findRoutesBuilders(item.getResource()); + LOGGER.debugf("Loading kamelet from: %s", resource); + Collection rbs = PluginHelper.getRoutesLoader(ecc).findRoutesBuilders(resource); for (RoutesBuilder rb : rbs) { RouteBuilder routeBuilder = (RouteBuilder) rb; routeBuilder.configure(); @@ -135,6 +143,24 @@ CamelContextCustomizerBuildItem configureTemplates( if (definition.getRoute() != null && definition.getRoute().getOutputs() != null) { definition.getRoute().getOutputs().forEach(o -> o.setCamelContext(null)); } + + if (definition.getTemplateBeans() != null) { + Set beanClassNames = new HashSet<>(); + definition.getTemplateBeans().forEach(bean -> { + bean.setResource(resource); + + String beanType = bean.getType(); + if (beanType != null && beanType.startsWith("#class:")) { + String className = beanType.substring("#class:".length()); + beanClassNames.add(className); + } + }); + + reflectiveClass.produce(ReflectiveClassBuildItem.builder(beanClassNames.toArray(new String[0])) + .fields() + .methods() + .build()); + } }); return new CamelContextCustomizerBuildItem( diff --git a/extensions/kamelet/runtime/src/main/java/org/apache/camel/quarkus/component/kamelet/KameletRecorder.java b/extensions/kamelet/runtime/src/main/java/org/apache/camel/quarkus/component/kamelet/KameletRecorder.java index b431298fc02d..2763ca5a5851 100644 --- a/extensions/kamelet/runtime/src/main/java/org/apache/camel/quarkus/component/kamelet/KameletRecorder.java +++ b/extensions/kamelet/runtime/src/main/java/org/apache/camel/quarkus/component/kamelet/KameletRecorder.java @@ -63,6 +63,13 @@ public void configure(CamelContext context) { if (definition.getRoute() != null && definition.getRoute().getOutputs() != null) { definition.getRoute().getOutputs().forEach(o -> o.setCamelContext(context)); } + + if (definition.getTemplateBeans() != null) { + definition.getTemplateBeans() + .stream() + .filter(bean -> bean.getResource() instanceof EmptyKameletResource) + .forEach(bean -> bean.setResource(resourceLoader.resolveResource(location))); + } } context.getCamelContextExtension().getContextPlugin(Model.class).addRouteTemplateDefinitions(definitions); } catch (Exception e) { diff --git a/integration-tests/kamelet/pom.xml b/integration-tests/kamelet/pom.xml index a266ae339fd1..e461be2830d4 100644 --- a/integration-tests/kamelet/pom.xml +++ b/integration-tests/kamelet/pom.xml @@ -51,6 +51,10 @@ org.apache.camel.quarkus camel-quarkus-xml-io-dsl + + org.apache.camel.quarkus + camel-quarkus-seda + io.quarkus quarkus-resteasy @@ -167,6 +171,19 @@ + + org.apache.camel.quarkus + camel-quarkus-seda-deployment + ${project.version} + pom + test + + + * + * + + + org.apache.camel.quarkus camel-quarkus-timer-deployment diff --git a/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletBean.java b/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletBean.java new file mode 100644 index 000000000000..475c1a3eeb0b --- /dev/null +++ b/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletBean.java @@ -0,0 +1,29 @@ +/* + * 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.quarkus.component.kamelet.it; + +public class KameletBean { + private final String greeting; + + public KameletBean(String greeting) { + this.greeting = greeting; + } + + public String getGreeting() { + return greeting; + } +} diff --git a/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletResource.java b/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletResource.java index 37a73e553999..f9bb2f9af769 100644 --- a/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletResource.java +++ b/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletResource.java @@ -103,4 +103,10 @@ public String kameletLocationAtRuntime(@PathParam("name") String name) { return fluentProducerTemplate.to("direct:kamelet-location-at-runtime").withBody(name).request(String.class); } + @Path("/greeting") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String greeting() { + return consumerTemplate.receiveBody("seda:greeting", 10000, String.class); + } } diff --git a/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletRoutes.java b/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletRoutes.java index e8b812ebb0ff..afb8520dac00 100644 --- a/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletRoutes.java +++ b/integration-tests/kamelet/src/main/java/org/apache/camel/quarkus/component/kamelet/it/KameletRoutes.java @@ -64,6 +64,9 @@ public void configure() throws Exception { from("direct:kamelet-location-at-runtime") .kamelet("upper?location=classpath:kamelets-runtime/upper-kamelet.xml"); + + from("kamelet:greeting") + .to("seda:greeting"); } @RegisterForReflection diff --git a/integration-tests/kamelet/src/main/resources/application.properties b/integration-tests/kamelet/src/main/resources/application.properties index cff0cd193461..0b6ffc878c5e 100644 --- a/integration-tests/kamelet/src/main/resources/application.properties +++ b/integration-tests/kamelet/src/main/resources/application.properties @@ -16,7 +16,7 @@ ## --------------------------------------------------------------------------- camel.kamelet.setBodyFromProperties.bodyValueFromProperty=Camel Quarkus Kamelet Property -quarkus.camel.kamelet.identifiers = injector,logger +quarkus.camel.kamelet.identifiers = injector,logger,greeting # this is needed to actually test that kamelet are preloaded at build time: camel.component.kamelet.location = file:/invalid diff --git a/integration-tests/kamelet/src/main/resources/kamelets/greeting.kamelet.yaml b/integration-tests/kamelet/src/main/resources/kamelets/greeting.kamelet.yaml new file mode 100644 index 000000000000..aff771f2c3fa --- /dev/null +++ b/integration-tests/kamelet/src/main/resources/kamelets/greeting.kamelet.yaml @@ -0,0 +1,42 @@ +# +# 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. +# + +apiVersion: camel.apache.org/v1 +kind: Kamelet +metadata: + name: greeting + labels: + camel.apache.org/kamelet.type: "source" + camel.apache.org/kamelet.name: "greeting" + camel.apache.org/kamelet.version: "v1" + camel.apache.org/kamelet.revision: "1" +spec: + definition: + title: "Greeting" + description: "Print a greeting" + dependencies: + - "camel:timer" + template: + beans: + - name: greetingBean + type: "#class:org.apache.camel.quarkus.component.kamelet.it.KameletBean" + constructors: + 0: "Hello World" + from: + uri: timer:greet?repeatCount=1&delay=-1 + steps: + - to: "bean:{{greetingBean}}?method=getGreeting" diff --git a/integration-tests/kamelet/src/test/java/org/apache/camel/quarkus/component/kamelet/it/KameletTest.java b/integration-tests/kamelet/src/test/java/org/apache/camel/quarkus/component/kamelet/it/KameletTest.java index 5aafba9df674..e95c0efdf9f1 100644 --- a/integration-tests/kamelet/src/test/java/org/apache/camel/quarkus/component/kamelet/it/KameletTest.java +++ b/integration-tests/kamelet/src/test/java/org/apache/camel/quarkus/component/kamelet/it/KameletTest.java @@ -93,7 +93,8 @@ public void testInvoke() { public void testDiscovered() { Response resp = RestAssured.given() .contentType(ContentType.JSON) - .when().get("/kamelet/list"); + .when() + .get("/kamelet/list"); resp.then().statusCode(200); ArrayList jsonAsArrayList = resp.body() @@ -102,6 +103,7 @@ public void testDiscovered() { assertTrue(jsonAsArrayList.contains("injector")); assertTrue(jsonAsArrayList.contains("logger")); assertTrue(jsonAsArrayList.contains("custom-log")); + assertTrue(jsonAsArrayList.contains("greeting")); } @Test @@ -112,4 +114,12 @@ public void testKameletLocationAtRuntime() { .statusCode(200) .body(is("HELLO")); } + + @Test + public void testKameletWithBean() { + RestAssured.get("/kamelet/greeting") + .then() + .statusCode(200) + .body(is("Hello World")); + } }