You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2019/06/19 12:23:32 UTC

[camel] 03/09: CAMEL-13663: camel-main-maven-plugin to generte spring-boot tooling metadata to fool Java editors to have code completions for Camel Main application.properties files.

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 0d9a90b1a61cba6762119ca463af43f779ed3f21
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jun 19 08:04:22 2019 +0200

    CAMEL-13663: camel-main-maven-plugin to generte spring-boot tooling metadata to fool Java editors to have code completions for Camel Main application.properties files.
---
 .../src/main/docs/camel-main-maven-plugin.adoc     |  57 ++++++++
 .../org/apache/camel/maven/AbstractMainMojo.java   |  86 ++++++++++-
 .../java/org/apache/camel/maven/AutowireMojo.java  |  69 +--------
 .../apache/camel/maven/SpringBootToolingMojo.java  | 162 +++++++++++++++++++++
 4 files changed, 304 insertions(+), 70 deletions(-)

diff --git a/catalog/camel-main-maven-plugin/src/main/docs/camel-main-maven-plugin.adoc b/catalog/camel-main-maven-plugin/src/main/docs/camel-main-maven-plugin.adoc
index d826227..e8d1364 100644
--- a/catalog/camel-main-maven-plugin/src/main/docs/camel-main-maven-plugin.adoc
+++ b/catalog/camel-main-maven-plugin/src/main/docs/camel-main-maven-plugin.adoc
@@ -3,6 +3,8 @@
 The Camel Main Maven Plugin supports the following goals
 
  - camel-main:autowire - To pre-scan your project and prepare autowiring by classpath scanning
+ - camel-main:spring-boot-tooling - To pre-scan your project and builds spring boot tooling metafiles
+   which fools tools to offer code completion for editing properties files.
 
 == camel:autowire
 
@@ -157,3 +159,58 @@ The maven plugin *autowire* goal supports the following options which can be con
 
 You can find more details and a working example at `examples/camel-example-main-artemis`.
 
