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 a0d5fa9fbd1355aa0e95ff144d536f29b91d3b4d Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Mar 14 15:23:17 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. --- .../aggregate/UseLatestAggregationStrategy.java | 9 +- .../maven/packaging/GenerateComponentMojo.java | 2 + .../apache/camel/maven/packaging/GenerateMojo.java | 2 + .../maven/packaging/GeneratePojoBeanMojo.java | 221 +++++++++++++++++++++ 4 files changed, 232 insertions(+), 2 deletions(-) diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/UseLatestAggregationStrategy.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/UseLatestAggregationStrategy.java index d43a6a7fa76..eb48f844d81 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/UseLatestAggregationStrategy.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/UseLatestAggregationStrategy.java @@ -19,12 +19,17 @@ package org.apache.camel.processor.aggregate; import org.apache.camel.AggregationStrategy; import org.apache.camel.Exchange; import org.apache.camel.ExchangePropertyKey; +import org.apache.camel.spi.Metadata; /** * An {@link AggregationStrategy} which just uses the latest exchange which is useful for status messages where old * status messages have no real value. Another example is things like market data prices, where old stock prices are not * that relevant, only the current price is. */ +@Metadata(label = "bean", + description = "An AggregationStrategy which just uses the latest exchange which is useful for status messages where old" + + " status messages have no real value. Another example is things like market data prices, where old stock prices are not" + + " that relevant, only the current price is.") public class UseLatestAggregationStrategy implements AggregationStrategy { @Override @@ -72,8 +77,8 @@ public class UseLatestAggregationStrategy implements AggregationStrategy { // propagate exception from old exchange if there isn't already an exception if (oldExchange.isFailed() || oldExchange.isRollbackOnly() || oldExchange.isRollbackOnlyLast() - || oldExchange.getExchangeExtension().isErrorHandlerHandledSet() - && oldExchange.getExchangeExtension().isErrorHandlerHandled()) { + || oldExchange.getExchangeExtension().isErrorHandlerHandledSet() + && oldExchange.getExchangeExtension().isErrorHandlerHandled()) { // propagate failure by using old exchange as the answer return oldExchange; } diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateComponentMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateComponentMojo.java index 54e8563c891..d20cd9aaa7c 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateComponentMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateComponentMojo.java @@ -50,6 +50,8 @@ public class GenerateComponentMojo extends AbstractGenerateMojo { invoke(GenerateInvokeOnHeaderMojo.class); // generate data-type-transformer invoke(GenerateDataTypeTransformerMojo.class); + // generate pojo-beans + invoke(GeneratePojoBeanMojo.class); // generate dev-console invoke(GenerateDevConsoleMojo.class); // prepare-components diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateMojo.java index 982f872be20..84b7d2752d2 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateMojo.java @@ -52,6 +52,8 @@ public class GenerateMojo extends AbstractGenerateMojo { invoke(GenerateInvokeOnHeaderMojo.class); // generate data-type-transformer invoke(GenerateDataTypeTransformerMojo.class); + // generate pojo-beans + invoke(GeneratePojoBeanMojo.class); // generate dev-console invoke(GenerateDevConsoleMojo.class); // prepare-components 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 new file mode 100644 index 00000000000..31fc4fc4ca3 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GeneratePojoBeanMojo.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.maven.packaging; + +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.StringJoiner; + +import org.apache.camel.maven.packaging.generics.PackagePluginUtils; +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; +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.DotName; +import org.jboss.jandex.Index; + +import static org.apache.camel.maven.packaging.MojoHelper.annotationValue; + +/** + * Factory for generating code for Camel pojo beans that are intended for end user to use with Camel EIPs and + * components. + */ +@Mojo(name = "generate-pojo-bean", threadSafe = true, defaultPhase = LifecyclePhase.PROCESS_CLASSES, + requiresDependencyCollection = ResolutionScope.COMPILE, + requiresDependencyResolution = ResolutionScope.COMPILE) +public class GeneratePojoBeanMojo extends AbstractGeneratorMojo { + + public static final DotName METADATA = DotName.createSimple("org.apache.camel.spi.Metadata"); + + /** + * The project build directory + */ + @Parameter(defaultValue = "${project.build.directory}") + protected File buildDir; + + @Parameter(defaultValue = "${project.basedir}/src/generated/resources") + protected File resourcesOutputDir; + + private static class BeanPojoModel { + private String name; + private String className; + private String interfaceName; + private String description; + private boolean deprecated; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getInterfaceName() { + return interfaceName; + } + + public void setInterfaceName(String interfaceName) { + this.interfaceName = interfaceName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isDeprecated() { + return deprecated; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + } + + public GeneratePojoBeanMojo() { + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if ("pom".equals(project.getPackaging())) { + return; + } + + buildDir = new File(project.getBuild().getDirectory()); + + if (resourcesOutputDir == null) { + resourcesOutputDir = new File(project.getBasedir(), "src/generated/resources"); + } + + Index index = PackagePluginUtils.readJandexIndexIgnoreMissing(project, getLog()); + if (index == null) { + return; + } + + List<BeanPojoModel> models = new ArrayList<>(); + List<AnnotationInstance> annotations = index.getAnnotations(METADATA); + annotations.forEach(a -> { + // only @Metadata(label="bean") is selected + String label = annotationValue(a, "label"); + if ("bean".equals(label)) { + BeanPojoModel model = new BeanPojoModel(); + String currentClass = a.target().asClass().name().toString(); + boolean deprecated = a.target().asClass().hasAnnotation(Deprecated.class); + model.setName(a.target().asClass().simpleName()); + model.setClassName(currentClass); + 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; + } + } + // TODO: getter/setter for options ala EIP/components + models.add(model); + } + }); + models.sort(Comparator.comparing(BeanPojoModel::getClassName)); + + if (!models.isEmpty()) { + try { + StringJoiner names = new StringJoiner(" "); + for (var model : models) { + names.add(model.getClassName()); + JsonObject jo = asJsonObject(model); + String json = jo.toJson(); + json = Jsoner.prettyPrint(json, 2); + String fn = sanitizeFileName(model.getName()) + PackageHelper.JSON_SUFIX; + boolean updated = updateResource(resourcesOutputDir.toPath(), + "META-INF/services/org/apache/camel/bean/" + fn, + json + NL); + if (updated) { + getLog().info("Updated bean json: " + model.getName()); + } + } + + // generate marker file + File camelMetaDir = new File(resourcesOutputDir, "META-INF/services/org/apache/camel/"); + int count = models.size(); + String properties = createProperties(project, "beans", names.toString()); + updateResource(camelMetaDir.toPath(), "beans.properties", properties); + getLog().info("Generated beans.properties containing " + count + " Camel " + + (count > 1 ? "beans: " : "bean: ") + names); + } catch (Exception e) { + throw new MojoExecutionException(e); + } + } + } + + private JsonObject asJsonObject(BeanPojoModel model) { + JsonObject jo = new JsonObject(); + // we need to know the maven GAV also + jo.put("kind", "bean"); + jo.put("name", model.getName()); + jo.put("javaType", model.getClassName()); + if (model.getInterfaceName() != null) { + jo.put("interfaceType", model.getInterfaceName()); + } + jo.put("title", asTitle(model.getClassName())); + if (model.getDescription() != null) { + jo.put("description", model.getDescription()); + } + jo.put("deprecated", model.isDeprecated()); + jo.put("groupId", project.getGroupId()); + jo.put("artifactId", project.getArtifactId()); + jo.put("version", project.getVersion()); + JsonObject root = new JsonObject(); + root.put("bean", jo); + return root; + } + + private String sanitizeFileName(String fileName) { + return fileName.replaceAll("[^A-Za-z0-9+-/]", "-"); + } + + private String asTitle(String name) { + name = Strings.camelDashToTitle(name); + String part = Strings.after(name, ":"); + if (part != null) { + part = Strings.capitalize(part); + name = Strings.before(name, ":") + " (" + part + ")"; + } + return name; + } + +}