blob: 2af371bac0a7647d6a881784c7fd1de83c0601a5 [file] [log] [blame]
/*
* 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.kafkaconnector.maven;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import javax.annotation.Generated;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import freemarker.template.Template;
import org.apache.camel.kafkaconnector.maven.dto.CamelKafkaConnectorModel;
import org.apache.camel.kafkaconnector.maven.dto.CamelKafkaConnectorOptionModel;
import org.apache.camel.kafkaconnector.maven.utils.MavenUtils;
import org.apache.camel.maven.packaging.MvelHelper;
import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.tooling.util.Strings;
import org.apache.camel.tooling.util.srcgen.JavaClass;
import org.apache.camel.tooling.util.srcgen.Method;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.execution.MavenSession;
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.codehaus.plexus.resource.loader.FileResourceCreationException;
import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
import org.mvel2.templates.TemplateRuntime;
import static org.apache.camel.kafkaconnector.maven.utils.MavenUtils.sanitizeMavenArtifactId;
import static org.apache.camel.kafkaconnector.maven.utils.MavenUtils.writeFileIfChanged;
import static org.apache.camel.kafkaconnector.maven.utils.MavenUtils.writeXmlFormatted;
import static org.apache.camel.tooling.util.PackageHelper.loadText;
import static org.apache.camel.tooling.util.PackageHelper.writeText;
/**
* Generate Camel Kafka Connector for the component
*/
@Mojo(name = "camel-kafka-connector-update", threadSafe = true,
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
public class CamelKafkaConnectorUpdateMojo extends AbstractCamelKafkaConnectorMojo {
private static final String GENERATED_SECTION_START = "START OF GENERATED CODE";
private static final String GENERATED_SECTION_START_COMMENT = "<!--" + GENERATED_SECTION_START + "-->";
private static final String GENERATED_SECTION_END = "END OF GENERATED CODE";
private static final String GENERATED_SECTION_END_COMMENT = "<!--" + GENERATED_SECTION_END + "-->";
private static final String EXCLUDE_DEPENDENCY_PROPERTY_PREFIX = "exclude_";
private static final String ADDITIONAL_COMMON_PROPERTIES_PROPERTY_PREFIX = "additional_properties_";
private static final String XML_FEATURES_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
private static final Map<String, Class<?>> PRIMITIVEMAP;
private static final Map<String, String> CONFIGDEFMAP;
private static final Map<String, String> RESERVEDWORDSUBSTITUTIONMAP;
static {
PRIMITIVEMAP = new HashMap<>();
PRIMITIVEMAP.put("boolean", Boolean.class);
PRIMITIVEMAP.put("long", Long.class);
PRIMITIVEMAP.put("int", Integer.class);
PRIMITIVEMAP.put("short", Short.class);
PRIMITIVEMAP.put("double", Double.class);
PRIMITIVEMAP.put("float", Float.class);
CONFIGDEFMAP = new HashMap<>();
CONFIGDEFMAP.put("boolean", "ConfigDef.Type.BOOLEAN");
CONFIGDEFMAP.put("long", "ConfigDef.Type.LONG");
CONFIGDEFMAP.put("int", "ConfigDef.Type.INT");
CONFIGDEFMAP.put("short", "ConfigDef.Type.SHORT");
CONFIGDEFMAP.put("double", "ConfigDef.Type.DOUBLE");
CONFIGDEFMAP.put("float", "ConfigDef.Type.DOUBLE");
RESERVEDWORDSUBSTITUTIONMAP = new HashMap<>();
RESERVEDWORDSUBSTITUTIONMAP.put("class", "clazz");
}
protected DynamicClassLoader projectClassLoader;
@Parameter(property = "name", required = true)
protected String name;
@Parameter(property = "componentJson", required = true)
protected String componentJson;
/**
* The maven session.
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;
/**
* A comma separated list of column separated GAV to include as dependencies
* to the generated camel kafka connector. (i.e.
* groupId:ArtifactId:version,groupId_2:ArtifactId_2:version_2)
*/
@Parameter(defaultValue = "", readonly = true)
private String additionalDependencies;
@Override
protected String getMainDepArtifactId() {
return "camel-" + name;
}
@Override
protected void executeAll() throws MojoFailureException {
if (name == null || name.isEmpty()) {
throw new MojoFailureException("Connector name must be specified as the parameter");
}
if (name.startsWith("camel-")) {
name = name.substring("camel-".length());
}
if (name.endsWith(KAFKA_CONNECTORS_SUFFIX)) {
name = name.substring(0, name.length() - KAFKA_CONNECTORS_SUFFIX.length());
}
try {
updateConnector();
} catch (Exception e) {
throw new MojoFailureException("Fail to update connector " + name, e);
}
}
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 void updateConnector() throws Exception {
String sanitizedName = sanitizeMavenArtifactId(name);
// create the starter directory
File connectorDir = new File(projectDir, "camel-" + sanitizedName + KAFKA_CONNECTORS_SUFFIX);
if (!connectorDir.exists() || !connectorDir.isDirectory()) {
getLog().info("Connector " + name + " can not be updated since directory " + connectorDir.getAbsolutePath() + " dose not exist.");
throw new MojoFailureException("Directory already exists: " + connectorDir);
}
// create the base pom.xml
Document pom = createBasePom(connectorDir);
// Apply changes to the starter pom
fixExcludedDependencies(pom);
fixAdditionalDependencies(pom, additionalDependencies);
fixAdditionalRepositories(pom);
// Write the starter pom
File pomFile = new File(connectorDir, "pom.xml");
writeXmlFormatted(pom, pomFile, getLog());
// write package
Document pkg = createPackageFile();
File pkgFile = new File(connectorDir, "src/main/assembly/package.xml");
writeXmlFormatted(pkg, pkgFile, getLog());
// write LICENSE, USAGE
writeStaticFiles(connectorDir);
// generate classes
ComponentModel model = JsonMapper.generateComponentModel(componentJson);
if (model.isConsumerOnly()) {
createClasses(sanitizedName, connectorDir, model, ConnectorType.SOURCE);
} else if (model.isProducerOnly()) {
createClasses(sanitizedName, connectorDir, model, ConnectorType.SINK);
} else {
createClasses(sanitizedName, connectorDir, model, ConnectorType.SOURCE);
createClasses(sanitizedName, connectorDir, model, ConnectorType.SINK);
}
}
private void fixExcludedDependencies(Document pom) throws Exception {
// add dependencies to be excluded form camel component dependency
Set<String> loggingImpl = new HashSet<>();
// excluded dependencies
Set<String> configExclusions = new HashSet<>();
Properties properties = new Properties();
properties.load(new FileInputStream(rm.getResourceAsFile(fixDependenciesProperties)));
String artExcl = properties.getProperty(EXCLUDE_DEPENDENCY_PROPERTY_PREFIX + getMainDepArtifactId());
getLog().debug("Configured exclusions: " + artExcl);
if (artExcl != null && artExcl.trim().length() > 0) {
for (String dep : artExcl.split(",")) {
getLog().debug("Adding configured exclusion: " + dep);
configExclusions.add(dep);
}
}
Set<String> libsToRemove = new TreeSet<>();
libsToRemove.addAll(loggingImpl);
libsToRemove.addAll(configExclusions);
if (libsToRemove.size() > 0) {
getLog().info("Camel-kafka-connector: the following dependencies will be removed from the connector: " + libsToRemove);
MavenUtils.addExclusionsToDependency(pom, getMainDepArtifactId(), libsToRemove, GENERATED_SECTION_START, GENERATED_SECTION_END);
}
}
private void fixAdditionalDependencies(Document pom, String additionalDependencies) throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream(rm.getResourceAsFile(fixDependenciesProperties)));
Set<String> deps = new TreeSet<>();
deps.addAll(MavenUtils.csvToSet(properties.getProperty(getMainDepArtifactId())));
deps.addAll(MavenUtils.csvToSet(additionalDependencies));
Set<String> globalProps = MavenUtils.csvToSet(properties.getProperty("global"));
boolean inGlobal = false;
for (String gp : globalProps) {
String camelGav = getMainDepGroupId() + ":" + getMainDepArtifactId();
String camelKafkaConnectorGav = project.getGroupId() + ":" + project.getArtifactId();
if (gp.equals(camelGav) || gp.equals(camelKafkaConnectorGav)) {
inGlobal = true;
break;
}
}
if (!inGlobal) {
// add global properties for all modules not in global properties
deps.addAll(globalProps);
}
if (deps.size() > 0) {
getLog().debug("The following dependencies will be added to the starter: " + deps);
MavenUtils.addDependencies(pom, deps, GENERATED_SECTION_START, GENERATED_SECTION_END);
}
}
private void fixAdditionalRepositories(Document pom) throws Exception {
if (project.getFile() != null) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
dbf.setFeature(XML_FEATURES_DISALLOW_DOCTYPE_DECL, true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document originalPom = builder.parse(project.getFile());
XPath xpath = XPathFactory.newInstance().newXPath();
Node repositories = (Node)xpath.compile("/project/repositories").evaluate(originalPom, XPathConstants.NODE);
if (repositories != null) {
pom.getDocumentElement().appendChild(pom.createComment(GENERATED_SECTION_START));
pom.getDocumentElement().appendChild(pom.importNode(repositories, true));
pom.getDocumentElement().appendChild(pom.createComment(GENERATED_SECTION_END));
}
} else {
getLog().warn("Cannot access the project pom file to retrieve repositories");
}
}
private Document createPackageFile() throws ResourceNotFoundException, FileResourceCreationException, IOException {
getLog().info("Creating a new package.xml for the connector.");
Template packageTemplate = MavenUtils.getTemplate(rm.getResourceAsFile(packageFileTemplate));
Map<String, String> props = new HashMap<>();
try {
return MavenUtils.createCrateXmlDocumentFromTemplate(packageTemplate, props);
} catch (Exception e) {
getLog().error("Cannot create package.xml file from Template: " + packageTemplate + " with properties: " + props, e);
}
return null;
}
private Document createBasePom(File connectorDir) throws IOException, SAXException, ParserConfigurationException {
File pomFile = new File(connectorDir, "pom.xml");
if (pomFile.exists()) {
try (InputStream in = new FileInputStream(pomFile)) {
String content = IOUtils.toString(in, StandardCharsets.UTF_8);
boolean editablePom = content.contains(GENERATED_SECTION_START_COMMENT);
if (editablePom) {
content = MavenUtils.removeGeneratedSections(content, GENERATED_SECTION_START_COMMENT, GENERATED_SECTION_END_COMMENT, 10);
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document pom;
try (InputStream contentIn = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
pom = builder.parse(contentIn);
}
getLog().debug("Reusing the existing pom.xml for the starter");
return pom;
} else {
getLog().error("Cannot use the existing pom.xml file since it is not editable. It does not contain " + GENERATED_SECTION_START_COMMENT);
throw new UnsupportedOperationException("Cannot use the existing pom.xml file since it is not editable. It does not contain "
+ GENERATED_SECTION_START_COMMENT);
}
}
} else {
getLog().error("The pom.xml file is not present, please use camel-kafka-connector-create first.");
throw new UnsupportedOperationException("The pom.xml file is not present, please use camel-kafka-connector-create first.");
}
}
private void writeStaticFiles(File connectorDir) throws IOException, ResourceNotFoundException, FileResourceCreationException {
String notice;
String license;
try (InputStream isNotice = new FileInputStream(rm.getResourceAsFile(noticeTemplate)); InputStream isLicense = new FileInputStream(rm.getResourceAsFile(licenseTemplate))) {
notice = IOUtils.toString(isNotice, StandardCharsets.UTF_8);
license = IOUtils.toString(isLicense, StandardCharsets.UTF_8);
}
writeFileIfChanged(notice, new File(connectorDir, "src/main/resources/META-INF/NOTICE.txt"), getLog());
writeFileIfChanged(license, new File(connectorDir, "src/main/resources/META-INF/LICENSE.txt"), getLog());
}
private String getComponentId() {
String componentName = getMainDepArtifactId();
String componentId = componentName.replace("camel-", "");
return componentId;
}
private void createClasses(String sanitizedName, File connectorDir, ComponentModel model, ConnectorType ct)
throws MojoFailureException, ResourceNotFoundException, FileResourceCreationException, IOException, MojoExecutionException {
String ctCapitalizedName = StringUtils.capitalize(ct.name().toLowerCase());
String ctLowercaseName = ct.name().toLowerCase();
String packageName = "org.apache.camel.kafkaconnector." + RESERVEDWORDSUBSTITUTIONMAP.getOrDefault(sanitizedName.replace("-", ""), sanitizedName.replace("-", ""));
Map<String, String> additionalProperties = new HashMap<>();
Properties properties = new Properties();
properties.load(new FileInputStream(rm.getResourceAsFile(fixDependenciesProperties)));
String commonPropertyValue = properties.getProperty(ADDITIONAL_COMMON_PROPERTIES_PROPERTY_PREFIX + getMainDepArtifactId());
getLog().debug("Additional common connector properties: " + commonPropertyValue);
addProperties(additionalProperties, commonPropertyValue);
String sourceOrSinkPropertyValue = properties.getProperty(ADDITIONAL_COMMON_PROPERTIES_PROPERTY_PREFIX + ctLowercaseName + "_" + getMainDepArtifactId());
getLog().debug("Additional " + ctLowercaseName + " connector properties: " + sourceOrSinkPropertyValue);
addProperties(additionalProperties, sourceOrSinkPropertyValue);
// Camel{sanitizedName}{Sink,Source}ConnectorConfig.java
String javaClassConnectorConfigName = "Camel" + StringUtils.capitalize(sanitizedName.replace("-", "")) + ctCapitalizedName + "ConnectorConfig";
final JavaClass javaClassConnectorConfig = new JavaClass(getProjectClassLoader());
javaClassConnectorConfig.setPackage(packageName);
javaClassConnectorConfig.setName(javaClassConnectorConfigName);
javaClassConnectorConfig.addAnnotation(Generated.class)
.setStringValue("value", "This class has been generated by camel-kafka-connector-generator-maven-plugin, remove this annotation to prevent it from being generated.");
javaClassConnectorConfig.extendSuperType("Camel" + ctCapitalizedName + "ConnectorConfig");
javaClassConnectorConfig.addImport("java.util.Map");
javaClassConnectorConfig.addImport("org.apache.camel.kafkaconnector.Camel" + ctCapitalizedName + "ConnectorConfig");
javaClassConnectorConfig.addImport("org.apache.kafka.common.config.ConfigDef");
javaClassConnectorConfig.addMethod().setConstructor(true).setName(javaClassConnectorConfigName).addParameter("ConfigDef", "config")
.addParameter("Map<String, String>", "parsedConfig").setPublic().setBody("super(config, parsedConfig);");
javaClassConnectorConfig.addMethod().setConstructor(true).setName(javaClassConnectorConfigName).addParameter("Map<String, String>", "parsedConfig").setPublic()
.setBody("this(conf(), parsedConfig);");
Method confMethod = javaClassConnectorConfig.addMethod().setConstructor(false).setName("conf").addParameter("Map<String, String>", "parsedConfig")
.setReturnType("ConfigDef").setPublic().setStatic().setBody("ConfigDef conf = new ConfigDef(Camel" + ctCapitalizedName + "ConnectorConfig.conf());\n");
Predicate<? super BaseOptionModel> filterEndpointOptions;
switch (ct) {
case SINK:
filterEndpointOptions = new Predicate<BaseOptionModel>() {
@Override
public boolean test(BaseOptionModel optionModel) {
return optionModel.getLabel() == null || optionModel.getLabel().contains("producer")
|| (!optionModel.getLabel().contains("producer") && !optionModel.getLabel().contains("consumer"));
}
};
break;
case SOURCE:
filterEndpointOptions = new Predicate<BaseOptionModel>() {
@Override
public boolean test(BaseOptionModel optionModel) {
return optionModel.getLabel() == null || optionModel.getLabel().contains("consumer")
|| (!optionModel.getLabel().contains("producer") && !optionModel.getLabel().contains("consumer"));
}
};
break;
default:
throw new UnsupportedOperationException("Connector type not supported: " + ct + " must be one of " + ConnectorType.SINK + ", " + ConnectorType.SOURCE);
}
List<CamelKafkaConnectorOptionModel> listOptions = new ArrayList<CamelKafkaConnectorOptionModel>();
model.getEndpointPathOptions().stream().filter(filterEndpointOptions)
.forEachOrdered(epo -> addConnectorOptions(sanitizedName, ct, javaClassConnectorConfig, confMethod, "PATH", ctLowercaseName, "path", epo, listOptions));
model.getEndpointParameterOptions().stream().filter(filterEndpointOptions)
.forEachOrdered(epo -> addConnectorOptions(sanitizedName, ct, javaClassConnectorConfig, confMethod, "ENDPOINT", ctLowercaseName, "endpoint", epo, listOptions));
model.getComponentOptions().stream().filter(filterEndpointOptions)
.forEachOrdered(co -> addConnectorOptions(sanitizedName, ct, javaClassConnectorConfig, confMethod, "COMPONENT", "component", sanitizedName, co, listOptions));
confMethod.setBody(confMethod.getBody() + "return conf;");
String javaClassConnectorConfigFileName = packageName.replaceAll("\\.", "\\/") + "/" + javaClassConnectorConfigName + ".java";
MavenUtils.writeSourceIfChanged(javaClassConnectorConfig, javaClassConnectorConfigFileName, false, connectorDir, rm.getResourceAsFile(javaFilesHeader));
// Camel{sanitizedName}{Sink,Source}Task.java
String javaClassTaskName = "Camel" + StringUtils.capitalize(sanitizedName.replace("-", "")) + ctCapitalizedName + "Task";
final JavaClass javaClassTask = new JavaClass(getProjectClassLoader());
javaClassTask.setPackage(packageName);
javaClassTask.setName(javaClassTaskName);
javaClassTask.addAnnotation(Generated.class)
.setStringValue("value", "This class has been generated by camel-kafka-connector-generator-maven-plugin, remove this annotation to prevent it from being generated.");
javaClassTask.extendSuperType("Camel" + ctCapitalizedName + "Task");
javaClassTask.addImport("java.util.HashMap");
javaClassTask.addImport("java.util.Map");
javaClassTask.addImport("org.apache.camel.kafkaconnector.Camel" + ctCapitalizedName + "ConnectorConfig");
javaClassTask.addImport("org.apache.camel.kafkaconnector.Camel" + ctCapitalizedName + "Task");
javaClassTask.addMethod().setConstructor(false).setName("getCamel" + ctCapitalizedName + "ConnectorConfig").setProtected().addParameter("Map<String, String>", "props")
.setReturnType("Camel" + ctCapitalizedName + "ConnectorConfig")
.setBody("return new Camel" + StringUtils.capitalize(sanitizedName.replace("-", "")) + ctCapitalizedName + "ConnectorConfig(props);").addAnnotation(Override.class);
Method getDefaultConfigMethod = javaClassTask.addMethod().setConstructor(false).setName("getDefaultConfig").setProtected().setReturnType("Map<String, String>")
.setBody("return new HashMap<String, String>() {{\n");
getDefaultConfigMethod
.setBody(getDefaultConfigMethod.getBody() + " put(Camel" + ctCapitalizedName + "ConnectorConfig.CAMEL_" + ct + "_COMPONENT_CONF, \"" + model.getScheme() + "\");\n");
for (String key : new TreeSet<String>(additionalProperties.keySet())) {
getDefaultConfigMethod.setBody(getDefaultConfigMethod.getBody() + " put(\"" + key + "\", \"" + additionalProperties.get(key) + "\");\n");
}
getDefaultConfigMethod.setBody(getDefaultConfigMethod.getBody() + "}};\n");
getDefaultConfigMethod.addAnnotation(Override.class);
String javaClassTaskFileName = packageName.replaceAll("\\.", "\\/") + "/" + javaClassTaskName + ".java";
MavenUtils.writeSourceIfChanged(javaClassTask, javaClassTaskFileName, false, connectorDir, rm.getResourceAsFile(javaFilesHeader));
// Camel{sanitizedName}{Sink,Source}Connector.java
String javaClassConnectorName = "Camel" + StringUtils.capitalize(sanitizedName.replace("-", "")) + ctCapitalizedName + "Connector";
final JavaClass javaClassConnector = new JavaClass(getProjectClassLoader());
javaClassConnector.setPackage(packageName);
javaClassConnector.setName(javaClassConnectorName);
javaClassConnector.addAnnotation(Generated.class)
.setStringValue("value", "This class has been generated by camel-kafka-connector-generator-maven-plugin, remove this annotation to prevent it from being generated.");
javaClassConnector.extendSuperType("Camel" + ctCapitalizedName + "Connector");
javaClassConnector.addImport("org.apache.camel.kafkaconnector.Camel" + ctCapitalizedName + "Connector");
javaClassConnector.addImport("org.apache.kafka.common.config.ConfigDef");
javaClassConnector.addImport("org.apache.kafka.connect.connector.Task");
javaClassConnector.addMethod().setConstructor(false).setName("config").setPublic().setReturnType("ConfigDef")
.setBody("return Camel" + StringUtils.capitalize(sanitizedName.replace("-", "")) + ctCapitalizedName + "ConnectorConfig.conf();").addAnnotation(Override.class);
javaClassConnector.addMethod().setConstructor(false).setName("taskClass").setPublic().setReturnType("Class<? extends Task>")
.setBody("return Camel" + StringUtils.capitalize(sanitizedName.replace("-", "")) + ctCapitalizedName + "Task.class;").addAnnotation(Override.class);
String javaClassConnectorFileName = packageName.replaceAll("\\.", "\\/") + "/" + javaClassConnectorName + ".java";
MavenUtils.writeSourceIfChanged(javaClassConnector, javaClassConnectorFileName, false, connectorDir, rm.getResourceAsFile(javaFilesHeader));
File docFolder = new File(connectorDir, "src/main/docs/");
File docFile = new File(docFolder, getMainDepArtifactId() + "-kafka-" + ct.name().toLowerCase() + "-connector.adoc");
File docFolderWebsite = new File(projectBaseDir, "docs/modules/ROOT/pages/connectors/");
File docFileWebsite = new File(docFolderWebsite, getMainDepArtifactId() + "-kafka-" + ct.name().toLowerCase() + "-connector.adoc");
String changed = templateAutoConfigurationOptions(listOptions, getMainDepArtifactId(), connectorDir, ct);
boolean updated = updateAutoConfigureOptions(docFile, changed);
if (updated) {
getLog().info("Updated doc file: " + docFile);
} else {
getLog().debug("No changes to doc file: " + docFile);
}
boolean updatedWebsite = updateAutoConfigureOptions(docFileWebsite, changed);
if (updatedWebsite) {
getLog().info("Updated website doc file: " + docFileWebsite);
} else {
getLog().debug("No changes to website doc file: " + docFileWebsite);
}
}
private void addProperties(Map<String, String> additionalProperties, String additionalProp) {
if (additionalProp != null && additionalProp.trim().length() > 0) {
for (String prop : additionalProp.split(",")) {
getLog().debug("Additional property before key value split: " + prop);
String[] keyValue = prop.split("=");
getLog().debug("Additional property key value: " + keyValue);
additionalProperties.put(keyValue[0], keyValue[1]);
}
}
}
private void addConnectorOptions(String sanitizedName, ConnectorType ct, JavaClass javaClass, Method confMethod, String propertyQualifier, String firstNamespace,
String secondNamespace, BaseOptionModel epo, List<CamelKafkaConnectorOptionModel> listOptions) {
String propertyName = epo.getName();
String regex = "([A-Z][a-z]+)";
String replacement = "$1_";
String propertyPrefix = "CAMEL_" + ct + "_" + sanitizedName.replace("-", "").toUpperCase() + "_" + propertyQualifier.toUpperCase() + "_"
+ StringUtils.capitalize(propertyName).replaceAll(regex, replacement).toUpperCase();
String propertyValue = "camel." + firstNamespace + "." + secondNamespace + "." + epo.getName();
String confFieldName = propertyPrefix + "CONF";
javaClass.addField().setFinal(true).setPublic().setStatic(true).setName(confFieldName).setType(String.class).setStringInitializer(propertyValue);
String docFieldName = propertyPrefix + "DOC";
String docLiteralInitializer = epo.getDescription();
if (epo.getEnums() != null && !epo.getEnums().isEmpty()) {
docLiteralInitializer = docLiteralInitializer + " One of:";
String enumOptionListing = epo.getEnums().stream().reduce("", (s, s2) -> s + " [" + s2 + "]");
docLiteralInitializer = docLiteralInitializer + enumOptionListing;
}
javaClass.addField().setFinal(true).setPublic().setStatic(true).setName(docFieldName).setType(String.class).setStringInitializer(docLiteralInitializer);
String defaultFieldName = propertyPrefix + "DEFAULT";
Class<?> defaultValueClass = PRIMITIVEMAP.getOrDefault(epo.getShortJavaType(), String.class);
String defaultValueClassLiteralInitializer = epo.getDefaultValue() == null ? "null" : epo.getDefaultValue().toString();
if (!defaultValueClassLiteralInitializer.equals("null") && defaultValueClass.equals(String.class)) {
defaultValueClassLiteralInitializer = "\"" + defaultValueClassLiteralInitializer + "\"";
} else if (!defaultValueClassLiteralInitializer.equals("null") && defaultValueClass.equals(Long.class)) {
defaultValueClassLiteralInitializer = defaultValueClassLiteralInitializer + "L";
} else if (!defaultValueClassLiteralInitializer.equals("null") && defaultValueClass.equals(Float.class)) {
defaultValueClassLiteralInitializer = defaultValueClassLiteralInitializer + "F";
} else if (!defaultValueClassLiteralInitializer.equals("null") && defaultValueClass.equals(Double.class)) {
defaultValueClassLiteralInitializer = defaultValueClassLiteralInitializer + "D";
}
javaClass.addField().setFinal(true).setPublic().setStatic(true).setName(defaultFieldName).setType(defaultValueClass)
.setLiteralInitializer(defaultValueClassLiteralInitializer);
String confType = CONFIGDEFMAP.getOrDefault(epo.getShortJavaType(), "ConfigDef.Type.STRING");
String confPriority = epo.isDeprecated() ? "ConfigDef.Importance.LOW" : "ConfigDef.Importance.MEDIUM";
confPriority = epo.isRequired() ? "ConfigDef.Importance.HIGH" : confPriority;
confMethod.setBody(confMethod.getBody() + "conf.define(" + confFieldName + ", " + confType + ", " + defaultFieldName + ", " + confPriority + ", " + docFieldName + ");\n");
CamelKafkaConnectorOptionModel optionModel = new CamelKafkaConnectorOptionModel();
optionModel.setName(propertyValue);
optionModel.setDescription(docLiteralInitializer);
optionModel.setPriority(StringUtils.removeStart(confPriority, "ConfigDef.Importance."));
optionModel.setDefaultValue(defaultValueClassLiteralInitializer);
listOptions.add(optionModel);
}
private String templateAutoConfigurationOptions(List<CamelKafkaConnectorOptionModel> options, String componentName, File connectorDir, ConnectorType ct)
throws MojoExecutionException {
CamelKafkaConnectorModel model = new CamelKafkaConnectorModel();
model.setOptions(options);
model.setArtifactId(getMainDepArtifactId());
model.setGroupId(getMainDepGroupId());
model.setVersion(getMainDepVersion());
if (getMainDepArtifactId().equalsIgnoreCase("camel-coap+tcp")) {
model.setTitle("camel-coap-tcp");
} else if (getMainDepArtifactId().equalsIgnoreCase("camel-coaps+tcp")) {
model.setTitle("camel-coaps-tcp");
} else {
model.setTitle(getMainDepArtifactId());
}
try {
String template = null;
if (ct.name().equals(ConnectorType.SINK.name())) {
template = loadText(CamelKafkaConnectorUpdateMojo.class.getClassLoader().getResourceAsStream("camel-kafka-connector-sink-options.mvel"));
} else if (ct.name().equals(ConnectorType.SOURCE.name())) {
template = loadText(CamelKafkaConnectorUpdateMojo.class.getClassLoader().getResourceAsStream("camel-kafka-connector-source-options.mvel"));
}
String out = (String)TemplateRuntime.eval(template, model, Collections.singletonMap("util", MvelHelper.INSTANCE));
return out;
} catch (Exception e) {
throw new MojoExecutionException("Error processing mvel template. Reason: " + e, e);
}
}
private boolean updateAutoConfigureOptions(File file, String changed) throws MojoExecutionException {
try {
if (!file.exists()) {
// include markers for new files
changed = "// kafka-connector options: START\n" + changed + "\n// kafka-connector options: END\n";
writeText(file, changed);
return true;
}
String text = loadText(new FileInputStream(file));
String existing = Strings.between(text, "// kafka-connector options: START", "// kafka-connector options: END");
if (existing != null) {
// remove leading line breaks etc
existing = existing.trim();
changed = changed.trim();
if (existing.equals(changed)) {
return false;
} else {
String before = Strings.before(text, "// kafka-connector options: START");
String after = Strings.after(text, "// kafka-connector options: END");
text = before + "// kafka-connector options: START\n" + changed + "\n// kafka-connector options: END" + after;
writeText(file, text);
return true;
}
} else {
getLog().warn("Cannot find markers in file " + file);
getLog().warn("Add the following markers");
getLog().warn("\t// kafka-connector options: START");
getLog().warn("\t// kafka-connector options: END");
return false;
}
} catch (Exception e) {
throw new MojoExecutionException("Error reading file " + file + " Reason: " + e, e);
}
}
private enum ConnectorType {
SINK, SOURCE
}
}