CAMEL-10795: PingCheck API - default parameter validation based on camel-catalog
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/6343f8cc Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/6343f8cc Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/6343f8cc Branch: refs/heads/master Commit: 6343f8cce9c0253209b6b4465f49ce5209c1bdf2 Parents: c6d54c0 Author: lburgazzoli <lburgazz...@gmail.com> Authored: Thu Mar 9 13:38:44 2017 +0100 Committer: lburgazzoli <lburgazz...@gmail.com> Committed: Fri Mar 10 14:36:00 2017 +0100 ---------------------------------------------------------------------- .../org/apache/camel/ComponentVerifier.java | 2 + .../camel/catalog/AbstractCamelCatalog.java | 192 +++++++++++++++++++ .../camel/catalog/EndpointValidationResult.java | 27 ++- .../camel/catalog/RuntimeCamelCatalog.java | 9 + .../impl/verifier/DefaultComponentVerifier.java | 111 ++++++++--- .../camel/impl/verifier/DefaultResult.java | 10 +- .../camel/impl/verifier/DefaultResultError.java | 12 +- .../camel/impl/verifier/ResultErrorBuilder.java | 7 + .../org/apache/camel/util/ObjectHelper.java | 4 +- .../java/org/apache/camel/util/StreamUtils.java | 46 +++++ .../verifier/DefaultComponentVerifierTest.java | 80 ++++++++ .../component/http/HttpComponentVerifier.java | 9 +- .../component/http4/HttpComponentVerifier.java | 8 +- .../salesforce/SalesforceComponent.java | 2 +- .../salesforce/SalesforceComponentVerifier.java | 23 ++- .../src/main/docs/servicenow-component.adoc | 4 +- .../servicenow/ServiceNowComponentVerifier.java | 16 +- .../servicenow/ServiceNowConfiguration.java | 3 + .../src/main/docs/twitter-component.adoc | 8 +- .../component/twitter/TwitterComponent.java | 2 +- .../twitter/TwitterComponentVerifier.java | 20 +- .../component/twitter/TwitterConfiguration.java | 4 + .../twitter/CamelComponentVerifierTest.java | 21 +- .../camel/catalog/AbstractCamelCatalog.java | 192 +++++++++++++++++++ .../camel/catalog/EndpointValidationResult.java | 27 ++- .../tools/apt/EndpointAnnotationProcessor.java | 5 +- 26 files changed, 752 insertions(+), 92 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/ComponentVerifier.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/ComponentVerifier.java b/camel-core/src/main/java/org/apache/camel/ComponentVerifier.java index 5628d40..14bec23 100644 --- a/camel-core/src/main/java/org/apache/camel/ComponentVerifier.java +++ b/camel-core/src/main/java/org/apache/camel/ComponentVerifier.java @@ -24,7 +24,9 @@ import java.util.Set; public interface ComponentVerifier { // Todo: should be an enum ? String CODE_EXCEPTION = "exception"; + String CODE_INTERNAL = "internal"; String CODE_MISSING_OPTION = "missing-option"; + String CODE_UNKNOWN_OPTION = "unknown-option"; String CODE_ILLEGAL_OPTION = "illegal-option"; String CODE_ILLEGAL_OPTION_VALUE = "illegal-option-value"; String CODE_UNSUPPORTED = "unsupported"; http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java b/camel-core/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java index 3295ca9..50c5fa3 100644 --- a/camel-core/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java +++ b/camel-core/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java @@ -26,12 +26,15 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.apache.camel.catalog.CatalogHelper.after; import static org.apache.camel.catalog.JSonSchemaHelper.getNames; @@ -98,6 +101,195 @@ public abstract class AbstractCamelCatalog { return validateEndpointProperties(uri, ignoreLenientProperties, false, false); } + public EndpointValidationResult validateProperties(String scheme, Map<String, String> properties) { + EndpointValidationResult result = new EndpointValidationResult(scheme); + + String json = jsonSchemaResolver.getComponentJSonSchema(scheme); + List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); + List<Map<String, String>> componentProps = JSonSchemaHelper.parseJsonSchema("componentProperties", json, true); + + // endpoint options have higher priority so remove those from component + // that may clash + componentProps.stream() + .filter(c -> rows.stream().noneMatch(e -> Objects.equals(e.get("name"), c.get("name")))) + .forEach(rows::add); + + boolean lenient = Boolean.getBoolean(properties.getOrDefault("lenient", "false")); + + // the dataformat component refers to a data format so lets add the properties for the selected + // data format to the list of rows + if ("dataformat".equals(scheme)) { + String dfName = properties.get("name"); + if (dfName != null) { + String dfJson = jsonSchemaResolver.getDataFormatJSonSchema(dfName); + List<Map<String, String>> dfRows = JSonSchemaHelper.parseJsonSchema("properties", dfJson, true); + if (dfRows != null && !dfRows.isEmpty()) { + rows.addAll(dfRows); + } + } + } + + for (Map.Entry<String, String> property : properties.entrySet()) { + String value = property.getValue(); + String originalName = property.getKey(); + String name = property.getKey(); + // the name may be using an optional prefix, so lets strip that because the options + // in the schema are listed without the prefix + name = stripOptionalPrefixFromName(rows, name); + // the name may be using a prefix, so lets see if we can find the real property name + String propertyName = getPropertyNameFromNameWithPrefix(rows, name); + if (propertyName != null) { + name = propertyName; + } + + String prefix = getPropertyPrefix(rows, name); + String kind = getPropertyKind(rows, name); + boolean namePlaceholder = name.startsWith("{{") && name.endsWith("}}"); + boolean valuePlaceholder = value.startsWith("{{") || value.startsWith("${") || value.startsWith("$simple{"); + boolean lookup = value.startsWith("#") && value.length() > 1; + // we cannot evaluate multi values as strict as the others, as we don't know their expected types + boolean multiValue = prefix != null && originalName.startsWith(prefix) && isPropertyMultiValue(rows, name); + + Map<String, String> row = getRow(rows, name); + if (row == null) { + // unknown option + + // only add as error if the component is not lenient properties, or not stub component + // and the name is not a property placeholder for one or more values + if (!namePlaceholder && !"stub".equals(scheme)) { + if (lenient) { + // as if we are lenient then the option is a dynamic extra option which we cannot validate + result.addLenient(name); + } else { + // its unknown + result.addUnknown(name); + if (suggestionStrategy != null) { + String[] suggestions = suggestionStrategy.suggestEndpointOptions(getNames(rows), name); + if (suggestions != null) { + result.addUnknownSuggestions(name, suggestions); + } + } + } + } + } else { + /* TODO: we may need to add something in the properties to know if they are related to a producer or consumer + if ("parameter".equals(kind)) { + // consumer only or producer only mode for parameters + if (consumerOnly) { + boolean producer = isPropertyProducerOnly(rows, name); + if (producer) { + // the option is only for producer so you cannot use it in consumer mode + result.addNotConsumerOnly(name); + } + } else if (producerOnly) { + boolean consumer = isPropertyConsumerOnly(rows, name); + if (consumer) { + // the option is only for consumer so you cannot use it in producer mode + result.addNotProducerOnly(name); + } + } + } + */ + + // default value + String defaultValue = getPropertyDefaultValue(rows, name); + if (defaultValue != null) { + result.addDefaultValue(name, defaultValue); + } + + // is required but the value is empty + boolean required = isPropertyRequired(rows, name); + if (required && isEmpty(value)) { + result.addRequired(name); + } + + // is enum but the value is not within the enum range + // but we can only check if the value is not a placeholder + String enums = getPropertyEnum(rows, name); + if (!multiValue && !valuePlaceholder && !lookup && enums != null) { + String[] choices = enums.split(","); + boolean found = false; + for (String s : choices) { + if (value.equalsIgnoreCase(s)) { + found = true; + break; + } + } + if (!found) { + result.addInvalidEnum(name, value); + result.addInvalidEnumChoices(name, choices); + if (suggestionStrategy != null) { + Set<String> names = new LinkedHashSet<>(); + names.addAll(Arrays.asList(choices)); + String[] suggestions = suggestionStrategy.suggestEndpointOptions(names, value); + if (suggestions != null) { + result.addInvalidEnumSuggestions(name, suggestions); + } + } + + } + } + + // is reference lookup of bean (not applicable for @UriPath, enums, or multi-valued) + if (!multiValue && enums == null && !"path".equals(kind) && isPropertyObject(rows, name)) { + // must start with # and be at least 2 characters + if (!value.startsWith("#") || value.length() <= 1) { + result.addInvalidReference(name, value); + } + } + + // is boolean + if (!multiValue && !valuePlaceholder && !lookup && isPropertyBoolean(rows, name)) { + // value must be a boolean + boolean bool = "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value); + if (!bool) { + result.addInvalidBoolean(name, value); + } + } + + // is integer + if (!multiValue && !valuePlaceholder && !lookup && isPropertyInteger(rows, name)) { + // value must be an integer + boolean valid = validateInteger(value); + if (!valid) { + result.addInvalidInteger(name, value); + } + } + + // is number + if (!multiValue && !valuePlaceholder && !lookup && isPropertyNumber(rows, name)) { + // value must be an number + boolean valid = false; + try { + valid = !Double.valueOf(value).isNaN() || !Float.valueOf(value).isNaN(); + } catch (Exception e) { + // ignore + } + if (!valid) { + result.addInvalidNumber(name, value); + } + } + } + } + + // now check if all required values are there, and that a default value does not exists + for (Map<String, String> row : rows) { + String name = row.get("name"); + boolean required = isPropertyRequired(rows, name); + if (required) { + String value = properties.get(name); + if (isEmpty(value)) { + value = getPropertyDefaultValue(rows, name); + } + if (isEmpty(value)) { + result.addRequired(name); + } + } + } + + return result; + } + public EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties, boolean consumerOnly, boolean producerOnly) { EndpointValidationResult result = new EndpointValidationResult(uri); http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java b/camel-core/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java index 11e2c5e..9bb98f0 100644 --- a/camel-core/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java +++ b/camel-core/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java @@ -18,8 +18,10 @@ package org.apache.camel.catalog; import java.io.Serializable; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -54,6 +56,10 @@ public class EndpointValidationResult implements Serializable { private Map<String, String> invalidNumber; private Map<String, String> defaultValues; + public EndpointValidationResult() { + this(null); + } + public EndpointValidationResult(String uri) { this.uri = uri; } @@ -62,6 +68,10 @@ public class EndpointValidationResult implements Serializable { return uri; } + public boolean hasErrors() { + return errors > 0; + } + public int getNumberOfErrors() { return errors; } @@ -257,6 +267,17 @@ public class EndpointValidationResult implements Serializable { return invalidEnumChoices; } + public List<String> getEnumChoices(String optionName) { + if (invalidEnumChoices != null) { + String[] enums = invalidEnumChoices.get(optionName); + if (enums != null) { + return Arrays.asList(enums); + } + } + + return Collections.emptyList(); + } + public Map<String, String> getInvalidReference() { return invalidReference; } @@ -415,7 +436,11 @@ public class EndpointValidationResult implements Serializable { sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n"); sb.append("\n"); } - sb.append("\t").append(uri).append("\n"); + if (uri != null) { + sb.append("\t").append(uri).append("\n"); + } else { + sb.append("\n"); + } for (Map.Entry<String, String> option : options.entrySet()) { String out = String.format(format, option.getKey(), option.getValue()); sb.append("\n\t").append(out); http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/catalog/RuntimeCamelCatalog.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/catalog/RuntimeCamelCatalog.java b/camel-core/src/main/java/org/apache/camel/catalog/RuntimeCamelCatalog.java index c9abf30..b46edab 100644 --- a/camel-core/src/main/java/org/apache/camel/catalog/RuntimeCamelCatalog.java +++ b/camel-core/src/main/java/org/apache/camel/catalog/RuntimeCamelCatalog.java @@ -85,6 +85,15 @@ public interface RuntimeCamelCatalog extends StaticService { boolean validateTimePattern(String pattern); /** + * Validates the properties for the given scheme against component and endpoint + * + * @param scheme the endpoint scheme + * @param properties the endpoint properties + * @return validation result + */ + EndpointValidationResult validateProperties(String scheme, Map<String, String> properties); + + /** * Parses and validates the endpoint uri and constructs a key/value properties of each option. * * @param uri the endpoint uri http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java index a799056..48de776 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java +++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultComponentVerifier.java @@ -19,40 +19,42 @@ package org.apache.camel.impl.verifier; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.camel.CamelContext; -import org.apache.camel.CamelContextAware; import org.apache.camel.ComponentVerifier; import org.apache.camel.NoSuchOptionException; import org.apache.camel.TypeConverter; +import org.apache.camel.catalog.EndpointValidationResult; +import org.apache.camel.catalog.RuntimeCamelCatalog; import org.apache.camel.util.CamelContextHelper; import org.apache.camel.util.EndpointHelper; import org.apache.camel.util.IntrospectionSupport; -public class DefaultComponentVerifier implements ComponentVerifier, CamelContextAware { - public static final ComponentVerifier INSTANCE = new DefaultComponentVerifier(); +import static org.apache.camel.util.StreamUtils.stream; - private CamelContext camelContext; +public class DefaultComponentVerifier implements ComponentVerifier { + private final String defaultScheme; + private final CamelContext camelContext; - public DefaultComponentVerifier() { - } - - public DefaultComponentVerifier(CamelContext camelContext) { + public DefaultComponentVerifier(String defaultScheme, CamelContext camelContext) { + this.defaultScheme = defaultScheme; this.camelContext = camelContext; } - @Override - public CamelContext getCamelContext() { - return camelContext; - } - - @Override - public void setCamelContext(CamelContext camelContext) { - this.camelContext = camelContext; - } + // ************************************* + // + // ************************************* @Override public Result verify(Scope scope, Map<String, Object> parameters) { + // Camel context is mandatory + if (this.camelContext == null) { + return ResultBuilder.withStatusAndScope(Result.Status.ERROR, scope) + .error(ResultErrorBuilder.withCodeAndDescription(ComponentVerifier.CODE_INTERNAL, "Missing camel-context").build()) + .build(); + } + switch (scope) { case PARAMETERS: return verifyParameters(parameters); @@ -63,30 +65,85 @@ public class DefaultComponentVerifier implements ComponentVerifier, CamelContext } } - // ************************************* - // Implementation - // ************************************* + protected Result verifyConnectivity(Map<String, Object> parameters) { + return ResultBuilder.withStatusAndScope(Result.Status.UNSUPPORTED, Scope.CONNECTIVITY).build(); + } protected Result verifyParameters(Map<String, Object> parameters) { - return new ResultBuilder().scope(Scope.PARAMETERS).build(); + ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS); + + // Validate against catalog + verifyParametersAgainstCatalog(builder, parameters); + + return builder.build(); } - protected Result verifyConnectivity(Map<String, Object> parameters) { - return new ResultBuilder().scope(Scope.CONNECTIVITY).build(); + // ************************************* + // Helpers :: Parameters validation + // ************************************* + + protected void verifyParametersAgainstCatalog(ResultBuilder builder, Map<String, Object> parameters) { + String scheme = defaultScheme; + if (parameters.containsKey("scheme")) { + scheme = parameters.get("scheme").toString(); + } + + // Grab the runtime catalog to check parameters + RuntimeCamelCatalog catalog = camelContext.getRuntimeCamelCatalog(); + + // Convert from Map<String, Object> to Map<String, String> as required + // by the Camel Catalog + EndpointValidationResult result = catalog.validateProperties( + scheme, + parameters.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + e -> camelContext.getTypeConverter().convertTo(String.class, e.getValue()) + ) + ) + ); + + if (!result.isSuccess()) { + stream(result.getUnknown()) + .map(option -> ResultErrorBuilder.withUnknownOption(option).build()) + .forEach(builder::error); + stream(result.getRequired()) + .map(option -> ResultErrorBuilder.withMissingOption(option).build()) + .forEach(builder::error); + stream(result.getInvalidBoolean()) + .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build()) + .forEach(builder::error); + stream(result.getInvalidInteger()) + .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build()) + .forEach(builder::error); + stream(result.getInvalidNumber()) + .map(entry -> ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()).build()) + .forEach(builder::error); + stream(result.getInvalidEnum()) + .map(entry -> + ResultErrorBuilder.withIllegalOption(entry.getKey(), entry.getValue()) + .attribute("enum.values", result.getEnumChoices(entry.getKey())) + .build()) + .forEach(builder::error); + } } // ************************************* // Helpers // ************************************* + protected CamelContext getCamelContext() { + return camelContext; + } + protected <T> T setProperties(T instance, Map<String, Object> properties) throws Exception { if (camelContext == null) { throw new IllegalStateException("Camel context is null"); } if (!properties.isEmpty()) { - final CamelContext context = getCamelContext(); - final TypeConverter converter = context.getTypeConverter(); + final TypeConverter converter = camelContext.getTypeConverter(); IntrospectionSupport.setProperties(converter, instance, properties); @@ -94,7 +151,7 @@ public class DefaultComponentVerifier implements ComponentVerifier, CamelContext if (entry.getValue() instanceof String) { String value = (String)entry.getValue(); if (EndpointHelper.isReferenceParameter(value)) { - IntrospectionSupport.setProperty(context, converter, instance, entry.getKey(), null, value, true); + IntrospectionSupport.setProperty(camelContext, converter, instance, entry.getKey(), null, value, true); } } } @@ -113,7 +170,7 @@ public class DefaultComponentVerifier implements ComponentVerifier, CamelContext protected <T> Optional<T> getOption(Map<String, Object> parameters, String key, Class<T> type) { Object value = parameters.get(key); if (value != null) { - return Optional.ofNullable(CamelContextHelper.convertTo(getCamelContext(), type, value)); + return Optional.ofNullable(CamelContextHelper.convertTo(camelContext, type, value)); } return Optional.empty(); http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResult.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResult.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResult.java index a2742a0..ef9c055 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResult.java +++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResult.java @@ -48,10 +48,10 @@ public class DefaultResult implements ComponentVerifier.Result { @Override public String toString() { - return "DefaultResult{" + - "scope=" + scope + - ", status=" + status + - ", errors=" + errors + - '}'; + return "DefaultResult{" + + "scope=" + scope + + ", status=" + status + + ", errors=" + errors + + '}'; } } http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResultError.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResultError.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResultError.java index b4bfa8c..0398f7c 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResultError.java +++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/DefaultResultError.java @@ -56,11 +56,11 @@ public class DefaultResultError implements ComponentVerifier.Error { @Override public String toString() { - return "DefaultResultError{" + - "code='" + code + '\'' + - ", description='" + description + '\'' + - ", parameters=" + parameters + - ", attributes=" + attributes + - '}'; + return "DefaultResultError{" + + "code='" + code + '\'' + + ", description='" + description + '\'' + + ", parameters=" + parameters + + ", attributes=" + attributes + + '}'; } } http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java b/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java index 58eb5ea..12e24d0 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java +++ b/camel-core/src/main/java/org/apache/camel/impl/verifier/ResultErrorBuilder.java @@ -146,6 +146,13 @@ public final class ResultErrorBuilder { .parameter(optionName); } + public static ResultErrorBuilder withUnknownOption(String optionName) { + return new ResultErrorBuilder() + .code(ComponentVerifier.CODE_UNKNOWN_OPTION) + .description("Unknown option " + optionName) + .parameter(optionName); + } + public static ResultErrorBuilder withIllegalOption(String optionName) { return new ResultErrorBuilder() .code(ComponentVerifier.CODE_ILLEGAL_OPTION) http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java b/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java index 91e3862..247853f 100644 --- a/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/ObjectHelper.java @@ -375,7 +375,7 @@ public final class ObjectHelper { } /** - * Tests whether the value is <b>not</b> <tt>null</tt>, an empty string or an empty collection. + * Tests whether the value is <b>not</b> <tt>null</tt>, an empty string or an empty collection/map. * * @param value the value, if its a String it will be tested for text length as well * @return true if <b>not</b> empty @@ -389,6 +389,8 @@ public final class ObjectHelper { return text.trim().length() > 0; } else if (value instanceof Collection) { return !((Collection<?>)value).isEmpty(); + } else if (value instanceof Map) { + return !((Map<?, ?>)value).isEmpty(); } else { return true; } http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/main/java/org/apache/camel/util/StreamUtils.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/StreamUtils.java b/camel-core/src/main/java/org/apache/camel/util/StreamUtils.java new file mode 100644 index 0000000..98427e1 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/util/StreamUtils.java @@ -0,0 +1,46 @@ +/** + * 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.util; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Stream; + +public final class StreamUtils { + private StreamUtils() { + } + + /** + * Creates a stream on the given collection if it is not null + * + * @param value the collection + * @return A stream of elements or an empty stream if the collection is null + */ + public static <C> Stream<C> stream(Collection<C> value) { + return value != null ? value.stream() : Stream.empty(); + } + + /** + * Creates a stream of entries on the given Map if it is not null + * + * @param value the map + * @return A stream of entries or an empty stream if the collection is null + */ + public static <K, V> Stream<Map.Entry<K, V>> stream(Map<K, V> value) { + return value != null ? value.entrySet().stream() : Stream.empty(); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/camel-core/src/test/java/org/apache/camel/impl/verifier/DefaultComponentVerifierTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/impl/verifier/DefaultComponentVerifierTest.java b/camel-core/src/test/java/org/apache/camel/impl/verifier/DefaultComponentVerifierTest.java new file mode 100644 index 0000000..5b626b0 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/impl/verifier/DefaultComponentVerifierTest.java @@ -0,0 +1,80 @@ +/** + * 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.impl.verifier; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.ComponentVerifier; +import org.apache.camel.ContextTestSupport; +import org.junit.Assert; + +public class DefaultComponentVerifierTest extends ContextTestSupport { + private ComponentVerifier verifier; + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + this.verifier = new DefaultComponentVerifier("timer", context); + } + + // ************************************* + // Tests + // ************************************* + + public void testParameters() throws Exception { + Map<String, Object> parameters = new HashMap<>(); + parameters.put("timerName", "dummy"); + parameters.put("period", "1s"); + + ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters); + Assert.assertEquals(ComponentVerifier.Result.Status.OK, result.getStatus()); + } + + public void testParametersWithMissingMandatoryOptions() throws Exception { + Map<String, Object> parameters = new HashMap<>(); + parameters.put("period", "1s"); + + ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters); + Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus()); + + Assert.assertEquals(1, result.getErrors().size()); + Assert.assertEquals(ComponentVerifier.CODE_MISSING_OPTION, result.getErrors().get(0).getCode()); + Assert.assertTrue(result.getErrors().get(0).getParameters().contains("timerName")); + } + + public void testParametersWithWrongOptions() throws Exception { + Map<String, Object> parameters = new HashMap<>(); + parameters.put("timerName", "dummy"); + parameters.put("period", "1s"); + parameters.put("fixedRate", "wrong"); + + ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, parameters); + Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus()); + + Assert.assertEquals(1, result.getErrors().size()); + Assert.assertEquals(ComponentVerifier.CODE_ILLEGAL_OPTION_VALUE, result.getErrors().get(0).getCode()); + Assert.assertEquals("fixedRate has wrong value (wrong)", result.getErrors().get(0).getDescription()); + Assert.assertTrue(result.getErrors().get(0).getParameters().contains("fixedRate")); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponentVerifier.java ---------------------------------------------------------------------- diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponentVerifier.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponentVerifier.java index f9ec8ca..2ef8c96 100644 --- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponentVerifier.java +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponentVerifier.java @@ -35,8 +35,8 @@ final class HttpComponentVerifier extends DefaultComponentVerifier { private final HttpComponent component; HttpComponentVerifier(HttpComponent component) { - super(component.getCamelContext()); - + super("http", component.getCamelContext()); + this.component = component; } @@ -49,9 +49,10 @@ final class HttpComponentVerifier extends DefaultComponentVerifier { // The default is success ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS); - // The httpUri is mandatory - builder.error(ResultErrorHelper.requiresOption("httpUri", parameters)); + // Validate using the catalog + super.verifyParametersAgainstCatalog(builder, parameters); + // Validate if the auth/proxy combination is properly set-up Optional<String> authMethod = getOption(parameters, "authMethod", String.class); if (authMethod.isPresent()) { // If auth method is set, username and password must be provided http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java ---------------------------------------------------------------------- diff --git a/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java b/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java index aa459cc..cd66968 100644 --- a/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java +++ b/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpComponentVerifier.java @@ -37,8 +37,8 @@ final class HttpComponentVerifier extends DefaultComponentVerifier { private final HttpComponent component; HttpComponentVerifier(HttpComponent component) { - super(component.getCamelContext()); - + super("http", component.getCamelContext()); + this.component = component; } @@ -51,8 +51,8 @@ final class HttpComponentVerifier extends DefaultComponentVerifier { // The default is success ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS); - // The httpUri is mandatory - builder.error(ResultErrorHelper.requiresOption("httpUri", parameters)); + // Validate using the catalog + super.verifyParametersAgainstCatalog(builder, parameters); return builder.build(); } http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java index 64fce53..9696207 100644 --- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java +++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java @@ -710,6 +710,6 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin * TODO: document */ public ComponentVerifier getVerifier() { - return new SalesforceComponentVerifier(getCamelContext()); + return new SalesforceComponentVerifier(this); } } http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponentVerifier.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponentVerifier.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponentVerifier.java index 11b331c..f7ce00a 100644 --- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponentVerifier.java +++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponentVerifier.java @@ -21,7 +21,6 @@ import java.net.URISyntaxException; import java.util.Map; import java.util.Optional; -import org.apache.camel.CamelContext; import org.apache.camel.ComponentVerifier; import org.apache.camel.NoSuchOptionException; import org.apache.camel.component.salesforce.api.SalesforceException; @@ -41,8 +40,8 @@ import org.eclipse.jetty.client.util.DigestAuthentication; import org.eclipse.jetty.util.ssl.SslContextFactory; public class SalesforceComponentVerifier extends DefaultComponentVerifier { - SalesforceComponentVerifier(CamelContext camelContext) { - super(camelContext); + SalesforceComponentVerifier(SalesforceComponent component) { + super("salesforce", component.getCamelContext()); } // ********************************* @@ -50,13 +49,19 @@ public class SalesforceComponentVerifier extends DefaultComponentVerifier { // ********************************* @Override - protected ComponentVerifier.Result verifyParameters(Map<String, Object> parameters) { - return ResultBuilder.withStatusAndScope(ComponentVerifier.Result.Status.OK, ComponentVerifier.Scope.PARAMETERS) + protected Result verifyParameters(Map<String, Object> parameters) { + // Validate mandatory component options, needed to be done here as these + // options are not properly marked as mandatory in the catalog. + ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS) .error(ResultErrorHelper.requiresOption("clientId", parameters)) .error(ResultErrorHelper.requiresOption("clientSecret", parameters)) .error(ResultErrorHelper.requiresOption("userName", parameters)) - .error(ResultErrorHelper.requiresOption("password", parameters)) - .build(); + .error(ResultErrorHelper.requiresOption("password", parameters)); + + // Validate using the catalog + super.verifyParametersAgainstCatalog(builder, parameters); + + return builder.build(); } // ********************************* @@ -102,11 +107,11 @@ public class SalesforceComponentVerifier extends DefaultComponentVerifier { httpClient.stop(); httpClient.destroy(); - } catch(NoSuchOptionException e) { + } catch (NoSuchOptionException e) { builder.error( ResultErrorBuilder.withMissingOption(e.getOptionName()).build() ); - } catch(SalesforceException e) { + } catch (SalesforceException e) { processSalesforceException(builder, Optional.of(e)); } catch (Exception e) { builder.error( http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-servicenow/src/main/docs/servicenow-component.adoc ---------------------------------------------------------------------- diff --git a/components/camel-servicenow/src/main/docs/servicenow-component.adoc b/components/camel-servicenow/src/main/docs/servicenow-component.adoc index c505b8d..81d915d 100644 --- a/components/camel-servicenow/src/main/docs/servicenow-component.adoc +++ b/components/camel-servicenow/src/main/docs/servicenow-component.adoc @@ -104,11 +104,11 @@ with the following path and query parameters: | oauthClientId | security | | String | OAuth2 ClientID | oauthClientSecret | security | | String | OAuth2 ClientSecret | oauthTokenUrl | security | | String | OAuth token Url -| password | security | | String | ServiceNow account password MUST be provided +| password | security | | String | *Required* ServiceNow account password MUST be provided | proxyPassword | security | | String | Password for proxy authentication | proxyUserName | security | | String | Username for proxy authentication | sslContextParameters | security | | SSLContextParameters | To configure security using SSLContextParameters. See http://camel.apache.org/camel-configuration-utilities.html -| userName | security | | String | ServiceNow user account name MUST be provided +| userName | security | | String | *Required* ServiceNow user account name MUST be provided |======================================================================= // endpoint options: END http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowComponentVerifier.java ---------------------------------------------------------------------- diff --git a/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowComponentVerifier.java b/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowComponentVerifier.java index f106632..cab1f82 100644 --- a/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowComponentVerifier.java +++ b/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowComponentVerifier.java @@ -25,13 +25,12 @@ import org.apache.camel.NoSuchOptionException; import org.apache.camel.impl.verifier.DefaultComponentVerifier; import org.apache.camel.impl.verifier.ResultBuilder; import org.apache.camel.impl.verifier.ResultErrorBuilder; -import org.apache.camel.impl.verifier.ResultErrorHelper; public class ServiceNowComponentVerifier extends DefaultComponentVerifier { private final ServiceNowComponent component; ServiceNowComponentVerifier(ServiceNowComponent component) { - super(component.getCamelContext()); + super("servicenow", component.getCamelContext()); this.component = component; } @@ -42,11 +41,12 @@ public class ServiceNowComponentVerifier extends DefaultComponentVerifier { @Override protected Result verifyParameters(Map<String, Object> parameters) { - return ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS) - .error(ResultErrorHelper.requiresOption("instanceName", parameters)) - .error(ResultErrorHelper.requiresOption("userName", parameters)) - .error(ResultErrorHelper.requiresOption("password", parameters)) - .build(); + ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS); + + // Validate using the catalog + super.verifyParametersAgainstCatalog(builder, parameters); + + return builder.build(); } // ********************************* @@ -82,7 +82,7 @@ public class ServiceNowComponentVerifier extends DefaultComponentVerifier { .path(tableName) .query(ServiceNowParams.SYSPARM_LIMIT.getId(), 1L) .invoke(HttpMethod.GET); - } catch(NoSuchOptionException e) { + } catch (NoSuchOptionException e) { builder.error( ResultErrorBuilder.withMissingOption(e.getOptionName()).build() ); http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowConfiguration.java b/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowConfiguration.java index d27d29e..3429ff9 100644 --- a/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowConfiguration.java +++ b/components/camel-servicenow/src/main/java/org/apache/camel/component/servicenow/ServiceNowConfiguration.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriParams; import org.apache.camel.util.ObjectHelper; @@ -42,8 +43,10 @@ public class ServiceNowConfiguration implements Cloneable { ); @UriParam(label = "security", secret = true) + @Metadata(required = "true") private String userName; @UriParam(label = "security", secret = true) + @Metadata(required = "true") private String password; @UriParam(label = "security", secret = true) private String oauthClientId; http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-twitter/src/main/docs/twitter-component.adoc ---------------------------------------------------------------------- diff --git a/components/camel-twitter/src/main/docs/twitter-component.adoc b/components/camel-twitter/src/main/docs/twitter-component.adoc index 2987fb7..3cf2c25 100644 --- a/components/camel-twitter/src/main/docs/twitter-component.adoc +++ b/components/camel-twitter/src/main/docs/twitter-component.adoc @@ -187,10 +187,10 @@ with the following path and query parameters: | httpProxyPassword | proxy | | String | The http proxy password which can be used for the camel-twitter. Can also be configured on the TwitterComponent level instead. | httpProxyPort | proxy | | Integer | The http proxy port which can be used for the camel-twitter. Can also be configured on the TwitterComponent level instead. | httpProxyUser | proxy | | String | The http proxy user which can be used for the camel-twitter. Can also be configured on the TwitterComponent level instead. -| accessToken | security | | String | The access token. Can also be configured on the TwitterComponent level instead. -| accessTokenSecret | security | | String | The access secret. Can also be configured on the TwitterComponent level instead. -| consumerKey | security | | String | The consumer key. Can also be configured on the TwitterComponent level instead. -| consumerSecret | security | | String | The consumer secret. Can also be configured on the TwitterComponent level instead. +| accessToken | security | | String | *Required* The access token. Can also be configured on the TwitterComponent level instead. +| accessTokenSecret | security | | String | *Required* The access secret. Can also be configured on the TwitterComponent level instead. +| consumerKey | security | | String | *Required* The consumer key. Can also be configured on the TwitterComponent level instead. +| consumerSecret | security | | String | *Required* The consumer secret. Can also be configured on the TwitterComponent level instead. |======================================================================= // endpoint options: END http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponent.java b/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponent.java index 9e33c2e..665a9f8 100644 --- a/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponent.java +++ b/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponent.java @@ -178,6 +178,6 @@ public class TwitterComponent extends UriEndpointComponent implements Verifiable * TODO: document */ public ComponentVerifier getVerifier() { - return new TwitterComponentVerifier(getCamelContext()); + return new TwitterComponentVerifier(this); } } http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponentVerifier.java ---------------------------------------------------------------------- diff --git a/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponentVerifier.java b/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponentVerifier.java index 062736c..c00d078 100644 --- a/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponentVerifier.java +++ b/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterComponentVerifier.java @@ -18,17 +18,15 @@ package org.apache.camel.component.twitter; import java.util.Map; -import org.apache.camel.CamelContext; import org.apache.camel.impl.verifier.DefaultComponentVerifier; import org.apache.camel.impl.verifier.ResultBuilder; import org.apache.camel.impl.verifier.ResultErrorBuilder; -import org.apache.camel.impl.verifier.ResultErrorHelper; import twitter4j.Twitter; import twitter4j.TwitterException; final class TwitterComponentVerifier extends DefaultComponentVerifier { - TwitterComponentVerifier(CamelContext camelContext) { - super(camelContext); + TwitterComponentVerifier(TwitterComponent component) { + super("twitter", component.getCamelContext()); } // ********************************* @@ -37,12 +35,12 @@ final class TwitterComponentVerifier extends DefaultComponentVerifier { @Override protected Result verifyParameters(Map<String, Object> parameters) { - return ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS) - .error(ResultErrorHelper.requiresOption("consumerKey", parameters)) - .error(ResultErrorHelper.requiresOption("consumerSecret", parameters)) - .error(ResultErrorHelper.requiresOption("accessToken", parameters)) - .error(ResultErrorHelper.requiresOption("accessTokenSecret", parameters)) - .build(); + ResultBuilder builder = ResultBuilder.withStatusAndScope(Result.Status.OK, Scope.PARAMETERS); + + // Validate using the catalog + super.verifyParametersAgainstCatalog(builder, parameters); + + return builder.build(); } // ********************************* @@ -62,7 +60,7 @@ final class TwitterComponentVerifier extends DefaultComponentVerifier { Twitter twitter = configuration.getTwitter(); twitter.verifyCredentials(); - } catch(TwitterException e) { + } catch (TwitterException e) { // verifyCredentials throws TwitterException when Twitter service or // network is unavailable or if supplied credential is wrong ResultErrorBuilder errorBuilder = ResultErrorBuilder.withHttpCodeAndText(e.getStatusCode(), e.getErrorMessage()) http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterConfiguration.java b/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterConfiguration.java index 336fc65..72ab7de 100644 --- a/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterConfiguration.java +++ b/components/camel-twitter/src/main/java/org/apache/camel/component/twitter/TwitterConfiguration.java @@ -38,12 +38,16 @@ public class TwitterConfiguration { @UriParam(label = "consumer", defaultValue = "polling", enums = "polling,direct,event") private EndpointType type = EndpointType.POLLING; @UriParam(label = "security", secret = true) + @Metadata(required = "true") private String accessToken; @UriParam(label = "security", secret = true) + @Metadata(required = "true") private String accessTokenSecret; @UriParam(label = "security", secret = true) + @Metadata(required = "true") private String consumerKey; @UriParam(label = "security", secret = true) + @Metadata(required = "true") private String consumerSecret; @UriParam private String user; http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/components/camel-twitter/src/test/java/org/apache/camel/component/twitter/CamelComponentVerifierTest.java ---------------------------------------------------------------------- diff --git a/components/camel-twitter/src/test/java/org/apache/camel/component/twitter/CamelComponentVerifierTest.java b/components/camel-twitter/src/test/java/org/apache/camel/component/twitter/CamelComponentVerifierTest.java index 7fcd3c7..3666e26 100644 --- a/components/camel-twitter/src/test/java/org/apache/camel/component/twitter/CamelComponentVerifierTest.java +++ b/components/camel-twitter/src/test/java/org/apache/camel/component/twitter/CamelComponentVerifierTest.java @@ -17,6 +17,8 @@ package org.apache.camel.component.twitter; import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import org.apache.camel.ComponentVerifier; @@ -86,11 +88,20 @@ public class CamelComponentVerifierTest extends CamelTwitterTestSupport { ComponentVerifier.Result result = verifier.verify(ComponentVerifier.Scope.PARAMETERS, Collections.emptyMap()); Assert.assertEquals(ComponentVerifier.Result.Status.ERROR, result.getStatus()); - Assert.assertEquals(4, result.getErrors().size()); - Assert.assertTrue(result.getErrors().get(0).getParameters().contains("consumerKey")); - Assert.assertTrue(result.getErrors().get(1).getParameters().contains("consumerSecret")); - Assert.assertTrue(result.getErrors().get(2).getParameters().contains("accessToken")); - Assert.assertTrue(result.getErrors().get(3).getParameters().contains("accessTokenSecret")); + Assert.assertEquals(5, result.getErrors().size()); + + List<String> expected = new LinkedList<>(); + expected.add("kind"); + expected.add("consumerKey"); + expected.add("consumerSecret"); + expected.add("accessToken"); + expected.add("accessTokenSecret"); + + for(ComponentVerifier.Error error : result.getErrors()) { + expected.removeAll(error.getParameters()); + } + + Assert.assertTrue("Missing expected params: " + expected.toString(), expected.isEmpty()); } { http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java index 3295ca9..50c5fa3 100644 --- a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java @@ -26,12 +26,15 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.apache.camel.catalog.CatalogHelper.after; import static org.apache.camel.catalog.JSonSchemaHelper.getNames; @@ -98,6 +101,195 @@ public abstract class AbstractCamelCatalog { return validateEndpointProperties(uri, ignoreLenientProperties, false, false); } + public EndpointValidationResult validateProperties(String scheme, Map<String, String> properties) { + EndpointValidationResult result = new EndpointValidationResult(scheme); + + String json = jsonSchemaResolver.getComponentJSonSchema(scheme); + List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true); + List<Map<String, String>> componentProps = JSonSchemaHelper.parseJsonSchema("componentProperties", json, true); + + // endpoint options have higher priority so remove those from component + // that may clash + componentProps.stream() + .filter(c -> rows.stream().noneMatch(e -> Objects.equals(e.get("name"), c.get("name")))) + .forEach(rows::add); + + boolean lenient = Boolean.getBoolean(properties.getOrDefault("lenient", "false")); + + // the dataformat component refers to a data format so lets add the properties for the selected + // data format to the list of rows + if ("dataformat".equals(scheme)) { + String dfName = properties.get("name"); + if (dfName != null) { + String dfJson = jsonSchemaResolver.getDataFormatJSonSchema(dfName); + List<Map<String, String>> dfRows = JSonSchemaHelper.parseJsonSchema("properties", dfJson, true); + if (dfRows != null && !dfRows.isEmpty()) { + rows.addAll(dfRows); + } + } + } + + for (Map.Entry<String, String> property : properties.entrySet()) { + String value = property.getValue(); + String originalName = property.getKey(); + String name = property.getKey(); + // the name may be using an optional prefix, so lets strip that because the options + // in the schema are listed without the prefix + name = stripOptionalPrefixFromName(rows, name); + // the name may be using a prefix, so lets see if we can find the real property name + String propertyName = getPropertyNameFromNameWithPrefix(rows, name); + if (propertyName != null) { + name = propertyName; + } + + String prefix = getPropertyPrefix(rows, name); + String kind = getPropertyKind(rows, name); + boolean namePlaceholder = name.startsWith("{{") && name.endsWith("}}"); + boolean valuePlaceholder = value.startsWith("{{") || value.startsWith("${") || value.startsWith("$simple{"); + boolean lookup = value.startsWith("#") && value.length() > 1; + // we cannot evaluate multi values as strict as the others, as we don't know their expected types + boolean multiValue = prefix != null && originalName.startsWith(prefix) && isPropertyMultiValue(rows, name); + + Map<String, String> row = getRow(rows, name); + if (row == null) { + // unknown option + + // only add as error if the component is not lenient properties, or not stub component + // and the name is not a property placeholder for one or more values + if (!namePlaceholder && !"stub".equals(scheme)) { + if (lenient) { + // as if we are lenient then the option is a dynamic extra option which we cannot validate + result.addLenient(name); + } else { + // its unknown + result.addUnknown(name); + if (suggestionStrategy != null) { + String[] suggestions = suggestionStrategy.suggestEndpointOptions(getNames(rows), name); + if (suggestions != null) { + result.addUnknownSuggestions(name, suggestions); + } + } + } + } + } else { + /* TODO: we may need to add something in the properties to know if they are related to a producer or consumer + if ("parameter".equals(kind)) { + // consumer only or producer only mode for parameters + if (consumerOnly) { + boolean producer = isPropertyProducerOnly(rows, name); + if (producer) { + // the option is only for producer so you cannot use it in consumer mode + result.addNotConsumerOnly(name); + } + } else if (producerOnly) { + boolean consumer = isPropertyConsumerOnly(rows, name); + if (consumer) { + // the option is only for consumer so you cannot use it in producer mode + result.addNotProducerOnly(name); + } + } + } + */ + + // default value + String defaultValue = getPropertyDefaultValue(rows, name); + if (defaultValue != null) { + result.addDefaultValue(name, defaultValue); + } + + // is required but the value is empty + boolean required = isPropertyRequired(rows, name); + if (required && isEmpty(value)) { + result.addRequired(name); + } + + // is enum but the value is not within the enum range + // but we can only check if the value is not a placeholder + String enums = getPropertyEnum(rows, name); + if (!multiValue && !valuePlaceholder && !lookup && enums != null) { + String[] choices = enums.split(","); + boolean found = false; + for (String s : choices) { + if (value.equalsIgnoreCase(s)) { + found = true; + break; + } + } + if (!found) { + result.addInvalidEnum(name, value); + result.addInvalidEnumChoices(name, choices); + if (suggestionStrategy != null) { + Set<String> names = new LinkedHashSet<>(); + names.addAll(Arrays.asList(choices)); + String[] suggestions = suggestionStrategy.suggestEndpointOptions(names, value); + if (suggestions != null) { + result.addInvalidEnumSuggestions(name, suggestions); + } + } + + } + } + + // is reference lookup of bean (not applicable for @UriPath, enums, or multi-valued) + if (!multiValue && enums == null && !"path".equals(kind) && isPropertyObject(rows, name)) { + // must start with # and be at least 2 characters + if (!value.startsWith("#") || value.length() <= 1) { + result.addInvalidReference(name, value); + } + } + + // is boolean + if (!multiValue && !valuePlaceholder && !lookup && isPropertyBoolean(rows, name)) { + // value must be a boolean + boolean bool = "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value); + if (!bool) { + result.addInvalidBoolean(name, value); + } + } + + // is integer + if (!multiValue && !valuePlaceholder && !lookup && isPropertyInteger(rows, name)) { + // value must be an integer + boolean valid = validateInteger(value); + if (!valid) { + result.addInvalidInteger(name, value); + } + } + + // is number + if (!multiValue && !valuePlaceholder && !lookup && isPropertyNumber(rows, name)) { + // value must be an number + boolean valid = false; + try { + valid = !Double.valueOf(value).isNaN() || !Float.valueOf(value).isNaN(); + } catch (Exception e) { + // ignore + } + if (!valid) { + result.addInvalidNumber(name, value); + } + } + } + } + + // now check if all required values are there, and that a default value does not exists + for (Map<String, String> row : rows) { + String name = row.get("name"); + boolean required = isPropertyRequired(rows, name); + if (required) { + String value = properties.get(name); + if (isEmpty(value)) { + value = getPropertyDefaultValue(rows, name); + } + if (isEmpty(value)) { + result.addRequired(name); + } + } + } + + return result; + } + public EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties, boolean consumerOnly, boolean producerOnly) { EndpointValidationResult result = new EndpointValidationResult(uri); http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java ---------------------------------------------------------------------- diff --git a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java index 11e2c5e..9bb98f0 100644 --- a/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java +++ b/platforms/camel-catalog/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java @@ -18,8 +18,10 @@ package org.apache.camel.catalog; import java.io.Serializable; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -54,6 +56,10 @@ public class EndpointValidationResult implements Serializable { private Map<String, String> invalidNumber; private Map<String, String> defaultValues; + public EndpointValidationResult() { + this(null); + } + public EndpointValidationResult(String uri) { this.uri = uri; } @@ -62,6 +68,10 @@ public class EndpointValidationResult implements Serializable { return uri; } + public boolean hasErrors() { + return errors > 0; + } + public int getNumberOfErrors() { return errors; } @@ -257,6 +267,17 @@ public class EndpointValidationResult implements Serializable { return invalidEnumChoices; } + public List<String> getEnumChoices(String optionName) { + if (invalidEnumChoices != null) { + String[] enums = invalidEnumChoices.get(optionName); + if (enums != null) { + return Arrays.asList(enums); + } + } + + return Collections.emptyList(); + } + public Map<String, String> getInvalidReference() { return invalidReference; } @@ -415,7 +436,11 @@ public class EndpointValidationResult implements Serializable { sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n"); sb.append("\n"); } - sb.append("\t").append(uri).append("\n"); + if (uri != null) { + sb.append("\t").append(uri).append("\n"); + } else { + sb.append("\n"); + } for (Map.Entry<String, String> option : options.entrySet()) { String out = String.format(format, option.getKey(), option.getValue()); sb.append("\n\t").append(out); http://git-wip-us.apache.org/repos/asf/camel/blob/6343f8cc/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java ---------------------------------------------------------------------- diff --git a/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java b/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java index c6e5f30..561dce8 100644 --- a/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java +++ b/tooling/apt/src/main/java/org/apache/camel/tools/apt/EndpointAnnotationProcessor.java @@ -192,10 +192,11 @@ public class EndpointAnnotationProcessor extends AbstractProcessor { } buffer.append("\n \"groupId\": \"").append(componentModel.getGroupId()).append("\","); buffer.append("\n \"artifactId\": \"").append(componentModel.getArtifactId()).append("\","); - buffer.append("\n \"version\": \"").append(componentModel.getVersionId()).append("\""); if (componentModel.getVerifiers() != null) { - buffer.append("\n \"verifiers\": \"").append(componentModel.getVerifiers()).append("\""); + buffer.append("\n \"verifiers\": \"").append(componentModel.getVerifiers()).append("\","); } + buffer.append("\n \"version\": \"").append(componentModel.getVersionId()).append("\""); + buffer.append("\n },"); // and component properties