CAMEL-11196: Camel connectors - Allow to configure in one place and let it figure out component vs endpoint level
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/3dd29006 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/3dd29006 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/3dd29006 Branch: refs/heads/master Commit: 3dd29006c3aa1c9612dc25461b64a4845e31138d Parents: 000e09a Author: lburgazzoli <lburgazz...@gmail.com> Authored: Wed May 10 18:20:48 2017 +0200 Committer: lburgazzoli <lburgazz...@gmail.com> Committed: Mon May 15 17:06:47 2017 +0200 ---------------------------------------------------------------------- .../apache/camel/util/IntrospectionSupport.java | 5 + connectors/camel-connector-maven-plugin/pom.xml | 7 + .../SpringBootAutoConfigurationMojo.java | 236 ++++++++++++++----- .../maven/connector/model/ComponentModel.java | 11 +- .../connector/model/ComponentOptionModel.java | 128 +--------- .../connector/model/EndpointOptionModel.java | 215 +++++++++++++++++ .../maven/connector/model/OptionModel.java | 146 ++++++++++++ .../src/main/docs/connector-component.adoc | 56 ++++- .../component/connector/ConnectorComponent.java | 39 ++- .../component/connector/ConnectorModel.java | 76 ++++++ .../connector/DefaultConnectorComponent.java | 199 ++++++++++------ .../PetStoreConnectorAutoConfiguration.java | 41 +++- .../PetStoreConnectorConfiguration.java | 33 +-- .../PetStoreConnectorConfigurationCommon.java | 61 +++++ connectors/examples/pom.xml | 2 + ...UpsertContactConnectorAutoConfiguration.java | 41 +++- ...orceUpsertContactConnectorConfiguration.java | 73 +----- ...sertContactConnectorConfigurationCommon.java | 113 +++++++++ .../examples/twitter-mention-connector/pom.xml | 2 + ...witterMentionConnectorAutoConfiguration.java | 41 +++- .../TwitterMentionConnectorConfiguration.java | 62 +---- ...tterMentionConnectorConfigurationCommon.java | 78 ++++++ .../examples/twitter-search-connector/pom.xml | 188 +++++++++++++++ .../org/foo/search/TwitterSearchComponent.java | 30 +++ ...TwitterSearchConnectorAutoConfiguration.java | 93 ++++++++ .../TwitterSearchConnectorConfiguration.java | 38 +++ ...itterSearchConnectorConfigurationCommon.java | 116 +++++++++ .../org/apache/camel/component/twitter-search | 18 ++ .../main/resources/META-INF/spring.factories | 19 ++ .../main/resources/camel-connector-schema.json | 30 +++ .../src/main/resources/camel-connector.json | 25 ++ .../springbot/TwitterSearchConnectorTest.java | 65 +++++ .../src/test/resources/application.properties | 7 + .../src/test/resources/logback.xml | 36 +++ .../examples/twitter-search-example/README.md | 18 ++ .../examples/twitter-search-example/pom.xml | 112 +++++++++ .../java/org/foo/TwitterSearchApplication.java | 32 +++ .../main/java/org/foo/TwitterSearchRoute.java | 29 +++ .../src/main/resources/META-INF/LICENSE.txt | 203 ++++++++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../src/main/resources/application.properties | 29 +++ 41 files changed, 2345 insertions(+), 419 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/camel-core/src/main/java/org/apache/camel/util/IntrospectionSupport.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/IntrospectionSupport.java b/camel-core/src/main/java/org/apache/camel/util/IntrospectionSupport.java index 185ef3b..21384cc 100755 --- a/camel-core/src/main/java/org/apache/camel/util/IntrospectionSupport.java +++ b/camel-core/src/main/java/org/apache/camel/util/IntrospectionSupport.java @@ -635,6 +635,11 @@ public final class IntrospectionSupport { return false; } + public static boolean setProperty(CamelContext context, Object target, String name, Object value) throws Exception { + // allow build pattern as a setter as well + return setProperty(context, context != null ? context.getTypeConverter() : null, target, name, value, null, true); + } + public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value) throws Exception { // allow build pattern as a setter as well return setProperty(context, typeConverter, target, name, value, null, true); http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector-maven-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/connectors/camel-connector-maven-plugin/pom.xml b/connectors/camel-connector-maven-plugin/pom.xml index 2e8bb3b..cbb2c84 100644 --- a/connectors/camel-connector-maven-plugin/pom.xml +++ b/connectors/camel-connector-maven-plugin/pom.xml @@ -85,6 +85,13 @@ <version>${spring-boot-version}</version> </dependency> + <!-- guava for some word wrap helper --> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>${google-guava-version}</version> + </dependency> + <!-- logging --> <dependency> <groupId>org.slf4j</groupId> http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/SpringBootAutoConfigurationMojo.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/SpringBootAutoConfigurationMojo.java b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/SpringBootAutoConfigurationMojo.java index 997878a..c8ca459 100644 --- a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/SpringBootAutoConfigurationMojo.java +++ b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/SpringBootAutoConfigurationMojo.java @@ -21,13 +21,18 @@ import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import javax.annotation.Generated; +import javax.annotation.PostConstruct; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.maven.connector.model.ComponentModel; import org.apache.camel.maven.connector.model.ComponentOptionModel; +import org.apache.camel.maven.connector.model.EndpointOptionModel; +import org.apache.camel.maven.connector.model.OptionModel; import org.apache.camel.maven.connector.util.JSonSchemaHelper; import org.apache.commons.io.FileUtils; import org.apache.maven.plugin.AbstractMojo; @@ -44,10 +49,13 @@ 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.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -89,7 +97,8 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { String javaType = null; String connectorScheme = null; - List<String> componentOptions = null; + List<String> componentOptions = Collections.emptyList(); + List<String> endpointOptions = Collections.emptyList(); File file = new File(classesDirectory, "camel-connector.json"); if (file.exists()) { @@ -99,6 +108,7 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { javaType = (String) dto.get("javaType"); connectorScheme = (String) dto.get("scheme"); componentOptions = (List) dto.get("componentOptions"); + endpointOptions = (List) dto.get("endpointOptions"); } // find the component dependency and get its .json file @@ -119,7 +129,7 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { if (hasOptions) { getLog().info("Generating Spring Boot AutoConfiguration for Connector: " + model.getScheme()); - createConnectorConfigurationSource(pkg, model, javaType, connectorScheme, componentOptions); + createConnectorConfigurationSource(pkg, model, javaType, connectorScheme, componentOptions, endpointOptions); createConnectorAutoConfigurationSource(pkg, hasOptions, javaType, connectorScheme); createConnectorSpringFactorySource(pkg, javaType); } @@ -165,23 +175,29 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { } } + + private void createConnectorConfigurationSource(String packageName, ComponentModel model, String javaType, - String connectorScheme, List<String> componentOptions) throws MojoFailureException { - final JavaClassSource javaClass = Roaster.create(JavaClassSource.class); + String connectorScheme, List<String> componentOptions, List<String> endpointOptions) throws MojoFailureException { - int pos = javaType.lastIndexOf("."); - String name = javaType.substring(pos + 1); - name = name.replace("Component", "ConnectorConfiguration"); - javaClass.setPackage(packageName).setName(name); + final int pos = javaType.lastIndexOf("."); + final String commonName = javaType.substring(pos + 1).replace("Component", "ConnectorConfigurationCommon"); + final String configName = javaType.substring(pos + 1).replace("Component", "ConnectorConfiguration"); - String doc = "Generated by camel-connector-maven-plugin - do not edit this file!"; + // 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; } // replace Component with Connector doc = doc.replaceAll("Component", "Connector"); doc = doc.replaceAll("component", "connector"); - javaClass.getJavaDoc().setFullText(doc); + commonClass.getJavaDoc().setFullText(doc); + commonClass.addAnnotation(Generated.class).setStringValue("value", SpringBootAutoConfigurationMojo.class.getName()); // compute the configuration prefix to use with spring boot configuration String prefix = ""; @@ -194,53 +210,53 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { } prefix += connectorScheme.toLowerCase(Locale.US); - javaClass.addAnnotation("org.springframework.boot.context.properties.ConfigurationProperties").setStringValue("prefix", prefix); - - for (ComponentOptionModel option : model.getComponentOptions()) { + for (OptionModel option : model.getComponentOptions()) { + boolean isComponentOption= componentOptions != null && componentOptions.stream().anyMatch(o -> o.equals(option.getName())); + boolean isEndpointOption = endpointOptions != null && endpointOptions.stream().anyMatch(o -> o.equals(option.getName())); - // only include the options that has been explicit configured in the camel-connector.json file - boolean accepted = false; - if (componentOptions != null) { - accepted = componentOptions.stream().anyMatch(o -> o.equals(option.getName())); + // only include the options that has been explicit configured in the + // componentOptions section of camel-connector.json file and exclude + // those configured on endpointOptions in the same file + if (isComponentOption && !isEndpointOption) { + addProperty(commonClass, model, option); } + } - if (accepted) { - String type = option.getJavaType(); - 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.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()); - } - } + for (OptionModel option : model.getEndpointOptions()) { + if (endpointOptions != null && endpointOptions.stream().anyMatch(o -> o.equals(option.getName()))) { + addProperty(commonClass, model, option); } } + sortImports(commonClass); + writeSourceIfChanged(commonClass, packageName.replaceAll("\\.", "\\/") + "/" + commonName + ".java"); - sortImports(javaClass); + // Config class + 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(ConfigurationProperties.class).setStringValue("prefix", prefix); + configClass.addImport(Map.class); + configClass.addImport(HashMap.class); + configClass.removeImport(commonClass); - String fileName = packageName.replaceAll("\\.", "\\/") + "/" + name + ".java"; - writeSourceIfChanged(javaClass, fileName); + 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;"); + + + sortImports(configClass); + writeSourceIfChanged(configClass, packageName.replaceAll("\\.", "\\/") + "/" + configName + ".java"); } private void createConnectorAutoConfigurationSource(String packageName, boolean hasOptions, @@ -251,12 +267,15 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { int pos = javaType.lastIndexOf("."); String name = javaType.substring(pos + 1); name = name.replace("Component", "ConnectorAutoConfiguration"); + final String configNameCommon = javaType.substring(pos + 1).replace("Component", "ConnectorConfigurationCommon"); + final String configName = javaType.substring(pos + 1).replace("Component", "ConnectorConfiguration"); javaClass.setPackage(packageName).setName(name); String doc = "Generated by camel-connector-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(AutoConfigureAfter.class).setStringValue("name", "org.apache.camel.spring.boot.CamelAutoConfiguration"); @@ -272,32 +291,44 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { } javaClass.addImport(javaType); + javaClass.addImport(BeanCreationException.class); javaClass.addImport("org.apache.camel.CamelContext"); + javaClass.addField() + .setName("camelContext") + .setType("org.apache.camel.CamelContext") + .setPrivate() + .addAnnotation(Autowired.class); + javaClass.addField() + .setName("configuration") + .setType(configName) + .setPrivate() + .addAnnotation(Autowired.class); + // add method for auto configure String shortJavaType = getShortJavaType(javaType); - String body = createComponentBody(shortJavaType, hasOptions); - String methodName = "configure" + shortJavaType; + // must be named -component because camel-spring-boot uses that to lookup components + String beanName = connectorScheme + "-component"; - MethodSource<JavaClassSource> method = javaClass.addMethod() - .setName(methodName) + MethodSource<JavaClassSource> configureMethod = javaClass.addMethod() + .setName("configure" + shortJavaType) .setPublic() - .setBody(body) + .setBody(createComponentBody(shortJavaType, hasOptions)) .setReturnType(shortJavaType) .addThrows(Exception.class); - method.addParameter("CamelContext", "camelContext"); + configureMethod.addAnnotation(Lazy.class); + configureMethod.addAnnotation(Bean.class).setStringValue("name", beanName); + configureMethod.addAnnotation(ConditionalOnClass.class).setLiteralValue("value", "CamelContext.class"); + configureMethod.addAnnotation(ConditionalOnMissingBean.class).setStringValue("name", beanName); - if (hasOptions) { - method.addParameter(configurationName, "configuration"); - } + MethodSource<JavaClassSource> postProcessMethod = javaClass.addMethod() + .setName("postConstruct" + shortJavaType) + .setPublic() + .setBody(createPostConstructBody(shortJavaType, configNameCommon)); - // must be named -component because camel-spring-boot uses that to lookup components - String beanName = connectorScheme + "-component"; - method.addAnnotation(Lazy.class); - method.addAnnotation(Bean.class).setStringValue("name", beanName); - method.addAnnotation(ConditionalOnClass.class).setLiteralValue("value", "CamelContext.class"); - method.addAnnotation(ConditionalOnMissingBean.class).setLiteralValue("value", javaType + ".class"); + + postProcessMethod.addAnnotation(PostConstruct.class); sortImports(javaClass); @@ -353,6 +384,31 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { return sb.toString(); } + private static String createPostConstructBody(String shortJavaType, String commonConfigurationName) { + StringBuilder sb = new StringBuilder(); + sb.append("if (camelContext != null) {\n"); + sb.append("Map<String, Object> parameters = new HashMap<>();\n"); + sb.append("\n"); + sb.append("for (Map.Entry<String, " + commonConfigurationName + "> entry : configuration.getConfigurations().entrySet()) {\n"); + sb.append("parameters.clear();\n"); + sb.append("\n"); + sb.append(shortJavaType).append(" connector = new ").append(shortJavaType).append("();\n"); + sb.append("connector.setCamelContext(camelContext);\n"); + sb.append("\n"); + sb.append("try {\n"); + sb.append("IntrospectionSupport.getProperties(entry.getValue(), parameters, null, false);\n"); + sb.append("IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), connector, parameters);\n"); + sb.append("connector.setComponentOptions(parameters);\n"); + sb.append("\n"); + sb.append("camelContext.addComponent(entry.getKey(), connector);\n"); + sb.append("} catch (Exception e) {\n"); + sb.append("throw new BeanCreationException(entry.getKey(), e.getMessage(), e);\n"); + sb.append("}\n"); + sb.append("}\n"); + sb.append("}\n"); + return sb.toString(); + } + private static void sortImports(JavaClassSource javaClass) { // sort imports List<Import> imports = javaClass.getImports(); @@ -439,7 +495,59 @@ public class SpringBootAutoConfigurationMojo extends AbstractMojo { 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 void addProperty(JavaClassSource clazz, ComponentModel model, OptionModel option) { + String type = option.getJavaType(); + PropertySource<JavaClassSource> prop = clazz.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.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()); + clazz.addImport(model.getJavaType()); + } + } + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentModel.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentModel.java b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentModel.java index 2cacb78..0d61b14 100644 --- a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentModel.java +++ b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentModel.java @@ -37,7 +37,8 @@ public class ComponentModel { private String groupId; private String artifactId; private String version; - private final List<ComponentOptionModel> componentOptions = new ArrayList<ComponentOptionModel>(); + private final List<ComponentOptionModel> componentOptions = new ArrayList<>(); + private final List<EndpointOptionModel> endpointOptions = new ArrayList<>(); public String getKind() { return kind; @@ -175,6 +176,14 @@ public class ComponentModel { componentOptions.add(option); } + public List<EndpointOptionModel> getEndpointOptions() { + return endpointOptions; + } + + public void addEndpointOption(EndpointOptionModel option) { + endpointOptions.add(option); + } + public String getShortJavaType() { if (javaType.startsWith("java.util.Map")) { return "Map"; http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentOptionModel.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentOptionModel.java b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentOptionModel.java index 96b5598..a97cf53 100644 --- a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentOptionModel.java +++ b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/ComponentOptionModel.java @@ -16,131 +16,5 @@ */ package org.apache.camel.maven.connector.model; -public class ComponentOptionModel { - - private String name; - private String displayName; - private String kind; - private String group; - private String required; - private String type; - private String javaType; - private String deprecated; - private String secret; - private String description; - private String defaultValue; - private String enums; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public String getKind() { - return kind; - } - - public void setKind(String kind) { - this.kind = kind; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getRequired() { - return required; - } - - public void setRequired(String required) { - this.required = required; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getJavaType() { - return javaType; - } - - public void setJavaType(String javaType) { - this.javaType = javaType; - } - - public String getDeprecated() { - return deprecated; - } - - public void setDeprecated(String deprecated) { - this.deprecated = deprecated; - } - - public String getSecret() { - return secret; - } - - public void setSecret(String secret) { - this.secret = secret; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getDefaultValue() { - return defaultValue; - } - - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - public String getEnums() { - return enums; - } - - public void setEnums(String enums) { - this.enums = enums; - } - - public String getShortJavaType() { - if (javaType.startsWith("java.util.Map")) { - return "Map"; - } else if (javaType.startsWith("java.util.Set")) { - return "Set"; - } else if (javaType.startsWith("java.util.List")) { - return "List"; - } - int pos = javaType.lastIndexOf("."); - if (pos != -1) { - return javaType.substring(pos + 1); - } else { - return javaType; - } - } - +public class ComponentOptionModel extends OptionModel { } http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/EndpointOptionModel.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/EndpointOptionModel.java b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/EndpointOptionModel.java new file mode 100644 index 0000000..07d536a --- /dev/null +++ b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/EndpointOptionModel.java @@ -0,0 +1,215 @@ +/** + * 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.connector.model; + +import com.google.common.base.CaseFormat; + +public class EndpointOptionModel extends OptionModel { + private String prefix; + private String multiValue; + private String enumValues; + + // special for documentation rendering + private boolean newGroup; + + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getMultiValue() { + return multiValue; + } + + public void setMultiValue(String multiValue) { + this.multiValue = multiValue; + } + + public String getEnumValues() { + return enumValues; + } + + public void setEnumValues(String enumValues) { + this.enumValues = enumValues; + } + + public boolean isNewGroup() { + return newGroup; + } + + public void setNewGroup(boolean newGroup) { + this.newGroup = newGroup; + } + + @Override + public String getShortJavaType() { + // TODO: use watermark in the others + return getShortJavaType(40); + } + + public String getShortJavaType(int watermark) { + String group = getGroup(); + String type = getType(); + String javaType = getJavaType(); + + if (javaType.startsWith("java.util.Map")) { + return "Map"; + } else if (javaType.startsWith("java.util.Set")) { + return "Set"; + } else if (javaType.startsWith("java.util.List")) { + return "List"; + } + + String text = javaType; + + int pos = text.lastIndexOf("."); + if (pos != -1) { + text = text.substring(pos + 1); + } + + // if its some kind of java object then lets wrap it as its long + if ("object".equals(type)) { + text = wrapCamelCaseWords(text, watermark, " "); + } + return text; + } + + public String getShortGroup() { + String group = getGroup(); + + if (group.endsWith(" (advanced)")) { + return group.substring(0, group.length() - 11); + } + return group; + } + + public String getShortDefaultValue(int watermark) { + String defaultValue = getDefaultValue(); + + if (defaultValue.isEmpty()) { + return ""; + } + String text = defaultValue; + if (text.endsWith("<T>")) { + text = text.substring(0, text.length() - 3); + } else if (text.endsWith("<T>>")) { + text = text.substring(0, text.length() - 4); + } + + // TODO: dirty hack for AUTO_ACKNOWLEDGE which we should wrap + if ("AUTO_ACKNOWLEDGE".equals(text)) { + return "AUTO_ ACKNOWLEDGE"; + } + + return text; + } + + public String getShortName(int watermark) { + String text = wrapCamelCaseWords(getName(), watermark, " "); + // ensure the option name starts with lower-case + return Character.toLowerCase(text.charAt(0)) + text.substring(1); + } + + /** + * To wrap long camel cased texts by words. + * + * @param option the option which is camel cased. + * @param watermark a watermark to denote the size to cut after + * @param newLine the new line to use when breaking into a new line + */ + private String wrapCamelCaseWords(String option, int watermark, String newLine) { + String text = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, option); + text = text.replace('-', ' '); + text = wrapWords(text, "\n", watermark, false); + text = text.replace(' ', '-'); + text = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, text); + + // upper case first char on each line + String[] lines = text.split("\n"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + line = Character.toUpperCase(line.charAt(0)) + line.substring(1); + sb.append(line); + if (i < lines.length - 1) { + sb.append(newLine); + } + } + return sb.toString(); + } + + /** + * To wrap a big line by words. + * + * @param line the big line + * @param newLine the new line to use when breaking into a new line + * @param watermark a watermark to denote the size to cut after + * @param wrapLongWords whether to wrap long words + */ + private String wrapWords(String line, String newLine, int watermark, boolean wrapLongWords) { + if (line == null) { + return null; + } else { + if (newLine == null) { + newLine = System.lineSeparator(); + } + + if (watermark < 1) { + watermark = 1; + } + + int inputLineLength = line.length(); + int offset = 0; + StringBuilder sb = new StringBuilder(inputLineLength + 32); + + while (inputLineLength - offset > watermark) { + if (line.charAt(offset) == 32) { + ++offset; + } else { + int spaceToWrapAt = line.lastIndexOf(32, watermark + offset); + if (spaceToWrapAt >= offset) { + sb.append(line.substring(offset, spaceToWrapAt)); + sb.append(newLine); + offset = spaceToWrapAt + 1; + } else if (wrapLongWords) { + sb.append(line.substring(offset, watermark + offset)); + sb.append(newLine); + offset += watermark; + } else { + spaceToWrapAt = line.indexOf(32, watermark + offset); + if (spaceToWrapAt >= 0) { + sb.append(line.substring(offset, spaceToWrapAt)); + sb.append(newLine); + offset = spaceToWrapAt + 1; + } else { + sb.append(line.substring(offset)); + offset = inputLineLength; + } + } + } + } + + sb.append(line.substring(offset)); + return sb.toString(); + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/OptionModel.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/OptionModel.java b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/OptionModel.java new file mode 100644 index 0000000..64d8002 --- /dev/null +++ b/connectors/camel-connector-maven-plugin/src/main/java/org/apache/camel/maven/connector/model/OptionModel.java @@ -0,0 +1,146 @@ +/** + * 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.connector.model; + +public class OptionModel { + + private String name; + private String displayName; + private String kind; + private String group; + private String required; + private String type; + private String javaType; + private String deprecated; + private String secret; + private String description; + private String defaultValue; + private String enums; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getRequired() { + return required; + } + + public void setRequired(String required) { + this.required = required; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getJavaType() { + return javaType; + } + + public void setJavaType(String javaType) { + this.javaType = javaType; + } + + public String getDeprecated() { + return deprecated; + } + + public void setDeprecated(String deprecated) { + this.deprecated = deprecated; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public String getEnums() { + return enums; + } + + public void setEnums(String enums) { + this.enums = enums; + } + + public String getShortJavaType() { + if (javaType.startsWith("java.util.Map")) { + return "Map"; + } else if (javaType.startsWith("java.util.Set")) { + return "Set"; + } else if (javaType.startsWith("java.util.List")) { + return "List"; + } + int pos = javaType.lastIndexOf("."); + if (pos != -1) { + return javaType.substring(pos + 1); + } else { + return javaType; + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector/src/main/docs/connector-component.adoc ---------------------------------------------------------------------- diff --git a/connectors/camel-connector/src/main/docs/connector-component.adoc b/connectors/camel-connector/src/main/docs/connector-component.adoc index f3f1e67..a5ea535 100644 --- a/connectors/camel-connector/src/main/docs/connector-component.adoc +++ b/connectors/camel-connector/src/main/docs/connector-component.adoc @@ -42,6 +42,7 @@ You can also copy the existing examples from the `connectors` directory where th - `foo-connector` - A connector that is based on the Timer component from `camel-core`. - `bar-connector` - A connector that is based on the 3rd party `beverage-component`. - `wine-connector` - Another connector that is based on the 3rd party `beverage-component`. +- 'twitter-search' - A connector based on twitter to search for keywords. You can find an example using these connectors in the `foo-bar-wine-example` in the `connectors` directory. @@ -111,14 +112,18 @@ And these following Maven plugins: ### Spring Boot compliant -A Camel connector works great with Spring Boot. If a connector has been configured to allow configuration - on the component level which is done in the `camel-connector.json` file, such as +A Camel connector works great with Spring Boot. If a connector has been configured to allow configuration on the component and/por endpoint level which is done in the `camel-connector.json` file, such as - "componentOptions" : [ "loginUrl", "clientId", "clientSecret", "refreshToken" ], + "componentOptions" : [ "consumerKey", "consumerSecret", "accessToken", "accessTokenSecret" ], + "endpointOptions" : [ "keywords" ], Then the `camel-connector-maven-plugin` will be able to generate Spring Boot auto configuration for these options. This allows users to essily configure these options if using Spring Boot with the connector. +NOTE: There's no disctinction between component/endpoint options on spring-boot auto configuration side so all the options are flattered to a single namespace thus they appear as connector options and then the conenctor figures out where they should be applied. + +TIP: The options configured through spring-boot auto configuration classes can be overridden by standard endpoint options. + To enable Spring Boot generation you need to enable this in the `camel-connector-maven-plugin` by adding an `<execution>` with the `prepare-spring-boot-auto-configuration` goal: @@ -157,6 +162,50 @@ You will also need to add the `spring-boot-configuration-processor` in the Maven </dependency> ------------ +An example on how to configure a component in spring boot can be taken from the twitter-search connector: + +[source] +------------ + camel.connector.twitter-search.consumer-key = ... + camel.connector.twitter-search.consumer-secret = ... + camel.connector.twitter-search.access-token = ... + camel.connector.twitter-search.access-token-secret = ... + camel.connector.twitter-search.keywords = apache-camel +------------ + +You may need to instantiate multiple instance of the connector i.e. to use different logins and this is now possible through the 'configurations' parameters: + +[source] +------------ + camel.connector.twitter-search.configurations.tw-search1.consumer-key = ... + camel.connector.twitter-search.configurations.tw-search1.consumer-secret = ... + camel.connector.twitter-search.configurations.tw-search1.access-token = ... + camel.connector.twitter-search.configurations.tw-search1.access-token-secret = ... + camel.connector.twitter-search.configurations.tw-search1.keywords = apache-camel + + camel.connector.twitter-search.configurations.tw-search2.consumer-key = ... + camel.connector.twitter-search.configurations.tw-search2.consumer-secret = ... + camel.connector.twitter-search.configurations.tw-search2.access-token = ... + camel.connector.twitter-search.configurations.tw-search2.access-token-secret = ... + camel.connector.twitter-search.configurations.tw-search2.keywords = apache-karaf +------------ + +This would create two instances of the twitter-search connector each one configured with its own list o options. +You can no use the nre connector as standard components like: + +[source,java] +------------ + @Component + public class MyRouteBuilder implements RouteBuilder{ + public void configure() throws Exception { + from("tw-search1") + .log("On account 1 I got: ${body}") + from("tw-search2") + .log("On account 2 I got: ${body}") + } + } +------------ + ### Input and Output Data Type @@ -192,4 +241,3 @@ The options the connector can provide is a limited set of all the existing optio its based upon. Each option can then also be pre-configured with a default-value. To understand this schema file, its easier to study those existing connectors from the `connectors` directory. - http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorComponent.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorComponent.java b/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorComponent.java index 901ed0b..b25ff70 100644 --- a/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorComponent.java +++ b/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorComponent.java @@ -68,20 +68,42 @@ public interface ConnectorComponent extends Component { String getCamelConnectorJSon(); /** - * A set of additional component options to use for the base component when creating connector endpoints. + * A set of additional component/endpoint options to use for the base component when creating connector endpoints. + * + * @deprecated use {@link #getOptions()} instead + */ + @Deprecated + default Map<String, Object> getComponentOptions() { + return getOptions(); + } + + /** + * A set of additional component/endpoint options to use for the base component when creating connector endpoints. + */ + Map<String, Object> getOptions(); + + /** + * A set of additional component/endpoint options to use for the base component when creating connector endpoints. + * + * @deprecated use {@link #setOptions(Map)} instead */ - Map<String, Object> getComponentOptions(); + default void setComponentOptions(Map<String, Object> options) { + setOptions(options); + } /** - * A set of additional component options to use for the base component when creating connector endpoints. + * A set of additional component/endpoint options to use for the base component when creating connector endpoints. */ - void setComponentOptions(Map<String, Object> baseComponentOptions); + void setOptions(Map<String, Object> options); /** * To perform custom processing before the producer is sending the message. */ void setBeforeProducer(Processor processor); + /** + * Gets the processor used to perform custom processing before the producer is sending the message. + */ Processor getBeforeProducer(); /** @@ -89,6 +111,9 @@ public interface ConnectorComponent extends Component { */ void setAfterProducer(Processor processor); + /** + * Gets the processor used to perform custom processing after the producer has sent the message and received any reply (if InOut). + */ Processor getAfterProducer(); /** @@ -96,6 +121,9 @@ public interface ConnectorComponent extends Component { */ void setBeforeConsumer(Processor processor); + /** + * Gets the processor used to perform custom processing when the consumer has just received a new incoming message. + */ Processor getBeforeConsumer(); /** @@ -103,5 +131,8 @@ public interface ConnectorComponent extends Component { */ void setAfterConsumer(Processor processor); + /** + * Gets the processor used to perform custom processing when the consumer is about to send back a reply message to the caller (if InOut). + */ Processor getAfterConsumer(); } http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorModel.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorModel.java b/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorModel.java index 2165d41..8d409f7 100644 --- a/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorModel.java +++ b/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/ConnectorModel.java @@ -61,6 +61,8 @@ final class ConnectorModel { private DataType outputDataType; private Map<String, String> defaultComponentOptions; private Map<String, String> defaultEndpointOptions; + private List<String> endpointOptions; + private List<String> componentOptions; ConnectorModel(String componentName, String className) { this.componentName = componentName; @@ -132,6 +134,22 @@ final class ConnectorModel { return defaultEndpointOptions; } + public List<String> getEndpointOptions() { + if (endpointOptions == null) { + endpointOptions = Collections.unmodifiableList(extractEndpointOptions(lines.get())); + } + + return endpointOptions; + } + + public List<String> getComponentOptions() { + if (endpointOptions == null) { + endpointOptions = Collections.unmodifiableList(extractComponentOptions(lines.get())); + } + + return endpointOptions; + } + public DataType getInputDataType() { if (inputDataType == null) { String line = extractInputDataType(lines.get()); @@ -334,4 +352,62 @@ final class ConnectorModel { return answer; } + + private List<String> extractComponentOptions(List<String> lines) { + List<String> answer = new ArrayList<>(); + + // extract the default options + for (String line : lines) { + line = line.trim(); + if (line.startsWith("\"componentOptions\"")) { + int start = line.indexOf('['); + if (start == -1) { + throw new IllegalStateException("Malformed camel-connector.json"); + } + + int end = line.indexOf(']', start); + if (end == -1) { + throw new IllegalStateException("Malformed camel-connector.json"); + } + + line = line.substring(start + 1, end).trim(); + for (String option : line.split(",")) { + answer.add(StringHelper.removeLeadingAndEndingQuotes(option)); + } + + break; + } + } + + return answer; + } + + private List<String> extractEndpointOptions(List<String> lines) { + List<String> answer = new ArrayList<>(); + + // extract the default options + for (String line : lines) { + line = line.trim(); + if (line.startsWith("\"endpointOptions\"")) { + int start = line.indexOf('['); + if (start == -1) { + throw new IllegalStateException("Malformed camel-connector.json"); + } + + int end = line.indexOf(']', start); + if (end == -1) { + throw new IllegalStateException("Malformed camel-connector.json"); + } + + line = line.substring(start + 1, end).trim(); + for (String option : line.split(",")) { + answer.add(StringHelper.removeLeadingAndEndingQuotes(option)); + } + + break; + } + } + + return answer; + } } http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/DefaultConnectorComponent.java ---------------------------------------------------------------------- diff --git a/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/DefaultConnectorComponent.java b/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/DefaultConnectorComponent.java index 192ebf1..50d1407 100644 --- a/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/DefaultConnectorComponent.java +++ b/connectors/camel-connector/src/main/java/org/apache/camel/component/connector/DefaultConnectorComponent.java @@ -20,13 +20,18 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import org.apache.camel.CamelContext; import org.apache.camel.Component; import org.apache.camel.ComponentVerifier; import org.apache.camel.Endpoint; +import org.apache.camel.NoTypeConversionAvailableException; import org.apache.camel.Processor; import org.apache.camel.VerifiableComponent; import org.apache.camel.catalog.CamelCatalog; @@ -35,6 +40,7 @@ import org.apache.camel.impl.DefaultComponent; import org.apache.camel.impl.verifier.ResultBuilder; import org.apache.camel.impl.verifier.ResultErrorBuilder; import org.apache.camel.util.IntrospectionSupport; +import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.URISupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,20 +54,35 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme private final Logger log = LoggerFactory.getLogger(getClass()); private final CamelCatalog catalog = new DefaultCamelCatalog(false); + private final String baseScheme; private final String componentName; + private final String componentScheme; private final ConnectorModel model; - private Map<String, Object> componentOptions; + private Map<String, Object> options; private Processor beforeProducer; private Processor afterProducer; private Processor beforeConsumer; private Processor afterConsumer; protected DefaultConnectorComponent(String componentName, String className) { - this.componentName = componentName; this.model = new ConnectorModel(componentName, className); + this.baseScheme = this.model.getBaseScheme(); + this.componentName = componentName; + this.componentScheme = componentName + "-component"; // add to catalog this.catalog.addComponent(componentName, className); + + // It may be a custom component so we need to register this in the camel catalog also + if (!catalog.findComponentNames().contains(baseScheme)) { + catalog.addComponent(baseScheme, model.getBaseJavaType()); + } + + // Add an alias for the base component so there's no clash between connectors + // if they set options targeting the component. + if (!catalog.findComponentNames().contains(componentScheme)) { + this.catalog.addComponent(componentScheme, this.model.getBaseJavaType(), catalog.componentJSonSchema(baseScheme)); + } } @Override @@ -78,16 +99,10 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme // grab the regular query parameters Map<String, String> options = buildEndpointOptions(remaining, parameters); - String scheme = model.getBaseScheme(); - - // now create the endpoint instance which either happens with a new - // base component which has been pre-configured for this connector - // or we fallback and use the default component in the camel context - createNewBaseComponent(scheme); - // create the uri of the base component - String delegateUri = createEndpointUri(scheme, options); + String delegateUri = createEndpointUri(componentScheme, options); Endpoint delegate = getCamelContext().getEndpoint(delegateUri); + if (log.isInfoEnabled()) { // the uris can have sensitive information so sanitize log.info("Connector resolved: {} -> {}", sanitizeUri(uri), sanitizeUri(delegateUri)); @@ -119,7 +134,10 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme @Override public void addConnectorOption(Map<String, String> options, String name, String value) { log.trace("Adding option: {}={}", name, value); - options.put(name, value); + Object val = options.put(name, value); + if (val != null) { + log.debug("Options {} overridden, old value was {}", name, val); + } } @Override @@ -142,48 +160,60 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme return componentName; } - public Map<String, Object> getComponentOptions() { - return componentOptions; + @Override + public Map<String, Object> getOptions() { + return options; } - public void setComponentOptions(Map<String, Object> baseComponentOptions) { - this.componentOptions = baseComponentOptions; + @Override + public void setOptions(Map<String, Object> baseComponentOptions) { + // Copy the map so if the given map is externally modified the connector + // is not impacted. + this.options = Collections.unmodifiableMap(new HashMap<>(baseComponentOptions)); } @SuppressWarnings("unchecked") @Override public ComponentVerifier getVerifier() { - final String scheme = model.getBaseScheme(); - // only get or create component but do NOT start it as component - final Component component = getCamelContext().getComponent(scheme, true, false); + try { + // Create the component but no need to add it to the camel context + // nor to start it. + final Component component = createNewBaseComponent(); + + if (component instanceof VerifiableComponent) { + return (scope, map) -> { + Map<String, Object> options; + + try { + // A little nasty hack required as verifier uses Map<String, Object> + // to be compatible with all the methods in CamelContext whereas + // catalog deals with Map<String, String> + options = (Map) buildEndpointOptions(null, map); + } catch (URISyntaxException | NoTypeConversionAvailableException e) { + // If a failure is detected while reading the catalog, wrap it + // and stop the validation step. + return ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.OK, scope) + .error(ResultErrorBuilder.withException(e).build()) + .build(); + } - if (component instanceof VerifiableComponent) { - return (scope, map) -> { - Map<String, Object> options; - - try { - // A little nasty hack required as verifier uses Map<String, Object> - // to be compatible with all the methods in CamelContext whereas - // catalog deals with Map<String, String> - options = (Map) buildEndpointOptions(null, map); - } catch (URISyntaxException e) { - // If a failure is detected while reading the catalog, wrap it - // and stop the validation step. - return ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.OK, scope) - .error(ResultErrorBuilder.withException(e).build()) + return ((VerifiableComponent) component).getVerifier().verify(scope, options); + }; + } else { + return (scope, map) -> { + return ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.UNSUPPORTED, scope) + .error( + ResultErrorBuilder.withCode(ComponentVerifier.VerificationError.StandardCode.UNSUPPORTED) + .detail("camel_connector_name", getConnectorName()) + .detail("camel_component_name", getComponentName()) + .build()) .build(); - } - - return ((VerifiableComponent)component).getVerifier().verify(scope, options); - }; - } else { + }; + } + } catch (Exception e) { return (scope, map) -> { - return ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.UNSUPPORTED, scope) - .error( - ResultErrorBuilder.withCode(ComponentVerifier.VerificationError.StandardCode.UNSUPPORTED) - .detail("camel_connector_name", getConnectorName()) - .detail("camel_component_name", getComponentName()) - .build()) + return ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.OK, scope) + .error(ResultErrorBuilder.withException(e).build()) .build(); }; } @@ -194,7 +224,6 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme @Override protected void doStart() throws Exception { // lets enforce that every connector must have an input and output data type - if (model.getInputDataType() == null) { throw new IllegalArgumentException("Camel connector must have inputDataType defined in camel-connector.json file"); } @@ -208,11 +237,13 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme throw new IllegalArgumentException("Camel connector must have baseJavaType defined in camel-connector.json file"); } - // it may be a custom component so we need to register this in the camel catalog also - String scheme = model.getBaseScheme(); - if (!catalog.findComponentNames().contains(scheme)) { - String javaType = model.getBaseJavaType(); - catalog.addComponent(scheme, javaType); + Component component = createNewBaseComponent(); + if (component != null) { + getCamelContext().removeComponent(this.componentScheme); + + // ensure component is started and stopped when Camel shutdown + getCamelContext().addService(component, true, true); + getCamelContext().addComponent(this.componentScheme, component); } log.debug("Starting connector: {}", componentName); @@ -269,8 +300,16 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme // Helpers // *************************************** - private Component createNewBaseComponent(String scheme) throws Exception { - String baseClassName = model.getBaseJavaType(); + /** + * Create the endpoint instance which either happens with a new base component + * which has been pre-configured for this connector or we fallback and use + * the default component in the camel context + */ + private Component createNewBaseComponent() throws Exception { + final String baseClassName = model.getBaseJavaType(); + final CamelContext context = getCamelContext(); + + Component base = null; if (baseClassName != null) { // create a new instance of this base component @@ -278,45 +317,46 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme Constructor ctr = getPublicDefaultConstructor(type); if (ctr != null) { // call default no-arg constructor - Object base = ctr.newInstance(); + base = (Component)ctr.newInstance(); + base.setCamelContext(context); // the connector may have default values for the component level also - // and if so we need to prepare these values and set on this component before we can start + // and if so we need to prepare these values and set on this component + // before we can start Map<String, String> defaultOptions = model.getDefaultComponentOptions(); if (!defaultOptions.isEmpty()) { - Map<String, Object> copy = new LinkedHashMap<>(); for (Map.Entry<String, String> entry : defaultOptions.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (value != null) { // also support {{ }} placeholders so resolve those first value = getCamelContext().resolvePropertyPlaceholders(value); + log.debug("Using component option: {}={}", key, value); - copy.put(key, value); + IntrospectionSupport.setProperty(context, base, key, value); } } - IntrospectionSupport.setProperties(getCamelContext(), getCamelContext().getTypeConverter(), base, copy); } // configure component with extra options - if (componentOptions != null && !componentOptions.isEmpty()) { - Map<String, Object> copy = new LinkedHashMap<>(componentOptions); - IntrospectionSupport.setProperties(getCamelContext(), getCamelContext().getTypeConverter(), base, copy); - } - - if (base instanceof Component) { - getCamelContext().removeComponent(scheme); - // ensure component is started and stopped when Camel shutdown - getCamelContext().addService(base, true, true); - getCamelContext().addComponent(scheme, (Component) base); - - return (Component) base; + if (options != null && !options.isEmpty()) { + // Get the list of options from the connector catalog that + // are configured to target the endpoint + List<String> endpointOptions = model.getEndpointOptions(); + + for (Map.Entry<String, Object> entry : options.entrySet()) { + // Only set options that are not targeting the endpoint + if (!endpointOptions.contains(entry.getKey())) { + log.debug("Using component option: {}={}", entry.getKey(), entry.getValue()); + IntrospectionSupport.setProperty(context, base, entry.getKey(), entry.getValue()); + } + } } } } - return null; + return base; } /** @@ -366,8 +406,7 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme } } - private Map<String, String> buildEndpointOptions(String remaining, Map<String, Object> parameters) throws URISyntaxException { - String scheme = model.getBaseScheme(); + private Map<String, String> buildEndpointOptions(String remaining, Map<String, Object> parameters) throws URISyntaxException, NoTypeConversionAvailableException { Map<String, String> defaultOptions = model.getDefaultEndpointOptions(); // gather all options to use when building the delegate uri @@ -382,6 +421,22 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme }); } + // Extract options from options that are supposed to be set at the endpoint + // level, those options can be overridden and extended using by the query + // parameters. + List<String> endpointOptions = model.getEndpointOptions(); + if (ObjectHelper.isNotEmpty(endpointOptions) && ObjectHelper.isNotEmpty(this.options)) { + for (String endpointOption : endpointOptions) { + Object value = this.options.get(endpointOption); + if (value != null) { + addConnectorOption( + options, + endpointOption, + getCamelContext().getTypeConverter().mandatoryConvertTo(String.class, value)); + } + } + } + // options from query parameters for (Map.Entry<String, Object> entry : parameters.entrySet()) { String key = entry.getKey(); @@ -396,7 +451,7 @@ public abstract class DefaultConnectorComponent extends DefaultComponent impleme // add extra options from remaining (context-path) if (remaining != null) { - String targetUri = scheme + ":" + remaining; + String targetUri = componentScheme + ":" + remaining; Map<String, String> extra = catalog.endpointProperties(targetUri); if (extra != null && !extra.isEmpty()) { extra.forEach((key, value) -> { http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorAutoConfiguration.java ---------------------------------------------------------------------- diff --git a/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorAutoConfiguration.java b/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorAutoConfiguration.java index b2739c5..85e27b0 100644 --- a/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorAutoConfiguration.java +++ b/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorAutoConfiguration.java @@ -18,9 +18,13 @@ package org.foo.connector.springboot; import java.util.HashMap; import java.util.Map; +import javax.annotation.Generated; +import javax.annotation.PostConstruct; import org.apache.camel.CamelContext; import org.apache.camel.util.IntrospectionSupport; import org.foo.connector.PetStoreComponent; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -33,19 +37,23 @@ import org.springframework.context.annotation.Lazy; /** * Generated by camel-connector-maven-plugin - do not edit this file! */ +@Generated("org.apache.camel.maven.connector.SpringBootAutoConfigurationMojo") @Configuration @ConditionalOnBean(type = "org.apache.camel.spring.boot.CamelAutoConfiguration") @AutoConfigureAfter(name = "org.apache.camel.spring.boot.CamelAutoConfiguration") @EnableConfigurationProperties(PetStoreConnectorConfiguration.class) public class PetStoreConnectorAutoConfiguration { + @Autowired + private CamelContext camelContext; + @Autowired + private PetStoreConnectorConfiguration configuration; + @Lazy @Bean(name = "petstore-component") @ConditionalOnClass(CamelContext.class) - @ConditionalOnMissingBean(org.foo.connector.PetStoreComponent.class) - public PetStoreComponent configurePetStoreComponent( - CamelContext camelContext, - PetStoreConnectorConfiguration configuration) throws Exception { + @ConditionalOnMissingBean(name = "petstore-component") + public PetStoreComponent configurePetStoreComponent() throws Exception { PetStoreComponent connector = new PetStoreComponent(); connector.setCamelContext(camelContext); Map<String, Object> parameters = new HashMap<>(); @@ -56,4 +64,29 @@ public class PetStoreConnectorAutoConfiguration { connector.setComponentOptions(parameters); return connector; } + + @PostConstruct + public void postConstructPetStoreComponent() { + if (camelContext != null) { + Map<String, Object> parameters = new HashMap<>(); + for (Map.Entry<String, PetStoreConnectorConfigurationCommon> entry : configuration + .getConfigurations().entrySet()) { + parameters.clear(); + PetStoreComponent connector = new PetStoreComponent(); + connector.setCamelContext(camelContext); + try { + IntrospectionSupport.getProperties(entry.getValue(), + parameters, null, false); + IntrospectionSupport.setProperties(camelContext, + camelContext.getTypeConverter(), connector, + parameters); + connector.setComponentOptions(parameters); + camelContext.addComponent(entry.getKey(), connector); + } catch (Exception e) { + throw new BeanCreationException(entry.getKey(), + e.getMessage(), e); + } + } + } + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfiguration.java ---------------------------------------------------------------------- diff --git a/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfiguration.java b/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfiguration.java index 0ae5ef3..fa19e29 100644 --- a/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfiguration.java +++ b/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfiguration.java @@ -16,34 +16,23 @@ */ package org.foo.connector.springboot; -import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Generated; import org.springframework.boot.context.properties.ConfigurationProperties; -/** - * An awesome REST endpoint backed by Swagger specifications. - * - * Generated by camel-connector-maven-plugin - do not edit this file! - */ +@Generated("org.apache.camel.maven.connector.SpringBootAutoConfigurationMojo") @ConfigurationProperties(prefix = "camel.connector.petstore") -public class PetStoreConnectorConfiguration { +public class PetStoreConnectorConfiguration + extends + PetStoreConnectorConfigurationCommon { /** - * Path to the Swagger specification file. The scheme host base path are - * taken from this specification but these can be overriden with properties - * on the component or endpoint level. If not given the component tries to - * load swagger.json resource. Note that the host defined on the component - * and endpoint of this Component should contain the scheme hostname and - * optionally the port in the URI syntax (i.e. - * https://api.example.com:8080). Can be overriden in endpoint - * configuration. + * Define additional configuration definitions */ - private URI specificationUri; - - public URI getSpecificationUri() { - return specificationUri; - } + private Map<String, PetStoreConnectorConfigurationCommon> configurations = new HashMap<>(); - public void setSpecificationUri(URI specificationUri) { - this.specificationUri = specificationUri; + public Map<String, PetStoreConnectorConfigurationCommon> getConfigurations() { + return configurations; } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfigurationCommon.java ---------------------------------------------------------------------- diff --git a/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfigurationCommon.java b/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfigurationCommon.java new file mode 100644 index 0000000..8af2c18 --- /dev/null +++ b/connectors/examples/petstore-connector/src/main/java/org/foo/connector/springboot/PetStoreConnectorConfigurationCommon.java @@ -0,0 +1,61 @@ +/** + * 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.foo.connector.springboot; + +import java.net.URI; +import javax.annotation.Generated; + +/** + * An awesome REST endpoint backed by Swagger specifications. + * + * Generated by camel-package-maven-plugin - do not edit this file! + */ +@Generated("org.apache.camel.maven.connector.SpringBootAutoConfigurationMojo") +public class PetStoreConnectorConfigurationCommon { + + /** + * Path to the Swagger specification file. The scheme host base path are + * taken from this specification but these can be overriden with properties + * on the component or endpoint level. If not given the component tries to + * load swagger.json resource. Note that the host defined on the component + * and endpoint of this Component should contain the scheme hostname and + * optionally the port in the URI syntax (i.e. + * https://api.example.com:8080). Can be overriden in endpoint + * configuration. + */ + private URI specificationUri; + /** + * ID of the operation from the Swagger specification. + */ + private String operationId; + + public URI getSpecificationUri() { + return specificationUri; + } + + public void setSpecificationUri(URI specificationUri) { + this.specificationUri = specificationUri; + } + + public String getOperationId() { + return operationId; + } + + public void setOperationId(String operationId) { + this.operationId = operationId; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/examples/pom.xml ---------------------------------------------------------------------- diff --git a/connectors/examples/pom.xml b/connectors/examples/pom.xml index 65bc229..8a4b10a 100644 --- a/connectors/examples/pom.xml +++ b/connectors/examples/pom.xml @@ -40,6 +40,8 @@ <module>wine-connector</module> <module>foo-bar-wine-example</module> <module>twitter-mention-connector</module> + <module>twitter-search-connector</module> + <module>twitter-search-example</module> <module>twitter-salesforce-example</module> <module>salesforce-upsert-contact-connector</module> <module>petstore-connector</module> http://git-wip-us.apache.org/repos/asf/camel/blob/3dd29006/connectors/examples/salesforce-upsert-contact-connector/src/main/java/org/foo/salesforce/contact/springboot/SalesforceUpsertContactConnectorAutoConfiguration.java ---------------------------------------------------------------------- diff --git a/connectors/examples/salesforce-upsert-contact-connector/src/main/java/org/foo/salesforce/contact/springboot/SalesforceUpsertContactConnectorAutoConfiguration.java b/connectors/examples/salesforce-upsert-contact-connector/src/main/java/org/foo/salesforce/contact/springboot/SalesforceUpsertContactConnectorAutoConfiguration.java index c9045ed..89c4dee 100644 --- a/connectors/examples/salesforce-upsert-contact-connector/src/main/java/org/foo/salesforce/contact/springboot/SalesforceUpsertContactConnectorAutoConfiguration.java +++ b/connectors/examples/salesforce-upsert-contact-connector/src/main/java/org/foo/salesforce/contact/springboot/SalesforceUpsertContactConnectorAutoConfiguration.java @@ -18,9 +18,13 @@ package org.foo.salesforce.contact.springboot; import java.util.HashMap; import java.util.Map; +import javax.annotation.Generated; +import javax.annotation.PostConstruct; import org.apache.camel.CamelContext; import org.apache.camel.util.IntrospectionSupport; import org.foo.salesforce.contact.SalesforceUpsertContactComponent; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -33,19 +37,23 @@ import org.springframework.context.annotation.Lazy; /** * Generated by camel-connector-maven-plugin - do not edit this file! */ +@Generated("org.apache.camel.maven.connector.SpringBootAutoConfigurationMojo") @Configuration @ConditionalOnBean(type = "org.apache.camel.spring.boot.CamelAutoConfiguration") @AutoConfigureAfter(name = "org.apache.camel.spring.boot.CamelAutoConfiguration") @EnableConfigurationProperties(SalesforceUpsertContactConnectorConfiguration.class) public class SalesforceUpsertContactConnectorAutoConfiguration { + @Autowired + private CamelContext camelContext; + @Autowired + private SalesforceUpsertContactConnectorConfiguration configuration; + @Lazy @Bean(name = "salesforce-upsert-contact-component") @ConditionalOnClass(CamelContext.class) - @ConditionalOnMissingBean(org.foo.salesforce.contact.SalesforceUpsertContactComponent.class) - public SalesforceUpsertContactComponent configureSalesforceUpsertContactComponent( - CamelContext camelContext, - SalesforceUpsertContactConnectorConfiguration configuration) + @ConditionalOnMissingBean(name = "salesforce-upsert-contact-component") + public SalesforceUpsertContactComponent configureSalesforceUpsertContactComponent() throws Exception { SalesforceUpsertContactComponent connector = new SalesforceUpsertContactComponent(); connector.setCamelContext(camelContext); @@ -57,4 +65,29 @@ public class SalesforceUpsertContactConnectorAutoConfiguration { connector.setComponentOptions(parameters); return connector; } + + @PostConstruct + public void postConstructSalesforceUpsertContactComponent() { + if (camelContext != null) { + Map<String, Object> parameters = new HashMap<>(); + for (Map.Entry<String, SalesforceUpsertContactConnectorConfigurationCommon> entry : configuration + .getConfigurations().entrySet()) { + parameters.clear(); + SalesforceUpsertContactComponent connector = new SalesforceUpsertContactComponent(); + connector.setCamelContext(camelContext); + try { + IntrospectionSupport.getProperties(entry.getValue(), + parameters, null, false); + IntrospectionSupport.setProperties(camelContext, + camelContext.getTypeConverter(), connector, + parameters); + connector.setComponentOptions(parameters); + camelContext.addComponent(entry.getKey(), connector); + } catch (Exception e) { + throw new BeanCreationException(entry.getKey(), + e.getMessage(), e); + } + } + } + } } \ No newline at end of file