You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2017/03/08 17:58:40 UTC
[4/4] camel git commit: CAMEL-10959: add RuntimeCamelCatalog to
camel-core so we can reuse more catalog logic at runtime and have
camel-catalog for tooling that has the complete catalog content. We need the
runtime for component health check and more in
CAMEL-10959: add RuntimeCamelCatalog to camel-core so we can reuse more catalog logic at runtime and have camel-catalog for tooling that has the complete catalog content. We need the runtime for component health check and more in the future.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/f5848e39
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/f5848e39
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/f5848e39
Branch: refs/heads/master
Commit: f5848e39c08b0abd99a5f4e9eba73c7740c60c05
Parents: 6e9c513
Author: Claus Ibsen <da...@apache.org>
Authored: Wed Mar 8 18:57:20 2017 +0100
Committer: Claus Ibsen <da...@apache.org>
Committed: Wed Mar 8 18:57:20 2017 +0100
----------------------------------------------------------------------
camel-core/pom.xml | 29 +
.../camel/catalog/AbstractCamelCatalog.java | 1029 +++++++++++++++++
.../catalog/CamelContextJSonSchemaResolver.java | 80 ++
.../org/apache/camel/catalog/CatalogHelper.java | 195 ++++
.../camel/catalog/CollectionStringBuffer.java | 57 +
.../catalog/DefaultRuntimeCamelCatalog.java | 126 +++
.../camel/catalog/EndpointValidationResult.java | 426 +++++++
.../apache/camel/catalog/JSonSchemaHelper.java | 424 +++++++
.../camel/catalog/JSonSchemaResolver.java | 64 ++
.../camel/catalog/LanguageValidationResult.java | 65 ++
.../camel/catalog/RuntimeCamelCatalog.java | 245 ++++
.../camel/catalog/SimpleValidationResult.java | 32 +
.../camel/catalog/SuggestionStrategy.java | 34 +
.../camel/catalog/TimePatternConverter.java | 120 ++
.../org/apache/camel/catalog/URISupport.java | 392 +++++++
.../catalog/UnsafeUriCharactersEncoder.java | 206 ++++
.../java/org/apache/camel/catalog/package.html | 25 +
.../org/apache/camel/util/EndpointHelper.java | 286 +----
.../camel/catalog/RuntimeCamelCatalogTest.java | 392 +++++++
platforms/camel-catalog/pom.xml | 49 +-
.../camel/catalog/AbstractCamelCatalog.java | 1029 +++++++++++++++++
.../catalog/CamelCatalogJSonSchemaResolver.java | 181 +++
.../camel/catalog/DefaultCamelCatalog.java | 1053 +-----------------
.../camel/catalog/JSonSchemaResolver.java | 64 ++
.../java/org/apache/camel/catalog/package.html | 25 +
25 files changed, 5301 insertions(+), 1327 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/pom.xml
----------------------------------------------------------------------
diff --git a/camel-core/pom.xml b/camel-core/pom.xml
index 1a7c30e..2b7ae8a 100644
--- a/camel-core/pom.xml
+++ b/camel-core/pom.xml
@@ -230,7 +230,36 @@
</plugin>
</plugins>
</pluginManagement>
+
<plugins>
+
+ <!-- Inline the contents of camel-catalog-core into this jar. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.10</version>
+ <executions>
+ <execution>
+ <id>unpack</id>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-catalog-core</artifactId>
+ <version>${project.version}</version>
+ <includes>org/apache/camel/catalog/**</includes>
+ <outputDirectory>${project.build.directory}/classes</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
<!-- shade caffeine cache for faster Camel and spi-annotations as needed by everybody -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/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
new file mode 100644
index 0000000..3295ca9
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/AbstractCamelCatalog.java
@@ -0,0 +1,1029 @@
+/**
+ * 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.catalog;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.camel.catalog.CatalogHelper.after;
+import static org.apache.camel.catalog.JSonSchemaHelper.getNames;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyDefaultValue;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyEnum;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyKind;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyNameFromNameWithPrefix;
+import static org.apache.camel.catalog.JSonSchemaHelper.getPropertyPrefix;
+import static org.apache.camel.catalog.JSonSchemaHelper.getRow;
+import static org.apache.camel.catalog.JSonSchemaHelper.isComponentConsumerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isComponentLenientProperties;
+import static org.apache.camel.catalog.JSonSchemaHelper.isComponentProducerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyBoolean;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyConsumerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyInteger;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyMultiValue;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyNumber;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyObject;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyProducerOnly;
+import static org.apache.camel.catalog.JSonSchemaHelper.isPropertyRequired;
+import static org.apache.camel.catalog.JSonSchemaHelper.stripOptionalPrefixFromName;
+import static org.apache.camel.catalog.URISupport.createQueryString;
+import static org.apache.camel.catalog.URISupport.isEmpty;
+import static org.apache.camel.catalog.URISupport.normalizeUri;
+import static org.apache.camel.catalog.URISupport.stripQuery;
+
+/**
+ * Base class for both the runtime RuntimeCamelCatalog from camel-core and the complete CamelCatalog from camel-catalog.
+ */
+public abstract class AbstractCamelCatalog {
+
+ // CHECKSTYLE:OFF
+
+ private static final Pattern SYNTAX_PATTERN = Pattern.compile("(\\w+)");
+
+ private SuggestionStrategy suggestionStrategy;
+ private JSonSchemaResolver jsonSchemaResolver;
+
+ public SuggestionStrategy getSuggestionStrategy() {
+ return suggestionStrategy;
+ }
+
+ public void setSuggestionStrategy(SuggestionStrategy suggestionStrategy) {
+ this.suggestionStrategy = suggestionStrategy;
+ }
+
+ public JSonSchemaResolver getJSonSchemaResolver() {
+ return jsonSchemaResolver;
+ }
+
+ public void setJSonSchemaResolver(JSonSchemaResolver resolver) {
+ this.jsonSchemaResolver = resolver;
+ }
+
+ public boolean validateTimePattern(String pattern) {
+ return validateInteger(pattern);
+ }
+
+ public EndpointValidationResult validateEndpointProperties(String uri) {
+ return validateEndpointProperties(uri, false, false, false);
+ }
+
+ public EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties) {
+ return validateEndpointProperties(uri, ignoreLenientProperties, false, false);
+ }
+
+ public EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties, boolean consumerOnly, boolean producerOnly) {
+ EndpointValidationResult result = new EndpointValidationResult(uri);
+
+ Map<String, String> properties;
+ List<Map<String, String>> rows;
+ boolean lenientProperties;
+ String scheme;
+
+ try {
+ String json = null;
+
+ // parse the uri
+ URI u = normalizeUri(uri);
+ scheme = u.getScheme();
+
+ if (scheme != null) {
+ json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+ }
+ if (json == null) {
+ // if the uri starts with a placeholder then we are also incapable of parsing it as we wasn't able to resolve the component name
+ if (uri.startsWith("{{")) {
+ result.addIncapable(uri);
+ } else if (scheme != null) {
+ result.addUnknownComponent(scheme);
+ } else {
+ result.addUnknownComponent(uri);
+ }
+ return result;
+ }
+
+ rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+
+ // is the component capable of both consumer and producer?
+ boolean canConsumeAndProduce = false;
+ if (!isComponentConsumerOnly(rows) && !isComponentProducerOnly(rows)) {
+ canConsumeAndProduce = true;
+ }
+
+ if (canConsumeAndProduce && consumerOnly) {
+ // lenient properties is not support in consumer only mode if the component can do both of them
+ lenientProperties = false;
+ } else {
+ // only enable lenient properties if we should not ignore
+ lenientProperties = !ignoreLenientProperties && isComponentLenientProperties(rows);
+ }
+ rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+ properties = endpointProperties(uri);
+ } catch (URISyntaxException e) {
+ if (uri.startsWith("{{")) {
+ // if the uri starts with a placeholder then we are also incapable of parsing it as we wasn't able to resolve the component name
+ result.addIncapable(uri);
+ } else {
+ result.addSyntaxError(e.getMessage());
+ }
+
+ return result;
+ }
+
+ // 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 mulitValue = 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 (lenientProperties) {
+ // 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 {
+ 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 (!mulitValue && !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 (!mulitValue && 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 (!mulitValue && !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 (!mulitValue && !valuePlaceholder && !lookup && isPropertyInteger(rows, name)) {
+ // value must be an integer
+ boolean valid = validateInteger(value);
+ if (!valid) {
+ result.addInvalidInteger(name, value);
+ }
+ }
+
+ // is number
+ if (!mulitValue && !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 Map<String, String> endpointProperties(String uri) throws URISyntaxException {
+ // need to normalize uri first
+ URI u = normalizeUri(uri);
+ String scheme = u.getScheme();
+
+ String json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+ if (json == null) {
+ throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
+ }
+
+ // grab the syntax
+ String syntax = null;
+ String alternativeSyntax = null;
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("syntax")) {
+ syntax = row.get("syntax");
+ }
+ if (row.containsKey("alternativeSyntax")) {
+ alternativeSyntax = row.get("alternativeSyntax");
+ }
+ }
+ if (syntax == null) {
+ throw new IllegalArgumentException("Endpoint with scheme " + scheme + " has no syntax defined in the json schema");
+ }
+
+ // only if we support alternative syntax, and the uri contains the username and password in the authority
+ // part of the uri, then we would need some special logic to capture that information and strip those
+ // details from the uri, so we can continue parsing the uri using the normal syntax
+ Map<String, String> userInfoOptions = new LinkedHashMap<String, String>();
+ if (alternativeSyntax != null && alternativeSyntax.contains("@")) {
+ // clip the scheme from the syntax
+ alternativeSyntax = after(alternativeSyntax, ":");
+ // trim so only userinfo
+ int idx = alternativeSyntax.indexOf("@");
+ String fields = alternativeSyntax.substring(0, idx);
+ String[] names = fields.split(":");
+
+ // grab authority part and grab username and/or password
+ String authority = u.getAuthority();
+ if (authority != null && authority.contains("@")) {
+ String username = null;
+ String password = null;
+
+ // grab unserinfo part before @
+ String userInfo = authority.substring(0, authority.indexOf("@"));
+ String[] parts = userInfo.split(":");
+ if (parts.length == 2) {
+ username = parts[0];
+ password = parts[1];
+ } else {
+ // only username
+ username = userInfo;
+ }
+
+ // remember the username and/or password which we add later to the options
+ if (names.length == 2) {
+ userInfoOptions.put(names[0], username);
+ if (password != null) {
+ // password is optional
+ userInfoOptions.put(names[1], password);
+ }
+ }
+ }
+ }
+
+ // clip the scheme from the syntax
+ syntax = after(syntax, ":");
+ // clip the scheme from the uri
+ uri = after(uri, ":");
+ String uriPath = stripQuery(uri);
+
+ // strip user info from uri path
+ if (!userInfoOptions.isEmpty()) {
+ int idx = uriPath.indexOf('@');
+ if (idx > -1) {
+ uriPath = uriPath.substring(idx + 1);
+ }
+ }
+
+ // strip double slash in the start
+ if (uriPath != null && uriPath.startsWith("//")) {
+ uriPath = uriPath.substring(2);
+ }
+
+ // parse the syntax and find the names of each option
+ Matcher matcher = SYNTAX_PATTERN.matcher(syntax);
+ List<String> word = new ArrayList<String>();
+ while (matcher.find()) {
+ String s = matcher.group(1);
+ if (!scheme.equals(s)) {
+ word.add(s);
+ }
+ }
+ // parse the syntax and find each token between each option
+ String[] tokens = SYNTAX_PATTERN.split(syntax);
+
+ // find the position where each option start/end
+ List<String> word2 = new ArrayList<String>();
+ int prev = 0;
+ int prevPath = 0;
+
+ // special for activemq/jms where the enum for destinationType causes a token issue as it includes a colon
+ // for 'temp:queue' and 'temp:topic' values
+ if ("activemq".equals(scheme) || "jms".equals(scheme)) {
+ if (uriPath.startsWith("temp:")) {
+ prevPath = 5;
+ }
+ }
+
+ for (String token : tokens) {
+ if (token.isEmpty()) {
+ continue;
+ }
+
+ // special for some tokens where :// can be used also, eg http://foo
+ int idx = -1;
+ int len = 0;
+ if (":".equals(token)) {
+ idx = uriPath.indexOf("://", prevPath);
+ len = 3;
+ }
+ if (idx == -1) {
+ idx = uriPath.indexOf(token, prevPath);
+ len = token.length();
+ }
+
+ if (idx > 0) {
+ String option = uriPath.substring(prev, idx);
+ word2.add(option);
+ prev = idx + len;
+ prevPath = prev;
+ }
+ }
+ // special for last or if we did not add anyone
+ if (prev > 0 || word2.isEmpty()) {
+ String option = uriPath.substring(prev);
+ word2.add(option);
+ }
+
+ rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+ boolean defaultValueAdded = false;
+
+ // now parse the uri to know which part isw what
+ Map<String, String> options = new LinkedHashMap<String, String>();
+
+ // include the username and password from the userinfo section
+ if (!userInfoOptions.isEmpty()) {
+ options.putAll(userInfoOptions);
+ }
+
+ // word contains the syntax path elements
+ Iterator<String> it = word2.iterator();
+ for (int i = 0; i < word.size(); i++) {
+ String key = word.get(i);
+
+ boolean allOptions = word.size() == word2.size();
+ boolean required = isPropertyRequired(rows, key);
+ String defaultValue = getPropertyDefaultValue(rows, key);
+
+ // we have all options so no problem
+ if (allOptions) {
+ String value = it.next();
+ options.put(key, value);
+ } else {
+ // we have a little problem as we do not not have all options
+ if (!required) {
+ String value = null;
+
+ boolean last = i == word.size() - 1;
+ if (last) {
+ // if its the last value then use it instead of the default value
+ value = it.hasNext() ? it.next() : null;
+ if (value != null) {
+ options.put(key, value);
+ } else {
+ value = defaultValue;
+ }
+ }
+ if (value != null) {
+ options.put(key, value);
+ defaultValueAdded = true;
+ }
+ } else {
+ String value = it.hasNext() ? it.next() : null;
+ if (value != null) {
+ options.put(key, value);
+ }
+ }
+ }
+ }
+
+ Map<String, String> answer = new LinkedHashMap<String, String>();
+
+ // remove all options which are using default values and are not required
+ for (Map.Entry<String, String> entry : options.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+
+ if (defaultValueAdded) {
+ boolean required = isPropertyRequired(rows, key);
+ String defaultValue = getPropertyDefaultValue(rows, key);
+
+ if (!required && defaultValue != null) {
+ if (defaultValue.equals(value)) {
+ continue;
+ }
+ }
+ }
+
+ // we should keep this in the answer
+ answer.put(key, value);
+ }
+
+ // now parse the uri parameters
+ Map<String, Object> parameters = URISupport.parseParameters(u);
+
+ // and covert the values to String so its JMX friendly
+ while (!parameters.isEmpty()) {
+ Map.Entry<String, Object> entry = parameters.entrySet().iterator().next();
+ String key = entry.getKey();
+ String value = entry.getValue() != null ? entry.getValue().toString() : "";
+
+ boolean multiValued = isPropertyMultiValue(rows, key);
+ if (multiValued) {
+ String prefix = getPropertyPrefix(rows, key);
+ // extra all the multi valued options
+ Map<String, Object> values = URISupport.extractProperties(parameters, prefix);
+ // build a string with the extra multi valued options with the prefix and & as separator
+ CollectionStringBuffer csb = new CollectionStringBuffer("&");
+ for (Map.Entry<String, Object> multi : values.entrySet()) {
+ String line = prefix + multi.getKey() + "=" + (multi.getValue() != null ? multi.getValue().toString() : "");
+ csb.append(line);
+ }
+ // append the extra multi-values to the existing (which contains the first multi value)
+ if (!csb.isEmpty()) {
+ value = value + "&" + csb.toString();
+ }
+ }
+
+ answer.put(key, value);
+ // remove the parameter as we run in a while loop until no more parameters
+ parameters.remove(key);
+ }
+
+ return answer;
+ }
+
+ public Map<String, String> endpointLenientProperties(String uri) throws URISyntaxException {
+ // need to normalize uri first
+
+ // parse the uri
+ URI u = normalizeUri(uri);
+ String scheme = u.getScheme();
+
+ String json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+ if (json == null) {
+ throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
+ }
+
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+ // now parse the uri parameters
+ Map<String, Object> parameters = URISupport.parseParameters(u);
+
+ // all the known options
+ Set<String> names = getNames(rows);
+
+ Map<String, String> answer = new LinkedHashMap<>();
+
+ // and covert the values to String so its JMX friendly
+ parameters.forEach((k, v) -> {
+ String key = k;
+ String value = v != null ? v.toString() : "";
+
+ // is the key a prefix property
+ int dot = key.indexOf('.');
+ if (dot != -1) {
+ String prefix = key.substring(0, dot + 1); // include dot in prefix
+ String option = getPropertyNameFromNameWithPrefix(rows, prefix);
+ if (option == null || !isPropertyMultiValue(rows, option)) {
+ answer.put(key, value);
+ }
+ } else if (!names.contains(key)) {
+ answer.put(key, value);
+ }
+ });
+
+ return answer;
+ }
+
+ public String endpointComponentName(String uri) {
+ if (uri != null) {
+ int idx = uri.indexOf(":");
+ if (idx > 0) {
+ return uri.substring(0, idx);
+ }
+ }
+ return null;
+ }
+
+ public String asEndpointUri(String scheme, String json, boolean encode) throws URISyntaxException {
+ return doAsEndpointUri(scheme, json, "&", encode);
+ }
+
+ public String asEndpointUriXml(String scheme, String json, boolean encode) throws URISyntaxException {
+ return doAsEndpointUri(scheme, json, "&", encode);
+ }
+
+ private String doAsEndpointUri(String scheme, String json, String ampersand, boolean encode) throws URISyntaxException {
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+ Map<String, String> copy = new HashMap<String, String>();
+ for (Map<String, String> row : rows) {
+ String name = row.get("name");
+ String required = row.get("required");
+ String value = row.get("value");
+ String defaultValue = row.get("defaultValue");
+
+ // only add if either required, or the value is != default value
+ String valueToAdd = null;
+ if ("true".equals(required)) {
+ valueToAdd = value != null ? value : defaultValue;
+ if (valueToAdd == null) {
+ valueToAdd = "";
+ }
+ } else {
+ // if we have a value and no default then add it
+ if (value != null && defaultValue == null) {
+ valueToAdd = value;
+ }
+ // otherwise only add if the value is != default value
+ if (value != null && defaultValue != null && !value.equals(defaultValue)) {
+ valueToAdd = value;
+ }
+ }
+
+ if (valueToAdd != null) {
+ copy.put(name, valueToAdd);
+ }
+ }
+
+ return doAsEndpointUri(scheme, copy, ampersand, encode);
+ }
+
+ public String asEndpointUri(String scheme, Map<String, String> properties, boolean encode) throws URISyntaxException {
+ return doAsEndpointUri(scheme, properties, "&", encode);
+ }
+
+ public String asEndpointUriXml(String scheme, Map<String, String> properties, boolean encode) throws URISyntaxException {
+ return doAsEndpointUri(scheme, properties, "&", encode);
+ }
+
+ private String doAsEndpointUri(String scheme, Map<String, String> properties, String ampersand, boolean encode) throws URISyntaxException {
+ String json = jsonSchemaResolver.getComponentJSonSchema(scheme);
+ if (json == null) {
+ throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme);
+ }
+
+ // grab the syntax
+ String syntax = null;
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("component", json, false);
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("syntax")) {
+ syntax = row.get("syntax");
+ break;
+ }
+ }
+ if (syntax == null) {
+ throw new IllegalArgumentException("Endpoint with scheme " + scheme + " has no syntax defined in the json schema");
+ }
+
+ // do any properties filtering which can be needed for some special components
+ properties = filterProperties(scheme, properties);
+
+ rows = JSonSchemaHelper.parseJsonSchema("properties", json, true);
+
+ // clip the scheme from the syntax
+ syntax = after(syntax, ":");
+
+ String originalSyntax = syntax;
+
+ // build at first according to syntax (use a tree map as we want the uri options sorted)
+ Map<String, String> copy = new TreeMap<String, String>();
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue() != null ? entry.getValue() : "";
+ if (syntax != null && syntax.contains(key)) {
+ syntax = syntax.replace(key, value);
+ } else {
+ copy.put(key, value);
+ }
+ }
+
+ // the tokens between the options in the path
+ String[] tokens = syntax.split("\\w+");
+
+ // parse the syntax into each options
+ Matcher matcher = SYNTAX_PATTERN.matcher(originalSyntax);
+ List<String> options = new ArrayList<String>();
+ while (matcher.find()) {
+ String s = matcher.group(1);
+ options.add(s);
+ }
+
+ // need to preserve {{ and }} from the syntax
+ // (we need to use words only as its provisional placeholders)
+ syntax = syntax.replaceAll("\\{\\{", "BEGINCAMELPLACEHOLDER");
+ syntax = syntax.replaceAll("\\}\\}", "ENDCAMELPLACEHOLDER");
+
+ // parse the syntax into each options
+ Matcher matcher2 = SYNTAX_PATTERN.matcher(syntax);
+ List<String> options2 = new ArrayList<String>();
+ while (matcher2.find()) {
+ String s = matcher2.group(1);
+ s = s.replaceAll("BEGINCAMELPLACEHOLDER", "\\{\\{");
+ s = s.replaceAll("ENDCAMELPLACEHOLDER", "\\}\\}");
+ options2.add(s);
+ }
+
+ // build the endpoint
+ StringBuilder sb = new StringBuilder();
+ sb.append(scheme);
+ sb.append(":");
+
+ int range = 0;
+ boolean first = true;
+ boolean hasQuestionmark = false;
+ for (int i = 0; i < options.size(); i++) {
+ String key = options.get(i);
+ String key2 = options2.get(i);
+ String token = null;
+ if (tokens.length > i) {
+ token = tokens[i];
+ }
+
+ boolean contains = properties.containsKey(key);
+ if (!contains) {
+ // if the key are similar we have no explicit value and can try to find a default value if the option is required
+ if (isPropertyRequired(rows, key)) {
+ String value = getPropertyDefaultValue(rows, key);
+ if (value != null) {
+ properties.put(key, value);
+ key2 = value;
+ }
+ }
+ }
+
+ // was the option provided?
+ if (properties.containsKey(key)) {
+ if (!first && token != null) {
+ sb.append(token);
+ }
+ hasQuestionmark |= key.contains("?") || (token != null && token.contains("?"));
+ sb.append(key2);
+ first = false;
+ }
+ range++;
+ }
+ // append any extra options that was in surplus for the last
+ while (range < options2.size()) {
+ String token = null;
+ if (tokens.length > range) {
+ token = tokens[range];
+ }
+ String key2 = options2.get(range);
+ sb.append(token);
+ sb.append(key2);
+ hasQuestionmark |= key2.contains("?") || (token != null && token.contains("?"));
+ range++;
+ }
+
+ if (!copy.isEmpty()) {
+ // the last option may already contain a ? char, if so we should use & instead of ?
+ sb.append(hasQuestionmark ? ampersand : '?');
+ String query = createQueryString(copy, ampersand, encode);
+ sb.append(query);
+ }
+
+ return sb.toString();
+ }
+
+ public SimpleValidationResult validateSimpleExpression(String simple) {
+ return doValidateSimple(null, simple, false);
+ }
+
+ public SimpleValidationResult validateSimpleExpression(ClassLoader classLoader, String simple) {
+ return doValidateSimple(classLoader, simple, false);
+ }
+
+ public SimpleValidationResult validateSimplePredicate(String simple) {
+ return doValidateSimple(null, simple, true);
+ }
+
+ public SimpleValidationResult validateSimplePredicate(ClassLoader classLoader, String simple) {
+ return doValidateSimple(classLoader, simple, true);
+ }
+
+ private SimpleValidationResult doValidateSimple(ClassLoader classLoader, String simple, boolean predicate) {
+ if (classLoader == null) {
+ classLoader = getClass().getClassLoader();
+ }
+
+ // if there are {{ }}} property placeholders then we need to resolve them to something else
+ // as the simple parse cannot resolve them before parsing as we dont run the actual Camel application
+ // with property placeholders setup so we need to dummy this by replace the {{ }} to something else
+ // therefore we use an more unlikely character: {{XXX}} to ~^XXX^~
+ String resolved = simple.replaceAll("\\{\\{(.+)\\}\\}", "~^$1^~");
+
+ SimpleValidationResult answer = new SimpleValidationResult(simple);
+
+ Object instance = null;
+ Class clazz = null;
+ try {
+ clazz = classLoader.loadClass("org.apache.camel.language.simple.SimpleLanguage");
+ instance = clazz.newInstance();
+ } catch (Exception e) {
+ // ignore
+ }
+
+ if (clazz != null && instance != null) {
+ Throwable cause = null;
+ try {
+ if (predicate) {
+ instance.getClass().getMethod("createPredicate", String.class).invoke(instance, resolved);
+ } else {
+ instance.getClass().getMethod("createExpression", String.class).invoke(instance, resolved);
+ }
+ } catch (InvocationTargetException e) {
+ cause = e.getTargetException();
+ } catch (Exception e) {
+ cause = e;
+ }
+
+ if (cause != null) {
+
+ // reverse ~^XXX^~ back to {{XXX}}
+ String errMsg = cause.getMessage();
+ errMsg = errMsg.replaceAll("\\~\\^(.+)\\^\\~", "{{$1}}");
+
+ answer.setError(errMsg);
+
+ // is it simple parser exception then we can grab the index where the problem is
+ if (cause.getClass().getName().equals("org.apache.camel.language.simple.types.SimpleIllegalSyntaxException")
+ || cause.getClass().getName().equals("org.apache.camel.language.simple.types.SimpleParserException")) {
+ try {
+ // we need to grab the index field from those simple parser exceptions
+ Method method = cause.getClass().getMethod("getIndex");
+ Object result = method.invoke(cause);
+ if (result != null) {
+ int index = (int) result;
+ answer.setIndex(index);
+ }
+ } catch (Throwable i) {
+ // ignore
+ }
+ }
+
+ // we need to grab the short message field from this simple syntax exception
+ if (cause.getClass().getName().equals("org.apache.camel.language.simple.types.SimpleIllegalSyntaxException")) {
+ try {
+ Method method = cause.getClass().getMethod("getShortMessage");
+ Object result = method.invoke(cause);
+ if (result != null) {
+ String msg = (String) result;
+ answer.setShortError(msg);
+ }
+ } catch (Throwable i) {
+ // ignore
+ }
+
+ if (answer.getShortError() == null) {
+ // fallback and try to make existing message short instead
+ String msg = answer.getError();
+ // grab everything before " at location " which would be regarded as the short message
+ int idx = msg.indexOf(" at location ");
+ if (idx > 0) {
+ msg = msg.substring(0, idx);
+ answer.setShortError(msg);
+ }
+ }
+ }
+ }
+ }
+
+ return answer;
+ }
+
+ public LanguageValidationResult validateLanguagePredicate(ClassLoader classLoader, String language, String text) {
+ return doValidateLanguage(classLoader, language, text, true);
+ }
+
+ public LanguageValidationResult validateLanguageExpression(ClassLoader classLoader, String language, String text) {
+ return doValidateLanguage(classLoader, language, text, false);
+ }
+
+ private LanguageValidationResult doValidateLanguage(ClassLoader classLoader, String language, String text, boolean predicate) {
+ if (classLoader == null) {
+ classLoader = getClass().getClassLoader();
+ }
+
+ SimpleValidationResult answer = new SimpleValidationResult(text);
+
+ String json = jsonSchemaResolver.getLanguageJSonSchema(language);
+ if (json == null) {
+ answer.setError("Unknown language " + language);
+ return answer;
+ }
+
+ List<Map<String, String>> rows = JSonSchemaHelper.parseJsonSchema("language", json, false);
+ String className = null;
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("javaType")) {
+ className = row.get("javaType");
+ }
+ }
+
+ if (className == null) {
+ answer.setError("Cannot find javaType for language " + language);
+ return answer;
+ }
+
+ Object instance = null;
+ Class clazz = null;
+ try {
+ clazz = classLoader.loadClass(className);
+ instance = clazz.newInstance();
+ } catch (Exception e) {
+ // ignore
+ }
+
+ if (clazz != null && instance != null) {
+ Throwable cause = null;
+ try {
+ if (predicate) {
+ instance.getClass().getMethod("createPredicate", String.class).invoke(instance, text);
+ } else {
+ instance.getClass().getMethod("createExpression", String.class).invoke(instance, text);
+ }
+ } catch (InvocationTargetException e) {
+ cause = e.getTargetException();
+ } catch (Exception e) {
+ cause = e;
+ }
+
+ if (cause != null) {
+ answer.setError(cause.getMessage());
+ }
+ }
+
+ return answer;
+ }
+
+ /**
+ * Special logic for log endpoints to deal when showAll=true
+ */
+ private Map<String, String> filterProperties(String scheme, Map<String, String> options) {
+ if ("log".equals(scheme)) {
+ String showAll = options.get("showAll");
+ if ("true".equals(showAll)) {
+ Map<String, String> filtered = new LinkedHashMap<String, String>();
+ // remove all the other showXXX options when showAll=true
+ for (Map.Entry<String, String> entry : options.entrySet()) {
+ String key = entry.getKey();
+ boolean skip = key.startsWith("show") && !key.equals("showAll");
+ if (!skip) {
+ filtered.put(key, entry.getValue());
+ }
+ }
+ return filtered;
+ }
+ }
+ // use as-is
+ return options;
+ }
+
+ private static boolean validateInteger(String value) {
+ boolean valid = false;
+ try {
+ valid = Integer.valueOf(value) != null;
+ } catch (Exception e) {
+ // ignore
+ }
+ if (!valid) {
+ // it may be a time pattern, such as 5s for 5 seconds = 5000
+ try {
+ TimePatternConverter.toMilliSeconds(value);
+ valid = true;
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ return valid;
+ }
+
+ // CHECKSTYLE:ON
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/CamelContextJSonSchemaResolver.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/CamelContextJSonSchemaResolver.java b/camel-core/src/main/java/org/apache/camel/catalog/CamelContextJSonSchemaResolver.java
new file mode 100644
index 0000000..8d95488
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/CamelContextJSonSchemaResolver.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.catalog;
+
+import java.io.IOException;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * Uses runtime {@link CamelContext} to resolve the JSon schema files.
+ */
+public class CamelContextJSonSchemaResolver implements JSonSchemaResolver {
+
+ private final CamelContext camelContext;
+
+ public CamelContextJSonSchemaResolver(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public String getComponentJSonSchema(String name) {
+ try {
+ return camelContext.getComponentParameterJsonSchema(name);
+ } catch (IOException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @Override
+ public String getDataFormatJSonSchema(String name) {
+ try {
+ return camelContext.getDataFormatParameterJsonSchema(name);
+ } catch (IOException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @Override
+ public String getLanguageJSonSchema(String name) {
+ try {
+ return camelContext.getLanguageParameterJsonSchema(name);
+ } catch (IOException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @Override
+ public String getOtherJSonSchema(String name) {
+ // not supported
+ return null;
+ }
+
+ @Override
+ public String getModelJSonSchema(String name) {
+ try {
+ return camelContext.getEipParameterJsonSchema(name);
+ } catch (IOException e) {
+ // ignore
+ }
+ return null;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/CatalogHelper.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/CatalogHelper.java b/camel-core/src/main/java/org/apache/camel/catalog/CatalogHelper.java
new file mode 100644
index 0000000..f7c0072
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/CatalogHelper.java
@@ -0,0 +1,195 @@
+/**
+ * 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.catalog;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.util.List;
+
+public final class CatalogHelper {
+
+ private CatalogHelper() {
+ }
+
+ /**
+ * Loads the entire stream into memory as a String and returns it.
+ * <p/>
+ * <b>Notice:</b> This implementation appends a <tt>\n</tt> as line
+ * terminator at the of the text.
+ * <p/>
+ * Warning, don't use for crazy big streams :)
+ */
+ public static void loadLines(InputStream in, List<String> lines) throws IOException {
+ InputStreamReader isr = new InputStreamReader(in);
+ try {
+ BufferedReader reader = new LineNumberReader(isr);
+ while (true) {
+ String line = reader.readLine();
+ if (line != null) {
+ lines.add(line);
+ } else {
+ break;
+ }
+ }
+ } finally {
+ isr.close();
+ in.close();
+ }
+ }
+
+ /**
+ * Loads the entire stream into memory as a String and returns it.
+ * <p/>
+ * <b>Notice:</b> This implementation appends a <tt>\n</tt> as line
+ * terminator at the of the text.
+ * <p/>
+ * Warning, don't use for crazy big streams :)
+ */
+ public static String loadText(InputStream in) throws IOException {
+ StringBuilder builder = new StringBuilder();
+ InputStreamReader isr = new InputStreamReader(in);
+ try {
+ BufferedReader reader = new LineNumberReader(isr);
+ while (true) {
+ String line = reader.readLine();
+ if (line != null) {
+ builder.append(line);
+ builder.append("\n");
+ } else {
+ break;
+ }
+ }
+ return builder.toString();
+ } finally {
+ isr.close();
+ in.close();
+ }
+ }
+
+ /**
+ * Matches the name with the pattern.
+ *
+ * @param name the name
+ * @param pattern the pattern
+ * @return <tt>true</tt> if matched, or <tt>false</tt> if not
+ */
+ public static boolean matchWildcard(String name, String pattern) {
+ // we have wildcard support in that hence you can match with: file* to match any file endpoints
+ if (pattern.endsWith("*") && name.startsWith(pattern.substring(0, pattern.length() - 1))) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the string after the given token
+ *
+ * @param text the text
+ * @param after the token
+ * @return the text after the token, or <tt>null</tt> if text does not contain the token
+ */
+ public static String after(String text, String after) {
+ if (!text.contains(after)) {
+ return null;
+ }
+ return text.substring(text.indexOf(after) + after.length());
+ }
+
+ /**
+ * Returns the string before the given token
+ *
+ * @param text the text
+ * @param before the token
+ * @return the text before the token, or <tt>null</tt> if text does not contain the token
+ */
+ public static String before(String text, String before) {
+ if (!text.contains(before)) {
+ return null;
+ }
+ return text.substring(0, text.indexOf(before));
+ }
+
+ /**
+ * Returns the string between the given tokens
+ *
+ * @param text the text
+ * @param after the before token
+ * @param before the after token
+ * @return the text between the tokens, or <tt>null</tt> if text does not contain the tokens
+ */
+ public static String between(String text, String after, String before) {
+ text = after(text, after);
+ if (text == null) {
+ return null;
+ }
+ return before(text, before);
+ }
+
+ /**
+ * Tests whether the value is <tt>null</tt> or an empty string.
+ *
+ * @param value the value, if its a String it will be tested for text length as well
+ * @return true if empty
+ */
+ public static boolean isEmpty(Object value) {
+ return !isNotEmpty(value);
+ }
+
+ /**
+ * Tests whether the value is <b>not</b> <tt>null</tt> or an empty string.
+ *
+ * @param value the value, if its a String it will be tested for text length as well
+ * @return true if <b>not</b> empty
+ */
+ public static boolean isNotEmpty(Object value) {
+ if (value == null) {
+ return false;
+ } else if (value instanceof String) {
+ String text = (String) value;
+ return text.trim().length() > 0;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Removes all leading and ending quotes (single and double) from the string
+ *
+ * @param s the string
+ * @return the string without leading and ending quotes (single and double)
+ */
+ public static String removeLeadingAndEndingQuotes(String s) {
+ if (isEmpty(s)) {
+ return s;
+ }
+
+ String copy = s.trim();
+ if (copy.startsWith("'") && copy.endsWith("'")) {
+ return copy.substring(1, copy.length() - 1);
+ }
+ if (copy.startsWith("\"") && copy.endsWith("\"")) {
+ return copy.substring(1, copy.length() - 1);
+ }
+
+ // no quotes, so return as-is
+ return s;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/CollectionStringBuffer.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/CollectionStringBuffer.java b/camel-core/src/main/java/org/apache/camel/catalog/CollectionStringBuffer.java
new file mode 100644
index 0000000..2844ca9
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/CollectionStringBuffer.java
@@ -0,0 +1,57 @@
+/**
+ * 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.catalog;
+
+public class CollectionStringBuffer {
+ private final StringBuilder buffer = new StringBuilder();
+ private String separator;
+ private boolean first = true;
+
+ public CollectionStringBuffer() {
+ this(", ");
+ }
+
+ public CollectionStringBuffer(String separator) {
+ this.separator = separator;
+ }
+
+ @Override
+ public String toString() {
+ return buffer.toString();
+ }
+
+ public void append(Object value) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.append(separator);
+ }
+ buffer.append(value);
+ }
+
+ public String getSeparator() {
+ return separator;
+ }
+
+ public void setSeparator(String separator) {
+ this.separator = separator;
+ }
+
+ public boolean isEmpty() {
+ return first;
+ }
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/DefaultRuntimeCamelCatalog.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/DefaultRuntimeCamelCatalog.java b/camel-core/src/main/java/org/apache/camel/catalog/DefaultRuntimeCamelCatalog.java
new file mode 100644
index 0000000..8579849
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/DefaultRuntimeCamelCatalog.java
@@ -0,0 +1,126 @@
+/**
+ * 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.catalog;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+
+/**
+ * Default {@link RuntimeCamelCatalog}.
+ */
+public class DefaultRuntimeCamelCatalog extends AbstractCamelCatalog implements RuntimeCamelCatalog {
+
+ // cache of operation -> result
+ private final Map<String, Object> cache = new HashMap<String, Object>();
+ private boolean caching;
+
+ /**
+ * Creates the {@link RuntimeCamelCatalog} without caching enabled.
+ *
+ * @param camelContext the camel context
+ */
+ public DefaultRuntimeCamelCatalog(CamelContext camelContext) {
+ this(camelContext, false);
+ }
+
+ /**
+ * Creates the {@link RuntimeCamelCatalog}
+ *
+ * @param camelContext the camel context
+ * @param caching whether to use cache
+ */
+ public DefaultRuntimeCamelCatalog(CamelContext camelContext, boolean caching) {
+ this.caching = caching;
+ setJSonSchemaResolver(new CamelContextJSonSchemaResolver(camelContext));
+ }
+
+ @Override
+ public String modelJSonSchema(String name) {
+ String answer = null;
+ if (caching) {
+ answer = (String) cache.get("model-" + name);
+ }
+
+ if (answer == null) {
+ answer = getJSonSchemaResolver().getModelJSonSchema(name);
+ if (caching) {
+ cache.put("model-" + name, answer);
+ }
+ }
+
+ return answer;
+ }
+
+ @Override
+ public String componentJSonSchema(String name) {
+ String answer = null;
+ if (caching) {
+ answer = (String) cache.get("component-" + name);
+ }
+
+ if (answer == null) {
+ answer = getJSonSchemaResolver().getComponentJSonSchema(name);
+ if (caching) {
+ cache.put("component-" + name, answer);
+ }
+ }
+
+ return answer;
+ }
+
+ @Override
+ public String dataFormatJSonSchema(String name) {
+ String answer = null;
+ if (caching) {
+ answer = (String) cache.get("dataformat-" + name);
+ }
+
+ if (answer == null) {
+ answer = getJSonSchemaResolver().getDataFormatJSonSchema(name);
+ if (caching) {
+ cache.put("dataformat-" + name, answer);
+ }
+ }
+
+ return answer;
+ }
+
+ @Override
+ public String languageJSonSchema(String name) {
+ // if we try to look method then its in the bean.json file
+ if ("method".equals(name)) {
+ name = "bean";
+ }
+
+ String answer = null;
+ if (caching) {
+ answer = (String) cache.get("language-" + name);
+ }
+
+ if (answer == null) {
+ answer = getJSonSchemaResolver().getLanguageJSonSchema(name);
+ if (caching) {
+ cache.put("language-" + name, answer);
+ }
+ }
+
+ return answer;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/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
new file mode 100644
index 0000000..11e2c5e
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/EndpointValidationResult.java
@@ -0,0 +1,426 @@
+/**
+ * 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.catalog;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.camel.catalog.URISupport.isEmpty;
+
+/**
+ * Details result of validating endpoint uri.
+ */
+public class EndpointValidationResult implements Serializable {
+
+ private final String uri;
+ private int errors;
+
+ // general
+ private String syntaxError;
+ private String unknownComponent;
+ private String incapable;
+
+ // options
+ private Set<String> unknown;
+ private Map<String, String[]> unknownSuggestions;
+ private Set<String> lenient;
+ private Set<String> notConsumerOnly;
+ private Set<String> notProducerOnly;
+ private Set<String> required;
+ private Map<String, String> invalidEnum;
+ private Map<String, String[]> invalidEnumChoices;
+ private Map<String, String[]> invalidEnumSuggestions;
+ private Map<String, String> invalidReference;
+ private Map<String, String> invalidBoolean;
+ private Map<String, String> invalidInteger;
+ private Map<String, String> invalidNumber;
+ private Map<String, String> defaultValues;
+
+ public EndpointValidationResult(String uri) {
+ this.uri = uri;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public int getNumberOfErrors() {
+ return errors;
+ }
+
+ public boolean isSuccess() {
+ boolean ok = syntaxError == null && unknownComponent == null && incapable == null
+ && unknown == null && required == null;
+ if (ok) {
+ ok = notConsumerOnly == null && notProducerOnly == null;
+ }
+ if (ok) {
+ ok = invalidEnum == null && invalidEnumChoices == null && invalidReference == null
+ && invalidBoolean == null && invalidInteger == null && invalidNumber == null;
+ }
+ return ok;
+ }
+
+ public void addSyntaxError(String syntaxError) {
+ this.syntaxError = syntaxError;
+ errors++;
+ }
+
+ public void addIncapable(String uri) {
+ this.incapable = uri;
+ errors++;
+ }
+
+ public void addUnknownComponent(String name) {
+ this.unknownComponent = name;
+ errors++;
+ }
+
+ public void addUnknown(String name) {
+ if (unknown == null) {
+ unknown = new LinkedHashSet<String>();
+ }
+ if (!unknown.contains(name)) {
+ unknown.add(name);
+ errors++;
+ }
+ }
+
+ public void addUnknownSuggestions(String name, String[] suggestions) {
+ if (unknownSuggestions == null) {
+ unknownSuggestions = new LinkedHashMap<String, String[]>();
+ }
+ unknownSuggestions.put(name, suggestions);
+ }
+
+ public void addLenient(String name) {
+ if (lenient == null) {
+ lenient = new LinkedHashSet<String>();
+ }
+ if (!lenient.contains(name)) {
+ lenient.add(name);
+ }
+ }
+
+ public void addRequired(String name) {
+ if (required == null) {
+ required = new LinkedHashSet<String>();
+ }
+ if (!required.contains(name)) {
+ required.add(name);
+ errors++;
+ }
+ }
+
+ public void addInvalidEnum(String name, String value) {
+ if (invalidEnum == null) {
+ invalidEnum = new LinkedHashMap<String, String>();
+ }
+ if (!invalidEnum.containsKey(name)) {
+ invalidEnum.put(name, value);
+ errors++;
+ }
+ }
+
+ public void addInvalidEnumChoices(String name, String[] choices) {
+ if (invalidEnumChoices == null) {
+ invalidEnumChoices = new LinkedHashMap<String, String[]>();
+ }
+ invalidEnumChoices.put(name, choices);
+ }
+
+ public void addInvalidEnumSuggestions(String name, String[] suggestions) {
+ if (invalidEnumSuggestions == null) {
+ invalidEnumSuggestions = new LinkedHashMap<String, String[]>();
+ }
+ invalidEnumSuggestions.put(name, suggestions);
+ }
+
+ public void addInvalidReference(String name, String value) {
+ if (invalidReference == null) {
+ invalidReference = new LinkedHashMap<String, String>();
+ }
+ if (!invalidReference.containsKey(name)) {
+ invalidReference.put(name, value);
+ errors++;
+ }
+ }
+
+ public void addInvalidBoolean(String name, String value) {
+ if (invalidBoolean == null) {
+ invalidBoolean = new LinkedHashMap<String, String>();
+ }
+ if (!invalidBoolean.containsKey(name)) {
+ invalidBoolean.put(name, value);
+ errors++;
+ }
+ }
+
+ public void addInvalidInteger(String name, String value) {
+ if (invalidInteger == null) {
+ invalidInteger = new LinkedHashMap<String, String>();
+ }
+ if (!invalidInteger.containsKey(name)) {
+ invalidInteger.put(name, value);
+ errors++;
+ }
+ }
+
+ public void addInvalidNumber(String name, String value) {
+ if (invalidNumber == null) {
+ invalidNumber = new LinkedHashMap<String, String>();
+ }
+ if (!invalidNumber.containsKey(name)) {
+ invalidNumber.put(name, value);
+ errors++;
+ }
+ }
+
+ public void addDefaultValue(String name, String value) {
+ if (defaultValues == null) {
+ defaultValues = new LinkedHashMap<String, String>();
+ }
+ defaultValues.put(name, value);
+ }
+
+ public void addNotConsumerOnly(String name) {
+ if (notConsumerOnly == null) {
+ notConsumerOnly = new LinkedHashSet<String>();
+ }
+ if (!notConsumerOnly.contains(name)) {
+ notConsumerOnly.add(name);
+ errors++;
+ }
+ }
+
+ public void addNotProducerOnly(String name) {
+ if (notProducerOnly == null) {
+ notProducerOnly = new LinkedHashSet<String>();
+ }
+ if (!notProducerOnly.contains(name)) {
+ notProducerOnly.add(name);
+ errors++;
+ }
+ }
+
+ public String getSyntaxError() {
+ return syntaxError;
+ }
+
+ public String getIncapable() {
+ return incapable;
+ }
+
+ public Set<String> getUnknown() {
+ return unknown;
+ }
+
+ public Set<String> getLenient() {
+ return lenient;
+ }
+
+ public Map<String, String[]> getUnknownSuggestions() {
+ return unknownSuggestions;
+ }
+
+ public String getUnknownComponent() {
+ return unknownComponent;
+ }
+
+ public Set<String> getRequired() {
+ return required;
+ }
+
+ public Map<String, String> getInvalidEnum() {
+ return invalidEnum;
+ }
+
+ public Map<String, String[]> getInvalidEnumChoices() {
+ return invalidEnumChoices;
+ }
+
+ public Map<String, String> getInvalidReference() {
+ return invalidReference;
+ }
+
+ public Map<String, String> getInvalidBoolean() {
+ return invalidBoolean;
+ }
+
+ public Map<String, String> getInvalidInteger() {
+ return invalidInteger;
+ }
+
+ public Map<String, String> getInvalidNumber() {
+ return invalidNumber;
+ }
+
+ public Map<String, String> getDefaultValues() {
+ return defaultValues;
+ }
+
+ public Set<String> getNotConsumerOnly() {
+ return notConsumerOnly;
+ }
+
+ public Set<String> getNotProducerOnly() {
+ return notProducerOnly;
+ }
+
+ /**
+ * A human readable summary of the validation errors.
+ *
+ * @param includeHeader whether to include a header
+ * @return the summary, or <tt>null</tt> if no validation errors
+ */
+ public String summaryErrorMessage(boolean includeHeader) {
+ if (isSuccess()) {
+ return null;
+ }
+
+ if (incapable != null) {
+ return "\tIncapable of parsing uri: " + incapable;
+ } else if (syntaxError != null) {
+ return "\tSyntax error: " + syntaxError;
+ } else if (unknownComponent != null) {
+ return "\tUnknown component: " + unknownComponent;
+ }
+
+ // for each invalid option build a reason message
+ Map<String, String> options = new LinkedHashMap<String, String>();
+ if (unknown != null) {
+ for (String name : unknown) {
+ if (unknownSuggestions != null && unknownSuggestions.containsKey(name)) {
+ String[] suggestions = unknownSuggestions.get(name);
+ if (suggestions != null && suggestions.length > 0) {
+ String str = Arrays.asList(suggestions).toString();
+ options.put(name, "Unknown option. Did you mean: " + str);
+ } else {
+ options.put(name, "Unknown option");
+ }
+ } else {
+ options.put(name, "Unknown option");
+ }
+ }
+ }
+ if (notConsumerOnly != null) {
+ for (String name : notConsumerOnly) {
+ options.put(name, "Option not applicable in consumer only mode");
+ }
+ }
+ if (notProducerOnly != null) {
+ for (String name : notProducerOnly) {
+ options.put(name, "Option not applicable in producer only mode");
+ }
+ }
+ if (required != null) {
+ for (String name : required) {
+ options.put(name, "Missing required option");
+ }
+ }
+ if (invalidEnum != null) {
+ for (Map.Entry<String, String> entry : invalidEnum.entrySet()) {
+ String name = entry.getKey();
+ String[] choices = invalidEnumChoices.get(name);
+ String defaultValue = defaultValues != null ? defaultValues.get(entry.getKey()) : null;
+ String str = Arrays.asList(choices).toString();
+ String msg = "Invalid enum value: " + entry.getValue() + ". Possible values: " + str;
+ if (invalidEnumSuggestions != null) {
+ String[] suggestions = invalidEnumSuggestions.get(name);
+ if (suggestions != null && suggestions.length > 0) {
+ str = Arrays.asList(suggestions).toString();
+ msg += ". Did you mean: " + str;
+ }
+ }
+ if (defaultValue != null) {
+ msg += ". Default value: " + defaultValue;
+ }
+
+ options.put(entry.getKey(), msg);
+ }
+ }
+ if (invalidReference != null) {
+ for (Map.Entry<String, String> entry : invalidReference.entrySet()) {
+ boolean empty = isEmpty(entry.getValue());
+ if (empty) {
+ options.put(entry.getKey(), "Empty reference value");
+ } else if (!entry.getValue().startsWith("#")) {
+ options.put(entry.getKey(), "Invalid reference value: " + entry.getValue() + " must start with #");
+ } else {
+ options.put(entry.getKey(), "Invalid reference value: " + entry.getValue());
+ }
+ }
+ }
+ if (invalidBoolean != null) {
+ for (Map.Entry<String, String> entry : invalidBoolean.entrySet()) {
+ boolean empty = isEmpty(entry.getValue());
+ if (empty) {
+ options.put(entry.getKey(), "Empty boolean value");
+ } else {
+ options.put(entry.getKey(), "Invalid boolean value: " + entry.getValue());
+ }
+ }
+ }
+ if (invalidInteger != null) {
+ for (Map.Entry<String, String> entry : invalidInteger.entrySet()) {
+ boolean empty = isEmpty(entry.getValue());
+ if (empty) {
+ options.put(entry.getKey(), "Empty integer value");
+ } else {
+ options.put(entry.getKey(), "Invalid integer value: " + entry.getValue());
+ }
+ }
+ }
+ if (invalidNumber != null) {
+ for (Map.Entry<String, String> entry : invalidNumber.entrySet()) {
+ boolean empty = isEmpty(entry.getValue());
+ if (empty) {
+ options.put(entry.getKey(), "Empty number value");
+ } else {
+ options.put(entry.getKey(), "Invalid number value: " + entry.getValue());
+ }
+ }
+ }
+
+ // build a table with the error summary nicely formatted
+ // lets use 24 as min length
+ int maxLen = 24;
+ for (String key : options.keySet()) {
+ maxLen = Math.max(maxLen, key.length());
+ }
+ String format = "%" + maxLen + "s %s";
+
+ // build the human error summary
+ StringBuilder sb = new StringBuilder();
+ if (includeHeader) {
+ sb.append("Endpoint validator error\n");
+ sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n");
+ sb.append("\n");
+ }
+ sb.append("\t").append(uri).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);
+ }
+
+ return sb.toString();
+ }
+}