You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2022/06/21 12:49:59 UTC

[camel] branch main updated: CAMEL-18141: camel-endpoint-dsl - Generate fluent builders for endpoint headers (#7839)

This is an automated email from the ASF dual-hosted git repository.

nfilotto pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 8fd3128851b CAMEL-18141: camel-endpoint-dsl - Generate fluent builders for endpoint headers (#7839)
8fd3128851b is described below

commit 8fd3128851bd55c5c9d8a14f766573f889c376e3
Author: Nicolas Filotto <es...@users.noreply.github.com>
AuthorDate: Tue Jun 21 14:49:52 2022 +0200

    CAMEL-18141: camel-endpoint-dsl - Generate fluent builders for endpoint headers (#7839)
    
    ## Motivation
    
    Now that every component has marked up the headers it supports, we can use that to generate fluent builders in camel-endpointdsl.
    
    ## Modifications:
    
    * Generates a new static inner class to propose all the headers' name of a component in the corresponding `EndpointBuilderFactory` interface
    * Generates a new method without parameter into the `Builders` interface to have access to the builder of the headers' name.
    * Adds some doc about this feature
    * Fixes an example of from `Endpoint-dsl.adoc` (not related to the initial issue)
---
 .../modules/ROOT/pages/Endpoint-dsl.adoc           | 32 ++++++--
 .../camel/maven/packaging/EndpointDslMojo.java     | 94 +++++++++++++++++++++-
 .../maven/packaging/generics/JavadocUtil.java      | 56 ++++++++-----
 3 files changed, 155 insertions(+), 27 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/Endpoint-dsl.adoc b/docs/user-manual/modules/ROOT/pages/Endpoint-dsl.adoc
index 5937fd506e5..7ec437b115f 100644
--- a/docs/user-manual/modules/ROOT/pages/Endpoint-dsl.adoc
+++ b/docs/user-manual/modules/ROOT/pages/Endpoint-dsl.adoc
@@ -13,11 +13,11 @@ The following is an example of an FTP route using the standard `RouteBuilder` Ja
 ----
 public class MyRoutes extends RouteBuilder {
     @Override
-    public void configure() throws Exception {
-        from("ftp://foo@myserver?password=secret&recursive=true&
-               ftpClient.dataTimeout=30000&
-               ftpClientConfig.serverLanguageCode=fr")
-        .to("bean:doSomething");
+    public void configure() {
+        from("ftp://foo@myserver?password=secret&recursive=true&" +
+                "ftpClient.dataTimeout=30000&" +
+                "ftpClientConfig.serverLanguageCode=fr")
+                .to("bean:doSomething");
     }
 }
 ----
@@ -63,6 +63,28 @@ from(jms("myWMQ", "cheese").concurrentConsumers(5))
 Notice how we can refer to their names as the first parameter in the `jms` fluent builder.
 The example would then consume messages from WebSphereMQ queue named cheese and route to ActiveMQ on a queue named smelly.
 
+=== Headers' name
+
+The endpoint-dsl can also be used to be assisted when selecting the name of a header to set or to get. The headers' name builder
+is accessible directly from the method of the class `EndpointRouteBuilder` without argument whose name is the scheme of
+the target component.
+
+In the example below the method `file()` available from `EndpointRouteBuilder`, gives access to the methods corresponding to the name of the headers of the file component. Here the method `fileName()` is called to get the name of the header for the name of the file.
+
+[source,java]
+----
+public class MyRoutes extends EndpointRouteBuilder {
+    @Override
+    public void configure() {
+        from(/*some endpoint*/)
+            // Some route start
+            .setHeader(file().fileName(), constant("foo.txt"))
+            // Some route end
+            ;
+    }
+}
+----
+
 === Using Endpoint-DSL outside route builders
 
 You can use the type-safe endpoint-dsl outside route builders with:
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java
index 9708ae905ff..986fe6ebb6b 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointDslMojo.java
@@ -34,14 +34,17 @@ import javax.annotation.Generated;
 import org.apache.camel.maven.packaging.dsl.DslHelper;
 import org.apache.camel.maven.packaging.generics.JavadocUtil;
 import org.apache.camel.tooling.model.BaseModel;
+import org.apache.camel.tooling.model.BaseOptionModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.ComponentModel.EndpointOptionModel;
 import org.apache.camel.tooling.model.JsonMapper;
 import org.apache.camel.tooling.util.JavadocHelper;
 import org.apache.camel.tooling.util.Strings;
+import org.apache.camel.tooling.util.srcgen.Field;
 import org.apache.camel.tooling.util.srcgen.GenericType;
 import org.apache.camel.tooling.util.srcgen.JavaClass;
 import org.apache.camel.tooling.util.srcgen.Method;
+import org.apache.commons.text.CaseUtils;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.LifecyclePhase;
@@ -361,6 +364,11 @@ public class EndpointDslMojo extends AbstractGeneratorMojo {
             method.addAnnotation(Deprecated.class);
         }
 
+        final JavaClass headerNameBuilderClass = addHeaderNameBuilderClass(javaClass, model);
+        if (headerNameBuilderClass != null) {
+            addHeaderNameBuilderMethod(dslClass, headerNameBuilderClass, model);
+        }
+
         if (aliases.size() == 1) {
             processAliases(model, staticBuilders, javaClass, builderClass, dslClass);
         } else {
@@ -371,6 +379,84 @@ public class EndpointDslMojo extends AbstractGeneratorMojo {
                 false);
     }
 
+    /**
+     * Adds the static inner class allowing to have access to all the headers of the given component.
+     *
+     * @param  javaClass the main class into which the static inner class is added.
+     * @param  model     the model from which the information of the component are extracted.
+     * @return           the static inner class that has been added if any, {@code null} otherwise.
+     */
+    private JavaClass addHeaderNameBuilderClass(JavaClass javaClass, ComponentModel model) {
+        final List<ComponentModel.EndpointHeaderModel> endpointHeaders = model.getEndpointHeaders();
+        if (endpointHeaders.isEmpty()) {
+            return null;
+        }
+        final JavaClass builderClass = javaClass.addNestedType();
+        builderClass.setName(getComponentNameFromType(model.getJavaType()) + "HeaderNameBuilder")
+                .setPublic()
+                .setStatic(true);
+        builderClass.getJavaDoc().setText("The builder of headers' name for the " + model.getTitle() + " component.");
+        generateDummyClass(builderClass.getCanonicalName());
+        final Field singleton = builderClass.addField();
+        singleton.setPrivate().setStatic(true).setFinal(true).setName("INSTANCE")
+                .setType(loadClass(builderClass.getCanonicalName()))
+                .setLiteralInitializer(String.format("new %s()", builderClass.getName()));
+        singleton.getJavaDoc().setText(
+                "The internal instance of the builder used to access to all the methods representing the name of headers.");
+
+        for (ComponentModel.EndpointHeaderModel header : endpointHeaders) {
+            addHeaderNameMethod(builderClass, header);
+        }
+        return builderClass;
+    }
+
+    /**
+     * Adds the method allowing to retrieve the header name of the given header.
+     *
+     * @param builderClass the static inner class to which the method is added.
+     * @param header       the header whose name is returned by the method
+     */
+    private void addHeaderNameMethod(JavaClass builderClass, ComponentModel.EndpointHeaderModel header) {
+        String headerName = header.getName();
+        final String camelPrefix = "camel";
+        if (headerName.toLowerCase().startsWith(camelPrefix)) {
+            headerName = headerName.substring(camelPrefix.length());
+        }
+        final String name;
+        if (headerName.chars().anyMatch(c -> c == ':' || c == '-' || c == '.' || c == '_')) {
+            name = CaseUtils.toCamelCase(headerName, false, ':', '-', '.', '_');
+        } else {
+            name = headerName.substring(0, 1).toLowerCase() + headerName.substring(1);
+        }
+        final Method method = builderClass.addMethod().setPublic().setReturnType(String.class)
+                .setName(name);
+        String javaDoc = createBaseDescription(header, "header", true).replace("@@REPLACE_ME@@",
+                "\nThe option is a: {@code " + header.getJavaType() + "} type.");
+        javaDoc += String.format("%n%n@return the name of the header {@code %s}.%n", headerName);
+        method.getJavaDoc().setText(javaDoc);
+        method.setBodyF("return \"%s\";", headerName);
+        if (header.isDeprecated()) {
+            method.addAnnotation(Deprecated.class);
+        }
+    }
+
+    /**
+     * Adds the method to the DSL class allowing to have access to all the headers of the component.
+     *
+     * @param dslClass     the DSL class into which the method should be added.
+     * @param builderClass the builder class that gives access to all the headers of the component.
+     * @param model        the model from which the information of the component are extracted.
+     */
+    private void addHeaderNameBuilderMethod(JavaClass dslClass, JavaClass builderClass, ComponentModel model) {
+        final Method method = dslClass.addMethod();
+        method.setDefault().setReturnType(loadClass(builderClass.getCanonicalName()))
+                .setName(camelCaseLower(model.getScheme()))
+                .setBodyF("return %s.INSTANCE;", builderClass.getName());
+        String javaDoc = getMainDescription(model, false);
+        javaDoc += "\n\n@return the dsl builder for the headers' name.\n";
+        method.getJavaDoc().setText(javaDoc);
+    }
+
     private void processMasterScheme(
             List<ComponentModel> aliases, List<Method> staticBuilders, JavaClass javaClass, JavaClass builderClass,
             JavaClass dslClass) {
@@ -616,7 +702,11 @@ public class EndpointDslMojo extends AbstractGeneratorMojo {
         fluent.getJavaDoc().setText(sb.toString());
     }
 
-    private String createBaseDescription(EndpointOptionModel option) {
+    private String createBaseDescription(BaseOptionModel option) {
+        return createBaseDescription(option, "parameter", false);
+    }
+
+    private String createBaseDescription(BaseOptionModel option, String kind, boolean ignoreMultiValue) {
         String baseDesc = option.getDescription();
         if (Strings.isEmpty(baseDesc)) {
             return baseDesc;
@@ -641,7 +731,7 @@ public class EndpointDslMojo extends AbstractGeneratorMojo {
         // context-path and not as individual options
         // so lets only mark query parameters that are required as
         // required
-        if ("parameter".equals(option.getKind()) && option.isRequired()) {
+        if (kind.equals(option.getKind()) && option.isRequired()) {
             baseDescBuilder.append("\nRequired: true");
         }
         // include default value (if any)
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/JavadocUtil.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/JavadocUtil.java
index c2942581126..1467992c867 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/JavadocUtil.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/generics/JavadocUtil.java
@@ -31,7 +31,21 @@ public final class JavadocUtil {
 
     }
 
+    /**
+     * @param  model the model from which the information are extracted.
+     * @return       the description of the given component with all the possible details.
+     */
     public static String getMainDescription(ComponentModel model) {
+        return getMainDescription(model, true);
+    }
+
+    /**
+     * @param  model                    the model from which the information are extracted.
+     * @param  withPathParameterDetails indicates whether the information about the path parameters should be added to
+     *                                  the description.
+     * @return                          the description of the given component.
+     */
+    public static String getMainDescription(ComponentModel model, boolean withPathParameterDetails) {
         StringBuilder descSb = new StringBuilder(512);
 
         descSb.append(model.getTitle()).append(" (").append(model.getArtifactId()).append(")");
@@ -40,26 +54,28 @@ public final class JavadocUtil {
         descSb.append("\nSince: ").append(model.getFirstVersionShort());
         descSb.append("\nMaven coordinates: ").append(model.getGroupId()).append(":").append(model.getArtifactId());
 
-        // include javadoc for all path parameters and mark which are required
-        descSb.append("\n\nSyntax: <code>").append(model.getSyntax()).append("</code>");
-        for (ComponentModel.EndpointOptionModel option : model.getEndpointOptions()) {
-            if ("path".equals(option.getKind())) {
-                descSb.append("\n\nPath parameter: ").append(option.getName());
-                if (option.isRequired()) {
-                    descSb.append(" (required)");
-                }
-                if (option.isDeprecated()) {
-                    descSb.append(" <strong>deprecated</strong>");
-                }
-                descSb.append("\n").append(option.getDescription());
-                if (option.getDefaultValue() != null) {
-                    descSb.append("\nDefault value: ").append(option.getDefaultValue());
-                }
-                // TODO: default value note ?
-                if (option.getEnums() != null && !option.getEnums().isEmpty()) {
-                    descSb.append("\nThere are ").append(option.getEnums().size())
-                            .append(" enums and the value can be one of: ")
-                            .append(wrapEnumValues(option.getEnums()));
+        if (withPathParameterDetails) {
+            // include javadoc for all path parameters and mark which are required
+            descSb.append("\n\nSyntax: <code>").append(model.getSyntax()).append("</code>");
+            for (ComponentModel.EndpointOptionModel option : model.getEndpointOptions()) {
+                if ("path".equals(option.getKind())) {
+                    descSb.append("\n\nPath parameter: ").append(option.getName());
+                    if (option.isRequired()) {
+                        descSb.append(" (required)");
+                    }
+                    if (option.isDeprecated()) {
+                        descSb.append(" <strong>deprecated</strong>");
+                    }
+                    descSb.append("\n").append(option.getDescription());
+                    if (option.getDefaultValue() != null) {
+                        descSb.append("\nDefault value: ").append(option.getDefaultValue());
+                    }
+                    // TODO: default value note ?
+                    if (option.getEnums() != null && !option.getEnums().isEmpty()) {
+                        descSb.append("\nThere are ").append(option.getEnums().size())
+                                .append(" enums and the value can be one of: ")
+                                .append(wrapEnumValues(option.getEnums()));
+                    }
                 }
             }
         }