+
+== camel:spring-boot-tooling
+
+To pre-scan your project and builds spring boot tooling metafiles
+which fools tools to offer code completion for editing properties files.
+
+----
+mvn camel-main:spring-boot-tooling
+----
+
+This will generate a Spring Boot tooling metadata file in `src/main/resouces/META-INF/spring-configuration-metadata.json`
+which contains all the options from Camel Main and the components from the classpath.
+
+For example if you have camel-jms on the classpath, then Java tools that has support for Spring Boot,
+can offer code completions when you edit `application.properties` file:
+
+----
+camel.component.jms.CURSOR HERE
+----
+
+Just press ctrl + space at the _CURSOR HERE_ location and you will get all the options you
+can configure on the JMS component.
+
+
+You can also enable the plugin to automatic run as part of the build to catch these errors.
+
+[source,xml]
+----
+<plugin>
+  <groupId>org.apache.camel</groupId>
+  <artifactId>camel-main-maven-plugin</artifactId>
+  <executions>
+    <execution>
+      <phase>process-classes</phase>
+      <goals>
+        <goal>spring-boot-tooling</goal>
+      </goals>
+    </execution>
+  </executions>
+</plugin>
+----
+
+The phase determines when the plugin runs. In the sample above the phase is `process-classes` which runs after
+the compilation of the main source code.
+
+=== Options
+
+The maven plugin *spring-boot-tooling* goal supports the following options which can be configured from the command line (use `-D` syntax), or defined in the `pom.xml` file in the `<configuration>` tag.
+
+|===
+| Parameter | Default Value | Description
+| logClasspath | false | Whether to log the classpath when starting
+| downloadVersion | true | Whether to allow downloading Camel catalog version from the internet.
+  This is needed if the project * uses a different Camel version than this plugin is using by default.
+|===
diff --git a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AbstractMainMojo.java b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AbstractMainMojo.java
index f1de650..0d3e20f 100644
--- a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AbstractMainMojo.java
+++ b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AbstractMainMojo.java
@@ -1,13 +1,13 @@
-/**
+/*
  * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
+ *
+ *      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.
@@ -32,6 +32,9 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
 
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.catalog.maven.MavenVersionManager;
 import org.apache.camel.util.IOHelper;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.repository.ArtifactRepository;
@@ -45,6 +48,10 @@ import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.repository.RepositorySystem;
 import org.codehaus.mojo.exec.AbstractExecMojo;
+import org.reflections.Reflections;
+import org.reflections.scanners.SubTypesScanner;
+import org.reflections.util.ClasspathHelper;
+import org.reflections.util.ConfigurationBuilder;
 
 /**
  * Base class for maven goals.
@@ -66,6 +73,12 @@ public abstract class AbstractMainMojo extends AbstractExecMojo {
 
     protected transient ClassLoader classLoader;
 
+    protected transient CamelCatalog catalog;
+
+    protected transient Reflections reflections;
+
+    protected transient Set<String> camelComponentsOnClasspath;
+
     @Component
     private RepositorySystem repositorySystem;
 
@@ -78,6 +91,62 @@ public abstract class AbstractMainMojo extends AbstractExecMojo {
     @Parameter(property = "project.remoteArtifactRepositories")
     private List remoteRepositories;
 
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        catalog = new DefaultCamelCatalog();
+        // add activemq as known component
+        catalog.addComponent("activemq", "org.apache.activemq.camel.component.ActiveMQComponent");
+        // enable loading other catalog versions dynamically
+        catalog.setVersionManager(new MavenVersionManager());
+        // enable caching
+        catalog.enableCache();
+
+        String detectedVersion = findCamelVersion(project);
+        if (detectedVersion != null) {
+            getLog().info("Detected Camel version used in project: " + detectedVersion);
+        }
+
+        if (downloadVersion) {
+            String catalogVersion = catalog.getCatalogVersion();
+            String version = findCamelVersion(project);
+            if (version != null && !version.equals(catalogVersion)) {
+                // the project uses a different Camel version so attempt to load it
+                getLog().info("Downloading Camel version: " + version);
+                boolean loaded = catalog.loadVersion(version);
+                if (!loaded) {
+                    getLog().warn("Error downloading Camel version: " + version);
+                }
+            }
+        }
+
+        if (catalog.getLoadedVersion() != null) {
+            getLog().info("Pre-scanning using downloaded Camel version: " + catalog.getLoadedVersion());
+        } else {
+            getLog().info("Pre-scanning using Camel version: " + catalog.getCatalogVersion());
+        }
+
+        // find all Camel components on classpath and check in the camel-catalog for all component options
+        // then check each option if its a complex type and an interface
+        // and if so scan class-path and find the single class implementing this interface
+        // write this to META-INF/services/org/apache/camel/autowire.properties
+
+        // find all Camel components on classpath
+        camelComponentsOnClasspath = resolveCamelComponentsFromClasspath();
+        if (camelComponentsOnClasspath.isEmpty()) {
+            getLog().warn("No Camel components discovered in classpath");
+            return;
+        } else {
+            getLog().info("Discovered " + camelComponentsOnClasspath.size() + " Camel components from classpath: " + camelComponentsOnClasspath);
+        }
+
+        // build index of classes on classpath
+        getLog().debug("Indexing classes on classpath");
+        reflections = new Reflections(new ConfigurationBuilder()
+                .addUrls(ClasspathHelper.forClassLoader(classLoader))
+                .addClassLoader(classLoader)
+                .setScanners(new SubTypesScanner()));
+    }
+
     protected static String findCamelVersion(MavenProject project) {
         Dependency candidate = null;
 
@@ -194,4 +263,13 @@ public abstract class AbstractMainMojo extends AbstractExecMojo {
 
         return artifacts;
     }
+
+    protected String safeJavaType(String javaType) {
+        int pos = javaType.indexOf('<');
+        if (pos > 0) {
+            return javaType.substring(0, pos);
+        }
+        return javaType;
+    }
+
 }
diff --git a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AutowireMojo.java b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AutowireMojo.java
index 1e5c955..63f0c61 100644
--- a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AutowireMojo.java
+++ b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AutowireMojo.java
@@ -30,9 +30,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.camel.catalog.CamelCatalog;
-import org.apache.camel.catalog.DefaultCamelCatalog;
 import org.apache.camel.catalog.JSonSchemaHelper;
-import org.apache.camel.catalog.maven.MavenVersionManager;
 import org.apache.camel.support.PatternHelper;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.OrderedProperties;
@@ -44,9 +42,6 @@ import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
 import org.reflections.Reflections;
-import org.reflections.scanners.SubTypesScanner;
-import org.reflections.util.ClasspathHelper;
-import org.reflections.util.ConfigurationBuilder;
 
 /**
  * Pre scans your project and prepare autowiring by classpath scanning
@@ -96,58 +91,8 @@ public class AutowireMojo extends AbstractMainMojo {
 
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
-        CamelCatalog catalog = new DefaultCamelCatalog();
-        // add activemq as known component
-        catalog.addComponent("activemq", "org.apache.activemq.camel.component.ActiveMQComponent");
-        // enable loading other catalog versions dynamically
-        catalog.setVersionManager(new MavenVersionManager());
-        // enable caching
-        catalog.enableCache();
-
-        String detectedVersion = findCamelVersion(project);
-        if (detectedVersion != null) {
-            getLog().info("Detected Camel version used in project: " + detectedVersion);
-        }
-
-        if (downloadVersion) {
-            String catalogVersion = catalog.getCatalogVersion();
-            String version = findCamelVersion(project);
-            if (version != null && !version.equals(catalogVersion)) {
-                // the project uses a different Camel version so attempt to load it
-                getLog().info("Downloading Camel version: " + version);
-                boolean loaded = catalog.loadVersion(version);
-                if (!loaded) {
-                    getLog().warn("Error downloading Camel version: " + version);
-                }
-            }
-        }
-
-        if (catalog.getLoadedVersion() != null) {
-            getLog().info("Pre-scanning using downloaded Camel version: " + catalog.getLoadedVersion());
-        } else {
-            getLog().info("Pre-scanning using Camel version: " + catalog.getCatalogVersion());
-        }
-
-        // find all Camel components on classpath and check in the camel-catalog for all component options
-        // then check each option if its a complex type and an interface
-        // and if so scan class-path and find the single class implementing this interface
-        // write this to META-INF/services/org/apache/camel/autowire.properties
-
-        // find all Camel components on classpath
-        Set<String> components = resolveCamelComponentsFromClasspath();
-        if (components.isEmpty()) {
-            getLog().warn("No Camel components discovered in classpath");
-            return;
-        } else {
-            getLog().info("Discovered " + components.size() + " Camel components from classpath: " + components);
-        }
-
-        // build index of classes on classpath
-        getLog().debug("Indexing classes on classpath");
-        Reflections reflections = new Reflections(new ConfigurationBuilder()
-                .addUrls(ClasspathHelper.forClassLoader(classLoader))
-                .addClassLoader(classLoader)
-                .setScanners(new SubTypesScanner()));
+        // perform common tasks
+        super.execute();
 
         // load default mappings
         Properties mappingProperties = loadDefaultMappings();
@@ -170,7 +115,7 @@ public class AutowireMojo extends AbstractMainMojo {
         }
 
         // find the autowire via classpath scanning
-        List<String> autowires = findAutowireComponentOptionsByClasspath(catalog, components, reflections, mappingProperties);
+        List<String> autowires = findAutowireComponentOptionsByClasspath(catalog, camelComponentsOnClasspath, reflections, mappingProperties);
 
         if (!autowires.isEmpty()) {
             outFolder.mkdirs();
@@ -348,12 +293,4 @@ public class AutowireMojo extends AbstractMainMojo {
         return !clazz.getName().startsWith("org.apache.camel");
     }
 
-    protected String safeJavaType(String javaType) {
-        int pos = javaType.indexOf('<');
-        if (pos > 0) {
-            return javaType.substring(0, pos);
-        }
-        return javaType;
-    }
-
 }
diff --git a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/SpringBootToolingMojo.java b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/SpringBootToolingMojo.java
new file mode 100644
index 0000000..a249e2d
--- /dev/null
+++ b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/SpringBootToolingMojo.java
@@ -0,0 +1,162 @@
+/*
+ * 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.maven;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.catalog.JSonSchemaHelper;
+import org.apache.camel.util.IOHelper;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+
+/**
+ * Pre scans your project and builds spring boot tooling metafiles which fools tools to
+ * offer code completion for editing properties files.
+ */
+@Mojo(name = "spring-boot-tooling", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE)
+public class SpringBootToolingMojo extends AbstractMainMojo {
+
+    /**
+     * The output directory for generated spring boot tooling file
+     */
+    @Parameter(readonly = true, defaultValue = "${project.build.directory}/../src/main/resources/META-INF/")
+    protected File outFolder;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        // perform common tasks
+        super.execute();
+
+        // TODO: generate for Camel Main configuration which can be a bit more complex than components from classpath
+        List<String[]> data = new ArrayList<>();
+
+        for (String componentName : camelComponentsOnClasspath) {
+            String json = catalog.componentJSonSchema(componentName);
+            if (json == null) {
+                getLog().debug("Cannot find component JSon metadata for component: " + componentName);
+                continue;
+            }
+
+
+            List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("componentProperties", json, true);
+            Set<String> names = JSonSchemaHelper.getNames(rows);
+            for (String name : names) {
+                Map<String, String> row = JSonSchemaHelper.getRow(rows, name);
+                String javaType = springBootJavaType(safeJavaType(row.get("javaType")));
+                String desc = row.get("description");
+                String defaultValue = row.get("defaultValue");
+                // we want to use dash in the name
+                String dash = camelCaseToDash(name);
+                String key = "camel.component." + componentName + "." + dash;
+                data.add(new String[]{key, javaType, desc, defaultValue});
+            }
+        }
+
+        if (!data.isEmpty()) {
+            StringBuilder sb = new StringBuilder();
+
+            sb.append("{\n");
+            sb.append("  \"properties\": [\n");
+            for (int i = 0; i < data.size(); i++) {
+                String[] row = data.get(i);
+                String name = row[0];
+                String javaType = row[1];
+                String desc = row[2];
+                String defaultValue = row[3];
+                sb.append("    {\n");
+                sb.append("      \"name\": \"" + name + "\",\n");
+                sb.append("      \"type\": \"" + javaType + "\",\n");
+                sb.append("      \"description\": \"" + desc + "\"");
+                if (defaultValue != null) {
+                    sb.append(",\n");
+                    if (springBootDefaultValueQuotes(javaType)) {
+                        sb.append("      \"defaultValue\": \"" + defaultValue + "\"\n");
+                    } else {
+                        sb.append("      \"defaultValue\": " + defaultValue + "\n");
+                    }
+                } else {
+                    sb.append("\n");
+                }
+                if (i < data.size() - 1) {
+                    sb.append("    },\n");
+                } else {
+                    sb.append("    }\n");
+                }
+            }
+            sb.append("  ]\n");
+            sb.append("}\n");
+
+            outFolder.mkdirs();
+            File file = new File(outFolder, "spring-configuration-metadata.json");
+            try {
+                FileOutputStream fos = new FileOutputStream(file, false);
+                fos.write(sb.toString().getBytes());
+                IOHelper.close(fos);
+                getLog().info("Created file: " + file);
+            } catch (Throwable e) {
+                throw new MojoFailureException("Cannot write to file " + file + " due " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private static String camelCaseToDash(String name) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < name.length(); i++) {
+            char ch = name.charAt(i);
+            if (Character.isUpperCase(ch)) {
+                sb.append("-");
+                sb.append(Character.toLowerCase(ch));
+            } else {
+                sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static String springBootJavaType(String javaType) {
+        if ("boolean".equalsIgnoreCase(javaType)) {
+            return "java.lang.Boolean";
+        } else if ("int".equalsIgnoreCase(javaType)) {
+            return "java.lang.Integer";
+        } else if ("long".equalsIgnoreCase(javaType)) {
+            return "java.lang.Long";
+        } else if ("string".equalsIgnoreCase(javaType)) {
+            return "java.lang.String";
+        }
+        return javaType;
+    }
+
+    private static boolean springBootDefaultValueQuotes(String javaType) {
+        if ("java.lang.Boolean".equalsIgnoreCase(javaType)) {
+            return false;
+        } else if ("java.lang.Integer".equalsIgnoreCase(javaType)) {
+            return false;
+        } else if ("java.lang.Long".equalsIgnoreCase(javaType)) {
+            return false;
+        }
+        return true;
+    }
+}