This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new c9f60a3b559 CAMEL-21542: camel-jbang - Detect known beans from Java imports (#16546) c9f60a3b559 is described below commit c9f60a3b559dc29934913c471b482b567187512f Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Dec 12 19:54:42 2024 +0100 CAMEL-21542: camel-jbang - Detect known beans from Java imports (#16546) * CAMEL-21542: camel-jbang - Detect known beans from Java imports --- .../org/apache/camel/spi/CompilePostProcessor.java | 4 +- ...PostProcessor.java => CompilePreProcessor.java} | 21 +++--- .../dsl/support/RouteBuilderLoaderSupport.java | 32 +++++++-- .../org/apache/camel/dsl/java/joor/Helper.java | 17 +++++ .../dsl/java/joor/JavaRoutesBuilderLoader.java | 7 ++ .../org/apache/camel/dsl/java/joor/DummyRoute.java | 3 + .../java/joor/{DummyRoute.java => HelperTest.java} | 22 ++++-- .../camel/dsl/jbang/core/commands/ExportTest.java | 25 +++++++ .../src/test/resources/MyKafkaRepo.java} | 18 +++-- .../java/org/apache/camel/main/KameletMain.java | 4 ++ .../main/download/JavaKnownImportsDownloader.java | 78 ++++++++++++++++++++++ .../apache/camel/tooling/model/ArtifactModel.java | 4 ++ 12 files changed, 205 insertions(+), 30 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java b/core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java index 0f58bc2a75b..0024c25466a 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java @@ -19,11 +19,13 @@ package org.apache.camel.spi; import org.apache.camel.CamelContext; /** - * Allows to plugin custom post processors that are processed after the DSL has loaded the source and compiled into a + * Allows to plugin custom post-processors that are processed after the DSL has loaded the source and compiled into a * Java object. * <p/> * This is used to detect and handle {@link org.apache.camel.BindToRegistry} and {@link org.apache.camel.Converter} * classes. + * + * @see CompilePreProcessor */ public interface CompilePostProcessor { diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java b/core/camel-api/src/main/java/org/apache/camel/spi/CompilePreProcessor.java similarity index 60% copy from core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java copy to core/camel-api/src/main/java/org/apache/camel/spi/CompilePreProcessor.java index 0f58bc2a75b..6214573a8d2 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/CompilePostProcessor.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/CompilePreProcessor.java @@ -19,27 +19,24 @@ package org.apache.camel.spi; import org.apache.camel.CamelContext; /** - * Allows to plugin custom post processors that are processed after the DSL has loaded the source and compiled into a + * Allows to plugin custom pre-processors that are processed before the DSL has loaded the source and compiled into a * Java object. * <p/> - * This is used to detect and handle {@link org.apache.camel.BindToRegistry} and {@link org.apache.camel.Converter} - * classes. + * This is used among others to detect imported classes that may need to be downloaded into classloader to allow to + * compile the class. + * + * @see CompilePostProcessor */ -public interface CompilePostProcessor { +public interface CompilePreProcessor { /** - * Invoked after the class has been compiled + * Invoked before the class has been compiled * * @param camelContext the camel context * @param name the name of the resource/class - * @param clazz the class - * @param byteCode byte code that was compiled from the source as the class (only supported on some DSLs) - * @param instance the object created as instance of the class (if any) + * @param code the source code of the class * @throws Exception is thrown if error during post-processing */ - void postCompile( - CamelContext camelContext, String name, - Class<?> clazz, byte[] byteCode, Object instance) - throws Exception; + void preCompile(CamelContext camelContext, String name, String code) throws Exception; } diff --git a/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java b/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java index 2c3a8b237e6..1689b8d78f7 100644 --- a/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java +++ b/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java @@ -32,6 +32,7 @@ import org.apache.camel.api.management.ManagedOperation; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.builder.RouteBuilderLifecycleStrategy; import org.apache.camel.spi.CompilePostProcessor; +import org.apache.camel.spi.CompilePreProcessor; import org.apache.camel.spi.Resource; import org.apache.camel.spi.RoutesBuilderLoader; import org.apache.camel.spi.StartupStepRecorder; @@ -42,6 +43,7 @@ import org.apache.camel.support.RoutesBuilderLoaderSupport; */ public abstract class RouteBuilderLoaderSupport extends RoutesBuilderLoaderSupport { private final String extension; + private final List<CompilePreProcessor> compilePreProcessors = new ArrayList<>(); private final List<CompilePostProcessor> compilePostProcessors = new ArrayList<>(); private StartupStepRecorder recorder; private SourceLoader sourceLoader = new DefaultSourceLoader(); @@ -62,6 +64,21 @@ public abstract class RouteBuilderLoaderSupport extends RoutesBuilderLoaderSuppo return super.isSupportedExtension(extension); } + /** + * Gets the registered {@link CompilePreProcessor}. + */ + public List<CompilePreProcessor> getCompilePreProcessors() { + return compilePreProcessors; + } + + /** + * Add a custom {@link CompilePreProcessor} to handle specific pre-processing before compiling the source into a + * Java object. + */ + public void addCompilePreProcessor(CompilePreProcessor preProcessor) { + this.compilePreProcessors.add(preProcessor); + } + /** * Gets the registered {@link CompilePostProcessor}. */ @@ -91,11 +108,18 @@ public abstract class RouteBuilderLoaderSupport extends RoutesBuilderLoaderSuppo super.doStart(); if (getCamelContext() != null) { - // discover optional compile post-processors to be used - Set<CompilePostProcessor> pres = getCamelContext().getRegistry().findByType(CompilePostProcessor.class); + // discover optional compile pre-processors to be used + Set<CompilePreProcessor> pres = getCamelContext().getRegistry().findByType(CompilePreProcessor.class); if (pres != null && !pres.isEmpty()) { - for (CompilePostProcessor pre : pres) { - addCompilePostProcessor(pre); + for (CompilePreProcessor pre : pres) { + addCompilePreProcessor(pre); + } + } + // discover optional compile post-processors to be used + Set<CompilePostProcessor> posts = getCamelContext().getRegistry().findByType(CompilePostProcessor.class); + if (posts != null && !posts.isEmpty()) { + for (CompilePostProcessor post : posts) { + addCompilePostProcessor(post); } } // discover a special source loader to be used diff --git a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/Helper.java b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/Helper.java index 70ac21efe2d..ced703d4677 100644 --- a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/Helper.java +++ b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/Helper.java @@ -16,6 +16,8 @@ */ package org.apache.camel.dsl.java.joor; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,6 +33,9 @@ public final class Helper { private static final Pattern PACKAGE_PATTERN = Pattern.compile( "^\\s*package\\s+([a-zA-Z][.\\w]*)\\s*;.*$", Pattern.MULTILINE); + private static final Pattern IMPORT_PATTERN = Pattern.compile( + "^import\\s+([a-zA-Z][.\\w]*)\\s*;", Pattern.MULTILINE); + private Helper() { } @@ -52,4 +57,16 @@ public final class Helper { ? matcher.group(1) + "." + name : name; } + + public static List<String> determineImports(String content) { + List<String> answer = new ArrayList<>(); + final Matcher matcher = IMPORT_PATTERN.matcher(content); + while (matcher.find()) { + String imp = matcher.group(1); + imp = imp.trim(); + answer.add(imp); + } + return answer; + } + } diff --git a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java index d805e559cd7..bed3da758b0 100644 --- a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java +++ b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java @@ -42,6 +42,7 @@ import org.apache.camel.language.joor.CompilationUnit; import org.apache.camel.language.joor.JavaJoorClassLoader; import org.apache.camel.language.joor.MultiCompile; import org.apache.camel.spi.CompilePostProcessor; +import org.apache.camel.spi.CompilePreProcessor; import org.apache.camel.spi.CompileStrategy; import org.apache.camel.spi.Resource; import org.apache.camel.spi.ResourceAware; @@ -189,6 +190,12 @@ public class JavaRoutesBuilderLoader extends ExtendedRouteBuilderLoaderSupport { } String content = IOHelper.loadText(is); String name = determineName(resource, content); + + // allow any pre-processing + for (CompilePreProcessor pre : getCompilePreProcessors()) { + pre.preCompile(getCamelContext(), name, content); + } + unit.addClass(name, content); // ensure class gets recompiled classLoader.removeClass(name); diff --git a/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java b/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java index eda67fb303a..95fd1a3f8e8 100644 --- a/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java +++ b/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java @@ -16,10 +16,13 @@ */ package org.apache.camel.dsl.java.joor; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; public class DummyRoute extends RouteBuilder { + private CamelContext camelContext; + @Override public void configure() throws Exception { from("direct:dummy") diff --git a/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java b/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/HelperTest.java similarity index 54% copy from dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java copy to dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/HelperTest.java index eda67fb303a..c5ff4da9e60 100644 --- a/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java +++ b/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/HelperTest.java @@ -16,13 +16,23 @@ */ package org.apache.camel.dsl.java.joor; -import org.apache.camel.builder.RouteBuilder; +import java.io.FileInputStream; +import java.util.Collections; +import java.util.List; -public class DummyRoute extends RouteBuilder { +import org.apache.camel.util.IOHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; - @Override - public void configure() throws Exception { - from("direct:dummy") - .to("mock:end"); +public class HelperTest { + + @Test + public void testImports() throws Exception { + List<String> list = Helper.determineImports( + IOHelper.loadText(new FileInputStream("src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java"))); + Collections.sort(list); + Assertions.assertEquals(2, list.size()); + Assertions.assertEquals("org.apache.camel.CamelContext", list.get(0)); + Assertions.assertEquals("org.apache.camel.builder.RouteBuilder", list.get(1)); } } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java index 0bacb326e45..bd50898cd9f 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportTest.java @@ -418,6 +418,31 @@ class ExportTest { } } + @ParameterizedTest + @MethodSource("runtimeProvider") + public void shouldExportKafka(RuntimeType rt) throws Exception { + Export command = createCommand(rt, new String[] { "src/test/resources/MyKafkaRepo.java" }, + "--gav=examples:route:1.0.0", "--dir=" + workingDir, "--quiet"); + int exit = command.doCall(); + + Assertions.assertEquals(0, exit); + Model model = readMavenModel(); + Assertions.assertEquals("examples", model.getGroupId()); + Assertions.assertEquals("route", model.getArtifactId()); + Assertions.assertEquals("1.0.0", model.getVersion()); + + if (rt == RuntimeType.main) { + Assertions.assertTrue(containsDependency(model.getDependencies(), "org.apache.camel", "camel-kafka", null)); + } else if (rt == RuntimeType.springBoot) { + Assertions.assertTrue( + containsDependency(model.getDependencies(), "org.apache.camel.springboot", "camel-kafka-starter", + null)); + } else if (rt == RuntimeType.quarkus) { + Assertions.assertTrue( + containsDependency(model.getDependencies(), "org.apache.camel.quarkus", "camel-quarkus-kafka", null)); + } + } + @ParameterizedTest @MethodSource("runtimeProvider") public void shouldExportSecretBean(RuntimeType rt) throws Exception { diff --git a/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java b/dsl/camel-jbang/camel-jbang-core/src/test/resources/MyKafkaRepo.java similarity index 66% copy from dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java copy to dsl/camel-jbang/camel-jbang-core/src/test/resources/MyKafkaRepo.java index eda67fb303a..3a2792d5013 100644 --- a/dsl/camel-java-joor-dsl/src/test/java/org/apache/camel/dsl/java/joor/DummyRoute.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/MyKafkaRepo.java @@ -14,15 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.dsl.java.joor; -import org.apache.camel.builder.RouteBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; -public class DummyRoute extends RouteBuilder { +import org.apache.camel.processor.idempotent.kafka.KafkaIdempotentRepository; - @Override - public void configure() throws Exception { - from("direct:dummy") - .to("mock:end"); +@Component +public class MyKafkaRepo { + + @Bean + public KafkaIdempotentRepository createRepo() { + KafkaIdempotentRepository repo = new KafkaIdempotentRepository("myrepo", "localhost:9091"); + return repo; } } + 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 3b24e69b4c3..326fe34a9b2 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 @@ -61,6 +61,7 @@ import org.apache.camel.main.download.DownloadListener; import org.apache.camel.main.download.DownloadModelineParser; import org.apache.camel.main.download.ExportPropertiesParser; import org.apache.camel.main.download.ExportTypeConverter; +import org.apache.camel.main.download.JavaKnownImportsDownloader; import org.apache.camel.main.download.KameletAutowiredLifecycleStrategy; import org.apache.camel.main.download.KameletMainInjector; import org.apache.camel.main.download.KnownDependenciesResolver; @@ -495,6 +496,9 @@ public class KameletMain extends MainCommandLineSupport { boolean lazyBean = "true".equals(getInitialProperties().get(getInstanceType() + ".lazyBean")); new AnnotationDependencyInjection(answer, lazyBean); + // add support for automatic downloaded needed JARs from java imports + new JavaKnownImportsDownloader(answer); + if (!silent) { // silent should not include cli-connector // setup cli-connector if not already done diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java new file mode 100644 index 00000000000..914f0472eae --- /dev/null +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java @@ -0,0 +1,78 @@ +/* + * 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.main.download; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.camel.CamelContext; +import org.apache.camel.catalog.CamelCatalog; +import org.apache.camel.catalog.DefaultCamelCatalog; +import org.apache.camel.spi.CompilePreProcessor; +import org.apache.camel.tooling.model.PojoBeanModel; + +/** + * Java DSL that can detect known classes from the java imports and download JARs + */ +public class JavaKnownImportsDownloader implements CompilePreProcessor { + + private static final Pattern IMPORT_PATTERN = Pattern.compile( + "^import\\s+([a-zA-Z][.\\w]*)\\s*;", Pattern.MULTILINE); + + private final CamelCatalog catalog = new DefaultCamelCatalog(); + private final DependencyDownloader downloader; + + public JavaKnownImportsDownloader(CamelContext camelContext) { + this.downloader = camelContext.hasService(DependencyDownloader.class); + camelContext.getRegistry().bind("JavaJoorKnownImportsDownloader", this); + } + + @Override + public void preCompile(CamelContext camelContext, String name, String code) throws Exception { + List<String> imports = determineImports(code); + for (String imp : imports) { + // is this a known bean then we can determine the dependency + for (String n : catalog.findBeansNames()) { + PojoBeanModel m = catalog.pojoBeanModel(n); + if (m != null && imp.equals(m.getJavaType())) { + downloadLoader(m.getGroupId(), m.getArtifactId(), m.getVersion()); + break; + } + } + } + } + + private void downloadLoader(String groupId, String artifactId, String version) { + if (!downloader.alreadyOnClasspath(groupId, artifactId, version)) { + downloader.downloadDependency(groupId, artifactId, version); + } + } + + private static List<String> determineImports(String content) { + List<String> answer = new ArrayList<>(); + final Matcher matcher = IMPORT_PATTERN.matcher(content); + while (matcher.find()) { + String imp = matcher.group(1); + imp = imp.trim(); + answer.add(imp); + } + return answer; + } + +} diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ArtifactModel.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ArtifactModel.java index 39bff116009..b7a6c3a2ec7 100644 --- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ArtifactModel.java +++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ArtifactModel.java @@ -51,4 +51,8 @@ public abstract class ArtifactModel<O extends BaseOptionModel> extends BaseModel this.version = version; } + public String toGav() { + return groupId + ":" + artifactId + ":" + version; + } + }