This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch pojo-beans in repository https://gitbox.apache.org/repos/asf/camel.git
commit fb1cee8406d4cb621e37782b3d51e1e1001772d2 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Mar 14 16:27:10 2024 +0100 CAMEL-17641: Generate json metadata for pojo beans in camel-core that end users can use such as AggregationStrategy implementations. And have that information in camel-catalog for tooling assistance. --- .../services/org/apache/camel/bean.properties | 2 +- .../bean/GroupedMessageAggregationStrategy.json | 15 ++++ .../camel/bean/StringAggregationStrategy.json | 16 ++++ .../GroupedMessageAggregationStrategy.java | 5 ++ .../aggregate/StringAggregationStrategy.java | 21 ++++++ .../packaging/EndpointSchemaGeneratorMojo.java | 82 +-------------------- .../maven/packaging/GeneratePojoBeanMojo.java | 85 +++++++++++++++++++--- .../apache/camel/maven/packaging/MojoHelper.java | 76 +++++++++++++++++++ 8 files changed, 210 insertions(+), 92 deletions(-) diff --git a/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean.properties b/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean.properties index 348c3b28c96..88fd1692084 100644 --- a/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean.properties +++ b/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean.properties @@ -1,5 +1,5 @@ # Generated by camel build tools - do NOT edit this file! -bean=UseLatestAggregationStrategy UseOriginalAggregationStrategy +bean=GroupedMessageAggregationStrategy StringAggregationStrategy UseLatestAggregationStrategy UseOriginalAggregationStrategy groupId=org.apache.camel artifactId=camel-core-processor version=4.5.0-SNAPSHOT diff --git a/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean/GroupedMessageAggregationStrategy.json b/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean/GroupedMessageAggregationStrategy.json new file mode 100644 index 00000000000..a03cad939d9 --- /dev/null +++ b/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean/GroupedMessageAggregationStrategy.json @@ -0,0 +1,15 @@ +{ + "bean": { + "kind": "bean", + "name": "GroupedMessageAggregationStrategy", + "javaType": "org.apache.camel.processor.aggregate.GroupedMessageAggregationStrategy", + "interfaceType": "org.apache.camel.AggregationStrategy", + "title": "Grouped Message Aggregation Strategy", + "description": "Aggregate all Message into a single combined Exchange holding all the aggregated messages in a List of Message as the message body. This aggregation strategy can be used in combination with Splitter to batch messages.", + "deprecated": false, + "groupId": "org.apache.camel", + "artifactId": "camel-core-processor", + "version": "4.5.0-SNAPSHOT" + } +} + diff --git a/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean/StringAggregationStrategy.json b/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean/StringAggregationStrategy.json new file mode 100644 index 00000000000..1702da816d7 --- /dev/null +++ b/core/camel-core-processor/src/generated/resources/META-INF/services/org/apache/camel/bean/StringAggregationStrategy.json @@ -0,0 +1,16 @@ +{ + "bean": { + "kind": "bean", + "name": "StringAggregationStrategy", + "javaType": "org.apache.camel.processor.aggregate.StringAggregationStrategy", + "interfaceType": "org.apache.camel.AggregationStrategy", + "title": "String Aggregation Strategy", + "description": "Aggregate result of pick expression into a single combined Exchange holding all the aggregated bodies in a String as the message body. This aggregation strategy can used in combination with Splitter to batch messages", + "deprecated": false, + "groupId": "org.apache.camel", + "artifactId": "camel-core-processor", + "version": "4.5.0-SNAPSHOT", + "options": { "delimiter": { "index": 0, "kind": "property", "displayName": "Delimiter", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Delimiter used for joining strings together." } } + } +} + diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/GroupedMessageAggregationStrategy.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/GroupedMessageAggregationStrategy.java index ef47d5d0a02..aca104407c3 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/GroupedMessageAggregationStrategy.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/GroupedMessageAggregationStrategy.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.camel.Exchange; import org.apache.camel.Message; +import org.apache.camel.spi.Metadata; import org.apache.camel.support.DefaultExchange; /** @@ -29,6 +30,10 @@ import org.apache.camel.support.DefaultExchange; * This aggregation strategy can be used in combination with {@link org.apache.camel.processor.Splitter} to batch * messages */ +@Metadata(label = "bean", + description = "Aggregate all Message into a single combined Exchange holding all the aggregated messages in a List" + + " of Message as the message body. This aggregation strategy can be used in combination with" + + " Splitter to batch messages.") public class GroupedMessageAggregationStrategy extends AbstractListAggregationStrategy<Message> { @Override diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/StringAggregationStrategy.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/StringAggregationStrategy.java index ed08e6a0a8b..5e8694fd923 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/StringAggregationStrategy.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/StringAggregationStrategy.java @@ -20,6 +20,7 @@ import org.apache.camel.AggregationStrategy; import org.apache.camel.Exchange; import org.apache.camel.ExchangePropertyKey; import org.apache.camel.Expression; +import org.apache.camel.spi.Metadata; import org.apache.camel.support.builder.ExpressionBuilder; /** @@ -28,11 +29,31 @@ import org.apache.camel.support.builder.ExpressionBuilder; * * This aggregation strategy can used in combination with {@link org.apache.camel.processor.Splitter} to batch messages */ +@Metadata(label = "bean", + description = "Aggregate result of pick expression into a single combined Exchange holding all the aggregated bodies in a" + + " String as the message body. This aggregation strategy can used in combination with Splitter to batch messages") public class StringAggregationStrategy implements AggregationStrategy { + @Metadata(description = "Delimiter used for joining strings together.") private String delimiter = ""; private Expression pickExpression = ExpressionBuilder.bodyExpression(); + public String getDelimiter() { + return delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public Expression getPickExpression() { + return pickExpression; + } + + public void setPickExpression(Expression pickExpression) { + this.pickExpression = pickExpression; + } + /** * Set delimiter used for joining aggregated String * diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java index 6f5328c0929..a3a47d330b3 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java @@ -27,8 +27,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; -import java.net.URI; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -36,7 +34,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -1077,7 +1074,7 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo { option.setKind("property"); option.setName(name); option.setDisplayName(displayName); - option.setType(getType(fieldTypeName, false, isDuration)); + option.setType(MojoHelper.getType(fieldTypeName, false, isDuration)); option.setJavaType(fieldTypeName); option.setRequired(required); option.setDefaultValue(defaultValue); @@ -1364,7 +1361,7 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo { } option.setName(name); option.setDisplayName(displayName); - option.setType(getType(fieldTypeName, false, isDuration)); + option.setType(MojoHelper.getType(fieldTypeName, false, isDuration)); option.setJavaType(fieldTypeName); option.setRequired(required); option.setDefaultValue(defaultValue); @@ -1553,7 +1550,7 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo { option.setName(name); option.setKind("path"); option.setDisplayName(displayName); - option.setType(getType(fieldTypeName, false, isDuration)); + option.setType(MojoHelper.getType(fieldTypeName, false, isDuration)); option.setJavaType(fieldTypeName); option.setRequired(required); option.setDefaultValue(defaultValue); @@ -1875,79 +1872,6 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo { return fieldTypeName; } - /** - * Gets the JSON schema type. - * - * @param type the java type - * @return the json schema type, is never null, but returns <tt>object</tt> as the generic type - */ - public static String getType(String type, boolean enumType, boolean isDuration) { - if (enumType) { - return "enum"; - } else if (isDuration) { - return "duration"; - } else if (type == null) { - // return generic type for unknown type - return "object"; - } else if (type.equals(URI.class.getName()) || type.equals(URL.class.getName())) { - return "string"; - } else if (type.equals(File.class.getName())) { - return "string"; - } else if (type.equals(Date.class.getName())) { - return "string"; - } else if (type.startsWith("java.lang.Class")) { - return "string"; - } else if (type.startsWith("java.util.List") || type.startsWith("java.util.Collection")) { - return "array"; - } - - String primitive = getPrimitiveType(type); - if (primitive != null) { - return primitive; - } - - return "object"; - } - - /** - * Gets the JSON schema primitive type. - * - * @param name the java type - * @return the json schema primitive type, or <tt>null</tt> if not a primitive - */ - public static String getPrimitiveType(String name) { - // special for byte[] or Object[] as its common to use - if ("java.lang.byte[]".equals(name) || "byte[]".equals(name)) { - return "string"; - } else if ("java.lang.Byte[]".equals(name) || "Byte[]".equals(name)) { - return "array"; - } else if ("java.lang.Object[]".equals(name) || "Object[]".equals(name)) { - return "array"; - } else if ("java.lang.String[]".equals(name) || "String[]".equals(name)) { - return "array"; - } else if ("java.lang.Character".equals(name) || "Character".equals(name) || "char".equals(name)) { - return "string"; - } else if ("java.lang.String".equals(name) || "String".equals(name)) { - return "string"; - } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name) || "boolean".equals(name)) { - return "boolean"; - } else if ("java.lang.Integer".equals(name) || "Integer".equals(name) || "int".equals(name)) { - return "integer"; - } else if ("java.lang.Long".equals(name) || "Long".equals(name) || "long".equals(name)) { - return "integer"; - } else if ("java.lang.Short".equals(name) || "Short".equals(name) || "short".equals(name)) { - return "integer"; - } else if ("java.lang.Byte".equals(name) || "Byte".equals(name) || "byte".equals(name)) { - return "integer"; - } else if ("java.lang.Float".equals(name) || "Float".equals(name) || "float".equals(name)) { - return "number"; - } else if ("java.lang.Double".equals(name) || "Double".equals(name) || "double".equals(name)) { - return "number"; - } - - return null; - } - /** * Gets the default value accordingly to its type * diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GeneratePojoBeanMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GeneratePojoBeanMojo.java index 438b0de9744..4787b307841 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GeneratePojoBeanMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GeneratePojoBeanMojo.java @@ -21,12 +21,14 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.StringJoiner; +import java.util.stream.Stream; import org.apache.camel.maven.packaging.generics.PackagePluginUtils; +import org.apache.camel.tooling.model.BaseOptionModel; +import org.apache.camel.tooling.model.JsonMapper; import org.apache.camel.tooling.util.PackageHelper; import org.apache.camel.tooling.util.Strings; import org.apache.camel.util.json.JsonObject; -import org.apache.camel.util.json.Jsoner; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; @@ -34,10 +36,13 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; import org.jboss.jandex.Index; import static org.apache.camel.maven.packaging.MojoHelper.annotationValue; +import static org.apache.camel.maven.packaging.MojoHelper.getType; /** * Factory for generating code for Camel pojo beans that are intended for end user to use with Camel EIPs and @@ -66,6 +71,7 @@ public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { private String interfaceName; private String description; private boolean deprecated; + private final List<BeanPojoOptionModel> options = new ArrayList<>(); public String getName() { return name; @@ -114,6 +120,18 @@ public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } + + public void addOption(BeanPojoOptionModel option) { + this.options.add(option); + } + + public List<BeanPojoOptionModel> getOptions() { + return options; + } + } + + private static class BeanPojoOptionModel extends BaseOptionModel { + } public GeneratePojoBeanMojo() { @@ -143,25 +161,47 @@ public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { String label = annotationValue(a, "label"); if ("bean".equals(label)) { BeanPojoModel model = new BeanPojoModel(); - model.setName(a.target().asClass().simpleName()); - boolean deprecated = a.target().asClass().hasAnnotation(Deprecated.class); + ClassInfo ci = a.target().asClass(); + model.setName(ci.simpleName()); + boolean deprecated = ci.hasAnnotation(Deprecated.class); String title = annotationValue(a, "title"); if (title == null) { title = Strings.camelCaseToDash(model.getName()); title = Strings.camelDashToTitle(title); } model.setTitle(title); - model.setClassName(a.target().asClass().name().toString()); + model.setClassName(ci.name().toString()); model.setDeprecated(deprecated); model.setDescription(annotationValue(a, "description")); - for (DotName dn : a.target().asClass().interfaceNames()) { - if (dn.packagePrefix().startsWith("org.apache.camel")) { - model.setInterfaceName(dn.toString()); - break; + model.setInterfaceName(interfaceName(index, ci)); + + // find all fields with @Metadata as options + for (FieldInfo fi : ci.fields()) { + AnnotationInstance ai = fi.annotation(METADATA); + if (ai != null) { + BeanPojoOptionModel o = new BeanPojoOptionModel(); + o.setKind("property"); + o.setName(fi.name()); + o.setLabel(annotationValue(ai, "label")); + o.setDefaultValue(annotationValue(ai, "defaultValue")); + o.setRequired("true".equals(annotationValue(ai, "required"))); + String displayName = annotationValue(ai, "title"); + if (displayName == null) { + displayName = Strings.asTitle(o.getName()); + } + o.setDisplayName(displayName); + o.setDeprecated(fi.hasAnnotation(Deprecated.class)); + o.setJavaType(fi.type().name().toString()); + o.setType(getType(o.getJavaType(), false, false)); + o.setDescription(annotationValue(ai, "description")); + String enums = annotationValue(ai, "enums"); + if (enums != null) { + String[] values = enums.split(","); + o.setEnums(Stream.of(values).map(String::trim).toList()); + } + model.addOption(o); } } - - // TODO: getter/setter for options ala EIP/components models.add(model); } }); @@ -173,8 +213,7 @@ public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { for (var model : models) { names.add(model.getName()); JsonObject jo = asJsonObject(model); - String json = jo.toJson(); - json = Jsoner.prettyPrint(json, 2); + String json = JsonMapper.serialize(jo); String fn = sanitizeFileName(model.getName()) + PackageHelper.JSON_SUFIX; boolean updated = updateResource(resourcesOutputDir.toPath(), "META-INF/services/org/apache/camel/bean/" + fn, @@ -197,6 +236,22 @@ public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { } } + private static String interfaceName(Index index, ClassInfo target) { + for (DotName dn : target.interfaceNames()) { + if (dn.packagePrefix().startsWith("org.apache.camel")) { + return dn.toString(); + } + } + if (target.superName() != null) { + DotName dn = target.superName(); + ClassInfo ci = index.getClassByName(dn); + if (ci != null) { + return interfaceName(index, ci); + } + } + return null; + } + private JsonObject asJsonObject(BeanPojoModel model) { JsonObject jo = new JsonObject(); // we need to know the maven GAV also @@ -214,6 +269,12 @@ public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { jo.put("groupId", project.getGroupId()); jo.put("artifactId", project.getArtifactId()); jo.put("version", project.getVersion()); + + if (!model.getOptions().isEmpty()) { + JsonObject options = JsonMapper.asJsonObject(model.getOptions()); + jo.put("options", options); + } + JsonObject root = new JsonObject(); root.put("bean", jo); return root; diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java index 350579b5fc6..95f5a1de142 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/MojoHelper.java @@ -16,9 +16,13 @@ */ package org.apache.camel.maven.packaging; +import java.io.File; +import java.net.URI; +import java.net.URL; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import org.jboss.jandex.AnnotationInstance; @@ -132,4 +136,76 @@ public final class MojoHelper { return s == null || s.isBlank() ? null : s; } + /** + * Gets the JSON schema type. + * + * @param type the java type + * @return the json schema type, is never null, but returns <tt>object</tt> as the generic type + */ + public static String getType(String type, boolean enumType, boolean isDuration) { + if (enumType) { + return "enum"; + } else if (isDuration) { + return "duration"; + } else if (type == null) { + // return generic type for unknown type + return "object"; + } else if (type.equals(URI.class.getName()) || type.equals(URL.class.getName())) { + return "string"; + } else if (type.equals(File.class.getName())) { + return "string"; + } else if (type.equals(Date.class.getName())) { + return "string"; + } else if (type.startsWith("java.lang.Class")) { + return "string"; + } else if (type.startsWith("java.util.List") || type.startsWith("java.util.Collection")) { + return "array"; + } + + String primitive = getPrimitiveType(type); + if (primitive != null) { + return primitive; + } + + return "object"; + } + + /** + * Gets the JSON schema primitive type. + * + * @param name the java type + * @return the json schema primitive type, or <tt>null</tt> if not a primitive + */ + public static String getPrimitiveType(String name) { + // special for byte[] or Object[] as its common to use + if ("java.lang.byte[]".equals(name) || "byte[]".equals(name)) { + return "string"; + } else if ("java.lang.Byte[]".equals(name) || "Byte[]".equals(name)) { + return "array"; + } else if ("java.lang.Object[]".equals(name) || "Object[]".equals(name)) { + return "array"; + } else if ("java.lang.String[]".equals(name) || "String[]".equals(name)) { + return "array"; + } else if ("java.lang.Character".equals(name) || "Character".equals(name) || "char".equals(name)) { + return "string"; + } else if ("java.lang.String".equals(name) || "String".equals(name)) { + return "string"; + } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name) || "boolean".equals(name)) { + return "boolean"; + } else if ("java.lang.Integer".equals(name) || "Integer".equals(name) || "int".equals(name)) { + return "integer"; + } else if ("java.lang.Long".equals(name) || "Long".equals(name) || "long".equals(name)) { + return "integer"; + } else if ("java.lang.Short".equals(name) || "Short".equals(name) || "short".equals(name)) { + return "integer"; + } else if ("java.lang.Byte".equals(name) || "Byte".equals(name) || "byte".equals(name)) { + return "integer"; + } else if ("java.lang.Float".equals(name) || "Float".equals(name) || "float".equals(name)) { + return "number"; + } else if ("java.lang.Double".equals(name) || "Double".equals(name) || "double".equals(name)) { + return "number"; + } + + return null; + } }