| /* |
| * 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.FileInputStream; |
| import java.io.IOError; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Type; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.function.Function; |
| import java.util.function.Supplier; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import javax.annotation.Generated; |
| import javax.xml.bind.annotation.XmlTransient; |
| |
| import org.apache.camel.maven.packaging.model.ComponentModel; |
| import org.apache.camel.maven.packaging.model.ComponentOptionModel; |
| import org.apache.camel.maven.packaging.model.DataFormatModel; |
| import org.apache.camel.maven.packaging.model.DataFormatOptionModel; |
| import org.apache.camel.maven.packaging.model.EndpointOptionModel; |
| import org.apache.camel.maven.packaging.model.LanguageModel; |
| import org.apache.camel.maven.packaging.model.LanguageOptionModel; |
| import org.apache.camel.maven.packaging.model.OtherModel; |
| import org.apache.camel.maven.packaging.model.OtherOptionModel; |
| import org.apache.camel.maven.packaging.srcgen.Annotation; |
| import org.apache.camel.maven.packaging.srcgen.GenericType; |
| import org.apache.camel.maven.packaging.srcgen.JavaClass; |
| import org.apache.camel.maven.packaging.srcgen.Method; |
| import org.apache.camel.maven.packaging.srcgen.Property; |
| import org.apache.camel.spi.Metadata; |
| import org.apache.camel.spi.UriParam; |
| import org.apache.camel.spi.UriPath; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.maven.plugin.AbstractMojo; |
| 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.apache.maven.project.MavenProject; |
| import org.jboss.forge.roaster.model.util.Strings; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
| import org.springframework.boot.autoconfigure.AutoConfigureAfter; |
| import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; |
| import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; |
| import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| import org.springframework.boot.context.properties.NestedConfigurationProperty; |
| import org.springframework.context.ApplicationContext; |
| import org.springframework.context.annotation.Bean; |
| import org.springframework.context.annotation.Conditional; |
| import org.springframework.context.annotation.Configuration; |
| import org.springframework.context.annotation.Lazy; |
| import org.springframework.context.annotation.Scope; |
| |
| import static org.apache.camel.maven.packaging.AbstractGeneratorMojo.updateResource; |
| import static org.apache.camel.maven.packaging.JSonSchemaHelper.getPropertyDefaultValue; |
| import static org.apache.camel.maven.packaging.JSonSchemaHelper.getPropertyDescriptionValue; |
| import static org.apache.camel.maven.packaging.JSonSchemaHelper.getPropertyJavaType; |
| import static org.apache.camel.maven.packaging.JSonSchemaHelper.getPropertyType; |
| import static org.apache.camel.maven.packaging.JSonSchemaHelper.getSafeValue; |
| import static org.apache.camel.maven.packaging.JSonSchemaHelper.parseJsonSchema; |
| import static org.apache.camel.maven.packaging.PackageHelper.loadText; |
| |
| /** |
| * Generate Spring Boot auto configuration files for Camel components and data |
| * formats. |
| */ |
| @Mojo(name = "prepare-spring-boot-auto-configuration", threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PROCESS_CLASSES) |
| public class SpringBootAutoConfigurationMojo extends AbstractMojo { |
| |
| /** |
| * Useful to move configuration towards starters. Warning: the |
| * spring.factories files sometimes are used also on the main artifacts. |
| * Make sure it is not the case before enabling this property. |
| */ |
| private static final boolean DELETE_FILES_ON_MAIN_ARTIFACTS = false; |
| |
| /** |
| * Suffix used for generating inner classes for nested component properties, |
| * e.g. endpoint configuration. |
| */ |
| private static final String INNER_TYPE_SUFFIX = "NestedConfiguration"; |
| |
| /** |
| * Classes to include when adding {@link NestedConfigurationProperty} |
| * annotations. |
| */ |
| private static final Pattern INCLUDE_INNER_PATTERN = Pattern.compile("org\\.apache\\.camel\\..*"); |
| |
| /** |
| * Whether to enable adding @NestedConfigurationProperty annotations to |
| * options. This is disabled as the generated options likely is not |
| * configurable as plain POJOs and there is also no documentation for each |
| * of the generated options. |
| */ |
| private static final boolean ADD_NESTED_CONFIGURATION_PROPERTY = false; |
| |
| private static final Map<String, String> PRIMITIVEMAP; |
| private static final Map<Type, Type> PRIMITIVE_CLASSES; |
| |
| static { |
| PRIMITIVE_CLASSES = new HashMap<>(); |
| PRIMITIVE_CLASSES.put(boolean.class, Boolean.class); |
| PRIMITIVE_CLASSES.put(char.class, Character.class); |
| PRIMITIVE_CLASSES.put(long.class, Long.class); |
| PRIMITIVE_CLASSES.put(int.class, Integer.class); |
| PRIMITIVE_CLASSES.put(byte.class, Byte.class); |
| PRIMITIVE_CLASSES.put(short.class, Short.class); |
| PRIMITIVE_CLASSES.put(double.class, Double.class); |
| PRIMITIVE_CLASSES.put(float.class, Float.class); |
| PRIMITIVEMAP = new HashMap<>(); |
| PRIMITIVEMAP.put("boolean", "java.lang.Boolean"); |
| PRIMITIVEMAP.put("char", "java.lang.Character"); |
| PRIMITIVEMAP.put("long", "java.lang.Long"); |
| PRIMITIVEMAP.put("int", "java.lang.Integer"); |
| PRIMITIVEMAP.put("integer", "java.lang.Integer"); |
| PRIMITIVEMAP.put("byte", "java.lang.Byte"); |
| PRIMITIVEMAP.put("short", "java.lang.Short"); |
| PRIMITIVEMAP.put("double", "java.lang.Double"); |
| PRIMITIVEMAP.put("float", "java.lang.Float"); |
| } |
| |
| private static final String[] IGNORE_MODULES = {/* Non-standard -> */ "camel-grape"}; |
| |
| /** |
| * The output directory for generated component schema file |
| */ |
| @Parameter(defaultValue = "${project.build.directory}/classes") |
| protected File classesDir; |
| |
| /** |
| * The maven project. |
| */ |
| @Parameter(property = "project", required = true, readonly = true) |
| protected MavenProject project; |
| |
| /** |
| * The project build directory |
| */ |
| @Parameter(defaultValue = "${project.build.directory}") |
| protected File buildDir; |
| |
| /** |
| * The base directory |
| */ |
| @Parameter(defaultValue = "${basedir}") |
| protected File baseDir; |
| |
| DynamicClassLoader projectClassLoader; |
| |
| @Override |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| // Do not generate code for ignored module |
| if (Arrays.asList(IGNORE_MODULES).contains(project.getArtifactId())) { |
| getLog().info("Component auto-configuration will not be created: component contained in the ignore list"); |
| return; |
| } |
| |
| // Spring-boot configuration has been moved on starters |
| File starterDir = SpringBootHelper.starterDir(baseDir, getStarterArtifactId()); |
| if (!starterDir.exists() || !(new File(starterDir, "pom.xml").exists())) { |
| // If the starter does not exist, no configuration can be created |
| getLog().info("Component auto-configuration will not be created: the starter does not exist"); |
| return; |
| } |
| |
| executeAll(); |
| } |
| |
| private void executeAll() throws MojoExecutionException, MojoFailureException { |
| Map<File, Supplier<String>> files = PackageHelper.findJsonFiles(buildDir, p -> p.isDirectory() || p.getName().endsWith(".json")).stream() |
| .collect(Collectors.toMap(Function.identity(), s -> cache(() -> loadJson(s)))); |
| |
| // special for camel-core-engine where we generate some special auto-configuration source code |
| if ("camel-core-engine".equals(project.getArtifactId())) { |
| executeModels(files); |
| } |
| |
| executeComponent(files); |
| executeDataFormat(files); |
| executeLanguage(files); |
| } |
| |
| private static String loadJson(File file) { |
| try (InputStream is = new FileInputStream(file)) { |
| return loadText(is); |
| } catch (IOException e) { |
| throw new IOError(e); |
| } |
| } |
| |
| private static <T> Supplier<T> cache(Supplier<T> supplier) { |
| return new Supplier<T>() { |
| T value; |
| |
| @Override |
| public T get() { |
| if (value == null) { |
| value = supplier.get(); |
| } |
| return value; |
| } |
| }; |
| } |
| |
| private void executeModels(Map<File, Supplier<String>> files) throws MojoExecutionException, MojoFailureException { |
| String json; |
| |
| // Hystrix |
| json = loadModelJson(files, "hystrixConfiguration"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.hystrix", true); |
| } |
| |
| // Resilience4j |
| json = loadModelJson(files, "resilience4jConfiguration"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.resilience4j", true); |
| } |
| |
| // Consul |
| json = loadModelJson(files, "consulServiceDiscovery"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.cloud.consul.service-discovery", true); |
| } |
| |
| // DNS |
| json = loadModelJson(files, "dnsServiceDiscovery"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.cloud.dns.service-discovery", true); |
| } |
| |
| // Etcd |
| json = loadModelJson(files, "etcdServiceDiscovery"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.cloud.etcd.service-discovery", true); |
| } |
| |
| // Kubernetes |
| json = loadModelJson(files, "kubernetesServiceDiscovery"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration happens in |
| // camel-kubernetes-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.cloud.kubernetes.service-discovery", true); |
| } |
| |
| // Ribbon |
| json = loadModelJson(files, "ribbonLoadBalancer"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createOtherModelConfigurationSource(pkg, model, "camel.cloud.ribbon.load-balancer", true); |
| } |
| |
| // Rest |
| json = loadModelJson(files, "restConfiguration"); |
| if (json != null) { |
| OtherModel model = generateOtherModel(json); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| // Generate properties, auto-configuration for camel-core-starter |
| createRestConfigurationSource(pkg, model, "camel.rest"); |
| createRestModuleAutoConfigurationSource(pkg, model); |
| } |
| } |
| |
| private void createOtherModelConfigurationSource(String packageName, OtherModel model, String propertiesPrefix, boolean generatedNestedConfig) throws MojoFailureException { |
| final int pos = model.getJavaType().lastIndexOf("."); |
| final String commonName = model.getJavaType().substring(pos + 1) + (generatedNestedConfig ? "Common" : "Properties"); |
| final String configName = model.getJavaType().substring(pos + 1) + (generatedNestedConfig ? "Properties" : null); |
| |
| // Common base class |
| JavaClass commonClass = new JavaClass(getProjectClassLoader()); |
| commonClass.setPackage(packageName); |
| commonClass.setName(commonName); |
| |
| String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; |
| if (!Strings.isBlank(model.getDescription())) { |
| doc = model.getDescription() + "\n\n" + doc; |
| } |
| commonClass.getJavaDoc().setFullText(doc); |
| commonClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| |
| for (OtherOptionModel option : model.getOptions()) { |
| String type = option.getJavaType(); |
| String name = option.getName(); |
| |
| if ("id".equalsIgnoreCase(name) || "parent".equalsIgnoreCase(name) || "camelContext".equalsIgnoreCase(name)) { |
| // Skip them as they should not be set via spring boot |
| continue; |
| } |
| |
| if ("java.util.List<org.apache.camel.model.PropertyDefinition>".equalsIgnoreCase(type)) { |
| type = "java.util.Map<java.lang.String, java.lang.String>"; |
| } |
| |
| // generate inner class for non-primitive options |
| Property prop = commonClass.addProperty(type, option.getName()); |
| if (!Strings.isBlank(option.getDescription())) { |
| prop.getField().getJavaDoc().setFullText(option.getDescription()); |
| } |
| if (!Strings.isBlank(option.getDefaultValue())) { |
| if ("java.lang.String".equals(type)) { |
| prop.getField().setStringInitializer(option.getDefaultValue()); |
| } else if ("long".equals(type) || "java.lang.Long".equals(type)) { |
| // the value should be a Long number |
| String value = option.getDefaultValue() + "L"; |
| prop.getField().setLiteralInitializer(value); |
| } else if ("integer".equals(option.getType()) || "boolean".equals(option.getType())) { |
| prop.getField().setLiteralInitializer(option.getDefaultValue()); |
| } else if (!Strings.isBlank(option.getEnums())) { |
| String enumShortName = type.substring(type.lastIndexOf(".") + 1); |
| prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue()); |
| commonClass.addImport(model.getJavaType()); |
| } |
| } |
| } |
| |
| writeSourceIfChanged(commonClass, packageName.replaceAll("\\.", "\\/") + "/" + commonName + ".java", true); |
| |
| Class commonClazz = generateDummyClass(commonClass.getCanonicalName()); |
| |
| // Config class |
| if (generatedNestedConfig) { |
| JavaClass configClass = new JavaClass(getProjectClassLoader()); |
| configClass.setPackage(packageName); |
| configClass.setName(configName); |
| configClass.extendSuperType(commonClass); |
| configClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| configClass.addAnnotation(loadClass("org.springframework.boot.context.properties.ConfigurationProperties")).setStringValue("prefix", propertiesPrefix); |
| configClass.addImport(Map.class); |
| configClass.addImport(HashMap.class); |
| configClass.removeImport(commonClass); |
| |
| configClass.addField().setName("enabled").setType(boolean.class).setPrivate().setLiteralInitializer("true").getJavaDoc().setFullText("Enable the component"); |
| configClass.addField().setName("configurations").setType(loadType("java.util.Map<java.lang.String, " + packageName + "." + commonName + ">")).setPrivate() |
| .setLiteralInitializer("new HashMap<>()").getJavaDoc().setFullText("Define additional configuration definitions"); |
| |
| Method method; |
| |
| method = configClass.addMethod(); |
| method.setName("getConfigurations"); |
| method.setReturnType(loadType("java.util.Map<java.lang.String, " + packageName + "." + commonName + ">")); |
| method.setPublic(); |
| method.setBody("return configurations;"); |
| |
| method = configClass.addMethod(); |
| method.setName("isEnabled"); |
| method.setReturnType(boolean.class); |
| method.setPublic(); |
| method.setBody("return enabled;"); |
| |
| method = configClass.addMethod(); |
| method.setName("setEnabled"); |
| method.addParameter(boolean.class, "enabled"); |
| method.setPublic(); |
| method.setBody("this.enabled = enabled;"); |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + configName + ".java"; |
| writeSourceIfChanged(configClass, fileName, true); |
| } |
| } |
| |
| private void createRestConfigurationSource(String packageName, OtherModel model, String propertiesPrefix) throws MojoFailureException { |
| final int pos = model.getJavaType().lastIndexOf("."); |
| final String className = model.getJavaType().substring(pos + 1) + "Properties"; |
| |
| generateDummyClass(packageName + "." + className); |
| |
| // Common base class |
| JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| javaClass.setPackage(packageName); |
| javaClass.setName(className); |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties").setStringValue("prefix", propertiesPrefix); |
| |
| String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; |
| if (!Strings.isBlank(model.getDescription())) { |
| doc = model.getDescription() + "\n\n" + doc; |
| } |
| javaClass.getJavaDoc().setFullText(doc); |
| |
| for (OtherOptionModel option : model.getOptions()) { |
| String type = option.getJavaType(); |
| String name = option.getName(); |
| |
| if ("id".equalsIgnoreCase(name) || "parent".equalsIgnoreCase(name) || "camelContext".equalsIgnoreCase(name)) { |
| // Skip them as they should not be set via spring boot |
| continue; |
| } |
| |
| if ("java.util.List<org.apache.camel.model.PropertyDefinition>".equalsIgnoreCase(type)) { |
| type = "java.util.Map<java.lang.String, java.lang.String>"; |
| } else if ("java.util.List<org.apache.camel.model.rest.RestPropertyDefinition>".equalsIgnoreCase(type)) { |
| type = "java.util.Map<java.lang.String, java.lang.Object>"; |
| } |
| |
| // to avoid ugly names such as c-o-r-s |
| if ("enableCORS".equalsIgnoreCase(name)) { |
| name = "enableCors"; |
| } |
| |
| // generate inner class for non-primitive options |
| Property prop = javaClass.addProperty(type, name); |
| if (!Strings.isBlank(option.getDescription())) { |
| prop.getField().getJavaDoc().setFullText(option.getDescription()); |
| } |
| if (!Strings.isBlank(option.getDefaultValue())) { |
| if ("java.lang.String".equals(type)) { |
| prop.getField().setStringInitializer(option.getDefaultValue()); |
| } else if ("long".equals(type) || "java.lang.Long".equals(type)) { |
| // the value should be a Long number |
| String value = option.getDefaultValue() + "L"; |
| prop.getField().setLiteralInitializer(value); |
| } else if ("integer".equals(option.getType()) || "boolean".equals(option.getType())) { |
| prop.getField().setLiteralInitializer(option.getDefaultValue()); |
| } else if (!Strings.isBlank(option.getEnums())) { |
| String enumShortName = type.substring(type.lastIndexOf(".") + 1); |
| prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue()); |
| javaClass.addImport(model.getJavaType()); |
| } |
| } |
| } |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + className + ".java"; |
| writeSourceIfChanged(javaClass, fileName, true); |
| } |
| |
| private void createRestModuleAutoConfigurationSource(String packageName, OtherModel model) throws MojoFailureException { |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| final int pos = model.getJavaType().lastIndexOf("."); |
| final String name = model.getJavaType().substring(pos + 1) + "AutoConfiguration"; |
| final String configType = model.getJavaType().substring(pos + 1) + "Properties"; |
| |
| javaClass.setPackage(packageName); |
| javaClass.setName(name); |
| |
| String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; |
| javaClass.getJavaDoc().setFullText(doc); |
| |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation(Configuration.class); |
| javaClass.addAnnotation(ConditionalOnBean.class).setStringValue("type", "org.apache.camel.spring.boot.CamelAutoConfiguration"); |
| javaClass.addAnnotation(ConditionalOnProperty.class).setStringValue("name", "camel.rest.enabled").setLiteralValue("matchIfMissing", "true"); |
| javaClass.addAnnotation(AutoConfigureAfter.class).setStringValue("name", "org.apache.camel.spring.boot.CamelAutoConfiguration"); |
| javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue("value", configType + ".class"); |
| |
| javaClass.addImport("java.util.Map"); |
| javaClass.addImport("java.util.HashMap"); |
| javaClass.addImport("org.apache.camel.util.CollectionHelper"); |
| javaClass.addImport("org.apache.camel.support.IntrospectionSupport"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.CamelPropertiesHelper"); |
| javaClass.addImport("org.apache.camel.CamelContext"); |
| javaClass.addImport("org.apache.camel.model.rest.RestConstants"); |
| javaClass.addImport("org.apache.camel.spi.RestConfiguration"); |
| |
| javaClass.addField().setName("camelContext").setType(loadClass("org.apache.camel.CamelContext")).setPrivate().addAnnotation(Autowired.class); |
| javaClass.addField().setName("config").setType(loadClass(packageName + "." + configType)).setPrivate().addAnnotation(Autowired.class); |
| |
| Method method; |
| |
| // Configuration |
| method = javaClass.addMethod(); |
| method.setName("configure" + model.getShortJavaType()); |
| method.setPublic(); |
| method.addThrows(Exception.class); |
| method.setReturnType(loadClass("org.apache.camel.spi.RestConfiguration")); |
| method.addAnnotation(Lazy.class); |
| method.addAnnotation(Bean.class).setLiteralValue("name", "RestConstants.DEFAULT_REST_CONFIGURATION_ID"); |
| method.addAnnotation(ConditionalOnClass.class).setLiteralValue("value", "CamelContext.class"); |
| method.addAnnotation(ConditionalOnMissingBean.class); |
| method.setBody("" + "Map<String, Object> properties = new HashMap<>();\n" + "IntrospectionSupport.getProperties(config, properties, null, false);\n" |
| + "// These options is configured specially further below, so remove them first\n" + "properties.remove(\"enableCors\");\n" |
| + "properties.remove(\"apiProperty\");\n" + "properties.remove(\"componentProperty\");\n" + "properties.remove(\"consumerProperty\");\n" |
| + "properties.remove(\"dataFormatProperty\");\n" + "properties.remove(\"endpointProperty\");\n" + "properties.remove(\"corsHeaders\");\n" + "\n" |
| + "RestConfiguration definition = new RestConfiguration();\n" + "CamelPropertiesHelper.setCamelProperties(camelContext, definition, properties, true);\n" |
| + "\n" + "// Workaround for spring-boot properties name as It would appear\n" + "// as enable-c-o-r-s if left uppercase in Configuration\n" |
| + "definition.setEnableCORS(config.getEnableCors());\n" + "\n" + "if (config.getApiProperty() != null) {\n" |
| + " definition.setApiProperties(new HashMap<>(CollectionHelper.flattenKeysInMap(config.getApiProperty(), \".\")));\n" + "}\n" |
| + "if (config.getComponentProperty() != null) {\n" |
| + " definition.setComponentProperties(new HashMap<>(CollectionHelper.flattenKeysInMap(config.getComponentProperty(), \".\")));\n" + "}\n" |
| + "if (config.getConsumerProperty() != null) {\n" |
| + " definition.setConsumerProperties(new HashMap<>(CollectionHelper.flattenKeysInMap(config.getConsumerProperty(), \".\")));\n" + "}\n" |
| + "if (config.getDataFormatProperty() != null) {\n" |
| + " definition.setDataFormatProperties(new HashMap<>(CollectionHelper.flattenKeysInMap(config.getDataFormatProperty(), \".\")));\n" + "}\n" |
| + "if (config.getEndpointProperty() != null) {\n" |
| + " definition.setEndpointProperties(new HashMap<>(CollectionHelper.flattenKeysInMap(config.getEndpointProperty(), \".\")));\n" + "}\n" |
| + "if (config.getCorsHeaders() != null) {\n" + " Map<String, Object> map = CollectionHelper.flattenKeysInMap(config.getCorsHeaders(), \".\");\n" |
| + " Map<String, String> target = new HashMap<>();\n" + " map.forEach((k, v) -> target.put(k, v.toString()));\n" |
| + " definition.setCorsHeaders(target);\n" + "}\n" + "return definition;"); |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, true); |
| writeComponentSpringFactorySource(packageName, name); |
| } |
| |
| private void executeComponent(Map<File, Supplier<String>> jsonFiles) throws 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 = generateComponentModel(componentName, 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); |
| ComponentModel model = compModels.get(0); // They should be |
| // equivalent |
| List<String> aliases = compModels.stream().map(ComponentModel::getScheme).sorted().collect(Collectors.toList()); |
| |
| // resolvePropertyPlaceholders is an option which only make |
| // sense to use if the component has other options |
| // boolean hasOptions = |
| // model.getComponentOptions().stream().anyMatch(o -> |
| // !o.getName().equals("resolvePropertyPlaceholders")); |
| |
| // use springboot as sub package name so the code is not in |
| // normal |
| // package so the Spring Boot JARs can be optional at runtime |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| String overrideComponentName = null; |
| if (aliases.size() > 1) { |
| // determine component name when there are multiple ones |
| overrideComponentName = model.getArtifactId().replace("camel-", ""); |
| } |
| |
| createComponentConfigurationSource(pkg, model, overrideComponentName); |
| createComponentAutoConfigurationSource(pkg, model, aliases, overrideComponentName); |
| createComponentSpringFactorySource(pkg, model); |
| } |
| } |
| } |
| |
| private void executeDataFormat(Map<File, Supplier<String>> jsonFiles) throws MojoFailureException { |
| // find the data format names |
| List<String> dataFormatNames = findDataFormatNames(); |
| |
| // create auto configuration for the data formats |
| if (!dataFormatNames.isEmpty()) { |
| getLog().debug("Found " + dataFormatNames.size() + " dataformats"); |
| |
| List<DataFormatModel> allModels = new LinkedList<>(); |
| for (String dataFormatName : dataFormatNames) { |
| String json = loadDataFormatJson(jsonFiles, dataFormatName); |
| if (json != null) { |
| DataFormatModel model = generateDataFormatModel(dataFormatName, json); |
| allModels.add(model); |
| } |
| } |
| |
| // Group the models by implementing classes |
| Map<String, List<DataFormatModel>> grModels = allModels.stream().collect(Collectors.groupingBy(DataFormatModel::getJavaType)); |
| for (String dataFormatClass : grModels.keySet()) { |
| List<DataFormatModel> dfModels = grModels.get(dataFormatClass); |
| DataFormatModel model = dfModels.get(0); // They should be |
| // equivalent |
| List<String> aliases = dfModels.stream().map(DataFormatModel::getName).sorted().collect(Collectors.toList()); |
| |
| // use springboot as sub package name so the code is not in |
| // normal |
| // package so the Spring Boot JARs can be optional at runtime |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| String overrideDataFormatName = null; |
| if (aliases.size() > 1) { |
| // determine component name when there are multiple ones |
| overrideDataFormatName = model.getArtifactId().replace("camel-", ""); |
| } |
| |
| createDataFormatConfigurationSource(pkg, model, overrideDataFormatName); |
| createDataFormatAutoConfigurationSource(pkg, model, aliases, overrideDataFormatName); |
| createDataFormatSpringFactorySource(pkg, model); |
| } |
| } |
| } |
| |
| private void executeLanguage(Map<File, Supplier<String>> jsonFiles) throws MojoFailureException { |
| // find the language names |
| List<String> languageNames = findLanguageNames(); |
| |
| // create auto configuration for the languages |
| if (!languageNames.isEmpty()) { |
| getLog().debug("Found " + languageNames.size() + " languages"); |
| |
| List<LanguageModel> allModels = new LinkedList<>(); |
| for (String languageName : languageNames) { |
| String json = loadLanguageJson(jsonFiles, languageName); |
| if (json != null) { |
| LanguageModel model = generateLanguageModel(languageName, json); |
| allModels.add(model); |
| } |
| } |
| |
| // Group the models by implementing classes |
| Map<String, List<LanguageModel>> grModels = allModels.stream().collect(Collectors.groupingBy(LanguageModel::getJavaType)); |
| for (String languageClass : grModels.keySet()) { |
| List<LanguageModel> dfModels = grModels.get(languageClass); |
| LanguageModel model = dfModels.get(0); // They should be |
| // equivalent |
| List<String> aliases = dfModels.stream().map(LanguageModel::getName).sorted().collect(Collectors.toList()); |
| |
| // use springboot as sub package name so the code is not in |
| // normal |
| // package so the Spring Boot JARs can be optional at runtime |
| int pos = model.getJavaType().lastIndexOf("."); |
| String pkg = model.getJavaType().substring(0, pos) + ".springboot"; |
| |
| String overrideLanguageName = null; |
| if (aliases.size() > 1) { |
| // determine language name when there are multiple ones |
| overrideLanguageName = model.getArtifactId().replace("camel-", ""); |
| } |
| |
| createLanguageConfigurationSource(pkg, model, overrideLanguageName); |
| createLanguageAutoConfigurationSource(pkg, model, aliases, overrideLanguageName); |
| createLanguageSpringFactorySource(pkg, model); |
| } |
| } |
| } |
| |
| private void createComponentConfigurationSource(String packageName, ComponentModel model, String overrideComponentName) throws MojoFailureException { |
| int pos = model.getJavaType().lastIndexOf("."); |
| String name = model.getJavaType().substring(pos + 1); |
| name = name.replace("Component", "ComponentConfiguration"); |
| |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| javaClass.setPackage(packageName); |
| javaClass.setName(name); |
| javaClass.extendSuperType("ComponentConfigurationPropertiesCommon"); |
| javaClass.addImport("org.apache.camel.spring.boot.ComponentConfigurationPropertiesCommon"); |
| |
| // add bogus field for enabled so spring boot tooling can get the |
| // javadoc as description in its metadata |
| Property bogus = javaClass.addProperty("java.lang.Boolean", "enabled"); |
| String scheme = overrideComponentName != null ? overrideComponentName : model.getScheme(); |
| bogus.getField().getJavaDoc().setText("Whether to enable auto configuration of the " + scheme + " component. This is enabled by default."); |
| bogus.removeAccessor(); |
| bogus.removeMutator(); |
| |
| String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; |
| if (!Strings.isBlank(model.getDescription())) { |
| doc = model.getDescription() + "\n\n" + doc; |
| } |
| javaClass.getJavaDoc().setText(doc); |
| |
| String prefix = "camel.component." + (overrideComponentName != null ? overrideComponentName : model.getScheme()); |
| // make sure prefix is in lower case |
| prefix = prefix.toLowerCase(Locale.US); |
| javaClass.addAnnotation(Generated.class.getName()).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties").setStringValue("prefix", prefix); |
| |
| Set<JavaClass> nestedTypes = new LinkedHashSet<>(); |
| for (ComponentOptionModel option : model.getComponentOptions()) { |
| |
| if (skipComponentOption(model, option)) { |
| // some component options should be skipped |
| continue; |
| } |
| |
| String type = option.getJavaType(); |
| |
| // generate inner class for non-primitive options |
| type = getSimpleJavaType(type); |
| JavaClass javaClassSource = readJavaType(type); |
| boolean isNestedProperty = isNestedProperty(nestedTypes, javaClassSource) || "org.apache.camel.converter.jaxp.XmlConverter".equals(type); |
| if (isNestedProperty) { |
| type = packageName + "." + name + "$" + option.getShortJavaType() + INNER_TYPE_SUFFIX; |
| } |
| |
| // spring-boot auto configuration does not support complex types |
| // (unless they are enum, nested) |
| // and if so then we should use a String type so spring-boot and its |
| // tooling support that |
| // as Camel will be able to convert the string value into a lookup |
| // of the bean in the registry anyway |
| // and therefore there is no problem, eg |
| // camel.component.jdbc.data-source = myDataSource |
| // where the type would have been javax.sql.DataSource |
| boolean complex = isComplexType(option) && !isNestedProperty && Strings.isBlank(option.getEnums()); |
| if (complex) { |
| // force to use a string type |
| type = "java.lang.String"; |
| } |
| |
| // need to generate and load this class so the compiler can compile |
| if (type.equals(packageName + "." + name + "$" + option.getShortJavaType() + INNER_TYPE_SUFFIX)) { |
| generateDummyClass(type); |
| } |
| |
| Property prop = javaClass.addProperty(type, option.getName()); |
| if (ADD_NESTED_CONFIGURATION_PROPERTY) { |
| if (!type.endsWith(INNER_TYPE_SUFFIX) && type.indexOf('[') == -1 && INCLUDE_INNER_PATTERN.matcher(type).matches() && Strings.isBlank(option.getEnums()) |
| && (javaClassSource == null || (javaClassSource.isClass() && !javaClassSource.isAbstract()))) { |
| // add nested configuration annotation for complex |
| // properties |
| prop.getField().addAnnotation(NestedConfigurationProperty.class); |
| } |
| } |
| if ("true".equals(option.getDeprecated())) { |
| prop.getField().addAnnotation(Deprecated.class); |
| prop.getAccessor().addAnnotation(Deprecated.class); |
| prop.getMutator().addAnnotation(Deprecated.class); |
| // DeprecatedConfigurationProperty must be on getter when |
| // deprecated |
| prop.getAccessor().addAnnotation(DeprecatedConfigurationProperty.class); |
| } |
| if (!Strings.isBlank(option.getDescription())) { |
| String desc = option.getDescription(); |
| if (complex) { |
| if (!desc.endsWith(".")) { |
| desc = desc + "."; |
| } |
| desc = desc + " The option is a " + option.getJavaType() + " type."; |
| } |
| prop.getField().getJavaDoc().setFullText(desc); |
| } |
| if (!Strings.isBlank(option.getDefaultValue())) { |
| if ("java.lang.String".equals(option.getJavaType())) { |
| prop.getField().setStringInitializer(option.getDefaultValue()); |
| } else if ("long".equals(option.getJavaType()) || "java.lang.Long".equals(option.getJavaType())) { |
| // the value should be a Long number |
| String value = option.getDefaultValue() + "L"; |
| prop.getField().setLiteralInitializer(value); |
| } else if ("integer".equals(option.getType()) || "boolean".equals(option.getType())) { |
| prop.getField().setLiteralInitializer(option.getDefaultValue()); |
| } else if (!Strings.isBlank(option.getEnums())) { |
| String enumShortName = type.substring(type.lastIndexOf(".") + 1); |
| prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue()); |
| javaClass.addImport(model.getJavaType()); |
| } |
| } |
| } |
| |
| createComponentConfigurationSourceInnerClass(javaClass, nestedTypes, model); |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, true); |
| } |
| |
| private void createComponentConfigurationSourceInnerClass(JavaClass javaClass, Set<JavaClass> nestedTypes, ComponentModel model) throws MojoFailureException { |
| // add inner classes for nested AutoConfiguration options |
| for (JavaClass nestedType : nestedTypes) { |
| |
| final JavaClass innerClass = javaClass.addNestedType().setPublic().setStatic(true).setName(nestedType.getName() + INNER_TYPE_SUFFIX); |
| // add source class name as a static field |
| innerClass.addField().setPublic().setStatic(true).setFinal(true).setType(Class.class).setName("CAMEL_NESTED_CLASS") |
| .setLiteralInitializer(nestedType.getCanonicalName() + ".class"); |
| |
| // parse option type |
| for (Property sourceProp : getProperties(nestedType)) { |
| |
| GenericType propType = sourceProp.getType(); |
| |
| // skip these types |
| boolean ignore = sourceProp.hasAnnotation(XmlTransient.class); |
| if (ignore || propType.getRawClass().getName().equals("org.apache.camel.CamelContext")) { |
| continue; |
| } |
| |
| String wt = PRIMITIVEMAP.get(propType.toString()); |
| GenericType ptype = wt != null ? loadType(wt) : propType; |
| final Property prop = innerClass.addProperty(ptype, sourceProp.getName()); |
| if (sourceProp.getField() != null) { |
| prop.getField().getJavaDoc().setText(sourceProp.getField().getJavaDoc().getText()); |
| prop.getField().setLiteralInitializer(sourceProp.getField().getLiteralInitializer()); |
| } |
| |
| ComponentOptionModel com = model.getComponentOptions().stream().filter(o -> o.getName().equals(sourceProp.getName())).findFirst().orElse(null); |
| EndpointOptionModel eom = Stream.concat(model.getEndpointOptions().stream(), model.getEndpointPathOptions().stream()) |
| .filter(o -> o.getName().equals(sourceProp.getName())).findFirst().orElse(null); |
| String deprecationNote = null; |
| if (eom != null) { |
| prop.getField().getJavaDoc().setText(eom.getDescription()); |
| prop.getField().setLiteralInitializer(asLiteralDefault(sourceProp.getType(), eom.getDefaultValue())); |
| deprecationNote = eom.getDeprecationNote(); |
| } else if (com != null) { |
| prop.getField().getJavaDoc().setText(com.getDescription()); |
| prop.getField().setLiteralInitializer(asLiteralDefault(sourceProp.getType(), com.getDefaultValue())); |
| deprecationNote = com.getDeprecationNote(); |
| } |
| |
| boolean anEnum; |
| Class<?> optionClass; |
| if (!propType.getRawClass().isArray()) { |
| optionClass = propType.getRawClass(); |
| anEnum = optionClass.isEnum(); |
| } else { |
| optionClass = null; |
| anEnum = false; |
| } |
| |
| // add nested configuration annotation for complex properties |
| if (ADD_NESTED_CONFIGURATION_PROPERTY) { |
| if (INCLUDE_INNER_PATTERN.matcher(propType.toString()).matches() && !propType.getRawClass().isArray() && !anEnum && optionClass != null |
| && !optionClass.isInterface() && !optionClass.isAnnotation() && !Modifier.isAbstract(optionClass.getModifiers())) { |
| prop.getField().addAnnotation(NestedConfigurationProperty.class); |
| } |
| } |
| if (sourceProp.hasAnnotation(Deprecated.class)) { |
| if (deprecationNote != null && !deprecationNote.isEmpty()) { |
| String jd = prop.getField().getJavaDoc().getText(); |
| if (jd != null) { |
| jd += "\n\n"; |
| } else { |
| jd = ""; |
| } |
| jd = "@deprecated " + deprecationNote; |
| prop.getField().getJavaDoc().setText(jd); |
| } |
| prop.getField().addAnnotation(Deprecated.class); |
| prop.getAccessor().addAnnotation(Deprecated.class); |
| prop.getMutator().addAnnotation(Deprecated.class); |
| // DeprecatedConfigurationProperty must be on getter when |
| // deprecated |
| prop.getAccessor().addAnnotation(DeprecatedConfigurationProperty.class); |
| } |
| |
| // find description for the nested type on its field/setter |
| // javadoc or via Camel annotations |
| String description = null; |
| final Method mutator = sourceProp.getMutator(); |
| if (mutator.hasJavaDoc()) { |
| description = mutator.getJavaDoc().getFullText(); |
| } else if (sourceProp.hasField()) { |
| description = sourceProp.getField().getJavaDoc().getFullText(); |
| } |
| if (Strings.isBlank(description) && sourceProp.hasAnnotation(UriPath.class)) { |
| description = sourceProp.getAnnotation(UriPath.class).getStringValue("description"); |
| } |
| if (Strings.isBlank(description) && sourceProp.hasAnnotation(UriParam.class)) { |
| description = sourceProp.getAnnotation(UriParam.class).getStringValue("description"); |
| } |
| if (Strings.isBlank(description) && sourceProp.hasAnnotation(Metadata.class)) { |
| description = sourceProp.getAnnotation(Metadata.class).getStringValue("description"); |
| } |
| if (!Strings.isBlank(description)) { |
| prop.getField().getJavaDoc().setFullText(description); |
| } |
| |
| // try to see if the source is actually reusing a shared Camel |
| // configuration that that has @UriParam options |
| // if so we can fetch the default value from the json file as it |
| // holds the correct value vs the annotation |
| // as the annotation can refer to a constant field which we wont |
| // have accessible at this point |
| if (sourceProp.hasAnnotation(UriParam.class) || sourceProp.hasAnnotation(UriPath.class)) { |
| String defaultValue = null; |
| String javaType = null; |
| String type = null; |
| |
| String fileName = model.getJavaType(); |
| fileName = fileName.substring(0, fileName.lastIndexOf(".")); |
| fileName = fileName.replace('.', '/'); |
| File jsonFile = new File(classesDir, fileName + "/" + model.getScheme() + ".json"); |
| if (jsonFile.isFile() && jsonFile.exists()) { |
| try { |
| String json = FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8); |
| List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); |
| |
| // grab name from annotation |
| String optionName; |
| if (sourceProp.hasAnnotation(UriParam.class)) { |
| optionName = sourceProp.getAnnotation(UriParam.class).getStringValue("name"); |
| } else { |
| optionName = sourceProp.getAnnotation(UriPath.class).getStringValue("name"); |
| } |
| if (optionName == null) { |
| optionName = sourceProp.hasField() ? sourceProp.getField().getName() : null; |
| } |
| |
| if (optionName != null) { |
| javaType = getPropertyJavaType(rows, optionName); |
| type = getPropertyType(rows, optionName); |
| defaultValue = getPropertyDefaultValue(rows, optionName); |
| // favour description from the model |
| description = getPropertyDescriptionValue(rows, optionName); |
| if (description != null) { |
| prop.getField().getJavaDoc().setFullText(description); |
| } |
| } |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| |
| if (!Strings.isBlank(defaultValue)) { |
| |
| // roaster can create the wrong type for some options so |
| // use the correct type we found in the json schema |
| String wrapperType = getSimpleJavaType(javaType); |
| if (wrapperType.startsWith("java.lang.")) { |
| // skip java.lang. as prefix for wrapper type |
| wrapperType = wrapperType.substring(10); |
| prop.setType(loadType(wrapperType)); |
| } |
| |
| if ("long".equals(javaType) || "java.lang.Long".equals(javaType)) { |
| // the value should be a Long number |
| String value = defaultValue + "L"; |
| prop.getField().setLiteralInitializer(value); |
| } else if ("integer".equals(type) || "boolean".equals(type)) { |
| prop.getField().setLiteralInitializer(defaultValue); |
| } else if ("string".equals(type)) { |
| prop.getField().setStringInitializer(defaultValue); |
| } else if (anEnum) { |
| String enumShortName = optionClass.getSimpleName(); |
| prop.getField().setLiteralInitializer(enumShortName + "." + defaultValue); |
| javaClass.addImport(model.getJavaType()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean isComplexType(ComponentOptionModel option) { |
| // we can configure map/list/set types from spring-boot so do not regard them as complex |
| if (option.getJavaType().startsWith("java.util.Map") || option.getJavaType().startsWith("java.util.List") || option.getJavaType().startsWith("java.util.Set")) { |
| return false; |
| } |
| // all the object types are complex |
| return "object".equals(option.getType()); |
| } |
| |
| private boolean isComplexType(DataFormatOptionModel option) { |
| // we can configure map/list/set types from spring-boot so do not regard them as complex |
| if (option.getJavaType().startsWith("java.util.Map") || option.getJavaType().startsWith("java.util.List") || option.getJavaType().startsWith("java.util.Set")) { |
| return false; |
| } |
| // all the object types are complex |
| return "object".equals(option.getType()); |
| } |
| |
| private boolean isComplexType(LanguageOptionModel option) { |
| // we can configure map/list/set types from spring-boot so do not regard them as complex |
| if (option.getJavaType().startsWith("java.util.Map") || option.getJavaType().startsWith("java.util.List") || option.getJavaType().startsWith("java.util.Set")) { |
| return false; |
| } |
| // all the object types are complex |
| return "object".equals(option.getType()); |
| } |
| |
| // get properties for nested type and super types, only properties with |
| // setters are supported!!! |
| private List<Property> getProperties(JavaClass nestedType) { |
| final List<Property> properties = new ArrayList<>(); |
| final Set<String> names = new HashSet<>(); |
| do { |
| for (Property propertySource : nestedType.getProperties()) { |
| // NOTE: fields with no setters are skipped |
| if (propertySource.isMutable() && !names.contains(propertySource.getName())) { |
| properties.add(propertySource); |
| names.add(propertySource.getName()); |
| } |
| } |
| nestedType = readJavaType(nestedType.getSuperType()); |
| } while (nestedType != null); |
| return properties; |
| } |
| |
| private String asLiteralDefault(GenericType type, String defaultValue) { |
| if (defaultValue != null && !defaultValue.isEmpty()) { |
| if (type.getRawClass() == String.class) { |
| return Annotation.quote(defaultValue); |
| } else if (type.getRawClass().isEnum()) { |
| return type.getRawClass().getSimpleName() + "." + defaultValue; |
| } else if (type.getRawClass() == boolean.class || type.getRawClass() == Boolean.class) { |
| return defaultValue; |
| } else if (type.getRawClass() == int.class || type.getRawClass() == Integer.class) { |
| return defaultValue; |
| } else if (type.getRawClass() == long.class || type.getRawClass() == Long.class) { |
| return defaultValue + "L"; |
| } else if (type.getRawClass() == float.class || type.getRawClass() == Float.class) { |
| return defaultValue + "f"; |
| } else if (type.getRawClass() == double.class || type.getRawClass() == Double.class) { |
| return defaultValue; |
| } else if (type.getRawClass() == Class.class) { |
| return defaultValue + ".class"; |
| } else { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| private GenericType loadType(String type) throws MojoFailureException { |
| try { |
| return GenericType.parse(type, getProjectClassLoader()); |
| } catch (ClassNotFoundException e) { |
| throw new MojoFailureException("Unable to load type", e); |
| } |
| } |
| |
| // try loading class, looking for inner classes if needed |
| private Class<?> loadClass(String loadClassName) throws MojoFailureException { |
| Class<?> optionClass; |
| while (true) { |
| try { |
| optionClass = getProjectClassLoader().loadClass(loadClassName); |
| break; |
| } catch (ClassNotFoundException e) { |
| int dotIndex = loadClassName.lastIndexOf('.'); |
| if (dotIndex == -1) { |
| throw new MojoFailureException(e.getMessage(), e); |
| } else { |
| loadClassName = loadClassName.substring(0, dotIndex) + "$" + loadClassName.substring(dotIndex + 1); |
| } |
| } |
| } |
| return optionClass; |
| } |
| |
| protected DynamicClassLoader getProjectClassLoader() { |
| if (projectClassLoader == null) { |
| final List<String> classpathElements; |
| try { |
| classpathElements = project.getTestClasspathElements(); |
| } catch (org.apache.maven.artifact.DependencyResolutionRequiredException e) { |
| throw new RuntimeException(e.getMessage(), e); |
| } |
| projectClassLoader = DynamicClassLoader.createDynamicClassLoader(classpathElements); |
| } |
| return projectClassLoader; |
| } |
| |
| private String getSimpleJavaType(String type) { |
| // use wrapper types for primitive types so a null mean that the option |
| // has not been configured |
| String wrapper = PRIMITIVEMAP.get(type); |
| if (wrapper != null) { |
| type = wrapper; |
| } |
| return type; |
| } |
| |
| // it's a nested property if the source exists and it's not an abstract |
| // class in this project, e.g. endpoint configuration |
| private boolean isNestedProperty(Set<JavaClass> nestedTypes, JavaClass type) { |
| if (type != null) { |
| // nested type MUST have some properties of it's own, besides those |
| // from super class |
| if (type.isClass() && !type.isEnum() && !type.isAbstract() && !type.getProperties().isEmpty()) { |
| nestedTypes.add(type); |
| } else { |
| type = null; |
| } |
| } |
| return type != null; |
| } |
| |
| // read java type from project, returns null if not found |
| private JavaClass readJavaType(String type) { |
| if (!type.startsWith("java.lang.") && (!type.contains("<") || !type.contains(">"))) { |
| final String fileName = type.replaceAll("[\\[\\]]", "").replaceAll("\\.", "\\/") + ".java"; |
| Path sourcePath = project.getCompileSourceRoots().stream().map(Paths::get).map(p -> p.resolve(fileName)).filter(Files::isRegularFile).findFirst().orElse(null); |
| if (sourcePath == null) { |
| return null; |
| } |
| String sourceCode; |
| try (InputStream is = Files.newInputStream(sourcePath)) { |
| sourceCode = loadText(is); |
| } catch (IOException e) { |
| throw new RuntimeException("Unable to load source code", e); |
| } |
| try { |
| Class<?> clazz = getProjectClassLoader().loadClass(type); |
| JavaClass nestedType = new JavaClass(getProjectClassLoader()).setPackage(clazz.getPackage().getName()).setName(clazz.getSimpleName()).setEnum(clazz.isEnum()) |
| .setClass(!clazz.isInterface()).setAbstract((clazz.getModifiers() & Modifier.ABSTRACT) != 0).setStatic((clazz.getModifiers() & Modifier.STATIC) != 0) |
| .extendSuperType(clazz.getGenericSuperclass() != null ? new GenericType(clazz.getGenericSuperclass()).toString() : null); |
| |
| List<java.lang.reflect.Method> publicMethods = Stream.of(clazz.getDeclaredMethods()).filter(m -> Modifier.isPublic(m.getModifiers())).collect(Collectors.toList()); |
| List<java.lang.reflect.Method> allSetters = publicMethods.stream().filter(m -> m.getReturnType() == void.class || m.getReturnType() == clazz) |
| .filter(m -> m.getParameterCount() == 1).filter(m -> m.getName().matches("set[A-Z][a-zA-Z0-9]*")).collect(Collectors.toList()); |
| List<java.lang.reflect.Method> allGetters = publicMethods.stream().filter(m -> m.getReturnType() != void.class).filter(m -> m.getParameterCount() == 0) |
| .filter(m -> m.getName().matches("(get|is)[A-Z][a-zA-Z0-9]*")).collect(Collectors.toList()); |
| allSetters.stream().sorted(Comparator.comparing(m -> getSetterPosition(sourceCode, m))).map(m -> Strings.uncapitalize(m.getName().substring(3))).forEach(fn -> { |
| Class<?> ft; |
| Type wft; |
| boolean isBoolean; |
| java.lang.reflect.Field field = Stream.of(clazz.getDeclaredFields()).filter(f -> f.getName().equals(fn)).findAny().orElse(null); |
| List<java.lang.reflect.Method> setters = allSetters.stream().filter(m -> m.getName().equals("set" + Strings.capitalize(fn))).collect(Collectors.toList()); |
| List<java.lang.reflect.Method> getters = allGetters.stream() |
| .filter(m -> m.getName().equals("get" + Strings.capitalize(fn)) || m.getName().equals("is" + Strings.capitalize(fn))).collect(Collectors.toList()); |
| java.lang.reflect.Method mutator; |
| java.lang.reflect.Method accessor; |
| if (setters.size() == 1) { |
| mutator = setters.get(0); |
| ft = mutator.getParameterTypes()[0]; |
| wft = PRIMITIVE_CLASSES.getOrDefault(ft, ft); |
| isBoolean = ft == boolean.class || ft == Boolean.class; |
| accessor = allGetters.stream() |
| .filter(m -> m.getName().equals("get" + Strings.capitalize(fn)) || isBoolean && m.getName().equals("is" + Strings.capitalize(fn))) |
| .filter(m -> PRIMITIVE_CLASSES.getOrDefault(m.getReturnType(), m.getReturnType()) == wft).findAny().orElse(null); |
| } else if (field != null) { |
| ft = field.getType(); |
| wft = PRIMITIVE_CLASSES.getOrDefault(ft, ft); |
| isBoolean = ft == boolean.class || ft == Boolean.class; |
| mutator = allSetters.stream().filter(m -> m.getName().equals("set" + Strings.capitalize(fn))) |
| .filter(m -> PRIMITIVE_CLASSES.getOrDefault(m.getParameterTypes()[0], m.getParameterTypes()[0]) == wft).findAny().orElse(null); |
| accessor = allGetters.stream() |
| .filter(m -> m.getName().equals("get" + Strings.capitalize(fn)) || isBoolean && m.getName().equals("is" + Strings.capitalize(fn))) |
| .filter(m -> PRIMITIVE_CLASSES.getOrDefault(m.getReturnType(), m.getReturnType()) == wft).findAny().orElse(null); |
| } else { |
| if (getters.size() == 1) { |
| ft = getters.get(0).getReturnType(); |
| } else { |
| throw new IllegalStateException("Unable to determine type for property " + fn); |
| } |
| wft = PRIMITIVE_CLASSES.getOrDefault(ft, ft); |
| mutator = setters.stream().filter(m -> PRIMITIVE_CLASSES.getOrDefault(m.getParameterTypes()[0], m.getParameterTypes()[0]) == wft).findAny().orElse(null); |
| accessor = getters.stream().filter(m -> PRIMITIVE_CLASSES.getOrDefault(m.getReturnType(), m.getReturnType()) == wft).findAny().orElse(null); |
| } |
| if (mutator == null) { |
| throw new IllegalStateException("Could not find mutator for property " + fn); |
| } |
| Property property = nestedType.addProperty(new GenericType(wft), fn); |
| property.getMutator().getJavaDoc().setText(getSetterJavaDoc(sourceCode, fn)); |
| for (java.lang.annotation.Annotation ann : mutator.getAnnotations()) { |
| addAnnotation(ac -> property.getMutator().addAnnotation(ac), ann); |
| } |
| if (accessor != null) { |
| for (java.lang.annotation.Annotation ann : accessor.getAnnotations()) { |
| addAnnotation(ac -> property.getAccessor().addAnnotation(ac), ann); |
| } |
| } else { |
| property.removeAccessor(); |
| } |
| if (field != null) { |
| for (java.lang.annotation.Annotation ann : field.getAnnotations()) { |
| addAnnotation(ac -> property.getField().addAnnotation(ac), ann); |
| } |
| } else { |
| property.removeField(); |
| } |
| }); |
| return nestedType; |
| } catch (ClassNotFoundException e) { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| private int getSetterPosition(String sourceCode, java.lang.reflect.Method m) { |
| int i0 = sourceCode.indexOf("void " + m.getName() + "("); |
| int i1 = sourceCode.indexOf(m.getDeclaringClass().getSimpleName() + " " + m.getName() + "("); |
| int l = sourceCode.length(); |
| return Math.min(i0 > 0 ? i0 : l, i1 > 0 ? i1 : l); |
| } |
| |
| private String getSetterJavaDoc(String sourceCode, String name) { |
| int idx = sourceCode.indexOf("public void set" + Strings.capitalize(name) + "("); |
| if (idx > 0) { |
| sourceCode = sourceCode.substring(0, idx); |
| idx = sourceCode.lastIndexOf("/**"); |
| if (idx > 0) { |
| sourceCode = sourceCode.substring(idx + 3); |
| idx = sourceCode.indexOf("*/"); |
| if (idx > 0) { |
| sourceCode = sourceCode.substring(0, idx); |
| List<String> lines = Stream.of(sourceCode.split("\n")).map(String::trim).map(s -> s.startsWith("*") ? s.substring(1) : s).map(String::trim) |
| .filter(s -> !s.isEmpty()).collect(Collectors.toList()); |
| int lastLine = 0; |
| while (lastLine < lines.size()) { |
| if (lines.get(lastLine).startsWith("@")) { |
| break; |
| } |
| lastLine++; |
| } |
| sourceCode = lines.subList(0, lastLine).stream().map(s -> s.replaceAll(" ", " ")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.joining(" ")); |
| return sourceCode; |
| } |
| |
| } |
| } |
| return null; |
| } |
| |
| private void addAnnotation(Function<Class<? extends java.lang.annotation.Annotation>, Annotation> creator, java.lang.annotation.Annotation ann) { |
| Class<? extends java.lang.annotation.Annotation> ac = ann.annotationType(); |
| Annotation a = creator.apply(ac); |
| for (java.lang.reflect.Method m : ac.getMethods()) { |
| if ("equals".equals(m.getName()) || "toString".equals(m.getName()) || "hashCode".equals(m.getName())) { |
| continue; |
| } |
| String n = m.getName(); |
| try { |
| Object v = m.invoke(ann); |
| if (v != null) { |
| a.setLiteralValue(n, v.toString()); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException("Unable to retrieve annotation value " + n + " on " + ac.getName()); |
| } |
| } |
| } |
| |
| // CHECKSTYLE:OFF |
| private static boolean skipComponentOption(ComponentModel model, ComponentOptionModel option) { |
| if ("netty-http".equals(model.getScheme())) { |
| String name = option.getName(); |
| if (name.equals("textline") || name.equals("delimiter") || name.equals("autoAppendDelimiter") || name.equals("decoderMaxLineLength") || name.equals("encoding") |
| || name.equals("allowDefaultCodec") || name.equals("udpConnectionlessSending") || name.equals("networkInterface") || name.equals("clientMode") |
| || name.equals("reconnect") || name.equals("reconnectInterval") || name.equals("useByteBuf") || name.equals("udpByteArrayCodec") || name.equals("broadcast")) { |
| return true; |
| } |
| } |
| return false; |
| } |
| // CHECKSTYLE:ON |
| |
| private void createDataFormatConfigurationSource(String packageName, DataFormatModel model, String overrideDataFormatName) throws MojoFailureException { |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String name = model.getJavaType().substring(pos + 1); |
| name = name.replace("DataFormat", "DataFormatConfiguration"); |
| javaClass.setPackage(packageName).setName(name); |
| javaClass.extendSuperType("DataFormatConfigurationPropertiesCommon"); |
| javaClass.addImport("org.apache.camel.spring.boot.DataFormatConfigurationPropertiesCommon"); |
| |
| // add bogus field for enabled so spring boot tooling can get the |
| // javadoc as description in its metadata |
| Property bogus = javaClass.addProperty("java.lang.Boolean", "enabled"); |
| bogus.getField().getJavaDoc().setText("Whether to enable auto configuration of the " + model.getName() + " data format. This is enabled by default."); |
| bogus.removeAccessor(); |
| bogus.removeMutator(); |
| |
| String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; |
| if (!Strings.isBlank(model.getDescription())) { |
| doc = model.getDescription() + "\n\n" + doc; |
| } |
| javaClass.getJavaDoc().setFullText(doc); |
| |
| String prefix = "camel.dataformat." + (overrideDataFormatName != null ? overrideDataFormatName : model.getName()); |
| // make sure prefix is in lower case |
| prefix = prefix.toLowerCase(Locale.US); |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties").setStringValue("prefix", prefix); |
| |
| for (DataFormatOptionModel option : model.getDataFormatOptions()) { |
| // skip option with name id in data format as we do not need that |
| if ("id".equals(option.getName())) { |
| continue; |
| } |
| String type = option.getJavaType(); |
| type = getSimpleJavaType(type); |
| |
| // special for bindy |
| if ("org.apache.camel.model.dataformat.BindyType".equals(option.getJavaType())) { |
| // force to use a string type |
| type = "java.lang.String"; |
| } else if (option.getJavaType().contains("org.apache.camel.model.dataformat")) { |
| // skip options that are from the model as they are not possible to configure anyway |
| continue; |
| } |
| |
| // spring-boot auto configuration does not support complex types |
| // (unless they are enum, nested) |
| // and if so then we should use a String type so spring-boot and its |
| // tooling support that |
| // as Camel will be able to convert the string value into a lookup |
| // of the bean in the registry anyway |
| // and therefore there is no problem, eg |
| // camel.component.jdbc.data-source = myDataSource |
| // where the type would have been javax.sql.DataSource |
| boolean complex = isComplexType(option) && Strings.isBlank(option.getEnumValues()); |
| if (complex) { |
| // force to use a string type |
| type = "java.lang.String"; |
| } |
| |
| Property prop = javaClass.addProperty(type, option.getName()); |
| if ("true".equals(option.getDeprecated())) { |
| prop.getField().addAnnotation(Deprecated.class); |
| prop.getAccessor().addAnnotation(Deprecated.class); |
| prop.getMutator().addAnnotation(Deprecated.class); |
| // DeprecatedConfigurationProperty must be on getter when |
| // deprecated |
| prop.getAccessor().addAnnotation(DeprecatedConfigurationProperty.class); |
| } |
| if (!Strings.isBlank(option.getDescription())) { |
| String desc = option.getDescription(); |
| if (complex) { |
| if (!desc.endsWith(".")) { |
| desc = desc + "."; |
| } |
| desc = desc + " The option is a " + option.getJavaType() + " type."; |
| } |
| prop.getField().getJavaDoc().setFullText(desc); |
| } |
| if (!Strings.isBlank(option.getDefaultValue())) { |
| if ("java.lang.String".equals(option.getJavaType())) { |
| prop.getField().setStringInitializer(option.getDefaultValue()); |
| } else if ("long".equals(option.getJavaType()) || "java.lang.Long".equals(option.getJavaType())) { |
| // the value should be a Long number |
| String value = option.getDefaultValue() + "L"; |
| prop.getField().setLiteralInitializer(value); |
| } else if ("integer".equals(option.getType()) || "boolean".equals(option.getType())) { |
| prop.getField().setLiteralInitializer(option.getDefaultValue()); |
| } else if (!Strings.isBlank(option.getEnumValues())) { |
| String enumShortName = type.substring(type.lastIndexOf(".") + 1); |
| prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue()); |
| javaClass.addImport(model.getJavaType()); |
| } |
| } |
| } |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, true); |
| } |
| |
| private void createLanguageConfigurationSource(String packageName, LanguageModel model, String overrideLanguageName) throws MojoFailureException { |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| |
| int pos = model.getJavaType().lastIndexOf("."); |
| String name = model.getJavaType().substring(pos + 1); |
| name = name.replace("Language", "LanguageConfiguration"); |
| javaClass.setPackage(packageName).setName(name); |
| javaClass.extendSuperType("LanguageConfigurationPropertiesCommon"); |
| javaClass.addImport("org.apache.camel.spring.boot.LanguageConfigurationPropertiesCommon"); |
| |
| // add bogus field for enabled so spring boot tooling can get the |
| // javadoc as description in its metadata |
| Property bogus = javaClass.addProperty("java.lang.Boolean", "enabled"); |
| bogus.getField().getJavaDoc().setText("Whether to enable auto configuration of the " + model.getName() + " language. This is enabled by default."); |
| bogus.removeAccessor(); |
| bogus.removeMutator(); |
| |
| String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; |
| if (!Strings.isBlank(model.getDescription())) { |
| doc = model.getDescription() + "\n\n" + doc; |
| } |
| javaClass.getJavaDoc().setFullText(doc); |
| |
| String prefix = "camel.language." + (overrideLanguageName != null ? overrideLanguageName : model.getName()); |
| // make sure prefix is in lower case |
| prefix = prefix.toLowerCase(Locale.US); |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties").setStringValue("prefix", prefix); |
| |
| for (LanguageOptionModel option : model.getLanguageOptions()) { |
| // skip option with name id, or expression in language as we do not |
| // need that and skip resultType as they are not global options |
| if ("id".equals(option.getName()) || "expression".equals(option.getName()) || "resultType".equals(option.getName())) { |
| continue; |
| } |
| // CHECKSTYLE:OFF |
| if ("bean".equals(model.getName())) { |
| // and skip following as they are not global options |
| if ("bean".equals(option.getName()) || "ref".equals(option.getName()) || "method".equals(option.getName()) || "beanType".equals(option.getName())) { |
| continue; |
| } |
| } else if ("tokenize".equals(model.getName())) { |
| // and skip following as they are not global options |
| if ("token".equals(option.getName()) || "endToken".equals(option.getName()) || "inheritNamespaceTagName".equals(option.getName()) |
| || "headerName".equals(option.getName()) || "regex".equals(option.getName()) || "xml".equals(option.getName()) || "includeTokens".equals(option.getName()) |
| || "group".equals(option.getName()) || "skipFirst".equals(option.getName())) { |
| continue; |
| } |
| } else if ("xtokenize".equals(model.getName())) { |
| // and skip following as they are not global options |
| if ("headerName".equals(option.getName()) || "group".equals(option.getName())) { |
| continue; |
| } |
| } else if ("xpath".equals(model.getName())) { |
| // and skip following as they are not global options |
| if ("headerName".equals(option.getName())) { |
| continue; |
| } |
| } else if ("xquery".equals(model.getName())) { |
| // and skip following as they are not global options |
| if ("headerName".equals(option.getName())) { |
| continue; |
| } |
| } |
| // CHECKSTYLE:ON |
| String type = option.getJavaType(); |
| type = getSimpleJavaType(type); |
| |
| // spring-boot auto configuration does not support complex types |
| // (unless they are enum, nested) |
| // and if so then we should use a String type so spring-boot and its |
| // tooling support that |
| // as Camel will be able to convert the string value into a lookup |
| // of the bean in the registry anyway |
| // and therefore there is no problem, eg |
| // camel.component.jdbc.data-source = myDataSource |
| // where the type would have been javax.sql.DataSource |
| boolean complex = isComplexType(option) && Strings.isBlank(option.getEnumValues()); |
| if (complex) { |
| // force to use a string type |
| type = "java.lang.String"; |
| } |
| |
| Property prop = javaClass.addProperty(type, option.getName()); |
| if ("true".equals(option.getDeprecated())) { |
| prop.getField().addAnnotation(Deprecated.class); |
| prop.getAccessor().addAnnotation(Deprecated.class); |
| prop.getMutator().addAnnotation(Deprecated.class); |
| // DeprecatedConfigurationProperty must be on getter when |
| // deprecated |
| prop.getAccessor().addAnnotation(DeprecatedConfigurationProperty.class); |
| } |
| if (!Strings.isBlank(option.getDescription())) { |
| String desc = option.getDescription(); |
| if (complex) { |
| if (!desc.endsWith(".")) { |
| desc = desc + "."; |
| } |
| desc = desc + " The option is a " + option.getJavaType() + " type."; |
| } |
| prop.getField().getJavaDoc().setFullText(desc); |
| } |
| if (!Strings.isBlank(option.getDefaultValue())) { |
| if ("java.lang.String".equals(option.getJavaType())) { |
| prop.getField().setStringInitializer(option.getDefaultValue()); |
| } else if ("long".equals(option.getJavaType()) || "java.lang.Long".equals(option.getJavaType())) { |
| // the value should be a Long number |
| String value = option.getDefaultValue() + "L"; |
| prop.getField().setLiteralInitializer(value); |
| } else if ("integer".equals(option.getType()) || "boolean".equals(option.getType())) { |
| prop.getField().setLiteralInitializer(option.getDefaultValue()); |
| } else if (!Strings.isBlank(option.getEnumValues())) { |
| String enumShortName = type.substring(type.lastIndexOf(".") + 1); |
| prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue()); |
| javaClass.addImport(model.getJavaType()); |
| } |
| } |
| } |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, true); |
| } |
| |
| private void createComponentAutoConfigurationSource(String packageName, ComponentModel model, List<String> componentAliases, String overrideComponentName) |
| throws MojoFailureException { |
| |
| final String name = model.getJavaType().substring(model.getJavaType().lastIndexOf(".") + 1).replace("Component", "ComponentAutoConfiguration"); |
| final String configurationName = name.replace("ComponentAutoConfiguration", "ComponentConfiguration"); |
| final String componentName = (overrideComponentName != null ? overrideComponentName : model.getScheme()).toLowerCase(Locale.US); |
| |
| Class configClass = generateDummyClass(packageName + "." + configurationName); |
| |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| |
| javaClass.setPackage(packageName); |
| javaClass.setName(name); |
| javaClass.getJavaDoc().setFullText("Generated by camel-package-maven-plugin - do not edit this file!"); |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation(Configuration.class); |
| javaClass.addAnnotation(Conditional.class).setLiteralValue("{ConditionalOnCamelContextAndAutoConfigurationBeans.class,\n " + name + ".GroupConditions.class}"); |
| javaClass.addAnnotation(AutoConfigureAfter.class).setLiteralValue("CamelAutoConfiguration.class"); |
| javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue("{ComponentConfigurationProperties.class,\n " + configurationName + ".class}"); |
| |
| javaClass.addImport(HashMap.class); |
| javaClass.addImport(List.class); |
| javaClass.addImport(Map.class); |
| javaClass.addImport(ApplicationContext.class); |
| javaClass.addImport(ConditionalOnBean.class); |
| javaClass.addImport("org.slf4j.Logger"); |
| javaClass.addImport("org.slf4j.LoggerFactory"); |
| javaClass.addImport("org.apache.camel.CamelContext"); |
| javaClass.addImport("org.apache.camel.spi.ComponentCustomizer"); |
| javaClass.addImport("org.apache.camel.spring.boot.CamelAutoConfiguration"); |
| javaClass.addImport("org.apache.camel.spring.boot.ComponentConfigurationProperties"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.CamelPropertiesHelper"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.ConditionalOnCamelContextAndAutoConfigurationBeans"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.GroupCondition"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.HierarchicalPropertiesEvaluator"); |
| javaClass.addImport("org.apache.camel.support.IntrospectionSupport"); |
| javaClass.addImport("org.apache.camel.util.ObjectHelper"); |
| javaClass.addImport("org.apache.camel.spi.HasId"); |
| javaClass.addImport(model.getJavaType()); |
| |
| javaClass.addField().setPrivate().setStatic(true).setFinal(true).setName("LOGGER").setType(loadClass("org.slf4j.Logger")) |
| .setLiteralInitializer("LoggerFactory\n .getLogger(" + name + ".class)"); |
| javaClass.addField().setPrivate().setName("applicationContext").setType(ApplicationContext.class).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("camelContext").setType(loadClass("org.apache.camel.CamelContext")).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("configuration").setType(configClass).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("customizers").setType(loadType("java.util.List<org.apache.camel.spi.ComponentCustomizer<" + model.getJavaType() + ">>")) |
| .addAnnotation(Autowired.class).setLiteralValue("required", "false"); |
| |
| javaClass.addNestedType().setName("GroupConditions").setStatic(true).setPackagePrivate().extendSuperType("GroupCondition").addMethod().setName("GroupConditions") |
| .setConstructor(true).setPublic().setBody("super(\"camel.component\", \"camel.component." + componentName + "\");"); |
| |
| // add method for auto configure |
| String body = createComponentBody(model.getShortJavaType(), componentName); |
| String methodName = "configure" + model.getShortJavaType(); |
| |
| Method method = javaClass.addMethod().setName(methodName).setPublic().setBody(body).setReturnType(loadType(model.getJavaType())).addThrows(Exception.class); |
| |
| // Determine all the aliases |
| String[] springBeanAliases = componentAliases.stream().map(alias -> alias + "-component").toArray(size -> new String[size]); |
| |
| method.addAnnotation(Lazy.class); |
| method.addAnnotation(Bean.class).setStringArrayValue("name", springBeanAliases); |
| method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue(model.getShortJavaType() + ".class"); |
| |
| sortImports(javaClass); |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, false); |
| } |
| |
| private Class generateDummyClass(String clazzName) { |
| return getProjectClassLoader().generateDummyClass(clazzName); |
| } |
| |
| private void createDataFormatAutoConfigurationSource(String packageName, DataFormatModel model, List<String> dataFormatAliases, String overrideDataFormatName) |
| throws MojoFailureException { |
| |
| final String name = model.getJavaType().substring(model.getJavaType().lastIndexOf(".") + 1).replace("DataFormat", "DataFormatAutoConfiguration"); |
| final String configurationName = name.replace("DataFormatAutoConfiguration", "DataFormatConfiguration"); |
| final String dataformatName = (overrideDataFormatName != null ? overrideDataFormatName : model.getName()).toLowerCase(Locale.US); |
| |
| Class configClass = generateDummyClass(packageName + "." + configurationName); |
| |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| |
| javaClass.setPackage(packageName); |
| javaClass.setName(name); |
| javaClass.getJavaDoc().setFullText("Generated by camel-package-maven-plugin - do not edit this file!"); |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation(Configuration.class); |
| javaClass.addAnnotation(Conditional.class).setLiteralValue("{ConditionalOnCamelContextAndAutoConfigurationBeans.class,\n " + name + ".GroupConditions.class}"); |
| javaClass.addAnnotation(AutoConfigureAfter.class).setStringValue("name", "org.apache.camel.spring.boot.CamelAutoConfiguration"); |
| javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue("{DataFormatConfigurationProperties.class,\n " + configurationName + ".class}"); |
| |
| javaClass.addImport(HashMap.class); |
| javaClass.addImport(List.class); |
| javaClass.addImport(Map.class); |
| javaClass.addImport(ApplicationContext.class); |
| javaClass.addImport(ConditionalOnBean.class); |
| javaClass.addImport("org.slf4j.Logger"); |
| javaClass.addImport("org.slf4j.LoggerFactory"); |
| javaClass.addImport("org.apache.camel.CamelContext"); |
| javaClass.addImport("org.apache.camel.CamelContextAware"); |
| javaClass.addImport("org.apache.camel.spring.boot.CamelAutoConfiguration"); |
| javaClass.addImport("org.apache.camel.spring.boot.DataFormatConfigurationProperties"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.CamelPropertiesHelper"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.ConditionalOnCamelContextAndAutoConfigurationBeans"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.GroupCondition"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.HierarchicalPropertiesEvaluator"); |
| javaClass.addImport("org.apache.camel.support.IntrospectionSupport"); |
| javaClass.addImport("org.apache.camel.util.ObjectHelper"); |
| javaClass.addImport("org.apache.camel.RuntimeCamelException"); |
| javaClass.addImport("org.apache.camel.spi.DataFormat"); |
| javaClass.addImport("org.apache.camel.spi.DataFormatCustomizer"); |
| javaClass.addImport("org.apache.camel.spi.DataFormatFactory"); |
| javaClass.addImport("org.apache.camel.spi.HasId"); |
| javaClass.addImport(model.getJavaType()); |
| |
| javaClass.addField().setPrivate().setStatic(true).setFinal(true).setName("LOGGER").setType(loadType("org.slf4j.Logger")) |
| .setLiteralInitializer("LoggerFactory\n .getLogger(" + name + ".class)"); |
| javaClass.addField().setPrivate().setName("applicationContext").setType(ApplicationContext.class).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("camelContext").setType(loadType("org.apache.camel.CamelContext")).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("configuration").setType(configClass).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("customizers").setType(loadType("java.util.List<org.apache.camel.spi.DataFormatCustomizer<" + model.getJavaType() + ">>")) |
| .addAnnotation(Autowired.class).setLiteralValue("required", "false"); |
| |
| JavaClass groupConditions = javaClass.addNestedType().setName("GroupConditions").setStatic(true).setPackagePrivate().extendSuperType("GroupCondition"); |
| groupConditions.addMethod().setName("GroupConditions").setConstructor(true).setPublic() |
| .setBody("super(\"camel.dataformat\", \"camel.dataformat." + dataformatName + "\");"); |
| |
| String body = createDataFormatBody(model.getShortJavaType(), dataformatName); |
| String methodName = "configure" + model.getShortJavaType() + "Factory"; |
| |
| Method method = javaClass.addMethod().setName(methodName).setPublic().setBody(body).setReturnType(loadType("org.apache.camel.spi.DataFormatFactory")) |
| .addThrows(Exception.class); |
| |
| // Determine all the aliases |
| // adding the '-dataformat' suffix to prevent collision with component |
| // names |
| String[] springBeanAliases = dataFormatAliases.stream().map(alias -> alias + "-dataformat-factory").toArray(String[]::new); |
| |
| method.addAnnotation(Bean.class).setStringArrayValue("name", springBeanAliases); |
| method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue("value", model.getShortJavaType() + ".class"); |
| |
| sortImports(javaClass); |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, false); |
| } |
| |
| private void createLanguageAutoConfigurationSource(String packageName, LanguageModel model, List<String> languageAliases, String overrideLanguageName) |
| throws MojoFailureException { |
| |
| final String name = model.getJavaType().substring(model.getJavaType().lastIndexOf(".") + 1).replace("Language", "LanguageAutoConfiguration"); |
| final String configurationName = name.replace("LanguageAutoConfiguration", "LanguageConfiguration"); |
| final String languageName = (overrideLanguageName != null ? overrideLanguageName : model.getName()).toLowerCase(Locale.US); |
| |
| Class configClass = generateDummyClass(packageName + "." + configurationName); |
| |
| final JavaClass javaClass = new JavaClass(getProjectClassLoader()); |
| |
| javaClass.setPackage(packageName); |
| javaClass.setName(name); |
| javaClass.getJavaDoc().setFullText("Generated by camel-package-maven-plugin - do not edit this file!"); |
| javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); |
| javaClass.addAnnotation(Configuration.class); |
| javaClass.addAnnotation(Conditional.class).setLiteralValue("{ConditionalOnCamelContextAndAutoConfigurationBeans.class,\n " + name + ".GroupConditions.class}"); |
| javaClass.addAnnotation(AutoConfigureAfter.class).setLiteralValue("CamelAutoConfiguration.class"); |
| javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue("{LanguageConfigurationProperties.class,\n " + configurationName + ".class}"); |
| |
| javaClass.addImport(HashMap.class); |
| javaClass.addImport(List.class); |
| javaClass.addImport(Map.class); |
| javaClass.addImport(ApplicationContext.class); |
| javaClass.addImport(ConditionalOnBean.class); |
| javaClass.addImport(ConfigurableBeanFactory.class); |
| javaClass.addImport("org.slf4j.Logger"); |
| javaClass.addImport("org.slf4j.LoggerFactory"); |
| javaClass.addImport("org.apache.camel.CamelContext"); |
| javaClass.addImport("org.apache.camel.CamelContextAware"); |
| javaClass.addImport("org.apache.camel.spring.boot.CamelAutoConfiguration"); |
| javaClass.addImport("org.apache.camel.spring.boot.LanguageConfigurationProperties"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.CamelPropertiesHelper"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.ConditionalOnCamelContextAndAutoConfigurationBeans"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.GroupCondition"); |
| javaClass.addImport("org.apache.camel.spring.boot.util.HierarchicalPropertiesEvaluator"); |
| javaClass.addImport("org.apache.camel.support.IntrospectionSupport"); |
| javaClass.addImport("org.apache.camel.util.ObjectHelper"); |
| javaClass.addImport("org.apache.camel.spi.HasId"); |
| javaClass.addImport("org.apache.camel.spi.LanguageCustomizer"); |
| javaClass.addImport(model.getJavaType()); |
| |
| javaClass.addField().setPrivate().setStatic(true).setFinal(true).setName("LOGGER").setType(loadType("org.slf4j.Logger")) |
| .setLiteralInitializer("LoggerFactory\n .getLogger(" + name + ".class)"); |
| javaClass.addField().setPrivate().setName("applicationContext").setType(ApplicationContext.class).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("camelContext").setType(loadType("org.apache.camel.CamelContext")).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("configuration").setType(configClass).addAnnotation(Autowired.class); |
| javaClass.addField().setPrivate().setName("customizers").setType(loadType("java.util.List<org.apache.camel.spi.LanguageCustomizer<" + model.getJavaType() + ">>")) |
| .addAnnotation(Autowired.class).setLiteralValue("required", "false"); |
| |
| javaClass.addNestedType().setName("GroupConditions").setStatic(true).setPackagePrivate().extendSuperType("GroupCondition").addMethod().setName("GroupConditions") |
| .setConstructor(true).setPublic().setBody("super(\"camel.component\", \"camel.component." + languageName + "\");"); |
| |
| String body = createLanguageBody(model.getShortJavaType(), languageName); |
| String methodName = "configure" + model.getShortJavaType(); |
| |
| Method method = javaClass.addMethod().setName(methodName).setPublic().setBody(body).setReturnType(loadType(model.getJavaType())).addThrows(Exception.class); |
| |
| // Determine all the aliases |
| // adding the '-language' suffix to prevent collision with component |
| // names |
| String[] springBeanAliases = languageAliases.stream().map(alias -> alias + "-language").toArray(String[]::new); |
| |
| method.addAnnotation(Bean.class).setStringArrayValue("name", springBeanAliases); |
| method.addAnnotation(Scope.class).setLiteralValue("ConfigurableBeanFactory.SCOPE_PROTOTYPE"); |
| method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue("value", model.getShortJavaType() + ".class"); |
| |
| String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; |
| writeSourceIfChanged(javaClass, fileName, false); |
| } |
| |
| private void createComponentSpringFactorySource(String packageName, ComponentModel model) throws MojoFailureException { |
| int pos = model.getJavaType().lastIndexOf("."); |
| String name = model.getJavaType().substring(pos + 1); |
| name = name.replace("Component", "ComponentAutoConfiguration"); |
| |
| writeComponentSpringFactorySource(packageName, name); |
| } |
| |
| private void createDataFormatSpringFactorySource(String packageName, DataFormatModel model) throws MojoFailureException { |
| int pos = model.getJavaType().lastIndexOf("."); |
| String name = model.getJavaType().substring(pos + 1); |
| name = name.replace("DataFormat", "DataFormatAutoConfiguration"); |
| |
| writeComponentSpringFactorySource(packageName, name); |
| } |
| |
| private void createLanguageSpringFactorySource(String packageName, LanguageModel model) throws MojoFailureException { |
| int pos = model.getJavaType().lastIndexOf("."); |
| String name = model.getJavaType().substring(pos + 1); |
| name = name.replace("Language", "LanguageAutoConfiguration"); |
| |
| writeComponentSpringFactorySource(packageName, name); |
| } |
| |
| private static String createComponentBody(String shortJavaType, String name) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(shortJavaType).append(" component = new ").append(shortJavaType).append("();").append("\n"); |
| sb.append("component.setCamelContext(camelContext);\n"); |
| sb.append("Map<String, Object> parameters = new HashMap<>();\n"); |
| sb.append("IntrospectionSupport.getProperties(configuration, parameters, null,\n" + " false);\n"); |
| sb.append("for (Map.Entry<String, Object> entry : parameters.entrySet()) {\n"); |
| sb.append(" Object value = entry.getValue();\n"); |
| sb.append(" Class<?> paramClass = value.getClass();\n"); |
| sb.append(" if (paramClass.getName().endsWith(\"NestedConfiguration\")) {\n"); |
| sb.append(" Class nestedClass = null;\n"); |
| sb.append(" try {\n"); |
| sb.append(" nestedClass = (Class) paramClass.getDeclaredField(\n" + " \"CAMEL_NESTED_CLASS\").get(null);\n"); |
| sb.append(" HashMap<String, Object> nestedParameters = new HashMap<>();\n"); |
| sb.append(" IntrospectionSupport.getProperties(value, nestedParameters,\n" + " null, false);\n"); |
| sb.append(" Object nestedProperty = nestedClass.newInstance();\n"); |
| sb.append(" CamelPropertiesHelper.setCamelProperties(camelContext,\n" + " nestedProperty, nestedParameters, false);\n"); |
| sb.append(" entry.setValue(nestedProperty);\n"); |
| sb.append(" } catch (NoSuchFieldException e) {\n"); |
| // sb.append(" // ignore, class must not be a nested configuration class after all\n"); |
| sb.append(" }\n"); |
| sb.append(" }\n"); |
| sb.append("}\n"); |
| sb.append("CamelPropertiesHelper.setCamelProperties(camelContext, component,\n"); |
| sb.append(" parameters, false);\n"); |
| sb.append("if (ObjectHelper.isNotEmpty(customizers)) {\n"); |
| sb.append(" for (ComponentCustomizer<").append(shortJavaType).append("> customizer : customizers) {\n"); |
| sb.append(" boolean useCustomizer = (customizer instanceof HasId)\n"); |
| sb.append(" ? HierarchicalPropertiesEvaluator.evaluate(\n"); |
| sb.append(" applicationContext.getEnvironment(),\n"); |
| sb.append(" \"camel.component.customizer\",\n"); |
| sb.append(" \"camel.component.").append(name).append(".customizer\",\n"); |
| sb.append(" ((HasId) customizer).getId())\n"); |
| sb.append(" : HierarchicalPropertiesEvaluator.evaluate(\n"); |
| sb.append(" applicationContext.getEnvironment(),\n"); |
| sb.append(" \"camel.component.customizer\",\n"); |
| sb.append(" \"camel.component.").append(name).append(".customizer\");\n"); |
| sb.append(" if (useCustomizer) {\n"); |
| sb.append(" LOGGER.debug(\"Configure component {}, with customizer {}\",\n"); |
| sb.append(" component, customizer);\n"); |
| sb.append(" customizer.customize(component);\n"); |
| sb.append(" }\n"); |
| sb.append(" }\n"); |
| sb.append("}\n"); |
| sb.append("return component;"); |
| |
| return sb.toString(); |
| } |
| |
| private static String createDataFormatBody(String shortJavaType, String name) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("return new DataFormatFactory() {\n"); |
| sb.append(" @Override\n"); |
| sb.append(" public DataFormat newInstance() {\n"); |
| sb.append(" ").append(shortJavaType).append(" dataformat = new ").append(shortJavaType).append("();").append("\n"); |
| sb.append(" if (CamelContextAware.class\n" + " .isAssignableFrom(").append(shortJavaType).append(".class)) {\n"); |
| sb.append(" CamelContextAware contextAware = CamelContextAware.class\n" + " .cast(dataformat);\n"); |
| sb.append(" if (contextAware != null) {\n"); |
| sb.append(" contextAware.setCamelContext(camelContext);\n"); |
| sb.append(" }\n"); |
| sb.append(" }\n"); |
| sb.append(" try {\n"); |
| sb.append(" Map<String, Object> parameters = new HashMap<>();\n"); |
| sb.append(" IntrospectionSupport.getProperties(configuration,\n" + " parameters, null, false);\n"); |
| sb.append(" CamelPropertiesHelper.setCamelProperties(camelContext,\n" + " dataformat, parameters, false);\n"); |
| sb.append(" } catch (Exception e) {\n"); |
| sb.append(" throw new RuntimeCamelException(e);\n"); |
| sb.append(" }\n"); |
| sb.append(" if (ObjectHelper.isNotEmpty(customizers)) {\n"); |
| sb.append(" for (DataFormatCustomizer<").append(shortJavaType).append("> customizer : customizers) {\n"); |
| sb.append(" boolean useCustomizer = (customizer instanceof HasId)\n"); |
| sb.append(" ? HierarchicalPropertiesEvaluator.evaluate(\n"); |
| sb.append(" applicationContext.getEnvironment(),\n"); |
| sb.append(" \"camel.dataformat.customizer\",\n"); |
| sb.append(" \"camel.dataformat.").append(name).append(".customizer\",\n"); |
| sb.append(" ((HasId) customizer).getId())\n"); |
| sb.append(" : HierarchicalPropertiesEvaluator.evaluate(\n" + " applicationContext.getEnvironment(),\n"); |
| sb.append(" \"camel.dataformat.customizer\",\n"); |
| sb.append(" \"camel.dataformat.").append(name).append(".customizer\");\n"); |
| sb.append(" if (useCustomizer) {\n"); |
| sb.append(" LOGGER.debug(\n" + " \"Configure dataformat {}, with customizer {}\",\n" |
| + " dataformat, customizer);\n"); |
| sb.append(" customizer.customize(dataformat);\n"); |
| sb.append(" }\n"); |
| sb.append(" }\n"); |
| sb.append(" }\n"); |
| sb.append(" return dataformat;\n"); |
| sb.append(" }\n"); |
| sb.append("};\n"); |
| |
| return sb.toString(); |
| } |
| |
| private static String createLanguageBody(String shortJavaType, String name) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(shortJavaType).append(" language = new ").append(shortJavaType).append("();").append("\n"); |
| sb.append("if (CamelContextAware.class.isAssignableFrom(").append(shortJavaType).append(".class)) {\n"); |
| sb.append(" CamelContextAware contextAware = CamelContextAware.class\n" + " .cast(language);\n"); |
| sb.append(" if (contextAware != null) {\n"); |
| sb.append(" contextAware.setCamelContext(camelContext);\n"); |
| sb.append(" }\n"); |
| sb.append("}\n"); |
| sb.append("Map<String, Object> parameters = new HashMap<>();\n"); |
| sb.append("IntrospectionSupport.getProperties(configuration, parameters, null,\n" + " false);\n"); |
| sb.append("CamelPropertiesHelper.setCamelProperties(camelContext, language,\n" + " parameters, false);\n"); |
| sb.append("if (ObjectHelper.isNotEmpty(customizers)) {\n"); |
| sb.append(" for (LanguageCustomizer<").append(shortJavaType).append("> customizer : customizers) {\n"); |
| sb.append(" boolean useCustomizer = (customizer instanceof HasId)\n"); |
| sb.append(" ? HierarchicalPropertiesEvaluator.evaluate(\n"); |
| sb.append(" applicationContext.getEnvironment(),\n"); |
| sb.append(" \"camel.language.customizer\",\n"); |
| sb.append(" \"camel.language.").append(name).append(".customizer\",\n"); |
| sb.append(" ((HasId) customizer).getId())\n"); |
| sb.append(" : HierarchicalPropertiesEvaluator.evaluate(\n"); |
| sb.append(" applicationContext.getEnvironment(),\n"); |
| sb.append(" \"camel.language.customizer\",\n"); |
| sb.append(" \"camel.language.").append(name).append(".customizer\");\n"); |
| sb.append(" if (useCustomizer) {\n"); |
| sb.append(" LOGGER.debug(\"Configure language {}, with customizer {}\",\n" + " language, customizer);\n"); |
| sb.append(" customizer.customize(language);\n"); |
| sb.append(" }\n"); |
| sb.append(" }\n"); |
| sb.append("}\n"); |
| sb.append("return language;"); |
| |
| return sb.toString(); |
| } |
| |
| private static void sortImports(JavaClass importer) { |
| // sort imports |
| // do nothing, as imports are sorted automatically when displayed |
| } |
| |
| private static String loadModelJson(Map<File, Supplier<String>> jsonFiles, String modelName) { |
| return loadJsonOfType(jsonFiles, modelName, "model"); |
| } |
| |
| private static String loadComponentJson(Map<File, Supplier<String>> jsonFiles, String componentName) { |
| return loadJsonOfType(jsonFiles, componentName, "component"); |
| } |
| |
| private static String loadDataFormatJson(Map<File, Supplier<String>> jsonFiles, String dataFormatName) { |
| return loadJsonOfType(jsonFiles, dataFormatName, "dataformat"); |
| } |
| |
| private static String loadLanguageJson(Map<File, Supplier<String>> jsonFiles, String languageName) { |
| return loadJsonOfType(jsonFiles, languageName, "language"); |
| } |
| |
| private 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; |
| } |
| |
| private static ComponentModel generateComponentModel(String componentName, String json) { |
| List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false); |
| |
| ComponentModel component = new ComponentModel(); |
| component.setScheme(getSafeValue("scheme", rows)); |
| component.setSyntax(getSafeValue("syntax", rows)); |
| component.setAlternativeSyntax(getSafeValue("alternativeSyntax", rows)); |
| component.setTitle(getSafeValue("title", rows)); |
| component.setDescription(getSafeValue("description", rows)); |
| component.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows)); |
| component.setLabel(getSafeValue("label", rows)); |
| component.setDeprecated(getSafeValue("deprecated", rows)); |
| component.setDeprecationNote(getSafeValue("deprecationNote", rows)); |
| component.setConsumerOnly(getSafeValue("consumerOnly", rows)); |
| component.setProducerOnly(getSafeValue("producerOnly", rows)); |
| component.setJavaType(getSafeValue("javaType", rows)); |
| component.setGroupId(getSafeValue("groupId", rows)); |
| component.setArtifactId(getSafeValue("artifactId", rows)); |
| component.setVersion(getSafeValue("version", rows)); |
| |
| rows = JSonSchemaHelper.parseJsonSchema("componentProperties", json, true); |
| for (Map<String, String> row : rows) { |
| ComponentOptionModel option = new ComponentOptionModel(); |
| option.setName(getSafeValue("name", row)); |
| option.setDisplayName(getSafeValue("displayName", row)); |
| option.setKind(getSafeValue("kind", row)); |
| option.setType(getSafeValue("type", row)); |
| option.setJavaType(getSafeValue("javaType", row)); |
| option.setDeprecated(getSafeValue("deprecated", row)); |
| option.setDeprecationNote(getSafeValue("deprecationNote", row)); |
| option.setDescription(getSafeValue("description", row)); |
| option.setDefaultValue(getSafeValue("defaultValue", row)); |
| option.setEnums(getSafeValue("enum", row)); |
| component.addComponentOption(option); |
| } |
| |
| rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); |
| for (Map<String, String> row : rows) { |
| EndpointOptionModel option = new EndpointOptionModel(); |
| option.setName(getSafeValue("name", row)); |
| option.setDisplayName(getSafeValue("displayName", row)); |
| option.setKind(getSafeValue("kind", row)); |
| option.setGroup(getSafeValue("group", row)); |
| option.setRequired(getSafeValue("required", row)); |
| option.setType(getSafeValue("type", row)); |
| option.setJavaType(getSafeValue("javaType", row)); |
| option.setEnums(getSafeValue("enum", row)); |
| option.setPrefix(getSafeValue("prefix", row)); |
| option.setMultiValue(getSafeValue("multiValue", row)); |
| option.setDeprecated(getSafeValue("deprecated", row)); |
| option.setDeprecationNote(getSafeValue("deprecationNote", row)); |
| option.setDefaultValue(getSafeValue("defaultValue", row)); |
| option.setDescription(getSafeValue("description", row)); |
| option.setEnumValues(getSafeValue("enum", row)); |
| component.addEndpointOption(option); |
| } |
| |
| return component; |
| } |
| |
| private static DataFormatModel generateDataFormatModel(String dataFormatName, String json) { |
| List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("dataformat", json, false); |
| |
| DataFormatModel dataFormat = new DataFormatModel(); |
| dataFormat.setTitle(getSafeValue("title", rows)); |
| dataFormat.setName(getSafeValue("name", rows)); |
| dataFormat.setModelName(getSafeValue("modelName", rows)); |
| dataFormat.setDescription(getSafeValue("description", rows)); |
| dataFormat.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows)); |
| dataFormat.setLabel(getSafeValue("label", rows)); |
| dataFormat.setDeprecated(getSafeValue("deprecated", rows)); |
| dataFormat.setDeprecationNote(getSafeValue("deprecationNote", rows)); |
| dataFormat.setJavaType(getSafeValue("javaType", rows)); |
| dataFormat.setGroupId(getSafeValue("groupId", rows)); |
| dataFormat.setArtifactId(getSafeValue("artifactId", rows)); |
| dataFormat.setVersion(getSafeValue("version", rows)); |
| |
| rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); |
| for (Map<String, String> row : rows) { |
| DataFormatOptionModel option = new DataFormatOptionModel(); |
| option.setName(getSafeValue("name", row)); |
| option.setDisplayName(getSafeValue("displayName", row)); |
| option.setKind(getSafeValue("kind", row)); |
| option.setType(getSafeValue("type", row)); |
| option.setJavaType(getSafeValue("javaType", row)); |
| option.setDeprecated(getSafeValue("deprecated", row)); |
| option.setDeprecationNote(getSafeValue("deprecationNote", row)); |
| option.setDescription(getSafeValue("description", row)); |
| option.setDefaultValue(getSafeValue("defaultValue", row)); |
| option.setEnumValues(getSafeValue("enum", row)); |
| dataFormat.addDataFormatOption(option); |
| } |
| |
| return dataFormat; |
| } |
| |
| private static LanguageModel generateLanguageModel(String languageName, String json) { |
| List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("language", json, false); |
| |
| LanguageModel language = new LanguageModel(); |
| language.setTitle(getSafeValue("title", rows)); |
| language.setName(getSafeValue("name", rows)); |
| language.setModelName(getSafeValue("modelName", rows)); |
| language.setDescription(getSafeValue("description", rows)); |
| language.setFirstVersion(JSonSchemaHelper.getSafeValue("firstVersion", rows)); |
| language.setLabel(getSafeValue("label", rows)); |
| language.setDeprecated(getSafeValue("deprecated", rows)); |
| language.setDeprecationNote(getSafeValue("deprecationNote", rows)); |
| language.setJavaType(getSafeValue("javaType", rows)); |
| language.setGroupId(getSafeValue("groupId", rows)); |
| language.setArtifactId(getSafeValue("artifactId", rows)); |
| language.setVersion(getSafeValue("version", rows)); |
| |
| rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); |
| for (Map<String, String> row : rows) { |
| LanguageOptionModel option = new LanguageOptionModel(); |
| option.setName(getSafeValue("name", row)); |
| option.setDisplayName(getSafeValue("displayName", row)); |
| option.setKind(getSafeValue("kind", row)); |
| option.setType(getSafeValue("type", row)); |
| option.setJavaType(getSafeValue("javaType", row)); |
| option.setDeprecated(getSafeValue("deprecated", row)); |
| option.setDeprecationNote(getSafeValue("deprecationNote", row)); |
| option.setDescription(getSafeValue("description", row)); |
| option.setDefaultValue(getSafeValue("defaultValue", row)); |
| option.setEnumValues(getSafeValue("enum", row)); |
| language.addLanguageOption(option); |
| } |
| |
| return language; |
| } |
| |
| private OtherModel generateOtherModel(String json) { |
| List<Map<String, String>> rows = parseJsonSchema("model", json, false); |
| |
| OtherModel model = new OtherModel(); |
| model.setName(getSafeValue("name", rows)); |
| model.setTitle(getSafeValue("title", rows)); |
| model.setDescription(getSafeValue("description", rows)); |
| model.setJavaType(getSafeValue("javaType", rows)); |
| model.setLabel(getSafeValue("label", rows)); |
| model.setDeprecated(getSafeValue("deprecated", rows)); |
| model.setDeprecationNote(getSafeValue("deprecationNote", rows)); |
| |
| rows = parseJsonSchema("properties", json, true); |
| for (Map<String, String> row : rows) { |
| OtherOptionModel option = new OtherOptionModel(); |
| option.setName(getSafeValue("name", row)); |
| option.setDisplayName(getSafeValue("displayName", row)); |
| option.setKind(getSafeValue("kind", row)); |
| option.setGroup(getSafeValue("group", row)); |
| option.setRequired(getSafeValue("required", row)); |
| option.setType(getSafeValue("type", row)); |
| option.setJavaType(getSafeValue("javaType", row)); |
| option.setEnums(getSafeValue("enum", row)); |
| option.setDeprecated(getSafeValue("deprecated", row)); |
| option.setDeprecationNote(getSafeValue("deprecationNote", row)); |
| option.setDefaultValue(getSafeValue("defaultValue", row)); |
| option.setDescription(getSafeValue("description", row)); |
| option.setEnums(getSafeValue("enums", row)); |
| |
| model.addOptionModel(option); |
| } |
| |
| return model; |
| } |
| |
| private 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); |
| } |
| } |
| } |
| } |
| } |
| |
| private List<String> findDataFormatNames() { |
| List<String> dataFormatNames = new ArrayList<>(); |
| File f = new File(project.getBasedir(), "target/classes"); |
| f = new File(f, "META-INF/services/org/apache/camel/dataformat"); |
| 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) != '.') { |
| dataFormatNames.add(name); |
| } |
| } |
| } |
| } |
| return dataFormatNames; |
| } |
| |
| private List<String> findLanguageNames() { |
| List<String> languageNames = new ArrayList<>(); |
| File f = new File(project.getBasedir(), "target/classes"); |
| f = new File(f, "META-INF/services/org/apache/camel/language"); |
| 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) != '.') { |
| languageNames.add(name); |
| } |
| } |
| } |
| } |
| return languageNames; |
| } |
| |
| private void writeSourceIfChanged(JavaClass source, String fileName, boolean innerClassesLast) throws MojoFailureException { |
| writeSourceIfChanged(source.printClass(innerClassesLast), fileName); |
| } |
| |
| private void writeSourceIfChanged(String source, String fileName) throws MojoFailureException { |
| |
| File target = new File(SpringBootHelper.starterSrcDir(baseDir, getStarterArtifactId()), fileName); |
| |
| deleteFileOnMainArtifact(target); |
| |
| try { |
| String header; |
| try (InputStream is = getClass().getClassLoader().getResourceAsStream("license-header-java.txt")) { |
| header = loadText(is); |
| } |
| String code = header + source; |
| getLog().debug("Source code generated:\n" + code); |
| |
| updateResource(null, target.toPath(), code); |
| } catch (Exception e) { |
| throw new MojoFailureException("IOError with file " + target, e); |
| } |
| } |
| |
| private void writeComponentSpringFactorySource(String packageName, String name) throws MojoFailureException { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\n"); |
| |
| String lineToAdd = packageName + "." + name + "\n"; |
| sb.append(lineToAdd); |
| |
| String fileName = "META-INF/spring.factories"; |
| File target = new File(SpringBootHelper.starterResourceDir(baseDir, getStarterArtifactId()), fileName); |
| |
| deleteFileOnMainArtifact(target); |
| |
| if (target.exists()) { |
| try { |
| // is the auto configuration already in the file |
| boolean found = false; |
| List<String> lines = FileUtils.readLines(target, StandardCharsets.UTF_8); |
| for (String line : lines) { |
| if (line.contains(name)) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (found) { |
| getLog().debug("No changes to existing file: " + target); |
| } else { |
| // find last non empty line, so we can add our new line |
| // after that |
| int lastLine = 0; |
| for (int i = lines.size() - 1; i >= 0; i--) { |
| String line = lines.get(i); |
| if (!line.trim().isEmpty()) { |
| // adjust existing line so its being continued |
| line = line + ",\\"; |
| lines.set(i, line); |
| lastLine = i; |
| break; |
| } |
| } |
| lines.add(lastLine + 1, lineToAdd); |
| |
| StringBuilder code = new StringBuilder(); |
| for (String line : lines) { |
| code.append(line).append("\n"); |
| } |
| |
| // update |
| FileUtils.write(target, code.toString(), StandardCharsets.UTF_8, false); |
| getLog().info("Updated existing file: " + target); |
| } |
| } catch (Exception e) { |
| throw new MojoFailureException("IOError with file " + target, e); |
| } |
| } else { |
| // create new file |
| try { |
| InputStream is = getClass().getClassLoader().getResourceAsStream("license-header.txt"); |
| String header = loadText(is); |
| String code = sb.toString(); |
| // add empty new line after header |
| code = header + "\n" + code; |
| getLog().debug("Source code generated:\n" + code); |
| |
| FileUtils.write(target, code, StandardCharsets.UTF_8); |
| getLog().info("Created file: " + target); |
| } catch (Exception e) { |
| throw new MojoFailureException("IOError with file " + target, e); |
| } |
| } |
| } |
| |
| private String getStarterArtifactId() { |
| if ("camel-core-engine".equals(project.getArtifactId())) { |
| return "camel-core"; |
| } else { |
| return project.getArtifactId(); |
| } |
| } |
| |
| private void deleteFileOnMainArtifact(File starterFile) { |
| if (!DELETE_FILES_ON_MAIN_ARTIFACTS) { |
| return; |
| } |
| |
| String relativePath = SpringBootHelper.starterDir(baseDir, getStarterArtifactId()).toPath().relativize(starterFile.toPath()).toString(); |
| File mainArtifactFile = new File(baseDir, relativePath); |
| if (mainArtifactFile.exists()) { |
| boolean deleted = mainArtifactFile.delete(); |
| if (!deleted) { |
| throw new IllegalStateException("Cannot delete file " + mainArtifactFile); |
| } |
| } |
| } |
| |
| } |