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/21 11:38:17 UTC

[camel] 02/03: 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 4ca51e34fb5f1ebf44147045998281ca568855db
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Jun 21 13:12:06 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.
---
 catalog/camel-main-maven-plugin/pom.xml            |   7 ++
 .../src/main/docs/camel-main-maven-plugin.adoc     |   1 +
 .../org/apache/camel/maven/GenerateHelper.java     | 111 +++++++++++++++++++++
 .../java/org/apache/camel/maven/GenerateMojo.java  |  77 ++++++++------
 examples/camel-example-main-artemis/pom.xml        |   4 +-
 .../META-INF/spring-configuration-metadata.json    |   6 +-
 .../src/main/resources/application.properties      |   2 +
 7 files changed, 171 insertions(+), 37 deletions(-)

diff --git a/catalog/camel-main-maven-plugin/pom.xml b/catalog/camel-main-maven-plugin/pom.xml
index f5e0d54..dbe295b 100644
--- a/catalog/camel-main-maven-plugin/pom.xml
+++ b/catalog/camel-main-maven-plugin/pom.xml
@@ -119,6 +119,13 @@
             <version>${project.version}</version>
         </dependency>
 
+        <!-- java source parser -->
+        <dependency>
+            <groupId>org.jboss.forge.roaster</groupId>
+            <artifactId>roaster-jdt</artifactId>
+            <version>${roaster-version}</version>
+        </dependency>
+
         <!-- logging -->
         <dependency>
             <groupId>org.apache.logging.log4j</groupId>
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 34f9007..b904168 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
@@ -169,6 +169,7 @@ The maven plugin *generate* goal supports the following options which can be con
 | logUnmapped | false | When autowiring has detected multiple implementations (2 or more) of a given interface, which cannot be mapped, should they be logged so you can see and add manual mapping if needed.
 | 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.
+| downloadSourceJars | true | Whether to allow downloading -source JARs when generating spring boot tooling to include javadoc as description for discovered options.
 | exclude | | To exclude autowiring specific properties with these key names. You can also configure a single entry and separate the excludes with comma.
 | include | | To include autowiring specific properties with these key names. You can also configure a single entry and separate the includes with comma.
 | mappings | | To setup special mappings between known types as key=value pairs. You can also configure a single entry and separate the mappings with comma.
