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 <claus.ib...@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; + } +}