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 <claus.ib...@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); + } + } + } + } + } + +}