diff --git a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateHelper.java b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateHelper.java
new file mode 100644
index 0000000..9f6f8ef
--- /dev/null
+++ b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateHelper.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+public final class GenerateHelper {
+
+    private static final String VALID_CHARS = ".,-='/\\!&%():;#${}";
+
+    private GenerateHelper() {
+    }
+
+    /**
+     * Sanitizes the javadoc to removed invalid characters so it can be used as json description
+     *
+     * @param javadoc  the javadoc
+     * @return the text that is valid as json
+     */
+    public static String sanitizeDescription(String javadoc, boolean summary) {
+        if (javadoc == null || javadoc.isEmpty()) {
+            return null;
+        }
+
+        // lets just use what java accepts as identifiers
+        StringBuilder sb = new StringBuilder();
+
+        // split into lines
+        String[] lines = javadoc.split("\n");
+
+        boolean first = true;
+        for (String line : lines) {
+            line = line.trim();
+
+            if (line.startsWith("**")) {
+                continue;
+            }
+            // remove leading javadoc *
+            if (line.startsWith("*")) {
+                line = line.substring(1);
+                line = line.trim();
+            }
+
+            // terminate if we reach @param, @return or @deprecated as we only want the javadoc summary
+            if (line.startsWith("@param") || line.startsWith("@return") || line.startsWith("@deprecated")) {
+                break;
+            }
+
+            // skip lines that are javadoc references
+            if (line.startsWith("@")) {
+                continue;
+            }
+
+            // remove all XML tags
+            line = line.replaceAll("<.*?>", "");
+
+            // remove all inlined javadoc links, eg such as {@link org.apache.camel.spi.Registry}
+            // use #? to remove leading # in case its a local reference
+            line = line.replaceAll("\\{\\@\\w+\\s#?([\\w.#(\\d,)]+)\\}", "$1");
+
+            // we are starting from a new line, so add a whitespace
+            if (!first) {
+                sb.append(' ');
+            }
+
+            // create a new line
+            StringBuilder cb = new StringBuilder();
+            for (char c : line.toCharArray()) {
+                if (Character.isJavaIdentifierPart(c) || VALID_CHARS.indexOf(c) != -1) {
+                    cb.append(c);
+                } else if (Character.isWhitespace(c)) {
+                    // always use space as whitespace, also for line feeds etc
+                    cb.append(' ');
+                }
+            }
+
+            // append data
+            String s = cb.toString().trim();
+            sb.append(s);
+
+            boolean empty = s.isEmpty();
+            boolean endWithDot = s.endsWith(".");
+            boolean haveText = sb.length() > 0;
+
+            if (haveText && summary && (empty || endWithDot)) {
+                // if we only want a summary, then skip at first empty line we encounter, or if the sentence ends with a dot
+                break;
+            }
+
+            first = false;
+        }
+
+        // remove double whitespaces, and trim
+        String s = sb.toString();
+        s = s.replaceAll("\\s+", " ");
+        return s.trim();
+    }
+
+}
diff --git a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateMojo.java b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateMojo.java
index dc460e6..158fe84 100644
--- a/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateMojo.java
+++ b/catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateMojo.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.
@@ -23,23 +23,16 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.net.JarURLConnection;
-import java.net.URL;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.logging.FileHandler;
 import java.util.stream.Collectors;
 
 import org.apache.camel.maven.model.AutowireData;
 import org.apache.camel.maven.model.SpringBootData;
 import org.apache.camel.support.IntrospectionSupport;
 import org.apache.camel.support.PatternHelper;
-import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.OrderedProperties;
 import org.apache.camel.util.StringHelper;
@@ -50,6 +43,11 @@ import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
 
+import org.jboss.forge.roaster.Roaster;
+import org.jboss.forge.roaster.model.source.JavaClassSource;
+import org.jboss.forge.roaster.model.source.MethodSource;
+
+import static org.apache.camel.maven.GenerateHelper.sanitizeDescription;
 import static org.apache.camel.util.StringHelper.camelCaseToDash;
 
 /**
@@ -71,6 +69,13 @@ public class GenerateMojo extends AbstractMainMojo {
     protected boolean springBootEnabled;
 
     /**
+     * Whether to allow downloading -source JARs when generating spring boot tooling to include
+     * javadoc as description for discovered options.
+     */
+    @Parameter(property = "camel.downloadSourceJars", defaultValue = "true")
+    protected boolean downloadSourceJars;
+
+    /**
      * When autowiring has detected multiple implementations (2 or more) of a given interface, which
      * cannot be mapped, should they be logged so you can see and add manual mapping if needed.
      */
