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/10/09 07:37:58 UTC
[camel-quarkus] 01/03: Generate extension list readme file via
tooling like we do at Apache Camel
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch extlist
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 349c8d9514bb794b459d61f17c5099d13485fb8f
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Oct 9 09:25:10 2019 +0200
Generate extension list readme file via tooling like we do at Apache Camel
---
catalog/camel-quarkus-catalog/pom.xml | 2 +
extensions/readme.adoc | 142 +++++
tooling/maven/package-maven-plugin/pom.xml | 5 +
.../camel/quarkus/maven/JSonSchemaHelper.java | 206 +++++++
.../org/apache/camel/quarkus/maven/MvelHelper.java | 46 ++
.../quarkus/maven/PrepareExtensionsReadmeMojo.java | 676 +++++++++++++++++++++
.../apache/camel/quarkus/maven/StringHelper.java | 200 ++++++
.../camel/quarkus/maven/model/ComponentModel.java | 190 ++++++
.../camel/quarkus/maven/model/DataFormatModel.java | 155 +++++
.../camel/quarkus/maven/model/LanguageModel.java | 155 +++++
.../camel/quarkus/maven/model/OtherModel.java | 145 +++++
.../src/main/resources/readme-components.mvel | 14 +
.../src/main/resources/readme-dataformats.mvel | 12 +
.../src/main/resources/readme-languages.mvel | 12 +
.../src/main/resources/readme-others.mvel | 12 +
15 files changed, 1972 insertions(+)
diff --git a/catalog/camel-quarkus-catalog/pom.xml b/catalog/camel-quarkus-catalog/pom.xml
index 4d2c64d..7ce0e6e 100644
--- a/catalog/camel-quarkus-catalog/pom.xml
+++ b/catalog/camel-quarkus-catalog/pom.xml
@@ -86,8 +86,10 @@
</dependencies>
<executions>
<execution>
+ <!-- prepare the catalog, readme files, etc. -->
<goals>
<goal>prepare-catalog-quarkus</goal>
+ <goal>prepare-extensions-readme</goal>
</goals>
<phase>process-resources</phase>
</execution>
diff --git a/extensions/readme.adoc b/extensions/readme.adoc
new file mode 100644
index 0000000..c70bded
--- /dev/null
+++ b/extensions/readme.adoc
@@ -0,0 +1,142 @@
+= Components
+
+// components: START
+Number of Components: 22 in 18 JAR artifacts (0 deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Component | Available From | Description
+
+| link:camel-quarkus-aws-eks/src/main/docs/aws-eks-component.adoc[AWS EKS] (camel-quarkus-aws-eks) +
+`aws-eks:label` | 3.0 | The aws-kms is used for managing Amazon EKS
+
+| link:camel-quarkus-aws-s3/src/main/docs/aws-s3-component.adoc[AWS S3 Storage Service] (camel-quarkus-aws-s3) +
+`aws-s3://bucketNameOrArn` | 2.8 | The aws-s3 component is used for storing and retrieving object from Amazon S3 Storage Service.
+
+| link:camel-quarkus-aws-sns/src/main/docs/aws-sns-component.adoc[AWS Simple Notification System] (camel-quarkus-aws-sns) +
+`aws-sns:topicNameOrArn` | 2.8 | The aws-sns component is used for sending messages to an Amazon Simple Notification Topic.
+
+| link:camel-quarkus-aws-sqs/src/main/docs/aws-sqs-component.adoc[AWS Simple Queue Service] (camel-quarkus-aws-sqs) +
+`aws-sqs:queueNameOrArn` | 2.6 | The aws-sqs component is used for sending and receiving messages to Amazon's SQS service.
+
+| link:camel-quarkus-bean/src/main/docs/bean-component.adoc[Bean] (camel-quarkus-bean) +
+`bean:beanName` | 1.0 | The bean component is for invoking Java beans from Camel.
+
+| link:camel-quarkus-bean/src/main/docs/class-component.adoc[Class] (camel-quarkus-bean) +
+`class:beanName` | 2.4 | The class component is for invoking Java classes (Java beans) from Camel.
+
+| link:camel-quarkus-direct/src/main/docs/direct-component.adoc[Direct] (camel-quarkus-direct) +
+`direct:name` | 1.0 | The direct component provides direct, synchronous call to another endpoint from the same CamelContext.
+
+| link:camel-quarkus-infinispan/src/main/docs/infinispan-component.adoc[Infinispan] (camel-quarkus-infinispan) +
+`infinispan:cacheName` | 2.13 | For reading/writing from/to Infinispan distributed key/value store and data grid.
+
+| link:camel-quarkus-jdbc/src/main/docs/jdbc-component.adoc[JDBC] (camel-quarkus-jdbc) +
+`jdbc:dataSourceName` | 1.2 | The jdbc component enables you to access databases through JDBC, where SQL queries are sent in the message body.
+
+| link:camel-quarkus-log/src/main/docs/log-component.adoc[Log] (camel-quarkus-log) +
+`log:loggerName` | 1.1 | The log component logs message exchanges to the underlying logging mechanism.
+
+| link:camel-quarkus-mail/src/main/docs/mail-component.adoc[Mail] (camel-quarkus-mail) +
+`imap:host:port` | 1.0 | To send or receive emails using imap/pop3 or smtp protocols.
+
+| link:camel-quarkus-microprofile-metrics/src/main/docs/microprofile-metrics-component.adoc[MicroProfile Metrics] (camel-quarkus-microprofile-metrics) +
+`microprofile-metrics:metricType:metricName` | 3.0 | Camel metrics exposed with Eclipse MicroProfile Metrics
+
+| link:camel-quarkus-netty-http/src/main/docs/netty-http-component.adoc[Netty HTTP] (camel-quarkus-netty-http) +
+`netty-http:protocol:host:port/path` | 2.14 | Netty HTTP server and client using the Netty 4.x library.
+
+| link:camel-quarkus-paho/src/main/docs/paho-component.adoc[Paho] (camel-quarkus-paho) +
+`paho:topic` | 2.16 | Component for communicating with MQTT M2M message brokers using Eclipse Paho MQTT Client.
+
+| link:camel-quarkus-rest/src/main/docs/rest-component.adoc[REST] (camel-quarkus-rest) +
+`rest:method:path:uriTemplate` | 2.14 | The rest component is used for either hosting REST services (consumer) or calling external REST services (producer).
+
+| link:camel-quarkus-rest/src/main/docs/rest-api-component.adoc[REST API] (camel-quarkus-rest) +
+`rest-api:path/contextIdPattern` | 2.16 | The rest-api component is used for providing Swagger API of the REST services which has been defined using the rest-dsl in Camel.
+
+| link:camel-quarkus-salesforce/src/main/docs/salesforce-component.adoc[Salesforce] (camel-quarkus-salesforce) +
+`salesforce:operationName:topicName` | 2.12 | The salesforce component is used for integrating Camel with the massive Salesforce API.
+
+| link:camel-quarkus-servlet/src/main/docs/servlet-component.adoc[Servlet] (camel-quarkus-servlet) +
+`servlet:contextPath` | 2.0 | To use a HTTP Servlet as entry for Camel routes when running in a servlet container.
+
+| link:camel-quarkus-timer/src/main/docs/timer-component.adoc[Timer] (camel-quarkus-timer) +
+`timer:timerName` | 1.0 | The timer component is used for generating message exchanges when a timer fires.
+
+| link:camel-quarkus-twitter/src/main/docs/twitter-directmessage-component.adoc[Twitter Direct Message] (camel-quarkus-twitter) +
+`twitter-directmessage:user` | 2.10 | The Twitter Direct Message Component consumes/produces user's direct messages.
+
+| link:camel-quarkus-twitter/src/main/docs/twitter-search-component.adoc[Twitter Search] (camel-quarkus-twitter) +
+`twitter-search:keywords` | 2.10 | The Twitter Search component consumes search results.
+
+| link:camel-quarkus-twitter/src/main/docs/twitter-timeline-component.adoc[Twitter Timeline] (camel-quarkus-twitter) +
+`twitter-timeline:timelineType` | 2.10 | The Twitter Timeline component consumes twitter timeline or update the status of specific user.
+
+|===
+// components: END
+
+
+== Data Formats
+
+// dataformats: START
+Number of Data Formats: 3 in 3 JAR artifacts (0 deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Data Format | Available From | Description
+
+| link:camel-quarkus-csv/src/main/docs/csv-dataformat.adoc[CSV] (camel-quarkus-csv) | 1.3 | The CSV data format is used for handling CSV payloads.
+
+| link:camel-quarkus-mail/src/main/docs/mime-multipart-dataformat.adoc[MIME Multipart] (camel-quarkus-mail) | 2.17 | The MIME Multipart data format can marshal a Camel message with attachments into a Camel message having a MIME-Multipart message as message body (and no attachments), and vise-versa when unmarshalling.
+
+| link:camel-quarkus-zipfile/src/main/docs/zipfile-dataformat.adoc[Zip File] (camel-quarkus-zipfile) | 2.11 | The Zip File data format is a message compression and de-compression format of zip files.
+|===
+// dataformats: END
+
+
+== Expression Languages
+
+// languages: START
+Number of Languages: 8 in 2 JAR artifacts (0 deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Language | Available From | Description
+
+| link:camel-quarkus-bean/src/main/docs/bean-language.adoc[Bean method] (camel-quarkus-bean) | 1.3 | To use a Java bean (aka method call) in Camel expressions or predicates.
+
+| link:camel-quarkus-core/src/main/docs/constant-language.adoc[Constant] (camel-quarkus-core) | 1.5 | To use a constant value in Camel expressions or predicates. Important: this is a fixed constant value that is only set once during starting up the route, do not use this if you want dynamic values during routing.
+
+| link:camel-quarkus-core/src/main/docs/exchangeProperty-language.adoc[ExchangeProperty] (camel-quarkus-core) | 2.0 | To use a Camel Exchange property in expressions or predicates.
+
+| link:camel-quarkus-core/src/main/docs/file-language.adoc[File] (camel-quarkus-core) | 1.1 | For expressions and predicates using the file/simple language
+
+| link:camel-quarkus-core/src/main/docs/header-language.adoc[Header] (camel-quarkus-core) | 1.5 | To use a Camel Message header in expressions or predicates.
+
+| link:camel-quarkus-core/src/main/docs/ref-language.adoc[Ref] (camel-quarkus-core) | 2.8 | Reference to an existing Camel expression or predicate, which is looked up from the Camel registry.
+
+| link:camel-quarkus-core/src/main/docs/simple-language.adoc[Simple] (camel-quarkus-core) | 1.1 | To use Camels built-in Simple language in Camel expressions or predicates.
+
+| link:camel-quarkus-core/src/main/docs/tokenize-language.adoc[Tokenize] (camel-quarkus-core) | 2.0 | To use Camel message body or header with a tokenizer in Camel expressions or predicates.
+|===
+// languages: END
+
+
+== Miscellaneous Components
+
+// others: START
+Number of Miscellaneous Components: 3 in 3 JAR artifacts (0 deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Component | Available From | Description
+
+| link:camel-quarkus-core-cloud/src/main/docs/core-cloud.adoc[Camel Quarkus Core Cloud] (camel-quarkus-core-cloud) | 0.2 | The Camel Quarkus core cloud module
+
+| link:camel-quarkus-platform-http/src/main/docs/platform-http.adoc[Camel Quarkus Platform HTTP] (camel-quarkus-platform-http) | 0.2.1 | HTTP platform component is used for integrating Camel HTTP with Quarkus HTTP layer
+
+| link:camel-quarkus-reactive-executor/src/main/docs/reactive-executor.adoc[Camel Quarkus Reactive Executor] (camel-quarkus-reactive-executor) | 0.2.1 | To use Quarkus reactive executor with Camel
+|===
+// others: END
+
diff --git a/tooling/maven/package-maven-plugin/pom.xml b/tooling/maven/package-maven-plugin/pom.xml
index 4130638..d9a1878 100644
--- a/tooling/maven/package-maven-plugin/pom.xml
+++ b/tooling/maven/package-maven-plugin/pom.xml
@@ -86,6 +86,11 @@
</dependency>
<dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-util-json</artifactId>
+ <version>${camel.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.4.4.Final</version>
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/JSonSchemaHelper.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/JSonSchemaHelper.java
new file mode 100644
index 0000000..22ad577
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/JSonSchemaHelper.java
@@ -0,0 +1,206 @@
+/*
+ * 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.quarkus.maven;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+
+public final class JSonSchemaHelper {
+
+ private JSonSchemaHelper() {
+ }
+
+ /**
+ * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata
+ *
+ * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>.
+ * @param json the json
+ * @return a list of all the rows, where each row is a set of key value pairs with metadata
+ */
+ public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) {
+ List<Map<String, String>> answer = new ArrayList<>();
+ if (json == null) {
+ return answer;
+ }
+
+ // convert into a List<Map<String, String>> structure which is expected as output from this parser
+ try {
+ JsonObject output = (JsonObject) Jsoner.deserialize(json);
+ for (String key : output.keySet()) {
+ Map<?, ?> row = output.getMap(key);
+ if (key.equals(group)) {
+ if (parseProperties) {
+ // flattern each entry in the row with name as they key, and its value as the content (its a map also)
+ for (Object obj : row.entrySet()) {
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
+ Map<String, String> newRow = new LinkedHashMap<>();
+ newRow.put("name", entry.getKey().toString());
+
+ Map<String, String> newData = transformMap((Map<?, ?>) entry.getValue());
+ newRow.putAll(newData);
+ answer.add(newRow);
+ }
+ } else {
+ // flattern each entry in the row as a list of single Map<key, value> elements
+ Map<?, ?> newData = transformMap(row);
+ for (Object obj : newData.entrySet()) {
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
+ Map<String, String> newRow = new LinkedHashMap<>();
+ newRow.put(entry.getKey().toString(), entry.getValue().toString());
+ answer.add(newRow);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ // wrap parsing exceptions as runtime
+ throw new RuntimeException("Cannot parse json", e);
+ }
+
+ return answer;
+ }
+
+ private static Map<String, String> transformMap(Map<?, ?> jsonMap) {
+ Map<String, String> answer = new LinkedHashMap<>();
+
+ for (Object rowObj : jsonMap.entrySet()) {
+ Map.Entry<?, ?> rowEntry = (Map.Entry<?, ?>) rowObj;
+ // if its a list type then its an enum, and we need to parse it as a single line separated with comma
+ // to be backwards compatible
+ Object newValue = rowEntry.getValue();
+ if (newValue instanceof List) {
+ List<?> list = (List<?>) newValue;
+ newValue = list.stream().map(Object::toString)
+ .collect(Collectors.joining(","));
+ }
+ // ensure value is escaped
+ String value = escapeJson(newValue.toString());
+ answer.put(rowEntry.getKey().toString(), value);
+ }
+
+ return answer;
+ }
+
+ private static String escapeJson(String value) {
+ // need to safe encode \r as \\r so its escaped
+ // need to safe encode \n as \\n so its escaped
+ // need to safe encode \t as \\t so its escaped
+ return value
+ .replaceAll("\\\\r", "\\\\\\r")
+ .replaceAll("\\\\n", "\\\\\\n")
+ .replaceAll("\\\\t", "\\\\\\t");
+ }
+
+ /**
+ * Gets the value with the key in a safe way, eg returning an empty string if there was no value for the key.
+ */
+ public static String getSafeValue(String key, List<Map<String, String>> rows) {
+ for (Map<String, String> row : rows) {
+ String value = row.get(key);
+ if (value != null) {
+ return value;
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Gets the value with the key in a safe way, eg returning an empty string if there was no value for the key.
+ */
+ public static String getSafeValue(String key, Map<String, String> rows) {
+ String value = rows.get(key);
+ if (value != null) {
+ return value;
+ }
+ return "";
+ }
+
+ public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String defaultValue = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("defaultValue")) {
+ defaultValue = row.get("defaultValue");
+ }
+ if (found) {
+ return defaultValue;
+ }
+ }
+ return null;
+ }
+
+ public static String getPropertyDescriptionValue(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String description = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("description")) {
+ description = row.get("description");
+ }
+ if (found) {
+ return description;
+ }
+ }
+ return null;
+ }
+
+ public static String getPropertyJavaType(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String javaType = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("javaType")) {
+ javaType = row.get("javaType");
+ }
+ if (found) {
+ return javaType;
+ }
+ }
+ return null;
+ }
+
+ public static String getPropertyType(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String type = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("type")) {
+ type = row.get("type");
+ }
+ if (found) {
+ return type;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/MvelHelper.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/MvelHelper.java
new file mode 100644
index 0000000..6ecb3fc
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/MvelHelper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.quarkus.maven;
+
+import java.util.regex.Pattern;
+
+public final class MvelHelper {
+
+ public static final MvelHelper INSTANCE = new MvelHelper();
+
+ private static final Pattern DOLLAR_ESCAPE = Pattern.compile("\\$");
+
+ private static final Pattern CURLY_BRACKET_ESCAPE = Pattern.compile("(\\{[a-zA-Z0-9]+?)\\}");
+
+ private static final Pattern URL_ESCAPE = Pattern.compile("(?<!href=\")(http(:?s)?://|(:?s)?ftp(?:s)?)");
+
+ private MvelHelper() {
+ // utility class
+ }
+
+ public static String escape(final String raw) {
+ if (raw == null) {
+ return null;
+ }
+
+ final String escapedDollars = DOLLAR_ESCAPE.matcher(raw).replaceAll("\\\\\\$");
+ final String escapedCurlyBrackets = CURLY_BRACKET_ESCAPE.matcher(escapedDollars).replaceAll("\\\\$1\\\\}");
+ final String escapedUrls = URL_ESCAPE.matcher(escapedCurlyBrackets).replaceAll("\\\\$1");
+
+ return escapedUrls;
+ }
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareExtensionsReadmeMojo.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareExtensionsReadmeMojo.java
new file mode 100644
index 0000000..e9ee00a
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/PrepareExtensionsReadmeMojo.java
@@ -0,0 +1,676 @@
+/*
+ * 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.quarkus.maven;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.quarkus.maven.model.ComponentModel;
+import org.apache.camel.quarkus.maven.model.DataFormatModel;
+import org.apache.camel.quarkus.maven.model.LanguageModel;
+import org.apache.camel.quarkus.maven.model.OtherModel;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.mvel2.templates.TemplateRuntime;
+
+import static java.util.stream.Collectors.toSet;
+import static org.apache.camel.quarkus.maven.PackageHelper.loadText;
+import static org.apache.camel.quarkus.maven.PackageHelper.writeText;
+
+/**
+ * Prepares the extensions/readme.adoc files content up to date with all the extensions that Apache Camel Quarkus ships.
+ */
+@Mojo(name = "prepare-extensions-readme", threadSafe = true)
+public class PrepareExtensionsReadmeMojo extends AbstractMojo {
+
+ /**
+ * The maven project.
+ */
+ @Parameter(property = "project", required = true, readonly = true)
+ protected MavenProject project;
+
+ /**
+ * The directory for components catalog
+ */
+ @Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/components")
+ protected File componentsDir;
+
+ /**
+ * The directory for data formats catalog
+ */
+ @Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/dataformats")
+ protected File dataFormatsDir;
+
+ /**
+ * The directory for languages catalog
+ */
+ @Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/languages")
+ protected File languagesDir;
+
+ /**
+ * The directory for others catalog
+ */
+ @Parameter(defaultValue = "${project.build.directory}/classes/org/apache/camel/catalog/quarkus/others")
+ protected File othersDir;
+
+ /**
+ * The directory for components
+ */
+ @Parameter(defaultValue = "${project.directory}/../../../extensions")
+ protected File readmeComponentsDir;
+
+ /**
+ * Maven ProjectHelper.
+ */
+ @Component
+ private MavenProjectHelper projectHelper;
+
+ /**
+ * Execute goal.
+ *
+ * @throws MojoExecutionException execution of the main class or one of the
+ * threads it generated failed.
+ * @throws MojoFailureException something bad happened...
+ */
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ // update readme file in extensions
+ executeComponentsReadme();
+ executeLanguagesReadme();
+ executeDataFormatsReadme();
+ executeOthersReadme();
+ }
+
+ protected void executeOthersReadme() throws MojoExecutionException, MojoFailureException {
+ Set<File> otherFiles = new TreeSet<>();
+
+ if (othersDir != null && othersDir.isDirectory()) {
+ File[] files = othersDir.listFiles();
+ if (files != null) {
+ otherFiles.addAll(Arrays.asList(files));
+ }
+ }
+
+ try {
+ List<OtherModel> others = new ArrayList<>();
+ for (File file : otherFiles) {
+ String json = loadText(new FileInputStream(file));
+ OtherModel model = generateOtherModel(json);
+ others.add(model);
+ }
+
+ // sort the models
+ Collections.sort(others, new OtherComparator());
+
+ // how many different artifacts
+ int count = others.stream()
+ .map(OtherModel::getArtifactId)
+ .collect(toSet()).size();
+
+ // how many deprecated
+ long deprecated = others.stream()
+ .filter(o -> "true".equals(o.getDeprecated()))
+ .count();
+
+ // update the big readme file in the components dir
+ File file = new File(readmeComponentsDir, "readme.adoc");
+
+ // update regular components
+ boolean exists = file.exists();
+ String changed = templateOthers(others, count, deprecated);
+ boolean updated = updateOthers(file, changed);
+
+ if (updated) {
+ getLog().info("Updated readme.adoc file: " + file);
+ } else if (exists) {
+ getLog().debug("No changes to readme.adoc file: " + file);
+ } else {
+ getLog().warn("No readme.adoc file: " + file);
+ }
+
+ } catch (IOException e) {
+ throw new MojoFailureException("Error due " + e.getMessage(), e);
+ }
+ }
+
+ protected void executeComponentsReadme() throws MojoExecutionException, MojoFailureException {
+ Set<File> componentFiles = new TreeSet<>();
+
+ if (componentsDir != null && componentsDir.isDirectory()) {
+ File[] files = componentsDir.listFiles();
+ if (files != null) {
+ componentFiles.addAll(Arrays.asList(files));
+ }
+ }
+
+ try {
+ List<ComponentModel> models = new ArrayList<>();
+ for (File file : componentFiles) {
+ String json = loadText(new FileInputStream(file));
+ ComponentModel model = generateComponentModel(json);
+
+ // filter out alternative schemas which reuses documentation
+ boolean add = true;
+ if (!model.getAlternativeSchemes().isEmpty()) {
+ String first = model.getAlternativeSchemes().split(",")[0];
+ if (!model.getScheme().equals(first)) {
+ add = false;
+ }
+ }
+ if (add) {
+ models.add(model);
+
+ // special for camel-mail where we want to refer its imap scheme to mail so its mail.adoc in the doc link
+ if ("imap".equals(model.getScheme())) {
+ model.setScheme("mail");
+ model.setTitle("Mail");
+ }
+ }
+ }
+
+ // sort the models
+ Collections.sort(models, new ComponentComparator());
+
+ // filter out unwanted components
+ List<ComponentModel> components = new ArrayList<>();
+ for (ComponentModel model : models) {
+ components.add(model);
+ }
+
+ // how many different artifacts
+ int count = components.stream()
+ .map(ComponentModel::getArtifactId)
+ .collect(toSet()).size();
+
+ // how many deprecated
+ long deprecated = components.stream()
+ .filter(c -> "true".equals(c.getDeprecated()))
+ .count();
+
+ // update the big readme file in the core/components dir
+ File file;
+ file = new File(readmeComponentsDir, "readme.adoc");
+
+ // update regular components
+ boolean exists = file.exists();
+ String changed = templateComponents(components, count, deprecated);
+ boolean updated = updateComponents(file, changed);
+
+ if (updated) {
+ getLog().info("Updated readme.adoc file: " + file);
+ } else if (exists) {
+ getLog().debug("No changes to readme.adoc file: " + file);
+ } else {
+ getLog().warn("No readme.adoc file: " + file);
+ }
+
+ } catch (IOException e) {
+ throw new MojoFailureException("Error due " + e.getMessage(), e);
+ }
+ }
+
+ protected void executeDataFormatsReadme() throws MojoExecutionException, MojoFailureException {
+ Set<File> dataFormatFiles = new TreeSet<>();
+
+ if (dataFormatsDir != null && dataFormatsDir.isDirectory()) {
+ File[] files = dataFormatsDir.listFiles();
+ if (files != null) {
+ dataFormatFiles.addAll(Arrays.asList(files));
+ }
+ }
+
+ try {
+ List<DataFormatModel> models = new ArrayList<>();
+ for (File file : dataFormatFiles) {
+ String json = loadText(new FileInputStream(file));
+ DataFormatModel model = generateDataFormatModel(json);
+
+ // special for bindy as we have one common file
+ if (model.getName().startsWith("bindy")) {
+ model.setName("bindy");
+ }
+
+ models.add(model);
+ }
+
+ // sort the models
+ Collections.sort(models, new DataFormatComparator());
+
+ // how many different artifacts
+ int count = models.stream()
+ .map(DataFormatModel::getArtifactId)
+ .collect(toSet()).size();
+
+ // how many deprecated
+ long deprecated = models.stream()
+ .filter(m -> "true".equals(m.getDeprecated()))
+ .count();
+
+ // filter out camel-core
+ List<DataFormatModel> dataFormats = new ArrayList<>();
+ for (DataFormatModel model : models) {
+ dataFormats.add(model);
+ }
+
+ // update the big readme file in the core/components dir
+ File file;
+ file = new File(readmeComponentsDir, "readme.adoc");
+
+ // update regular data formats
+ boolean exists = file.exists();
+ String changed = templateDataFormats(dataFormats, count, deprecated);
+ boolean updated = updateDataFormats(file, changed);
+
+ if (updated) {
+ getLog().info("Updated readme.adoc file: " + file);
+ } else if (exists) {
+ getLog().debug("No changes to readme.adoc file: " + file);
+ } else {
+ getLog().warn("No readme.adoc file: " + file);
+ }
+
+ } catch (IOException e) {
+ throw new MojoFailureException("Error due " + e.getMessage(), e);
+ }
+ }
+
+ protected void executeLanguagesReadme() throws MojoExecutionException, MojoFailureException {
+ Set<File> languageFiles = new TreeSet<>();
+
+ if (languagesDir != null && languagesDir.isDirectory()) {
+ File[] files = languagesDir.listFiles();
+ if (files != null) {
+ languageFiles.addAll(Arrays.asList(files));
+ }
+ }
+
+ try {
+ List<LanguageModel> models = new ArrayList<>();
+ for (File file : languageFiles) {
+ String json = loadText(new FileInputStream(file));
+ LanguageModel model = generateLanguageModel(json);
+ models.add(model);
+ }
+
+ // sort the models
+ Collections.sort(models, new LanguageComparator());
+
+ // filter out camel-core
+ List<LanguageModel> languages = new ArrayList<>();
+ for (LanguageModel model : models) {
+ languages.add(model);
+ }
+
+ // how many different artifacts
+ int count = languages.stream()
+ .map(LanguageModel::getArtifactId)
+ .collect(toSet()).size();
+
+ // how many deprecated
+ long deprecated = languages.stream()
+ .filter(l -> "true".equals(l.getDeprecated()))
+ .count();
+
+ // update the big readme file in the core/components dir
+ File file;
+ file = new File(readmeComponentsDir, "readme.adoc");
+
+ // update regular data formats
+ boolean exists = file.exists();
+ String changed = templateLanguages(languages, count, deprecated);
+ boolean updated = updateLanguages(file, changed);
+
+ if (updated) {
+ getLog().info("Updated readme.adoc file: " + file);
+ } else if (exists) {
+ getLog().debug("No changes to readme.adoc file: " + file);
+ } else {
+ getLog().warn("No readme.adoc file: " + file);
+ }
+
+ } catch (IOException e) {
+ throw new MojoFailureException("Error due " + e.getMessage(), e);
+ }
+ }
+
+ private String templateComponents(List<ComponentModel> models, int artifacts, long deprecated) throws MojoExecutionException {
+ try {
+ String template = loadText(PrepareExtensionsReadmeMojo.class.getClassLoader().getResourceAsStream("readme-components.mvel"));
+ Map<String, Object> map = new HashMap<>();
+ map.put("components", models);
+ map.put("numberOfArtifacts", artifacts);
+ map.put("numberOfDeprecated", deprecated);
+ String out = (String) TemplateRuntime.eval(template, map, Collections.singletonMap("util", MvelHelper.INSTANCE));
+ return out;
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
+ }
+ }
+
+ private String templateOthers(List<OtherModel> models, int artifacts, long deprecated) throws MojoExecutionException {
+ try {
+ String template = loadText(PrepareExtensionsReadmeMojo.class.getClassLoader().getResourceAsStream("readme-others.mvel"));
+ Map<String, Object> map = new HashMap<>();
+ map.put("others", models);
+ map.put("numberOfArtifacts", artifacts);
+ map.put("numberOfDeprecated", deprecated);
+ String out = (String) TemplateRuntime.eval(template, map, Collections.singletonMap("util", MvelHelper.INSTANCE));
+ return out;
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
+ }
+ }
+
+ private String templateDataFormats(List<DataFormatModel> models, int artifacts, long deprecated) throws MojoExecutionException {
+ try {
+ String template = loadText(PrepareExtensionsReadmeMojo.class.getClassLoader().getResourceAsStream("readme-dataformats.mvel"));
+ Map<String, Object> map = new HashMap<>();
+ map.put("dataformats", models);
+ map.put("numberOfArtifacts", artifacts);
+ map.put("numberOfDeprecated", deprecated);
+ String out = (String) TemplateRuntime.eval(template, map, Collections.singletonMap("util", MvelHelper.INSTANCE));
+ return out;
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
+ }
+ }
+
+ private String templateLanguages(List<LanguageModel> models, int artifacts, long deprecated) throws MojoExecutionException {
+ try {
+ String template = loadText(PrepareExtensionsReadmeMojo.class.getClassLoader().getResourceAsStream("readme-languages.mvel"));
+ Map<String, Object> map = new HashMap<>();
+ map.put("languages", models);
+ map.put("numberOfArtifacts", artifacts);
+ map.put("numberOfDeprecated", deprecated);
+ String out = (String) TemplateRuntime.eval(template, map, Collections.singletonMap("util", MvelHelper.INSTANCE));
+ return out;
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
+ }
+ }
+
+ private boolean updateComponents(File file, String changed) throws MojoExecutionException {
+ if (!file.exists()) {
+ return false;
+ }
+
+ try {
+ String text = loadText(new FileInputStream(file));
+
+ String existing = StringHelper.between(text, "// components: START", "// components: 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, "// components: START");
+ String after = StringHelper.after(text, "// components: END");
+ text = before + "// components: START\n" + changed + "\n// components: 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// components: START");
+ getLog().warn("\t// components: END");
+ return false;
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e);
+ }
+ }
+
+ private boolean updateOthers(File file, String changed) throws MojoExecutionException {
+ if (!file.exists()) {
+ return false;
+ }
+
+ try {
+ String text = loadText(new FileInputStream(file));
+
+ String existing = StringHelper.between(text, "// others: START", "// others: 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, "// others: START");
+ String after = StringHelper.after(text, "// others: END");
+ text = before + "// others: START\n" + changed + "\n// others: 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// others: START");
+ getLog().warn("\t// others: END");
+ return false;
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e);
+ }
+ }
+
+ private boolean updateDataFormats(File file, String changed) throws MojoExecutionException {
+ if (!file.exists()) {
+ return false;
+ }
+
+ try {
+ String text = loadText(new FileInputStream(file));
+
+ String existing = StringHelper.between(text, "// dataformats: START", "// dataformats: 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, "// dataformats: START");
+ String after = StringHelper.after(text, "// dataformats: END");
+ text = before + "// dataformats: START\n" + changed + "\n// dataformats: 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// dataformats: START");
+ getLog().warn("\t// dataformats: END");
+ return false;
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e);
+ }
+ }
+
+ private boolean updateLanguages(File file, String changed) throws MojoExecutionException {
+ if (!file.exists()) {
+ return false;
+ }
+
+ try {
+ String text = loadText(new FileInputStream(file));
+
+ String existing = StringHelper.between(text, "// languages: START", "// languages: 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, "// languages: START");
+ String after = StringHelper.after(text, "// languages: END");
+ text = before + "// languages: START\n" + changed + "\n// languages: 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// languages: START");
+ getLog().warn("\t// languages: END");
+ return false;
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e);
+ }
+ }
+
+ private static class ComponentComparator implements Comparator<ComponentModel> {
+
+ @Override
+ public int compare(ComponentModel o1, ComponentModel o2) {
+ // lets sort by title
+ return o1.getTitle().compareToIgnoreCase(o2.getTitle());
+ }
+ }
+
+ private static class OtherComparator implements Comparator<OtherModel> {
+
+ @Override
+ public int compare(OtherModel o1, OtherModel o2) {
+ // lets sort by title
+ return o1.getTitle().compareToIgnoreCase(o2.getTitle());
+ }
+ }
+
+ private static class DataFormatComparator implements Comparator<DataFormatModel> {
+
+ @Override
+ public int compare(DataFormatModel o1, DataFormatModel o2) {
+ // lets sort by title
+ return o1.getTitle().compareToIgnoreCase(o2.getTitle());
+ }
+ }
+
+ private static class LanguageComparator implements Comparator<LanguageModel> {
+
+ @Override
+ public int compare(LanguageModel o1, LanguageModel o2) {
+ // lets sort by title
+ return o1.getTitle().compareToIgnoreCase(o2.getTitle());
+ }
+ }
+
+ private ComponentModel generateComponentModel(String json) {
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+
+ ComponentModel component = new ComponentModel();
+ component.setScheme(JSonSchemaHelper.getSafeValue("scheme", rows));
+ component.setSyntax(JSonSchemaHelper.getSafeValue("syntax", rows));
+ component.setAlternativeSyntax(JSonSchemaHelper.getSafeValue("alternativeSyntax", rows));
+ component.setAlternativeSchemes(JSonSchemaHelper.getSafeValue("alternativeSchemes", rows));
+ component.setTitle(JSonSchemaHelper.getSafeValue("title", rows));
+ component.setDescription(JSonSchemaHelper.getSafeValue("description", rows));
+ component.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows));
+ component.setLabel(JSonSchemaHelper.getSafeValue("label", rows));
+ component.setDeprecated(JSonSchemaHelper.getSafeValue("deprecated", rows));
+ component.setDeprecationNote(JSonSchemaHelper.getSafeValue("deprecationNote", rows));
+ component.setConsumerOnly(JSonSchemaHelper.getSafeValue("consumerOnly", rows));
+ component.setProducerOnly(JSonSchemaHelper.getSafeValue("producerOnly", rows));
+ component.setJavaType(JSonSchemaHelper.getSafeValue("javaType", rows));
+ component.setGroupId(JSonSchemaHelper.getSafeValue("groupId", rows));
+ component.setArtifactId(JSonSchemaHelper.getSafeValue("artifactId", rows));
+ component.setVersion(JSonSchemaHelper.getSafeValue("version", rows));
+
+ return component;
+ }
+
+ private OtherModel generateOtherModel(String json) {
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("other", json, false);
+
+ OtherModel other = new OtherModel();
+ other.setName(JSonSchemaHelper.getSafeValue("name", rows));
+ other.setTitle(JSonSchemaHelper.getSafeValue("title", rows));
+ other.setDescription(JSonSchemaHelper.getSafeValue("description", rows));
+ other.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows));
+ other.setLabel(JSonSchemaHelper.getSafeValue("label", rows));
+ other.setDeprecated(JSonSchemaHelper.getSafeValue("deprecated", rows));
+ other.setDeprecationNote(JSonSchemaHelper.getSafeValue("deprecationNote", rows));
+ other.setGroupId(JSonSchemaHelper.getSafeValue("groupId", rows));
+ other.setArtifactId(JSonSchemaHelper.getSafeValue("artifactId", rows));
+ other.setVersion(JSonSchemaHelper.getSafeValue("version", rows));
+
+ return other;
+ }
+
+ private DataFormatModel generateDataFormatModel(String json) {
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("dataformat", json, false);
+
+ DataFormatModel dataFormat = new DataFormatModel();
+ dataFormat.setName(JSonSchemaHelper.getSafeValue("name", rows));
+ dataFormat.setTitle(JSonSchemaHelper.getSafeValue("title", rows));
+ dataFormat.setModelName(JSonSchemaHelper.getSafeValue("modelName", rows));
+ dataFormat.setDescription(JSonSchemaHelper.getSafeValue("description", rows));
+ dataFormat.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows));
+ dataFormat.setLabel(JSonSchemaHelper.getSafeValue("label", rows));
+ dataFormat.setDeprecated(JSonSchemaHelper.getSafeValue("deprecated", rows));
+ dataFormat.setDeprecationNote(JSonSchemaHelper.getSafeValue("deprecationNote", rows));
+ dataFormat.setJavaType(JSonSchemaHelper.getSafeValue("javaType", rows));
+ dataFormat.setGroupId(JSonSchemaHelper.getSafeValue("groupId", rows));
+ dataFormat.setArtifactId(JSonSchemaHelper.getSafeValue("artifactId", rows));
+ dataFormat.setVersion(JSonSchemaHelper.getSafeValue("version", rows));
+
+ return dataFormat;
+ }
+
+ private LanguageModel generateLanguageModel(String json) {
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("language", json, false);
+
+ LanguageModel language = new LanguageModel();
+ language.setTitle(JSonSchemaHelper.getSafeValue("title", rows));
+ language.setName(JSonSchemaHelper.getSafeValue("name", rows));
+ language.setModelName(JSonSchemaHelper.getSafeValue("modelName", rows));
+ language.setDescription(JSonSchemaHelper.getSafeValue("description", rows));
+ language.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows));
+ language.setLabel(JSonSchemaHelper.getSafeValue("label", rows));
+ language.setDeprecated(JSonSchemaHelper.getSafeValue("deprecated", rows));
+ language.setDeprecationNote(JSonSchemaHelper.getSafeValue("deprecationNote", rows));
+ language.setJavaType(JSonSchemaHelper.getSafeValue("javaType", rows));
+ language.setGroupId(JSonSchemaHelper.getSafeValue("groupId", rows));
+ language.setArtifactId(JSonSchemaHelper.getSafeValue("artifactId", rows));
+ language.setVersion(JSonSchemaHelper.getSafeValue("version", rows));
+
+ return language;
+ }
+
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/StringHelper.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/StringHelper.java
new file mode 100644
index 0000000..02274bf
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/StringHelper.java
@@ -0,0 +1,200 @@
+/*
+ * 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.quarkus.maven;
+
+import java.util.Collection;
+
+import com.google.common.base.CaseFormat;
+
+public final class StringHelper {
+
+ private StringHelper() {
+ // Utils Class
+ }
+
+ public static boolean isEmpty(String s) {
+ return s == null || s.trim().isEmpty();
+ }
+
+ public static String after(String text, String after) {
+ if (!text.contains(after)) {
+ return null;
+ }
+ return text.substring(text.indexOf(after) + after.length());
+ }
+
+ public static String before(String text, String before) {
+ if (!text.contains(before)) {
+ return null;
+ }
+ return text.substring(0, text.indexOf(before));
+ }
+
+ public static String between(String text, String after, String before) {
+ text = after(text, after);
+ if (text == null) {
+ return null;
+ }
+ return before(text, before);
+ }
+
+ public static String indentCollection(String indent, Collection<String> list) {
+ StringBuilder sb = new StringBuilder();
+ for (String text : list) {
+ sb.append(indent).append(text);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Converts the value to use title style instead of dash cased
+ */
+ public static String camelDashToTitle(String value) {
+ StringBuilder sb = new StringBuilder(value.length());
+ boolean dash = false;
+
+ for (char c : value.toCharArray()) {
+ if ('-' == c) {
+ dash = true;
+ continue;
+ }
+
+ if (dash) {
+ sb.append(' ');
+ sb.append(Character.toUpperCase(c));
+ } else {
+ // upper case first
+ if (sb.length() == 0) {
+ sb.append(Character.toUpperCase(c));
+ } else {
+ sb.append(c);
+ }
+ }
+ dash = false;
+ }
+ return sb.toString();
+ }
+
+ public static String cutLastZeroDigit(String version) {
+ String answer = version;
+ // cut last digit so its not 2.18.0 but 2.18
+ String[] parts = version.split("\\.");
+ if (parts.length == 3 && parts[2].equals("0")) {
+ answer = parts[0] + "." + parts[1];
+ }
+ return answer;
+ }
+
+ /**
+ * To wrap long camel cased texts by words.
+ *
+ * @param option the option which is camel cased.
+ * @param watermark a watermark to denote the size to cut after
+ * @param newLine the new line to use when breaking into a new line
+ */
+ public static String wrapCamelCaseWords(String option, int watermark, String newLine) {
+ String text = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, option);
+ text = text.replace('-', ' ');
+ text = wrapWords(text, "\n", watermark, false);
+ text = text.replace(' ', '-');
+ text = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, text);
+
+ // upper case first char on each line
+ String[] lines = text.split("\n");
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ line = Character.toUpperCase(line.charAt(0)) + line.substring(1);
+ sb.append(line);
+ if (i < lines.length - 1) {
+ sb.append(newLine);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * To wrap a big line by words.
+ *
+ * @param line the big line
+ * @param newLine the new line to use when breaking into a new line
+ * @param watermark a watermark to denote the size to cut after
+ * @param wrapLongWords whether to wrap long words
+ */
+ public static String wrapWords(String line, String newLine, int watermark, boolean wrapLongWords) {
+ if (line == null) {
+ return null;
+ } else {
+ if (newLine == null) {
+ newLine = System.lineSeparator();
+ }
+
+ if (watermark < 1) {
+ watermark = 1;
+ }
+
+ int inputLineLength = line.length();
+ int offset = 0;
+ StringBuilder sb = new StringBuilder(inputLineLength + 32);
+
+ while (inputLineLength - offset > watermark) {
+ if (line.charAt(offset) == 32) {
+ ++offset;
+ } else {
+ int spaceToWrapAt = line.lastIndexOf(32, watermark + offset);
+ if (spaceToWrapAt >= offset) {
+ sb.append(line.substring(offset, spaceToWrapAt));
+ sb.append(newLine);
+ offset = spaceToWrapAt + 1;
+ } else if (wrapLongWords) {
+ sb.append(line.substring(offset, watermark + offset));
+ sb.append(newLine);
+ offset += watermark;
+ } else {
+ spaceToWrapAt = line.indexOf(32, watermark + offset);
+ if (spaceToWrapAt >= 0) {
+ sb.append(line.substring(offset, spaceToWrapAt));
+ sb.append(newLine);
+ offset = spaceToWrapAt + 1;
+ } else {
+ sb.append(line.substring(offset));
+ offset = inputLineLength;
+ }
+ }
+ }
+ }
+
+ sb.append(line.substring(offset));
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Returns the base class name, i.e. without package and generic related
+ * information.
+ *
+ * @param className The class name which base class is to be computed.
+ * @return the base class name, i.e. without package and generic related
+ * information.
+ */
+ public static String getClassShortName(String className) {
+ if (className != null) {
+ return className.replaceAll("<.*>", "").replaceAll(".*[.]([^.]+)", "$1");
+ }
+ return className;
+ }
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/ComponentModel.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/ComponentModel.java
new file mode 100644
index 0000000..dcf8831
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/ComponentModel.java
@@ -0,0 +1,190 @@
+/*
+ * 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.quarkus.maven.model;
+
+import org.apache.camel.quarkus.maven.StringHelper;
+
+import static org.apache.camel.quarkus.maven.StringHelper.cutLastZeroDigit;
+
+public class ComponentModel {
+
+ private String kind;
+ private String scheme;
+ private String syntax;
+ private String alternativeSyntax;
+ private String alternativeSchemes;
+ private String title;
+ private String description;
+ private String firstVersion;
+ private String label;
+ private String deprecated;
+ private String deprecationNote;
+ private String consumerOnly;
+ private String producerOnly;
+ private String javaType;
+ private String groupId;
+ private String artifactId;
+ private String version;
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+
+ public String getScheme() {
+ return scheme;
+ }
+
+ public void setScheme(String scheme) {
+ this.scheme = scheme;
+ }
+
+ public String getSyntax() {
+ return syntax;
+ }
+
+ public void setSyntax(String syntax) {
+ this.syntax = syntax;
+ }
+
+ public String getAlternativeSyntax() {
+ return alternativeSyntax;
+ }
+
+ public void setAlternativeSyntax(String alternativeSyntax) {
+ this.alternativeSyntax = alternativeSyntax;
+ }
+
+ public String getAlternativeSchemes() {
+ return alternativeSchemes;
+ }
+
+ public void setAlternativeSchemes(String alternativeSchemes) {
+ this.alternativeSchemes = alternativeSchemes;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFirstVersion() {
+ return firstVersion;
+ }
+
+ public void setFirstVersion(String firstVersion) {
+ this.firstVersion = firstVersion;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDeprecated() {
+ return deprecated;
+ }
+
+ public void setDeprecated(String deprecated) {
+ this.deprecated = deprecated;
+ }
+
+ public String getDeprecationNote() {
+ return deprecationNote;
+ }
+
+ public void setDeprecationNote(String deprecationNote) {
+ this.deprecationNote = deprecationNote;
+ }
+
+ public String getConsumerOnly() {
+ return consumerOnly;
+ }
+
+ public void setConsumerOnly(String consumerOnly) {
+ this.consumerOnly = consumerOnly;
+ }
+
+ public String getProducerOnly() {
+ return producerOnly;
+ }
+
+ public void setProducerOnly(String producerOnly) {
+ this.producerOnly = producerOnly;
+ }
+
+ public String getJavaType() {
+ return javaType;
+ }
+
+ public void setJavaType(String javaType) {
+ this.javaType = javaType;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getShortJavaType() {
+ return StringHelper.getClassShortName(javaType);
+ }
+
+ public String getDocLink() {
+ return artifactId + "/src/main/docs";
+ }
+
+ public String getFirstVersionShort() {
+ return cutLastZeroDigit(firstVersion);
+ }
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/DataFormatModel.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/DataFormatModel.java
new file mode 100644
index 0000000..4b3f517
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/DataFormatModel.java
@@ -0,0 +1,155 @@
+/*
+ * 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.quarkus.maven.model;
+
+import org.apache.camel.quarkus.maven.StringHelper;
+
+import static org.apache.camel.quarkus.maven.StringHelper.cutLastZeroDigit;
+
+public class DataFormatModel {
+
+ private String kind;
+ private String name;
+ private String modelName;
+ private String title;
+ private String description;
+ private String firstVersion;
+ private String label;
+ private String deprecated;
+ private String deprecationNote;
+ private String javaType;
+ private String groupId;
+ private String artifactId;
+ private String version;
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getModelName() {
+ return modelName;
+ }
+
+ public void setModelName(String modelName) {
+ this.modelName = modelName;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFirstVersion() {
+ return firstVersion;
+ }
+
+ public void setFirstVersion(String firstVersion) {
+ this.firstVersion = firstVersion;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDeprecated() {
+ return deprecated;
+ }
+
+ public void setDeprecated(String deprecated) {
+ this.deprecated = deprecated;
+ }
+
+ public String getDeprecationNote() {
+ return deprecationNote;
+ }
+
+ public void setDeprecationNote(String deprecationNote) {
+ this.deprecationNote = deprecationNote;
+ }
+
+ public String getJavaType() {
+ return javaType;
+ }
+
+ public void setJavaType(String javaType) {
+ this.javaType = javaType;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getShortJavaType() {
+ return StringHelper.getClassShortName(javaType);
+ }
+
+ public String getDocLink() {
+ return artifactId + "/src/main/docs";
+ }
+
+ public String getFirstVersionShort() {
+ return cutLastZeroDigit(firstVersion);
+ }
+
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/LanguageModel.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/LanguageModel.java
new file mode 100644
index 0000000..6813056
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/LanguageModel.java
@@ -0,0 +1,155 @@
+/*
+ * 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.quarkus.maven.model;
+
+import org.apache.camel.quarkus.maven.StringHelper;
+
+import static org.apache.camel.quarkus.maven.StringHelper.cutLastZeroDigit;
+
+public class LanguageModel {
+
+ private String kind;
+ private String name;
+ private String modelName;
+ private String title;
+ private String description;
+ private String firstVersion;
+ private String label;
+ private String deprecated;
+ private String deprecationNote;
+ private String javaType;
+ private String groupId;
+ private String artifactId;
+ private String version;
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getModelName() {
+ return modelName;
+ }
+
+ public void setModelName(String modelName) {
+ this.modelName = modelName;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFirstVersion() {
+ return firstVersion;
+ }
+
+ public void setFirstVersion(String firstVersion) {
+ this.firstVersion = firstVersion;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDeprecated() {
+ return deprecated;
+ }
+
+ public void setDeprecated(String deprecated) {
+ this.deprecated = deprecated;
+ }
+
+ public String getDeprecationNote() {
+ return deprecationNote;
+ }
+
+ public void setDeprecationNote(String deprecationNote) {
+ this.deprecationNote = deprecationNote;
+ }
+
+ public String getJavaType() {
+ return javaType;
+ }
+
+ public void setJavaType(String javaType) {
+ this.javaType = javaType;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getShortJavaType() {
+ return StringHelper.getClassShortName(javaType);
+ }
+
+ public String getDocLink() {
+ return artifactId + "/src/main/docs";
+ }
+
+ public String getFirstVersionShort() {
+ return cutLastZeroDigit(firstVersion);
+ }
+
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/OtherModel.java b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/OtherModel.java
new file mode 100644
index 0000000..05b9788
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/java/org/apache/camel/quarkus/maven/model/OtherModel.java
@@ -0,0 +1,145 @@
+/*
+ * 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.quarkus.maven.model;
+
+import org.apache.camel.quarkus.maven.StringHelper;
+
+import static org.apache.camel.quarkus.maven.StringHelper.cutLastZeroDigit;
+
+public class OtherModel {
+
+ private String kind;
+ private String name;
+ private String title;
+ private String description;
+ private String firstVersion;
+ private String label;
+ private String deprecated;
+ private String deprecationNote;
+ private String groupId;
+ private String artifactId;
+ private String version;
+ private String javaType;
+
+ public String getJavaType() {
+ return javaType;
+ }
+
+ public void setJavaType(String javaType) {
+ this.javaType = javaType;
+ }
+
+ public String getKind() {
+ return kind;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFirstVersion() {
+ return firstVersion;
+ }
+
+ public void setFirstVersion(String firstVersion) {
+ this.firstVersion = firstVersion;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String getDeprecated() {
+ return deprecated;
+ }
+
+ public void setDeprecated(String deprecated) {
+ this.deprecated = deprecated;
+ }
+
+ public String getDeprecationNote() {
+ return deprecationNote;
+ }
+
+ public void setDeprecationNote(String deprecationNote) {
+ this.deprecationNote = deprecationNote;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getDocLink() {
+ return artifactId + "/src/main/docs";
+ }
+
+ public String getFirstVersionShort() {
+ return cutLastZeroDigit(firstVersion);
+ }
+
+ public String getShortJavaType() {
+ return StringHelper.getClassShortName(javaType);
+ }
+}
diff --git a/tooling/maven/package-maven-plugin/src/main/resources/readme-components.mvel b/tooling/maven/package-maven-plugin/src/main/resources/readme-components.mvel
new file mode 100644
index 0000000..16d3573
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/resources/readme-components.mvel
@@ -0,0 +1,14 @@
+@if{!components.isEmpty()}
+
+Number of Components: @{components.size} in @{numberOfArtifacts} JAR artifacts (@{numberOfDeprecated} deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Component | Available From | Description
+@foreach{row : components}
+| link:@{row.docLink}/${row.scheme}-component.adoc[@{row.title}] (@{row.artifactId}) +
+`@{row.syntax}` | @{row.firstVersionShort} | @if{row.deprecated == "true"}*deprecated* @end{}@{util.escape(row.description)}
+@end{}
+|===
+
+@end{}
\ No newline at end of file
diff --git a/tooling/maven/package-maven-plugin/src/main/resources/readme-dataformats.mvel b/tooling/maven/package-maven-plugin/src/main/resources/readme-dataformats.mvel
new file mode 100644
index 0000000..72d12c1
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/resources/readme-dataformats.mvel
@@ -0,0 +1,12 @@
+@if{!dataformats.isEmpty()}
+
+Number of Data Formats: @{dataformats.size} in @{numberOfArtifacts} JAR artifacts (@{numberOfDeprecated} deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Data Format | Available From | Description
+@foreach{row : dataformats}
+| link:@{row.docLink}/${row.name}-dataformat.adoc[@{row.title}] (@{row.artifactId}) | @{row.firstVersionShort} | @if{row.deprecated == "true"}*deprecated* @end{}@{util.escape(row.description)}
+@end{}|===
+
+@end{}
\ No newline at end of file
diff --git a/tooling/maven/package-maven-plugin/src/main/resources/readme-languages.mvel b/tooling/maven/package-maven-plugin/src/main/resources/readme-languages.mvel
new file mode 100644
index 0000000..ba0e1ca
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/resources/readme-languages.mvel
@@ -0,0 +1,12 @@
+@if{!languages.isEmpty()}
+
+Number of Languages: @{languages.size} in @{numberOfArtifacts} JAR artifacts (@{numberOfDeprecated} deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Language | Available From | Description
+@foreach{row : languages}
+| link:@{row.docLink}/${row.name}-language.adoc[@{row.title}] (@{row.artifactId}) | @{row.firstVersionShort} | @if{row.deprecated == "true"}*deprecated* @end{}@{util.escape(row.description)}
+@end{}|===
+
+@end{}
\ No newline at end of file
diff --git a/tooling/maven/package-maven-plugin/src/main/resources/readme-others.mvel b/tooling/maven/package-maven-plugin/src/main/resources/readme-others.mvel
new file mode 100644
index 0000000..cbd06ce
--- /dev/null
+++ b/tooling/maven/package-maven-plugin/src/main/resources/readme-others.mvel
@@ -0,0 +1,12 @@
+@if{!others.isEmpty()}
+
+Number of Miscellaneous Components: @{others.size} in @{numberOfArtifacts} JAR artifacts (@{numberOfDeprecated} deprecated)
+
+[width="100%",cols="4,1,5",options="header"]
+|===
+| Component | Available From | Description
+@foreach{row : others}
+| link:@{row.docLink}/${row.name}.adoc[@{row.title}] (@{row.artifactId}) | @{row.firstVersionShort} | @if{row.deprecated == "true"}*deprecated* @end{}@{util.escape(row.description)}
+@end{}|===
+
+@end{}
\ No newline at end of file