/** * 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.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; 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.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Generated; 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.spi.UriParam; import org.apache.camel.spi.UriPath; import org.apache.commons.io.FileUtils; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.JavaType; import org.jboss.forge.roaster.model.Type; import org.jboss.forge.roaster.model.source.Import; import org.jboss.forge.roaster.model.source.Importer; import org.jboss.forge.roaster.model.source.JavaClassSource; import org.jboss.forge.roaster.model.source.MethodSource; import org.jboss.forge.roaster.model.source.PropertySource; import org.jboss.forge.roaster.model.util.Formatter; import org.jboss.forge.roaster.model.util.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.AllNestedConditions; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 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.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.bind.RelaxedPropertyResolver; 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.annotation.Bean; import org.springframework.context.annotation.ConditionContext; 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 org.springframework.core.type.AnnotatedTypeMetadata; import static org.apache.camel.maven.packaging.JSonSchemaHelper.getPropertyDefaultValue; 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. * * @goal prepare-spring-boot-auto-configuration * @requiresDependencyResolution compile+runtime */ 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 exclude when adding {@link NestedConfigurationProperty} annotations. */ private static final Pattern EXCLUDE_INNER_PATTERN = Pattern.compile("^((java\\.)|(javax\\.)|(org\\.springframework\\.context\\.ApplicationContext)|(freemarker\\.template\\.Configuration)).*"); private static final Map<String, String> PRIMITIVEMAP; static { 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 List<String> JAVA_LANG_TYPES = Arrays.asList("Boolean", "Byte", "Character", "Class", "Double", "Float", "Integer", "Long", "Object", "Short", "String"); private static final String[] IGNORE_MODULES = {/* Non-standard -> */ "camel-grape", "camel-connector"}; /** * The output directory for generated component schema file * * @parameter default-value="${project.build.directory}/classes" */ protected File classesDir; /** * The maven project. * * @parameter property="project" * @required * @readonly */ protected MavenProject project; /** * The project build directory * * @parameter default-value="${project.build.directory}" */ protected File buildDir; /** * The base directory * * @parameter default-value="${basedir}" */ protected File baseDir; @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, project.getArtifactId()); 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; } executeModels(); executeComponent(); executeDataFormat(); executeLanguage(); } private void executeModels() throws MojoExecutionException, MojoFailureException { final Set<File> files = PackageHelper.findJsonFiles(buildDir, p -> p.isDirectory() || p.getName().endsWith(".json")); 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 happens in camel-hystrix-starter createOtherModelConfigurationSource(pkg, model, "camel.hystrix", 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 happens in camel-consul-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 happens in camel-dns-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 happens in camel-etcd-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 happens in camel-kubernetes-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 happens in camel-kubernetes-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 JavaClassSource commonClass = Roaster.create(JavaClassSource.class); 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 PropertySource<JavaClassSource> 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()); } } } sortImports(commonClass); writeSourceIfChanged(commonClass, packageName.replaceAll("\\.", "\\/") + "/" + commonName + ".java"); // Config class if (generatedNestedConfig) { JavaClassSource configClass = Roaster.create(JavaClassSource.class); configClass.setPackage(packageName); configClass.setName(configName); configClass.extendSuperType(commonClass); configClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); configClass.addAnnotation("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("Map<String, " + commonName + "> configurations = new HashMap<>()") .setPrivate() .getJavaDoc().setFullText("Define additional configuration definitions"); MethodSource<JavaClassSource> method; method = configClass.addMethod(); method.setName("getConfigurations"); method.setReturnType("Map<String, " + 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;"); sortImports(configClass); writeSourceIfChanged(configClass, packageName.replaceAll("\\.", "\\/") + "/" + configName + ".java"); } } 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"; // Common base class JavaClassSource javaClass = Roaster.create(JavaClassSource.class); 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>"; } if ("enableCORS".equalsIgnoreCase(name)) { name = "enableCors"; } // generate inner class for non-primitive options PropertySource<JavaClassSource> 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()); } } } sortImports(javaClass); writeSourceIfChanged(javaClass, packageName.replaceAll("\\.", "\\/") + "/" + className + ".java"); } private void createRestModuleAutoConfigurationSource(String packageName, OtherModel model) throws MojoFailureException { final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); 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.IntrospectionSupport"); 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("org.apache.camel.CamelContext") .setPrivate() .addAnnotation(Autowired.class); javaClass.addField() .setName("config") .setType(configType) .setPrivate() .addAnnotation(Autowired.class); MethodSource<JavaClassSource> method; // Configuration method = javaClass.addMethod(); method.setName("configure" + model.getShortJavaType()); method.setPublic(); method.addThrows(Exception.class); method.setReturnType("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" + "\n" + "RestConfiguration definition = new RestConfiguration();\n" + "IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), definition, properties);\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" + "return definition;" ); sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); writeComponentSpringFactorySource(packageName, name); } private void executeComponent() throws MojoExecutionException, MojoFailureException { // find the component names List<String> componentNames = findComponentNames(); final Set<File> jsonFiles = new TreeSet<File>(); PackageHelper.findJsonFiles(buildDir, jsonFiles, new PackageHelper.CamelComponentsModelFilter()); // 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() throws MojoExecutionException, MojoFailureException { // find the data format names List<String> dataFormatNames = findDataFormatNames(); final Set<File> jsonFiles = new TreeSet<File>(); // we can reuse the component model filter PackageHelper.findJsonFiles(buildDir, jsonFiles, new PackageHelper.CamelComponentsModelFilter()); // 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() throws MojoExecutionException, MojoFailureException { // find the language names List<String> languageNames = findLanguageNames(); final Set<File> jsonFiles = new TreeSet<File>(); // we can reuse the component model filter PackageHelper.findJsonFiles(buildDir, jsonFiles, new PackageHelper.CamelComponentsModelFilter()); // 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 { final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); int pos = model.getJavaType().lastIndexOf("."); String name = model.getJavaType().substring(pos + 1); name = name.replace("Component", "ComponentConfiguration"); javaClass.setPackage(packageName).setName(name); javaClass.extendSuperType(Roaster.create(JavaClassSource.class).setName("ComponentConfigurationPropertiesCommon")); javaClass.addImport("org.apache.camel.spring.boot.ComponentConfigurationPropertiesCommon"); 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.component." + (overrideComponentName != null ? overrideComponentName : model.getScheme()); // 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); Set<JavaClassSource> nestedTypes = new HashSet<>(); 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); JavaClassSource javaClassSource = readJavaType(type); if (isNestedProperty(nestedTypes, javaClassSource)) { type = option.getShortJavaType() + INNER_TYPE_SUFFIX; } PropertySource<JavaClassSource> prop = javaClass.addProperty(type, option.getName()); if (!type.endsWith(INNER_TYPE_SUFFIX) && type.indexOf('[') == -1 && !EXCLUDE_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())) { prop.getField().getJavaDoc().setFullText(option.getDescription()); } 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()); } } } // add inner classes for nested AutoConfiguration options ClassLoader projectClassLoader = getProjectClassLoader(); for (JavaClassSource nestedType : nestedTypes) { final JavaClassSource innerClass = javaClass.addNestedType("public static class " + 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 (ResolvedProperty resolvedProperty : getProperties(nestedType)) { String optionType = resolvedProperty.propertyType; PropertySource<JavaClassSource> sourceProp = resolvedProperty.propertySource; Type<JavaClassSource> propType = sourceProp.getType(); final PropertySource<JavaClassSource> prop = innerClass.addProperty(optionType, sourceProp.getName()); boolean anEnum; Class optionClass; if (!propType.isArray()) { optionClass = loadClass(projectClassLoader, optionType); anEnum = optionClass.isEnum(); } else { optionClass = null; anEnum = false; } // add nested configuration annotation for complex properties if (!EXCLUDE_INNER_PATTERN.matcher(optionType).matches() && !propType.isArray() && !anEnum && optionClass != null && !optionClass.isInterface() && !optionClass.isAnnotation() && !Modifier.isAbstract(optionClass.getModifiers())) { prop.getField().addAnnotation(NestedConfigurationProperty.class); } if (sourceProp.hasAnnotation(Deprecated.class)) { 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); } String description = null; final MethodSource<JavaClassSource> mutator = sourceProp.getMutator(); if (mutator.hasJavaDoc()) { description = mutator.getJavaDoc().getFullText(); } else if (sourceProp.hasField()) { description = sourceProp.getField().getJavaDoc().getFullText(); } 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); 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); } } 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(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()); } } } } } sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); } // resolved property type name and property source, Roaster doesn't resolve inner classes correctly private class ResolvedProperty { private String propertyType; private PropertySource<JavaClassSource> propertySource; ResolvedProperty(String propertyType, PropertySource<JavaClassSource> propertySource) { this.propertyType = propertyType; this.propertySource = propertySource; } } // get properties for nested type and super types, only properties with setters are supported!!! private List<ResolvedProperty> getProperties(JavaClassSource nestedType) { final List<ResolvedProperty> properties = new ArrayList<>(); final Set<String> names = new HashSet<>(); do { for (PropertySource<JavaClassSource> propertySource : nestedType.getProperties()) { // NOTE: fields with no setters are skipped if (propertySource.isMutable() && !names.contains(propertySource.getName())) { properties.add(new ResolvedProperty(getSimpleJavaType(resolveParamType(nestedType, propertySource.getType().getName())), propertySource)); names.add(propertySource.getName()); } } nestedType = readJavaType(nestedType.getSuperType()); } while (nestedType != null); return properties; } // try loading class, looking for inner classes if needed private Class loadClass(ClassLoader projectClassLoader, String loadClassName) throws MojoFailureException { Class optionClass; while (true) { try { optionClass = projectClassLoader.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; } // Roaster doesn't resolve inner classes correctly private String resolveParamType(JavaClassSource nestedType, String type) { String result; int innerStart = type.indexOf('.'); int arrayStart = type.indexOf('['); if (innerStart != -1) { result = nestedType.resolveType(type.substring(0, innerStart)) + type.substring(innerStart); } else { result = nestedType.resolveType(type); } return arrayStart == -1 ? result : result + type.substring(arrayStart); } protected ClassLoader getProjectClassLoader() throws MojoFailureException { final List classpathElements; try { classpathElements = project.getTestClasspathElements(); } catch (org.apache.maven.artifact.DependencyResolutionRequiredException e) { throw new MojoFailureException(e.getMessage(), e); } final URL[] urls = new URL[classpathElements.size()]; int i = 0; for (Iterator it = classpathElements.iterator(); it.hasNext(); i++) { try { urls[i] = new File((String) it.next()).toURI().toURL(); } catch (MalformedURLException e) { throw new MojoFailureException(e.getMessage(), e); } } final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); return new URLClassLoader(urls, tccl != null ? tccl : getClass().getClassLoader()); } private String getSimpleJavaType(String type) { // remove <?> as generic type as Roaster (Eclipse JDT) cannot use that type = type.replaceAll("\\<\\?\\>", ""); // 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<JavaClassSource> nestedTypes, JavaClassSource 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 JavaClassSource readJavaType(String type) { JavaClassSource nestedType = null; if (!type.startsWith("java.lang.")) { final String fileName = type.replaceAll("[\\[\\]]", "").replaceAll("\\.", "\\/") + ".java"; for (Object sourceRoot : project.getCompileSourceRoots()) { File sourceFile = new File(sourceRoot.toString(), fileName); if (sourceFile.isFile()) { try { JavaType<?> classSource = Roaster.parse(sourceFile); if (classSource instanceof JavaClassSource) { nestedType = (JavaClassSource) classSource; break; } } catch (FileNotFoundException e) { throw new IllegalArgumentException("Missing source file " + type); } } } } return nestedType; } // CHECKSTYLE:OFF private static boolean skipComponentOption(ComponentModel model, ComponentOptionModel option) { if ("netty4-http".equals(model.getScheme()) || "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 JavaClassSource javaClass = Roaster.create(JavaClassSource.class); int pos = model.getJavaType().lastIndexOf("."); String name = model.getJavaType().substring(pos + 1); name = name.replace("DataFormat", "DataFormatConfiguration"); javaClass.setPackage(packageName).setName(name); javaClass.extendSuperType(Roaster.create(JavaClassSource.class).setName("DataFormatConfigurationPropertiesCommon")); javaClass.addImport("org.apache.camel.spring.boot.DataFormatConfigurationPropertiesCommon"); 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); PropertySource<JavaClassSource> 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())) { prop.getField().getJavaDoc().setFullText(option.getDescription()); } if (!Strings.isBlank(option.getDefaultValue())) { if ("java.lang.String".equals(option.getType())) { 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()); } } } sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); } private void createLanguageConfigurationSource(String packageName, LanguageModel model, String overrideLanguageName) throws MojoFailureException { final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); int pos = model.getJavaType().lastIndexOf("."); String name = model.getJavaType().substring(pos + 1); name = name.replace("Language", "LanguageConfiguration"); javaClass.setPackage(packageName).setName(name); javaClass.extendSuperType(Roaster.create(JavaClassSource.class).setName("LanguageConfigurationPropertiesCommon")); javaClass.addImport("org.apache.camel.spring.boot.LanguageConfigurationPropertiesCommon"); 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); PropertySource<JavaClassSource> 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())) { prop.getField().getJavaDoc().setFullText(option.getDescription()); } if (!Strings.isBlank(option.getDefaultValue())) { if ("java.lang.String".equals(option.getType())) { 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()); } } } sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); } private void createComponentAutoConfigurationSource( String packageName, ComponentModel model, List<String> componentAliases, String overrideComponentName) throws MojoFailureException { final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); int pos = model.getJavaType().lastIndexOf("."); String name = model.getJavaType().substring(pos + 1); name = name.replace("Component", "ComponentAutoConfiguration"); javaClass.setPackage(packageName).setName(name); String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; javaClass.getJavaDoc().setFullText(doc); javaClass.extendSuperType(AllNestedConditions.class); javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); javaClass.addAnnotation(Configuration.class); javaClass.addAnnotation(Conditional.class).setLiteralValue(name + ".Condition.class"); javaClass.addAnnotation(AutoConfigureAfter.class).setLiteralValue("CamelAutoConfiguration.class"); String configurationName = name.replace("ComponentAutoConfiguration", "ComponentConfiguration"); javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue( "{ ComponentConfigurationProperties.class, " + configurationName + ".class }" ); javaClass.addImport(HashMap.class); javaClass.addImport(Map.class); javaClass.addImport("org.apache.camel.util.IntrospectionSupport"); javaClass.addImport(model.getJavaType()); javaClass.addImport(List.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.GroupCondition"); javaClass.addImport("org.apache.camel.util.ObjectHelper"); javaClass.addField() .setPrivate() .setStatic(true) .setFinal(true) .setName("LOGGER") .setType("Logger") .setLiteralInitializer("LoggerFactory.getLogger(" + name + ".class)"); javaClass.addField() .setPrivate() .setName("camelContext") .setType("CamelContext") .addAnnotation(Autowired.class); javaClass.addField() .setPrivate() .setName("customizers") .setType("List<ComponentCustomizer<" + model.getShortJavaType() + ">>") .addAnnotation(Autowired.class).setLiteralValue("required", "false"); javaClass.addField() .setPrivate() .setName("globalConfiguration") .setType("ComponentConfigurationProperties") .addAnnotation(Autowired.class); javaClass.addField() .setPrivate() .setName("componentConfiguration") .setType(configurationName) .addAnnotation(Autowired.class); javaClass.addMethod() .setConstructor(true) .setPublic() .setBody("super(ConfigurationPhase.REGISTER_BEAN);"); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("OnCamelContext") .setStatic(true) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelContext.class") .getOrigin() ); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("OnCamelAutoConfiguration") .setStatic(true) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelAutoConfiguration.class") .getOrigin() ); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("Condition") .setStatic(true) .extendSuperType(Roaster.create(JavaClassSource.class).setName("GroupCondition")) .addMethod() .setName("Condition") .setConstructor(true) .setPublic() .setBody("super(\"camel.component\", \"camel.component." + (overrideComponentName != null ? overrideComponentName : model.getScheme()).toLowerCase(Locale.US) + "\");") .getOrigin() ); // add method for auto configure String body = createComponentBody(model.getShortJavaType()); String methodName = "configure" + model.getShortJavaType(); MethodSource<JavaClassSource> method = javaClass.addMethod() .setName(methodName) .setPublic() .setBody(body) .setReturnType(model.getShortJavaType()) .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(ConditionalOnClass.class).setLiteralValue("CamelContext.class"); method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue(model.getShortJavaType() + ".class"); sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); } private void createDataFormatAutoConfigurationSource( String packageName, DataFormatModel model, List<String> dataFormatAliases, String overrideDataFormatName) throws MojoFailureException { final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); int pos = model.getJavaType().lastIndexOf("."); String name = model.getJavaType().substring(pos + 1); name = name.replace("DataFormat", "DataFormatAutoConfiguration"); javaClass.setPackage(packageName).setName(name); String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; javaClass.getJavaDoc().setFullText(doc); javaClass.extendSuperType(AllNestedConditions.class); javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); javaClass.addAnnotation(Configuration.class); javaClass.addAnnotation(Conditional.class).setLiteralValue(name + ".Condition.class"); javaClass.addAnnotation(AutoConfigureAfter.class).setStringValue("name", "org.apache.camel.spring.boot.CamelAutoConfiguration"); String configurationName = name.replace("DataFormatAutoConfiguration", "DataFormatConfiguration"); javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue( "{ DataFormatConfigurationProperties.class, " + configurationName + ".class }" ); javaClass.addImport(HashMap.class); javaClass.addImport(Map.class); javaClass.addImport("org.apache.camel.util.IntrospectionSupport"); javaClass.addImport(model.getJavaType()); javaClass.addImport(List.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.GroupCondition"); 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.addField() .setPrivate() .setStatic(true) .setFinal(true) .setName("LOGGER") .setType("Logger") .setLiteralInitializer("LoggerFactory.getLogger(" + name + ".class)"); javaClass.addField() .setPrivate() .setName("camelContext") .setType("CamelContext") .addAnnotation(Autowired.class); javaClass.addField() .setPrivate() .setName("customizers") .setType("List<DataFormatCustomizer<" + model.getShortJavaType() + ">>") .addAnnotation(Autowired.class) .setLiteralValue("required", "false"); javaClass.addField() .setPrivate() .setName("globalConfiguration") .setType("DataFormatConfigurationProperties") .addAnnotation(Autowired.class); javaClass.addField() .setPrivate() .setName("dataformatConfiguration") .setType(configurationName) .addAnnotation(Autowired.class); javaClass.addMethod() .setConstructor(true) .setPublic() .setBody("super(ConfigurationPhase.REGISTER_BEAN);"); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("OnCamelContext") .setStatic(true) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelContext.class") .getOrigin() ); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("OnCamelAutoConfiguration") .setStatic(true) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelAutoConfiguration.class") .getOrigin() ); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("Condition") .setStatic(true) .extendSuperType(Roaster.create(JavaClassSource.class).setName("GroupCondition")) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelAutoConfiguration.class") .getOrigin() .addMethod() .setName("Condition") .setConstructor(true) .setPublic() .setBody("super(\"camel.dataformat\", \"camel.dataformat." + (overrideDataFormatName != null ? overrideDataFormatName : model.getName()).toLowerCase(Locale.US) + "\");") .getOrigin() ); String body = createDataFormatBody(model.getShortJavaType()); String methodName = "configure" + model.getShortJavaType() + "Factory"; MethodSource<JavaClassSource> method = javaClass.addMethod() .setName(methodName) .setPublic() .setBody(body) .setReturnType("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(size -> new String[size]); method.addAnnotation(Bean.class).setStringArrayValue("name", springBeanAliases); method.addAnnotation(ConditionalOnClass.class).setLiteralValue("value", "CamelContext.class"); method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue("value", model.getShortJavaType() + ".class"); sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); } private void createLanguageAutoConfigurationSource( String packageName, LanguageModel model, List<String> languageAliases, String overrideLanguageName) throws MojoFailureException { final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); int pos = model.getJavaType().lastIndexOf("."); String name = model.getJavaType().substring(pos + 1); name = name.replace("Language", "LanguageAutoConfiguration"); javaClass.setPackage(packageName).setName(name); String doc = "Generated by camel-package-maven-plugin - do not edit this file!"; javaClass.getJavaDoc().setFullText(doc); javaClass.extendSuperType(AllNestedConditions.class); javaClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); javaClass.addAnnotation(Configuration.class); javaClass.addAnnotation(Conditional.class).setLiteralValue(name + ".Condition.class"); javaClass.addAnnotation(AutoConfigureAfter.class).setLiteralValue("CamelAutoConfiguration.class"); String configurationName = name.replace("LanguageAutoConfiguration", "LanguageConfiguration"); javaClass.addAnnotation(EnableConfigurationProperties.class).setLiteralValue( "{ LanguageConfigurationProperties.class, " + configurationName + ".class }" ); javaClass.addImport(HashMap.class); javaClass.addImport(Map.class); javaClass.addImport("org.apache.camel.util.IntrospectionSupport"); javaClass.addImport(model.getJavaType()); javaClass.addImport(List.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.LanguageConfigurationProperties"); javaClass.addImport("org.apache.camel.spring.boot.util.GroupCondition"); javaClass.addImport("org.apache.camel.util.ObjectHelper"); javaClass.addImport("org.apache.camel.spi.LanguageCustomizer"); javaClass.addField() .setPrivate() .setStatic(true) .setFinal(true) .setName("LOGGER") .setType("Logger") .setLiteralInitializer("LoggerFactory.getLogger(" + name + ".class)"); javaClass.addField() .setPrivate() .setName("camelContext") .setType("CamelContext") .addAnnotation(Autowired.class); javaClass.addField() .setPrivate() .setName("customizers") .setType("List<LanguageCustomizer<" + model.getShortJavaType() + ">>") .addAnnotation(Autowired.class).setLiteralValue("required", "false"); javaClass.addField() .setPrivate() .setName("globalConfiguration") .setType("LanguageConfigurationProperties") .addAnnotation(Autowired.class); javaClass.addField() .setPrivate() .setName("languageConfiguration") .setType(configurationName) .addAnnotation(Autowired.class); javaClass.addMethod() .setConstructor(true) .setPublic() .setBody("super(ConfigurationPhase.REGISTER_BEAN);"); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("OnCamelContext") .setStatic(true) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelContext.class") .getOrigin() ); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("OnCamelAutoConfiguration") .setStatic(true) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelAutoConfiguration.class") .getOrigin() ); javaClass.addNestedType( Roaster.create(JavaClassSource.class) .setName("Condition") .setStatic(true) .extendSuperType(Roaster.create(JavaClassSource.class).setName("GroupCondition")) .addAnnotation(ConditionalOnBean.class) .setLiteralValue("CamelAutoConfiguration.class") .getOrigin() .addMethod() .setName("Condition") .setConstructor(true) .setPublic() .setBody("super(\"camel.component\", \"camel.component." + (overrideLanguageName != null ? overrideLanguageName : model.getName()).toLowerCase(Locale.US) + "\");") .getOrigin() ); String body = createLanguageBody(model.getShortJavaType()); String methodName = "configure" + model.getShortJavaType(); MethodSource<JavaClassSource> method = javaClass.addMethod() .setName(methodName) .setPublic() .setBody(body) .setReturnType(model.getShortJavaType()) .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(size -> new String[size]); method.addAnnotation(Bean.class).setStringArrayValue("name", springBeanAliases); method.addAnnotation(Scope.class).setStringValue("prototype"); method.addAnnotation(ConditionalOnClass.class).setLiteralValue("value", "CamelContext.class"); method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue("value", model.getShortJavaType() + ".class"); sortImports(javaClass); String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; writeSourceIfChanged(javaClass, fileName); } 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) { StringBuilder sb = new StringBuilder(); sb.append(shortJavaType).append(" component = new ").append(shortJavaType).append("();").append("\n"); sb.append("component.setCamelContext(camelContext);\n"); sb.append("\n"); sb.append("Map<String, Object> parameters = new HashMap<>();\n"); sb.append("IntrospectionSupport.getProperties(componentConfiguration, parameters, null, false);\n"); sb.append("\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(\"CAMEL_NESTED_CLASS\").get(null);\n"); sb.append(" HashMap<String, Object> nestedParameters = new HashMap<>();\n"); sb.append(" IntrospectionSupport.getProperties(value, nestedParameters, null, false);\n"); sb.append(" Object nestedProperty = nestedClass.newInstance();\n"); sb.append(" IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), nestedProperty, nestedParameters);\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("IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), component, parameters);\n"); sb.append("\n"); sb.append("boolean useCustomizers = globalConfiguration.getCustomizer().isEnabled() && componentConfiguration.getCustomizer().isEnabled();\n"); sb.append("\n"); sb.append("if (useCustomizers && ObjectHelper.isNotEmpty(customizers)) {\n"); sb.append(" for (ComponentCustomizer<").append(shortJavaType).append("> customizer : customizers) {\n"); sb.append(" LOGGER.debug(\"Configure component {}, with customizer {}\", 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) { StringBuilder sb = new StringBuilder(); sb.append("return new DataFormatFactory() {\n"); sb.append(" public DataFormat newInstance() {\n"); sb.append(" ").append(shortJavaType).append(" dataformat = new ").append(shortJavaType).append("();").append("\n"); sb.append(" if (CamelContextAware.class.isAssignableFrom(").append(shortJavaType).append(".class)) {\n"); sb.append(" CamelContextAware contextAware = CamelContextAware.class.cast(dataformat);\n"); sb.append(" if (contextAware != null) {\n"); sb.append(" contextAware.setCamelContext(camelContext);\n"); sb.append(" }\n"); sb.append(" }\n"); sb.append("\n"); sb.append(" try {\n"); sb.append(" Map<String, Object> parameters = new HashMap<>();\n"); sb.append(" IntrospectionSupport.getProperties(dataformatConfiguration, parameters, null, false);\n"); sb.append(" IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), dataformat, parameters);\n"); sb.append(" } catch (Exception e) {\n"); sb.append(" throw new RuntimeCamelException(e);\n"); sb.append(" }\n"); sb.append("\n"); sb.append("boolean useCustomizers = globalConfiguration.getCustomizer().isEnabled() && dataformatConfiguration.getCustomizer().isEnabled();\n"); sb.append("\n"); sb.append("if (useCustomizers && ObjectHelper.isNotEmpty(customizers)) {\n"); sb.append(" for (DataFormatCustomizer<").append(shortJavaType).append("> customizer : customizers) {\n"); sb.append(" LOGGER.debug(\"Configure dataformat {}, with customizer {}\", 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) { 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.cast(language);\n"); sb.append(" if (contextAware != null) {\n"); sb.append(" contextAware.setCamelContext(camelContext);\n"); sb.append(" }\n"); sb.append("}\n"); sb.append("\n"); sb.append("Map<String, Object> parameters = new HashMap<>();\n"); sb.append("IntrospectionSupport.getProperties(languageConfiguration, parameters, null, false);\n"); sb.append("IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), language, parameters);\n"); sb.append("\n"); sb.append("boolean useCustomizers = globalConfiguration.getCustomizer().isEnabled() && languageConfiguration.getCustomizer().isEnabled();\n"); sb.append("\n"); sb.append("if (useCustomizers && ObjectHelper.isNotEmpty(customizers)) {\n"); sb.append(" for (LanguageCustomizer<").append(shortJavaType).append("> customizer : customizers) {\n"); sb.append(" LOGGER.debug(\"Configure language {}, with customizer {}\", 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(Importer importer) { // sort imports List<Import> imports = importer.getImports(); // sort imports List<String> names = new ArrayList<>(); for (Import imp : imports) { names.add(imp.getQualifiedName()); } // sort Collections.sort(names, (s1, s2) -> { // java comes first if (s1.startsWith("java.")) { s1 = "___" + s1; } if (s2.startsWith("java.")) { s2 = "___" + s2; } // then javax comes next if (s1.startsWith("javax.")) { s1 = "__" + s1; } if (s2.startsWith("javax.")) { s2 = "__" + s2; } // org.w3c is for some odd reason also before others if (s1.startsWith("org.w3c.")) { s1 = "_" + s1; } if (s2.startsWith("org.w3c.")) { s2 = "_" + s2; } return s1.compareTo(s2); }); // remove all imports first for (String name : names) { importer.removeImport(name); } // and add them back in correct order for (String name : names) { importer.addImport(name); } } private static String loadModelJson(Set<File> jsonFiles, String modelName) { try { for (File file : jsonFiles) { if (file.getName().equals(modelName + ".json")) { String json = loadText(new FileInputStream(file)); boolean isModel = json.contains("\"kind\": \"model\""); if (isModel) { return json; } } } } catch (IOException e) { // ignore } return null; } private static String loadComponentJson(Set<File> jsonFiles, String componentName) { try { for (File file : jsonFiles) { if (file.getName().equals(componentName + ".json")) { String json = loadText(new FileInputStream(file)); boolean isComponent = json.contains("\"kind\": \"component\""); if (isComponent) { return json; } } } } catch (IOException e) { // ignore } return null; } private static String loadDataFormatJson(Set<File> jsonFiles, String dataFormatName) { try { for (File file : jsonFiles) { if (file.getName().equals(dataFormatName + ".json")) { String json = loadText(new FileInputStream(file)); boolean isDataFormat = json.contains("\"kind\": \"dataformat\""); if (isDataFormat) { return json; } } } } catch (IOException e) { // ignore } return null; } private static String loadLanguageJson(Set<File> jsonFiles, String languageName) { try { for (File file : jsonFiles) { if (file.getName().equals(languageName + ".json")) { String json = loadText(new FileInputStream(file)); boolean isLanguage = json.contains("\"kind\": \"language\""); if (isLanguage) { return json; } } } } catch (IOException e) { // ignore } return null; } private static ComponentModel generateComponentModel(String componentName, String json) { List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false); ComponentModel component = new ComponentModel(true); 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.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.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.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.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.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.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.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)); 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.setDefaultValue(getSafeValue("defaultValue", row)); option.setDescription(getSafeValue("description", row)); option.setEnums(getSafeValue("enums", row)); model.addOptionModel(option); } return model; } private List<String> findComponentNames() { List<String> componentNames = new ArrayList<String>(); for (Resource r : project.getBuild().getResources()) { File f = new File(r.getDirectory()); if (!f.exists()) { f = new File(project.getBasedir(), r.getDirectory()); } f = new File(f, "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); } } } } } return componentNames; } private List<String> findDataFormatNames() { List<String> dataFormatNames = new ArrayList<String>(); for (Resource r : project.getBuild().getResources()) { File f = new File(r.getDirectory()); if (!f.exists()) { f = new File(project.getBasedir(), r.getDirectory()); } 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<String>(); for (Resource r : project.getBuild().getResources()) { File f = new File(r.getDirectory()); if (!f.exists()) { f = new File(project.getBasedir(), r.getDirectory()); } 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(JavaClassSource source, String fileName) throws MojoFailureException { writeSourceIfChanged(source.toString(), fileName); } private void writeSourceIfChanged(String source, String fileName) throws MojoFailureException { source = Formatter.format(source); source = source.replaceAll("\\t", " "); File target = new File(SpringBootHelper.starterSrcDir(baseDir, project.getArtifactId()), fileName); deleteFileOnMainArtifact(target); try { InputStream is = getClass().getClassLoader().getResourceAsStream("license-header-java.txt"); String header = loadText(is); String code = source; code = header + code; getLog().debug("Source code generated:\n" + code); if (target.exists()) { String existing = FileUtils.readFileToString(target); if (!code.equals(existing)) { FileUtils.write(target, code, false); getLog().info("Updated existing file: " + target); } else { getLog().debug("No changes to existing file: " + target); } } else { FileUtils.write(target, code); getLog().info("Created file: " + target); } } 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, project.getArtifactId()), fileName); deleteFileOnMainArtifact(target); if (target.exists()) { try { // is the auto configuration already in the file boolean found = false; List<String> lines = FileUtils.readLines(target); 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(), 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); getLog().info("Created file: " + target); } catch (Exception e) { throw new MojoFailureException("IOError with file " + target, e); } } } /* private void writeAdditionalSpringMetaData(String prefix, String type, String name) throws MojoFailureException { String fullQualifiedName = prefix + "." + type + "." + name + "." + "enabled"; String fileName = "META-INF/additional-spring-configuration-metadata.json"; File target = new File(SpringBootHelper.starterResourceDir(baseDir, project.getArtifactId()), fileName); deleteFileOnMainArtifact(target); try { Gson gson = new GsonBuilder().setPrettyPrinting().create(); Map<String, Object> map = null; List<Map<String, Object>> properties = null; if (target.exists()) { BufferedReader br = new BufferedReader(new FileReader(target)); map = gson.fromJson(br, Map.class); properties = (List<Map<String, Object>>)map.get("properties"); if (properties != null && properties.stream().anyMatch(m -> fullQualifiedName.equals(m.get("name")))) { getLog().debug("No changes to existing file: " + target); return; } } Map<String, Object> meta = new HashMap(); meta.put("name", fullQualifiedName); meta.put("type", "java.lang.Boolean"); meta.put("defaultValue", true); meta.put("description", "Enable " + name + " " + type); if (properties == null) { properties = new ArrayList<>(1); } if (map == null) { map = new HashMap(); } properties.add(meta); map.put("properties", properties); FileUtils.write(target, gson.toJson(map)); } catch (Exception e) { throw new MojoFailureException("IOError with file " + target, e); } } */ private void deleteFileOnMainArtifact(File starterFile) { if (!DELETE_FILES_ON_MAIN_ARTIFACTS) { return; } String relativePath = SpringBootHelper.starterDir(baseDir, project.getArtifactId()).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); } } } private JavaClassSource createConditionType(JavaClassSource parentClass, String prefix, String type) { parentClass.addImport(ConditionMessage.class); parentClass.addImport(ConditionContext.class); parentClass.addImport(ConditionOutcome.class); parentClass.addImport(RelaxedPropertyResolver.class); parentClass.addImport(AnnotatedTypeMetadata.class); parentClass.addImport(SpringBootCondition.class); JavaClassSource condition = Roaster.create(JavaClassSource.class); condition.setName("Condition"); condition.extendSuperType(SpringBootCondition.class); condition.setPublic(); condition.setStatic(true); condition.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); String fullQualifiedType = prefix.endsWith(".") ? prefix + type : prefix + "." + type; MethodSource<JavaClassSource> isEnabled = condition.addMethod(); isEnabled.setName("isEnabled"); isEnabled.setPrivate(); isEnabled.addParameter(ConditionContext.class, "context"); isEnabled.addParameter(String.class, "prefix"); isEnabled.addParameter(boolean.class, "defaultValue"); isEnabled.setReturnType(boolean.class); isEnabled.setBody(new StringBuilder() .append("RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment(), prefix);\n") .append("return resolver.getProperty(\"enabled\", Boolean.class, defaultValue);") .toString() ); MethodSource<JavaClassSource> matchMethod = condition.getMethod("getMatchOutcome", ConditionContext.class, AnnotatedTypeMetadata.class); matchMethod.setBody(new StringBuilder() .append("boolean groupEnabled = isEnabled(conditionContext, \"").append(prefix).append(".\", true);\n") .append("ConditionMessage.Builder message = ConditionMessage.forCondition(\"").append(fullQualifiedType).append("\");\n") .append("if (isEnabled(conditionContext, \"").append(fullQualifiedType).append(".\", groupEnabled)) {\n") .append(" return ConditionOutcome.match(message.because(\"enabled\"));\n") .append("}\n") .append("return ConditionOutcome.noMatch(message.because(\"not enabled\"));\n") .toString() ); return condition; } }