This is an automated email from the ASF dual-hosted git repository. nfilotto pushed a commit to branch CAMEL-18141/headers-dsl in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4b579a6aca16bc113ed8d6960ba6e507f00dd3b9 Author: Nicolas Filotto <[email protected]> AuthorDate: Tue Jun 21 11:43:21 2022 +0200 CAMEL-18141: camel-endpoint-dsl - Generate fluent builders for endpoint headers --- .../modules/ROOT/pages/Endpoint-dsl.adoc | 32 ++++++-- .../camel/tooling/util/srcgen/JavaClass.java | 5 +- .../camel/maven/packaging/EndpointDslMojo.java | 94 +++++++++++++++++++++- .../maven/packaging/generics/JavadocUtil.java | 56 ++++++++----- 4 files changed, 158 insertions(+), 29 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/camel-tooling-util/src/main/java/org/apache/camel/tooling/util/srcgen/JavaClass.java b/tooling/camel-tooling-util/src/main/java/org/apache/camel/tooling/util/srcgen/JavaClass.java index 4ebf5b2707c..6cb7862e2f1 100644 --- a/tooling/camel-tooling-util/src/main/java/org/apache/camel/tooling/util/srcgen/JavaClass.java +++ b/tooling/camel-tooling-util/src/main/java/org/apache/camel/tooling/util/srcgen/JavaClass.java @@ -19,6 +19,7 @@ package org.apache.camel.tooling.util.srcgen; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -34,7 +35,7 @@ public class JavaClass { String name; String extendsName = "java.lang.Object"; List<String> implementNames = new ArrayList<>(); - List<String> imports = new ArrayList<>(); + Set<String> imports = new LinkedHashSet<>(); List<Annotation> annotations = new ArrayList<>(); List<Property> properties = new ArrayList<>(); List<Field> fields = new ArrayList<>(); @@ -134,7 +135,7 @@ public class JavaClass { return this; } - public List<String> getImports() { + public Set<String> getImports() { return imports; } 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())); + } } } }
