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;
+    }
+
 }

Reply via email to