This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-12644 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 844167a19c3ac041135fa5ec4aab191354a0f2b5 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Jul 16 20:02:53 2018 +0200 CAMEL-12644: Generate spring boot auto configuration in the docs. --- .../camel-jdbc/src/main/docs/jdbc-component.adoc | 90 ++++----- platforms/spring-boot/components-starter/pom.xml | 18 ++ ...pdateSpringBootAutoConfigurationReadmeMojo.java | 214 +++++++++++++++++++++ .../model/SpringBootAutoConfigureOptionModel.java | 112 +++++++++++ .../spring-boot-auto-configure-options.mvel | 15 ++ 5 files changed, 406 insertions(+), 43 deletions(-) diff --git a/components/camel-jdbc/src/main/docs/jdbc-component.adoc b/components/camel-jdbc/src/main/docs/jdbc-component.adoc index 1fdb4ad..3ac2713 100644 --- a/components/camel-jdbc/src/main/docs/jdbc-component.adoc +++ b/components/camel-jdbc/src/main/docs/jdbc-component.adoc @@ -12,35 +12,32 @@ spring-jdbc. Maven users will need to add the following dependency to their `pom.xml` for this component: -[source,java] ------------------------------------------------------------- +[source,xml] +---- <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-jdbc</artifactId> <version>x.x.x</version> <!-- use the same version as your Camel core version --> </dependency> ------------------------------------------------------------- +---- This component can only be used to define producer endpoints, which means that you cannot use the JDBC component in a `from()` statement. -### URI format +=== URI format -[source,java] ------------------------------ +[source,text] +---- jdbc:dataSourceName[?options] ------------------------------ +---- This component only supports producer endpoints. You can append query options to the URI in the following format, `?option=value&option=value&...` -### Options - - - +=== Options // component options: START The JDBC component supports 2 options, which are listed below. @@ -101,9 +98,27 @@ with the following path and query parameters: |=== // endpoint options: END +// spring-boot-auto-configure options: START +=== Spring Boot Auto-Configuration + + +The component supports 3 options, which are listed below. + -### Result +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *camel.component.jdbc.data-source* | To use the DataSource instance instead of looking up the data source by + name from the registry. The option is a javax.sql.DataSource type. | | String +| *camel.component.jdbc.enabled* | Enable jdbc component | true | boolean +| *camel.component.jdbc.resolve-property-placeholders* | Whether the component should resolve property placeholders on itself when + starting. Only properties which are of String type can use property + placeholders. | true | boolean +|=== +// spring-boot-auto-configure options: END + +=== Result By default the result is returned in the OUT body as an `ArrayList<HashMap<String, Object>>`. The `List` object contains the @@ -114,10 +129,10 @@ the result. *Note:* This component fetches `ResultSetMetaData` to be able to return the column name as the key in the `Map`. -#### Message Headers +==== Message Headers [width="100%",cols="10%,90%",options="header",] -|======================================================================= +|=== |Header |Description |`CamelJdbcRowCount` |If the query is a `SELECT`, query the row count is returned in this OUT @@ -136,9 +151,9 @@ type. |`CamelJdbcParametes` |*Camel 2.12:* A `java.util.Map` which has the headers to be used if `useHeadersAsParameters` has been enabled. -|======================================================================= +|=== -### Generated keys +=== Generated keys *Available as of Camel 2.10* @@ -155,7 +170,7 @@ test]. Using generated keys does not work with together with named parameters. -### Using named parameters +=== Using named parameters *Available as of Camel 2.12* @@ -167,18 +182,18 @@ Notice in the example above we set two headers with constant value + for the named parameters: [source,java] ----------------------------------------------------------------------------------------- +---- from("direct:projects") .setHeader("lic", constant("ASF")) .setHeader("min", constant(123)) .setBody("select * from projects where license = :?lic and id > :?min order by id") .to("jdbc:myDataSource?useHeadersAsParameters=true") ----------------------------------------------------------------------------------------- +---- You can also store the header values in a `java.util.Map` and store the map on the headers with the key `CamelJdbcParameters`. -### Samples +=== Samples In the following example, we fetch the rows from the customer table. @@ -198,12 +213,8 @@ If you want to work on the rows one by one instead of the entire ResultSet at once you need to use the Splitter EIP such as: -In Camel 2.13.x or older - -In Camel 2.14.x or newer - [source,java] -------------------------------------------------------------------------------------------------- +---- from("direct:hello") // here we split the data from the testdb into new messages one by one // so the mock endpoint will receive a message per row in the table @@ -212,9 +223,9 @@ from("direct:hello") .to("jdbc:testdb?outputType=StreamList") .split(body()).streaming() .to("mock:result"); -------------------------------------------------------------------------------------------------- +---- -### Sample - Polling the database every minute +=== Sample - Polling the database every minute If we want to poll a database using the JDBC component, we need to combine it with a polling scheduler such as the <<timer-component,Timer>> @@ -222,11 +233,14 @@ or <<quartz-component,Quartz>> etc. In the following example, we retrieve data from the database every 60 seconds: [source,java] ------------------------------------------------------------------------------------------------------------------------------- -from("timer://foo?period=60000").setBody(constant("select * from customer")).to("jdbc:testdb").to("activemq:queue:customers"); ------------------------------------------------------------------------------------------------------------------------------- +---- +from("timer://foo?period=60000") + .setBody(constant("select * from customer")) + .to("jdbc:testdb") + .to("activemq:queue:customers"); +---- -### Sample - Move Data Between Data Sources + +=== Sample - Move Data Between Data Sources A common use case is to query for data, process it and move it to another data source (ETL operations). In the following example, we @@ -234,7 +248,7 @@ retrieve new customer records from the source table every hour, filter/transform them and move them to a destination table: [source,java] ------------------------------------------------------------------------------------------------- +---- from("timer://MoveNewCustomersEveryHour?period=3600000") .setBody(constant("select * from customer where create_time > (sysdate-1/24)")) .to("jdbc:testdb") @@ -242,15 +256,5 @@ from("timer://MoveNewCustomersEveryHour?period=3600000") .process(new MyCustomerProcessor()) //filter/transform results as needed .setBody(simple("insert into processed_customer values('${body[ID]}','${body[NAME]}')")) .to("jdbc:testdb"); ------------------------------------------------------------------------------------------------- - - - -### See Also - -* Configuring Camel -* Component -* Endpoint -* Getting Started +---- -* <<sql-component,SQL>> diff --git a/platforms/spring-boot/components-starter/pom.xml b/platforms/spring-boot/components-starter/pom.xml index 5b0ee13..0e65b06 100644 --- a/platforms/spring-boot/components-starter/pom.xml +++ b/platforms/spring-boot/components-starter/pom.xml @@ -77,6 +77,24 @@ <failIfNoTests>false</failIfNoTests> </configuration> </plugin> + <plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-package-maven-plugin</artifactId> + <version>${project.version}</version> + <configuration> + <!-- set to true to make build fail fast if missing documentation in docs files --> + <failFast>false</failFast> + </configuration> + <executions> + <execution> + <id>readme</id> + <goals> + <goal>update-spring-boot-auto-configuration-readme</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + </plugin> </plugins> </build> <!-- WARNING: do not edit the modules section, it is update automatically by the camel-package plugin --> diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java new file mode 100644 index 0000000..8ebb68f --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateSpringBootAutoConfigurationReadmeMojo.java @@ -0,0 +1,214 @@ +/** + * 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> + * 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.packaging; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.camel.maven.packaging.model.SpringBootAutoConfigureOptionModel; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; +import org.json.simple.DeserializationException; +import org.json.simple.JsonArray; +import org.json.simple.JsonObject; +import org.json.simple.Jsoner; +import org.mvel2.templates.TemplateRuntime; +import org.sonatype.plexus.build.incremental.BuildContext; + +import static org.apache.camel.maven.packaging.PackageHelper.loadText; +import static org.apache.camel.maven.packaging.PackageHelper.writeText; + +/** + * Generate or updates the component/dataformat/language/eip readme.md and .adoc files in the project root directory + * to include spring boot auto configuration options. + * + * @goal update-spring-boot-auto-configuration-readme + */ +public class UpdateSpringBootAutoConfigurationReadmeMojo extends AbstractMojo { + + /** + * The maven project. + * + * @parameter property="project" + * @required + * @readonly + */ + protected MavenProject project; + + /** + * The project build directory + * + * @parameter default-value="${project.build.directory}" + */ + protected File buildDir; + + /** + * The documentation directory + * + * @parameter default-value="${basedir}/../../../../components/" + */ + protected File componentsDir; + + /** + * Whether to fail the build fast if any Warnings was detected. + * + * @parameter + */ + protected Boolean failFast; + + /** + * build context to check changed files and mark them for refresh (used for + * m2e compatibility) + * + * @component + * @readonly + */ + private BuildContext buildContext; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + try { + executeStarter(project.getBasedir()); + } catch (Exception e) { + throw new MojoFailureException("Error processing spring-configuration-metadata.json", e); + } + } + + private void executeStarter(File starter) throws Exception { + File jsonFile = new File(buildDir, "classes/META-INF/spring-configuration-metadata.json"); + + // only if there is components we should update the documentation files + if (jsonFile.exists()) { + getLog().info("Processing file: " + jsonFile); + Object js = Jsoner.deserialize(new FileReader(jsonFile)); + if (js != null) { + String name = starter.getName(); + // skip camel- and -starter in the end + String componentName = name.substring(6, name.length() - 8); + getLog().debug("Camel component: " + componentName); + File docFolder = new File(componentsDir, "camel-" + componentName + "/src/main/docs/"); + // update all adoc files (as it may be component, language, data-format or just other kind) + File[] docFiles = docFolder.listFiles((f) -> f.getName().startsWith(componentName) && f.getName().endsWith(".adoc")); + if (docFiles != null && docFiles.length > 0) { + List models = parseSpringBootAutoConfigreModels(jsonFile); + String options = templateAutoConfigurationOptions(models); + for (File docFile : docFiles) { + boolean updated = updateAutoConfigureOptions(docFile, options); + if (updated) { + getLog().info("Updated doc file: " + docFile); + } else { + getLog().debug("No changes to doc file: " + docFile); + } + if (isFailFast()) { + throw new MojoExecutionException("Failed build due failFast=true"); + } + } + } else { + getLog().warn("No component docs found in folder: " + docFolder); + } + } + } + } + + private List parseSpringBootAutoConfigreModels(File file) throws IOException, DeserializationException { + List<SpringBootAutoConfigureOptionModel> answer = new ArrayList<>(); + + JsonObject obj = (JsonObject) Jsoner.deserialize(new FileReader(file)); + + JsonArray arr = obj.getCollection("properties"); + if (arr != null && !arr.isEmpty()) { + arr.forEach((e) -> { + JsonObject row = (JsonObject) e; + String name = row.getString("name"); + String javaType = row.getString("type"); + String desc = row.getString("description"); + String defaultValue = row.getString("defaultValue"); + + // skip this special option + boolean skip = name.endsWith("customizer.enabled"); + if (!skip) { + SpringBootAutoConfigureOptionModel model = new SpringBootAutoConfigureOptionModel(); + model.setName(name); + model.setJavaType(javaType); + model.setDefaultValue(defaultValue); + model.setDescription(desc); + answer.add(model); + } + }); + } + + return answer; + } + + private boolean updateAutoConfigureOptions(File file, String changed) throws MojoExecutionException { + if (!file.exists()) { + return false; + } + + try { + String text = loadText(new FileInputStream(file)); + + String existing = StringHelper.between(text, "// spring-boot-auto-configure options: START", "// spring-boot-auto-configure options: END"); + if (existing != null) { + // remove leading line breaks etc + existing = existing.trim(); + changed = changed.trim(); + if (existing.equals(changed)) { + return false; + } else { + String before = StringHelper.before(text, "// spring-boot-auto-configure options: START"); + String after = StringHelper.after(text, "// spring-boot-auto-configure options: END"); + text = before + "// spring-boot-auto-configure options: START\n" + changed + "\n// spring-boot-auto-configure options: END" + after; + writeText(file, text); + return true; + } + } else { + getLog().warn("Cannot find markers in file " + file); + getLog().warn("Add the following markers"); + getLog().warn("\t// spring-boot-auto-configure options: START"); + getLog().warn("\t// spring-boot-auto-configure options: END"); + if (isFailFast()) { + throw new MojoExecutionException("Failed build due failFast=true"); + } + return false; + } + } catch (Exception e) { + throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e); + } + } + + private String templateAutoConfigurationOptions(List<SpringBootAutoConfigureOptionModel> options) throws MojoExecutionException { + try { + String template = loadText(UpdateSpringBootAutoConfigurationReadmeMojo.class.getClassLoader().getResourceAsStream("spring-boot-auto-configure-options.mvel")); + String out = (String) TemplateRuntime.eval(template, options); + return out; + } catch (Exception e) { + throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e); + } + } + + private boolean isFailFast() { + return failFast != null && failFast; + } + +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/SpringBootAutoConfigureOptionModel.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/SpringBootAutoConfigureOptionModel.java new file mode 100644 index 0000000..686896a --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/model/SpringBootAutoConfigureOptionModel.java @@ -0,0 +1,112 @@ +/** + * 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.packaging.model; + +import static org.apache.camel.maven.packaging.StringHelper.wrapCamelCaseWords; + +public class SpringBootAutoConfigureOptionModel { + + private String name; + private String javaType; + private String defaultValue; + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getJavaType() { + return javaType; + } + + public void setJavaType(String javaType) { + this.javaType = javaType; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getShortJavaType() { + return getShortJavaType(40); + } + + public String getShortJavaType(int watermark) { + if (javaType.startsWith("java.util.Map")) { + return "Map"; + } else if (javaType.startsWith("java.util.Set")) { + return "Set"; + } else if (javaType.startsWith("java.util.List")) { + return "List"; + } + + String text = javaType; + + int pos = text.lastIndexOf("."); + if (pos != -1) { + text = text.substring(pos + 1); + } + + // use non wrapped types + if ("Boolean".equals(text)) { + text = "boolean"; + } else if ("Long".equals(text)) { + text = "long"; + } else if ("Integer".equals(text)) { + text = "int"; + } + + return text; + } + + public String getShortDefaultValue(int watermark) { + if (defaultValue == null || defaultValue.isEmpty()) { + return ""; + } + String text = defaultValue; + if (text.endsWith("<T>")) { + text = text.substring(0, text.length() - 3); + } else if (text.endsWith("<T>>")) { + text = text.substring(0, text.length() - 4); + } + + // TODO: dirty hack for AUTO_ACKNOWLEDGE which we should wrap + if ("AUTO_ACKNOWLEDGE".equals(text)) { + return "AUTO_ ACKNOWLEDGE"; + } + + return text; + } + + +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/resources/spring-boot-auto-configure-options.mvel b/tooling/maven/camel-package-maven-plugin/src/main/resources/spring-boot-auto-configure-options.mvel new file mode 100644 index 0000000..47f07e2 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/resources/spring-boot-auto-configure-options.mvel @@ -0,0 +1,15 @@ +=== Spring Boot Auto-Configuration + +@if{this.isEmpty()} +The component has no Spring Boot auto configuration options. +@else{} +The component supports @{this.size()} options, which are listed below. +@end{} + +@if{!this.isEmpty()} +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +@foreach{row : this}| *@{row.name}* | @{row.description} | @{row.getShortDefaultValue(20)} | @{row.getShortJavaType(25)} +@end{}|=== +@end{} \ No newline at end of file