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 c316a27ffc80f75dc5852ebdb44eabe93722cd5c Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Fri Jun 21 09:50:55 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 | 98 ++++--------- .../org/apache/camel/maven/AbstractMainMojo.java | 5 +- .../java/org/apache/camel/maven/AutowireMojo.java | 1 + .../maven/{AutowireMojo.java => GenerateMojo.java} | 162 +++++++++++++++++---- .../apache/camel/maven/SpringBootToolingMojo.java | 1 + examples/camel-example-main-artemis/pom.xml | 3 +- 6 files changed, 171 insertions(+), 99 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 e8d1364..97f6eca 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 @@ -2,11 +2,9 @@ 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-main:generate - To pre-scan your project and prepare autowiring and sprint boot tooling support by classpath scanning. -== camel:autowire +== Autowiring To pre-scan your project and prepare autowiring by classpath scanning. The idea is to use this maven plugin at build/compile time and detect what's on the classpath @@ -22,8 +20,28 @@ JMS client you are using. Another example is JDBC drivers etc. In other words its a bit like Spring Boot _starter_ JARs that also offers a similar concept, but without the phase of doing this during build time. +== 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. + +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. + +== Using the plugin + ---- -mvn camel-main:autowire +mvn camel-main:generate ---- You can also enable the plugin to automatic run as part of the build to catch these errors. @@ -37,7 +55,7 @@ You can also enable the plugin to automatic run as part of the build to catch th <execution> <phase>process-classes</phase> <goals> - <goal>autowire</goal> + <goal>generate</goal> </goals> </execution> </executions> @@ -50,14 +68,14 @@ the compilation of the main source code. === Include and Exclude properties or components -By default the `autowire` goal will scan all the detected Camel components from the classpath. +By default the plugin will scan all the detected Camel components from the classpath. For example as shown below, there are 29 detected Camel components, and 1 mapping was created in the `autowire.properties` file [source,text] ---- -[INFO] --- camel-main-maven-plugin:3.0.0-SNAPSHOT:autowire (generate) @ camel-example-main-artemis --- +[INFO] --- camel-main-maven-plugin:3.0.0-SNAPSHOT:generate (generate) @ camel-example-main-artemis --- [INFO] Detected Camel version used in project: 3.0.0-SNAPSHOT [INFO] Pre-scanning using Camel version: 3.0.0-SNAPSHOT [INFO] Discovered 29 Camel components from classpath: [bean, browse, class, controlbus, dataformat, dataset, dataset-test, direct, direct-vm, file, jms, language, log, mock, properties, quartz, quartz2, ref, rest, rest-api, saga, scheduler, seda, spring-event, stub, timer, validator, vm, xslt] @@ -82,7 +100,7 @@ For example to only include the JMS component you can do: <execution> <phase>process-classes</phase> <goals> - <goal>autowire</goal> + <goal>generate</goal> </goals> </execution> </executions> @@ -118,7 +136,7 @@ For example one of the default mappings is as shown: javax.jms.ConnectionFactory=org.apache.activemq.artemis.jms.client.ActiveMQJMSConnectionFactory;org.apache.activemq.ActiveMQConnectionFactory ---- -This tells the `autowire` goal that if the value of the property is a `javax.jms.ConnectionFactory` type +This tells the `generate` goal that if the value of the property is a `javax.jms.ConnectionFactory` type then we should only accept the following implementations when scanning the classpath: - `org.apache.activemq.artemis.jms.client.ActiveMQJMSConnectionFactory` @@ -137,11 +155,11 @@ You can also specify the skip certain types, such as org.springframework.jms.core.JmsOperations=#skip# ---- -Which means the `autowire` goal skips any property values that are of type `org.springframework.jms.core.JmsOperations`. +Which means the `generate` goal skips any property values that are of type `org.springframework.jms.core.JmsOperations`. === Options -The maven plugin *autowire* 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. +The maven plugin *generate* 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 @@ -155,62 +173,8 @@ The maven plugin *autowire* goal supports the following options which can be con | mappingsFile | | Optional mappings file loaded from classpath, with mapping that override any default mappings. Will by default load the file `camel-main-mappings.properties` from the classpath root. |=== -==== Examples +=== Examples 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 44a8891..5705d47 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 @@ -32,14 +32,11 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; -import java.util.function.Function; 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.maven.model.AutowireData; -import org.apache.camel.maven.model.SpringBootData; import org.apache.camel.util.IOHelper; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.repository.ArtifactRepository; @@ -293,7 +290,7 @@ public abstract class AbstractMainMojo extends AbstractExecMojo { return artifacts; } - protected String safeJavaType(String javaType) { + static String safeJavaType(String javaType) { int pos = javaType.indexOf('<'); if (pos > 0) { return javaType.substring(0, pos); 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 d46b653..a742f4a 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 @@ -48,6 +48,7 @@ import org.reflections.Reflections; * Pre scans your project and prepare autowiring by classpath scanning */ @Mojo(name = "autowire", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) +@Deprecated public class AutowireMojo extends AbstractMainMojo { /** 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/GenerateMojo.java similarity index 64% copy from catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/AutowireMojo.java copy to catalog/camel-main-maven-plugin/src/main/java/org/apache/camel/maven/GenerateMojo.java index d46b653..c3e3b21 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/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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> * 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. @@ -24,14 +24,12 @@ import java.io.InputStream; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; -import org.apache.camel.catalog.CamelCatalog; -import org.apache.camel.catalog.JSonSchemaHelper; import org.apache.camel.maven.model.AutowireData; +import org.apache.camel.maven.model.SpringBootData; import org.apache.camel.support.PatternHelper; import org.apache.camel.util.IOHelper; import org.apache.camel.util.OrderedProperties; @@ -42,13 +40,14 @@ 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; -import org.reflections.Reflections; + +import static org.apache.camel.util.StringHelper.camelCaseToDash; /** - * Pre scans your project and prepare autowiring by classpath scanning + * Pre scans your project and prepares autowiring and spring-boot tooling support by classpath scanning. */ -@Mojo(name = "autowire", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) -public class AutowireMojo extends AbstractMainMojo { +@Mojo(name = "generate", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) +public class GenerateMojo extends AbstractMainMojo { /** * When autowiring has detected multiple implementations (2 or more) of a given interface, which @@ -61,7 +60,13 @@ public class AutowireMojo extends AbstractMainMojo { * The output directory for generated autowire file */ @Parameter(readonly = true, defaultValue = "${project.build.directory}/classes/META-INF/services/org/apache/camel/") - protected File outFolder; + protected File outAutowireFolder; + + /** + * The output directory for generated spring boot tooling file + */ + @Parameter(readonly = true, defaultValue = "${project.build.directory}/../src/main/resources/META-INF/") + protected File outSpringBootFolder; /** * To exclude autowiring specific properties with these key names. @@ -90,6 +95,7 @@ public class AutowireMojo extends AbstractMainMojo { @Parameter(defaultValue = "${project.build.directory}/classes/camel-main-mappings.properties") protected File mappingsFile; + @Override public void execute() throws MojoExecutionException, MojoFailureException { // load default mappings @@ -112,10 +118,19 @@ public class AutowireMojo extends AbstractMainMojo { mappingProperties.putAll(mappingFileProperties); } - final List<AutowireData> autowires = new ArrayList<>(); + final List<AutowireData> autowireData = new ArrayList<>(); + final List<SpringBootData> springBootData = new ArrayList<>(); + ComponentCallback callback = (componentName, name, type, javaType, description, defaultValue) -> { + // gather spring boot data + // we want to use dash in the name + String dash = camelCaseToDash(name); + String key = "camel.component." + componentName + "." + dash; + springBootData.add(new SpringBootData(key, springBootJavaType(javaType), description, defaultValue)); + + // check if we can do automatic autowire to complex singleton objects from classes in the classpath if ("object".equals(type)) { - if (!isValidPropertyName(componentName, name)) { + if (!isValidAutowirePropertyName(componentName, name)) { getLog().debug("Skipping property name: " + name); return; } @@ -133,15 +148,14 @@ public class AutowireMojo extends AbstractMainMojo { .collect(Collectors.toSet()); Class best = chooseBestKnownType(componentName, name, clazz, classes, mappingProperties); if (best != null) { - String key = "camel.component." + componentName + "." + name; + key = "camel.component." + componentName + "." + name; String value = "#class:" + best.getName(); getLog().debug(key + "=" + value); - autowires.add(new AutowireData(key, value)); + autowireData.add(new AutowireData(key, value)); // TODO: get options from best class (getter/setter pairs) // we dont have documentation // add as spring boot options - } } @@ -152,23 +166,84 @@ public class AutowireMojo extends AbstractMainMojo { } }; - // perform the work with this callback + // execute with the callback doExecute(callback); - if (!autowires.isEmpty()) { - outFolder.mkdirs(); - File file = new File(outFolder, "autowire.properties"); + // write the output files + writeAutowireFile(autowireData); + writeSpringBootFile(springBootData); + } + + protected void writeSpringBootFile(List<SpringBootData> springBootData) throws MojoFailureException { + if (!springBootData.isEmpty()) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < springBootData.size(); i++) { + SpringBootData row = springBootData.get(i); + sb.append(" {\n"); + sb.append(" \"name\": \"" + row.getName() + "\",\n"); + sb.append(" \"type\": \"" + row.getJavaType() + "\",\n"); + sb.append(" \"description\": \"" + row.getDescription() + "\""); + if (row.getDefaultValue() != null) { + sb.append(",\n"); + if (springBootDefaultValueQuotes(row.getJavaType())) { + sb.append(" \"defaultValue\": \"" + row.getDefaultValue() + "\"\n"); + } else { + sb.append(" \"defaultValue\": " + row.getDefaultValue() + "\n"); + } + } else { + sb.append("\n"); + } + if (i < springBootData.size() - 1) { + sb.append(" },\n"); + } else { + sb.append(" }\n"); + } + } + sb.append(" ]\n"); + sb.append("}\n"); + + // okay then add the components into the main json at the end so they get merged together + // load camel-main metadata + String mainJson = loadCamelMainConfigurationMetadata(); + if (mainJson == null) { + getLog().warn("Cannot load camel-main-configuration-metadata.json from within the camel-main JAR from the classpath." + + " Not possible to build spring boot configuration file for this project"); + return; + } + int pos = mainJson.lastIndexOf(" }"); + String newJson = mainJson.substring(0, pos); + newJson = newJson + " },\n"; + newJson = newJson + sb.toString(); + + outSpringBootFolder.mkdirs(); + File file = new File(outSpringBootFolder, "spring-configuration-metadata.json"); + try { + FileOutputStream fos = new FileOutputStream(file, false); + fos.write(newJson.getBytes()); + IOHelper.close(fos); + getLog().info("Created file: " + file); + } catch (Throwable e) { + throw new MojoFailureException("Cannot write to file " + file + " due " + e.getMessage(), e); + } + } + } + + protected void writeAutowireFile(List<AutowireData> autowireData) throws MojoFailureException { + if (!autowireData.isEmpty()) { + outAutowireFolder.mkdirs(); + File file = new File(outAutowireFolder, "autowire.properties"); try { FileOutputStream fos = new FileOutputStream(file, false); fos.write("# Generated by camel build tools\n".getBytes()); - for (AutowireData data : autowires) { + for (AutowireData data : autowireData) { fos.write(data.getKey().getBytes()); fos.write('='); fos.write(data.getValue().getBytes()); fos.write('\n'); } IOHelper.close(fos); - getLog().info("Created file: " + file + " (autowire by classpath: " + autowires.size() + ")"); + getLog().info("Created file: " + file + " (autowire by classpath: " + autowireData.size() + ")"); } catch (Throwable e) { throw new MojoFailureException("Cannot write to file " + file + " due " + e.getMessage(), e); } @@ -178,7 +253,7 @@ public class AutowireMojo extends AbstractMainMojo { protected Properties loadDefaultMappings() throws MojoFailureException { Properties mappings = new OrderedProperties(); try { - InputStream is = AutowireMojo.class.getResourceAsStream("/default-mappings.properties"); + InputStream is = GenerateMojo.class.getResourceAsStream("/default-mappings.properties"); if (is != null) { mappings.load(is); } @@ -231,7 +306,7 @@ public class AutowireMojo extends AbstractMainMojo { return null; } - protected boolean isValidPropertyName(String componentName, String name) { + protected boolean isValidAutowirePropertyName(String componentName, String name) { // we want to regard names as the same if they are using dash or not, and also to be case insensitive. String prefix = "camel.component." + componentName + "."; name = StringHelper.dashToCamelCase(name); @@ -273,9 +348,44 @@ public class AutowireMojo extends AbstractMainMojo { return type != null && !type.isPrimitive() && !type.getName().startsWith("java."); } - protected boolean isValidAutowireClass(Class clazz) { + private static boolean isValidAutowireClass(Class clazz) { // skip all from Apache Camel and regular JDK as they would be default anyway return !clazz.getName().startsWith("org.apache.camel"); } + private String loadCamelMainConfigurationMetadata() throws MojoFailureException { + try { + InputStream is = classLoader.getResourceAsStream("META-INF/camel-main-configuration-metadata.json"); + String text = IOHelper.loadText(is); + IOHelper.close(is); + return text; + } catch (Throwable e) { + throw new MojoFailureException("Error during discovering camel-main from classpath due " + e.getMessage(), e); + } + } + + 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; + } + } 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 index dc56282..9691afc 100644 --- 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 @@ -38,6 +38,7 @@ import static org.apache.camel.util.StringHelper.camelCaseToDash; * offer code completion for editing properties files. */ @Mojo(name = "spring-boot-tooling", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) +@Deprecated public class SpringBootToolingMojo extends AbstractMainMojo { /** diff --git a/examples/camel-example-main-artemis/pom.xml b/examples/camel-example-main-artemis/pom.xml index e1cad5a..f77c46b 100644 --- a/examples/camel-example-main-artemis/pom.xml +++ b/examples/camel-example-main-artemis/pom.xml @@ -115,8 +115,7 @@ <execution> <id>generate</id> <goals> - <goal>autowire</goal> - <goal>spring-boot-tooling</goal> + <goal>generate</goal> </goals> <phase>process-classes</phase> </execution>