@@ -165,12 +170,12 @@ public class GenerateMojo extends AbstractMainMojo {
                         Set<Class<?>> classes = reflections.getSubTypesOf(clazz);
                         // filter classes (must not be interfaces, must be public, must not be abstract, must be top level) and also a valid autowire class
                         classes = classes.stream().filter(
-                                c -> !c.isInterface()
-                                        && Modifier.isPublic(c.getModifiers())
-                                        && !Modifier.isAbstract(c.getModifiers())
-                                        && c.getEnclosingClass() == null
-                                        && isValidAutowireClass(c))
-                                .collect(Collectors.toSet());
+                            c -> !c.isInterface()
+                            && Modifier.isPublic(c.getModifiers())
+                            && !Modifier.isAbstract(c.getModifiers())
+                            && c.getEnclosingClass() == null
+                            && isValidAutowireClass(c))
+                            .collect(Collectors.toSet());
                         Class best = chooseBestKnownType(componentName, name, clazz, classes, mappingProperties);
                         if (best != null) {
                             key = "camel.component." + componentName + "." + dash;
@@ -186,17 +191,18 @@ public class GenerateMojo extends AbstractMainMojo {
                                 // sort the setters
                                 setters.sort((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
 
-                                String javaSource = null;
-                                if (!setters.isEmpty()) {
+                                JavaClassSource javaClassSource = null;
+                                if (downloadSourceJars && !setters.isEmpty()) {
                                     String path = best.getName().replace('.', '/') + ".java";
                                     getLog().debug("Loading Java source: " + path);
 
                                     InputStream is = getSourcesClassLoader().getResourceAsStream(path);
                                     if (is != null) {
-                                        javaSource = IOHelper.loadText(is);
+                                        String text = IOHelper.loadText(is);
                                         IOHelper.close(is);
+                                        javaClassSource = (JavaClassSource) Roaster.parse(text);
                                     }
-                                    getLog().info("Loaded source code: " + javaSource);
+                                    getLog().debug("Loaded source code: " + clazz);
                                 }
 
                                 for (Method m : setters) {
@@ -208,8 +214,17 @@ public class GenerateMojo extends AbstractMainMojo {
                                     getLog().debug("Spring Boot option: " + bootKey);
 
                                     // find the setter method and grab the javadoc
+                                    String desc = extractJavaDocFromMethod(javaClassSource, m);
+                                    if (desc == null) {
+                                        desc = "";
+                                    } else {
+                                        desc = sanitizeDescription(desc, false);
+                                        if (!desc.endsWith(".")) {
+                                            desc += ". ";
+                                        }
+                                    }
+                                    desc += "Auto discovered option from class: " + best.getName() + " to set the option via setter: " + m.getName();
 
-                                    String desc = "Auto discovered option from class: " + best.getName() + " to set the option via setter: " + m.getName();
                                     springBootData.add(new SpringBootData(bootKey, springBootJavaType(bootJavaType), desc, sourceType, null));
                                 }
                             }
@@ -465,15 +480,15 @@ public class GenerateMojo extends AbstractMainMojo {
         }
     }
 
-    public static void main(String[] args) throws Exception {
-        JarFile jar = new JarFile("/Users/davsclaus/.m2/repository/org/springframework/spring-jms/5.1.8.RELEASE/spring-jms-5.1.8.RELEASE-sources.jar");
-        System.out.println(jar);
-        JarEntry en = jar.getJarEntry("org/springframework/jms/connection/CachingConnectionFactory.java");
-        System.out.println(en);
-        InputStream is = jar.getInputStream(en);
-        String source = IOHelper.loadText(is);
-        IOHelper.close(is);
-        System.out.println(source);
+    private static String extractJavaDocFromMethod(JavaClassSource clazz, Method method) {
+        if (clazz == null) {
+            return null;
+        }
+        MethodSource ms = clazz.getMethod(method.getName(), method.getParameterTypes()[0]);
+        if (ms != null) {
+            return ms.getJavaDoc().getText();
+        }
+        return null;
     }
 
 }
diff --git a/examples/camel-example-main-artemis/pom.xml b/examples/camel-example-main-artemis/pom.xml
index 5d0ddc0..c918bb2 100644
--- a/examples/camel-example-main-artemis/pom.xml
+++ b/examples/camel-example-main-artemis/pom.xml
@@ -40,12 +40,10 @@
     <dependencies>
 
         <!-- to have spring boot tooling support in IDEA you need spring-boot JARs on the classpath -->
-<!--
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-spring-boot-starter</artifactId>
         </dependency>
--->
 
         <dependency>
             <groupId>org.apache.camel</groupId>
@@ -113,7 +111,7 @@
                 <artifactId>camel-main-maven-plugin</artifactId>
                 <version>${project.version}</version>
                 <configuration>
-                    <logClasspath>true</logClasspath>
+                    <logClasspath>false</logClasspath>
                     <!-- lets show which options are unmapped -->
                     <!-- <logUnmapped>true</logUnmapped> -->
                     <!-- just include only the jms component -->
diff --git a/examples/camel-example-main-artemis/src/main/resources/META-INF/spring-configuration-metadata.json b/examples/camel-example-main-artemis/src/main/resources/META-INF/spring-configuration-metadata.json
index 06960b3..c771da6 100644
--- a/examples/camel-example-main-artemis/src/main/resources/META-INF/spring-configuration-metadata.json
+++ b/examples/camel-example-main-artemis/src/main/resources/META-INF/spring-configuration-metadata.json
@@ -787,13 +787,13 @@
       "name": "camel.component.jms.connection-factory.cache-consumers",
       "type": "java.lang.Boolean",
       "sourceType": "org.springframework.jms.connection.CachingConnectionFactory",
-      "description": "Auto discovered option from class: org.springframework.jms.connection.CachingConnectionFactory to set the option via setter: setCacheConsumers"
+      "description": "Specify whether to cache JMS MessageConsumers per JMS Session instance(more specifically: one MessageConsumer per Destination, selector Stringand Session). Note that durable subscribers will only be cached untillogical closing of the Session handle.Default is true. Switch this to false in order to alwaysrecreate MessageConsumers on demand.Auto discovered option from class: org.springframework.jms.connection.CachingConnectionFactory to set the option via setter: setC [...]
     },
     {
       "name": "camel.component.jms.connection-factory.cache-producers",
       "type": "java.lang.Boolean",
       "sourceType": "org.springframework.jms.connection.CachingConnectionFactory",
-      "description": "Auto discovered option from class: org.springframework.jms.connection.CachingConnectionFactory to set the option via setter: setCacheProducers"
+      "description": "Specify whether to cache JMS MessageProducers per JMS Session instance(more specifically: one MessageProducer per Destination and Session).Default is true. Switch this to false in order to alwaysrecreate MessageProducers on demand.Auto discovered option from class: org.springframework.jms.connection.CachingConnectionFactory to set the option via setter: setCacheProducers"
     },
     {
       "name": "camel.component.jms.connection-factory.client-id",
@@ -817,7 +817,7 @@
       "name": "camel.component.jms.connection-factory.session-cache-size",
       "type": "java.lang.Integer",
       "sourceType": "org.springframework.jms.connection.CachingConnectionFactory",
-      "description": "Auto discovered option from class: org.springframework.jms.connection.CachingConnectionFactory to set the option via setter: setSessionCacheSize"
+      "description": "Specify the desired size for the JMS Session cache (per JMS Session type).This cache size is the maximum limit for the number of cached Sessionsper session acknowledgement type (auto, client, dups_ok, transacted).As a consequence, the actual number of cached Sessions may be up tofour times as high as the specified value - in the unlikely caseof mixing and matching different acknowledgement types.Default is 1: caching a single Session, (re-)creating further ones onde [...]
     },
     {
       "name": "camel.component.jms.connection-factory.target-connection-factory",
diff --git a/examples/camel-example-main-artemis/src/main/resources/application.properties b/examples/camel-example-main-artemis/src/main/resources/application.properties
index 309db5c..14ea669 100644
--- a/examples/camel-example-main-artemis/src/main/resources/application.properties
+++ b/examples/camel-example-main-artemis/src/main/resources/application.properties
@@ -29,6 +29,8 @@ camel.main.jmx-enabled = false
 # which contains some additional autowires so we only need here to specify the URL to the broker
 camel.component.jms.connection-factory.brokerURL=tcp://localhost:61616
 
+camel.component.jms.connection-factory.cl
+
 # in case you want to use Spring's CachingConnectionFactory, you can configure it as follows:
 # (then you don't need the camel-main-maven-plugin, as you can explicit configure it all here)
 ### camel.component.jms.connectionFactory=#class:org.springframework.jms.connection.CachingConnectionFactory