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;
+    }
+
+}

Reply via email to