You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2020/09/25 18:44:59 UTC

[camel] 02/16: CAMEL-15567: components - Generate source code for creating endpoint uri via a map of properties. WIP

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

davsclaus pushed a commit to branch uri-assembler
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 167118391f62f098edba9de1ba46c81c76e2aad4
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Sep 24 20:08:11 2020 +0200

    CAMEL-15567: components - Generate source code for creating endpoint uri via a map of properties. WIP
---
 .../component/EndpointUriAssemblerSupport.java     |  94 +++++++++
 .../packaging/EndpointUriAssemblerGenerator.java   |  69 ++++++
 .../maven/packaging/GenerateComponentMojo.java     |   2 +
 .../apache/camel/maven/packaging/GenerateMojo.java |   2 +
 .../GenerateUriEndpointAssemblerMojo.java          | 232 +++++++++++++++++++++
 5 files changed, 399 insertions(+)

diff --git a/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriAssemblerSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriAssemblerSupport.java
new file mode 100644
index 0000000..95ea8c1
--- /dev/null
+++ b/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriAssemblerSupport.java
@@ -0,0 +1,94 @@
+/*
+ * 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.support.component;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.NoSuchBeanException;
+import org.apache.camel.support.EndpointHelper;
+import org.apache.camel.util.TimeUtils;
+
+/**
+ * Base class used by Camel Package Maven Plugin when it generates source code for fast endpoint uri assembler via
+ * {@link org.apache.camel.spi.EndpointUriAssembler}.
+ */
+public abstract class EndpointUriAssemblerSupport {
+
+    /**
+     * Converts the property to the expected type
+     *
+     * @param  camelContext the camel context
+     * @param  type         the expected type
+     * @param  value        the value
+     * @return              the value converted to the expected type
+     */
+    public static <T> T property(CamelContext camelContext, Class<T> type, Object value) {
+        // if the type is not string based and the value is a bean reference, then we need to lookup
+        // the bean from the registry
+        if (value instanceof String && String.class != type) {
+            String text = value.toString();
+
+            if (EndpointHelper.isReferenceParameter(text)) {
+                Object obj;
+                // special for a list where we refer to beans which can be either a list or a single element
+                // so use Object.class as type
+                if (type == List.class) {
+                    obj = EndpointHelper.resolveReferenceListParameter(camelContext, text, Object.class);
+                } else {
+                    obj = EndpointHelper.resolveReferenceParameter(camelContext, text, type);
+                }
+                if (obj == null) {
+                    // no bean found so throw an exception
+                    throw new NoSuchBeanException(text, type.getName());
+                }
+                value = obj;
+            } else if (type == long.class || type == Long.class || type == int.class || type == Integer.class) {
+                Object obj = null;
+                // string to long/int then it may be a duration where we can convert the value to milli seconds
+                // it may be a time pattern, such as 5s for 5 seconds = 5000
+                try {
+                    long num = TimeUtils.toMilliSeconds(text);
+                    if (type == int.class || type == Integer.class) {
+                        // need to cast to int
+                        obj = (int) num;
+                    } else {
+                        obj = num;
+                    }
+                } catch (IllegalArgumentException e) {
+                    // ignore
+                }
+                if (obj != null) {
+                    value = obj;
+                }
+            }
+        }
+
+        // special for boolean values with string values as we only want to accept "true" or "false"
+        if ((type == Boolean.class || type == boolean.class) && value instanceof String) {
+            String text = (String) value;
+            if (!text.equalsIgnoreCase("true") && !text.equalsIgnoreCase("false")) {
+                throw new IllegalArgumentException(
+                        "Cannot convert the String value: " + value + " to type: " + type
+                                                   + " as the value is not true or false");
+            }
+        }
+
+        return camelContext.getTypeConverter().convertTo(type, value);
+    }
+
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriAssemblerGenerator.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriAssemblerGenerator.java
new file mode 100644
index 0000000..c097e03
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointUriAssemblerGenerator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.stream.Collectors;
+
+import org.apache.camel.tooling.model.BaseOptionModel;
+
+public final class EndpointUriAssemblerGenerator {
+
+    private EndpointUriAssemblerGenerator() {
+    }
+
+    public static void generateEndpointUriAssembler(
+            String pn, String cn, String en,
+            String pfqn, String psn,
+            Collection<? extends BaseOptionModel> options, Writer w)
+            throws IOException {
+
+        w.write("/* " + AbstractGeneratorMojo.GENERATED_MSG + " */\n");
+        w.write("package " + pn + ";\n");
+        w.write("\n");
+        w.write("import java.net.URISyntaxException;\n");
+        w.write("import java.util.Map;\n");
+        w.write("\n");
+        w.write("import org.apache.camel.CamelContext;\n");
+        w.write("import org.apache.camel.spi.EndpointUriAssembler;\n");
+        w.write("import " + pfqn + ";\n");
+        w.write("\n");
+        w.write("/**\n");
+        w.write(" * " + AbstractGeneratorMojo.GENERATED_MSG + "\n");
+        w.write(" */\n");
+        w.write("@SuppressWarnings(\"unchecked\")\n");
+        w.write("public class " + cn + " extends " + psn
+                + " implements EndpointUriAssembler {\n");
+
+        // sort options A..Z so they always have same order
+        options = options.stream().sorted(Comparator.comparing(BaseOptionModel::getName)).collect(Collectors.toList());
+
+        // generate API that returns all the options
+        w.write("\n");
+        w.write("    @Override\n");
+        w.write("    public String buildUri(CamelContext camelContext, String scheme, Map<String, String> parameters) throws URISyntaxException {\n");
+        w.write("        return null;\n");
+        w.write("    }\n");
+
+        w.write("}\n");
+        w.write("\n");
+    }
+
+}
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 c672a72..fc199c5 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
@@ -44,6 +44,8 @@ public class GenerateComponentMojo extends AbstractGenerateMojo {
         invoke(GenerateConfigurerMojo.class);
         // generate-endpoint-schema
         invoke(EndpointSchemaGeneratorMojo.class);
+        // generate uri-endpoint-assembler
+        invoke(GenerateUriEndpointAssemblerMojo.class);
         // prepare-components
         invoke(PrepareComponentMojo.class);
         // validate-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 bb4ed04..de3a88d 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
@@ -44,6 +44,8 @@ public class GenerateMojo extends AbstractGenerateMojo {
         invoke(PackageModelMojo.class);
         // generate-endpoint-schema
         invoke(EndpointSchemaGeneratorMojo.class);
+        // generate uri-endpoint-assembler
+        invoke(GenerateUriEndpointAssemblerMojo.class);
         // prepare-components
         invoke(PrepareComponentMojo.class);
         // prepare-main
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateUriEndpointAssemblerMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateUriEndpointAssemblerMojo.java
new file mode 100644
index 0000000..431f2d7
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/GenerateUriEndpointAssemblerMojo.java
@@ -0,0 +1,232 @@
+/*
+ * 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.io.IOError;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.JsonMapper;
+import org.apache.camel.tooling.util.PackageHelper;
+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 static org.apache.camel.tooling.util.PackageHelper.loadText;
+
+/**
+ * Abstract class for endpoint uri assembler generator.
+ */
+@Mojo(name = "generate-uri-endpoint-assembler", threadSafe = true, defaultPhase = LifecyclePhase.PROCESS_CLASSES,
+      requiresDependencyCollection = ResolutionScope.COMPILE,
+      requiresDependencyResolution = ResolutionScope.COMPILE)
+public class GenerateUriEndpointAssemblerMojo extends AbstractGeneratorMojo {
+
+    /**
+     * The project build directory
+     */
+    @Parameter(defaultValue = "${project.build.directory}")
+    protected File buildDir;
+
+    @Parameter(defaultValue = "${project.basedir}/src/generated/java")
+    protected File sourcesOutputDir;
+    @Parameter(defaultValue = "${project.basedir}/src/generated/resources")
+    protected File resourcesOutputDir;
+
+    public GenerateUriEndpointAssemblerMojo() {
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        if ("pom".equals(project.getPackaging())) {
+            return;
+        }
+
+        buildDir = new File(project.getBuild().getDirectory());
+
+        if (sourcesOutputDir == null) {
+            sourcesOutputDir = new File(project.getBasedir(), "src/generated/java");
+        }
+        if (resourcesOutputDir == null) {
+            resourcesOutputDir = new File(project.getBasedir(), "src/generated/resources");
+        }
+
+        Map<File, Supplier<String>> files;
+        try {
+            files = Files
+                    .find(buildDir.toPath(), Integer.MAX_VALUE,
+                            (p, a) -> a.isRegularFile() && p.toFile().getName().endsWith(PackageHelper.JSON_SUFIX))
+                    .collect(Collectors.toMap(Path::toFile, s -> cache(() -> loadJson(s.toFile()))));
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+
+        executeComponent(files);
+    }
+
+    private void executeComponent(Map<File, Supplier<String>> jsonFiles) throws MojoExecutionException, MojoFailureException {
+        // find the component names
+        Set<String> componentNames = new TreeSet<>();
+        findComponentNames(buildDir, componentNames);
+
+        // create auto configuration for the components
+        if (!componentNames.isEmpty()) {
+            getLog().debug("Found " + componentNames.size() + " components");
+
+            List<ComponentModel> allModels = new LinkedList<>();
+            for (String componentName : componentNames) {
+                String json = loadComponentJson(jsonFiles, componentName);
+                if (json != null) {
+                    ComponentModel model = JsonMapper.generateComponentModel(json);
+                    allModels.add(model);
+                }
+            }
+
+            // Group the models by implementing classes
+            Map<String, List<ComponentModel>> grModels
+                    = allModels.stream().collect(Collectors.groupingBy(ComponentModel::getJavaType));
+            for (String componentClass : grModels.keySet()) {
+                List<ComponentModel> compModels = grModels.get(componentClass);
+                for (ComponentModel model : compModels) {
+                    // if more than one, we have a component class with multiple components aliases
+                    try {
+                        createEndpointUrlAssembler(model);
+                    } catch (IOException e) {
+                        throw new MojoExecutionException("Error generating source code", e);
+                    }
+                }
+            }
+        }
+    }
+
+    protected void createEndpointUrlAssembler(ComponentModel model) throws IOException {
+        getLog().info("Generating endpoint-uri-assembler: " + model.getScheme());
+
+        String fqn = model.getJavaType();
+        generateEndpointUriAssembler(fqn, fqn, model.getEndpointOptions(), sourcesOutputDir);
+
+        int pos = fqn.lastIndexOf('.');
+        String pn = fqn.substring(0, pos);
+        String cn = fqn.substring(pos + 1) + "EndpointUriAssembler";
+        // remove component from name
+        cn = cn.replace("Component", "");
+        fqn = pn + "." + cn;
+
+        String pval = model.getScheme() + "-endpoint";
+        updateResource(resourcesOutputDir.toPath(),
+                "META-INF/services/org/apache/camel/assembler/" + pval,
+                "# " + GENERATED_MSG + NL + "class=" + fqn + NL);
+
+        // META-INF/services/org/apache/camel/configurer/
+        if (model.getAlternativeSchemes() != null) {
+            String[] schemes = model.getAlternativeSchemes().split(",");
+            for (String alt : schemes) {
+                pval = alt + "-endpoint";
+                updateResource(resourcesOutputDir.toPath(),
+                        "META-INF/services/org/apache/camel/assembler/" + pval,
+                        "# " + GENERATED_MSG + NL + "class=" + fqn + NL);
+            }
+        }
+    }
+
+    @Deprecated
+    private void generateEndpointUriAssembler(
+            String fqn, String targetFqn, List<ComponentModel.EndpointOptionModel> options, File outputDir)
+            throws IOException {
+
+        int pos = targetFqn.lastIndexOf('.');
+        String pn = targetFqn.substring(0, pos);
+        String cn = targetFqn.substring(pos + 1) + "EndpointUriAssembler";
+        // remove component from name
+        cn = cn.replace("Component", "");
+        String en = fqn;
+        String pfqn = fqn;
+        String psn = "org.apache.camel.support.component.EndpointUriAssemblerSupport";
+
+        StringWriter sw = new StringWriter();
+        EndpointUriAssemblerGenerator.generateEndpointUriAssembler(pn, cn, en, pfqn, psn, options, sw);
+
+        String source = sw.toString();
+
+        String fileName = pn.replace('.', '/') + "/" + cn + ".java";
+        outputDir.mkdirs();
+        boolean updated = updateResource(buildContext, outputDir.toPath().resolve(fileName), source);
+        if (updated) {
+            getLog().info("Updated " + fileName);
+        }
+    }
+
+    protected static String loadJson(File file) {
+        try {
+            return loadText(file);
+        } catch (IOException e) {
+            throw new IOError(e);
+        }
+    }
+
+    protected static String loadComponentJson(Map<File, Supplier<String>> jsonFiles, String componentName) {
+        return loadJsonOfType(jsonFiles, componentName, "component");
+    }
+
+    protected static String loadJsonOfType(Map<File, Supplier<String>> jsonFiles, String modelName, String type) {
+        for (Map.Entry<File, Supplier<String>> entry : jsonFiles.entrySet()) {
+            if (entry.getKey().getName().equals(modelName + ".json")) {
+                String json = entry.getValue().get();
+                if (json.contains("\"kind\": \"" + type + "\"")) {
+                    return json;
+                }
+            }
+        }
+        return null;
+    }
+
+    protected void findComponentNames(File dir, Set<String> componentNames) {
+        File f = new File(dir, "classes/META-INF/services/org/apache/camel/component");
+
+        if (f.exists() && f.isDirectory()) {
+            File[] files = f.listFiles();
+            if (files != null) {
+                for (File file : files) {
+                    // skip directories as there may be a sub .resolver
+                    // directory
+                    if (file.isDirectory()) {
+                        continue;
+                    }
+                    String name = file.getName();
+                    if (name.charAt(0) != '.') {
+                        componentNames.add(name);
+                    }
+                }
+            }
+        }
+    }
+
+}