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:39 UTC
[3/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
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java b/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java
new file mode 100644
index 0000000..318cbf4
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaHelper.java
@@ -0,0 +1,424 @@
+/**
+ * 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.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class JSonSchemaHelper {
+
+ // 0 = text, 1 = enum, 2 = boolean, 3 = integer or number
+ private static final Pattern PATTERN = Pattern.compile("\"(.+?)\"|\\[(.+)\\]|(true|false)|(\\d+\\.?\\d*)");
+ private static final String QUOT = """;
+
+ private JSonSchemaHelper() {
+ }
+
+ /**
+ * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata
+ *
+ * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>.
+ * @param json the json
+ * @return a list of all the rows, where each row is a set of key value pairs with metadata
+ */
+ public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) {
+ List<Map<String, String>> answer = new ArrayList<Map<String, String>>();
+ if (json == null) {
+ return answer;
+ }
+
+ boolean found = false;
+
+ // parse line by line
+ String[] lines = json.split("\n");
+ for (String line : lines) {
+ // we need to find the group first
+ if (!found) {
+ String s = line.trim();
+ found = s.startsWith("\"" + group + "\":") && s.endsWith("{");
+ continue;
+ }
+
+ // we should stop when we end the group
+ if (line.equals(" },") || line.equals(" }")) {
+ break;
+ }
+
+ // need to safe encode \" so we can parse the line
+ line = line.replaceAll("\"\\\\\"\"", '"' + QUOT + '"');
+
+ Map<String, String> row = new LinkedHashMap<String, String>();
+ Matcher matcher = PATTERN.matcher(line);
+
+ String key;
+ if (parseProperties) {
+ // when parsing properties the first key is given as name, so the first parsed token is the value of the name
+ key = "name";
+ } else {
+ key = null;
+ }
+ while (matcher.find()) {
+ if (key == null) {
+ key = matcher.group(1);
+ } else {
+ String value = matcher.group(1);
+ if (value != null) {
+ // its text based
+ value = value.trim();
+ // decode
+ value = value.replaceAll(QUOT, "\"");
+ value = decodeJson(value);
+ }
+ if (value == null) {
+ // not text then its maybe an enum?
+ value = matcher.group(2);
+ if (value != null) {
+ // its an enum so strip out " and trim spaces after comma
+ value = value.replaceAll("\"", "");
+ value = value.replaceAll(", ", ",");
+ value = value.trim();
+ }
+ }
+ if (value == null) {
+ // not text then its maybe a boolean?
+ value = matcher.group(3);
+ }
+ if (value == null) {
+ // not text then its maybe a integer?
+ value = matcher.group(4);
+ }
+ if (value != null) {
+ row.put(key, value);
+ }
+ // reset
+ key = null;
+ }
+ }
+ if (!row.isEmpty()) {
+ answer.add(row);
+ }
+ }
+
+ return answer;
+ }
+
+ private static String decodeJson(String value) {
+ // json encodes a \ as \\ so we need to decode from \\ back to \
+ if ("\\\\".equals(value)) {
+ value = "\\";
+ }
+ return value;
+ }
+
+ public static boolean isComponentLenientProperties(List<Map<String, String>> rows) {
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("lenientProperties")) {
+ return "true".equals(row.get("lenientProperties"));
+ }
+ }
+ return false;
+ }
+
+ public static boolean isComponentConsumerOnly(List<Map<String, String>> rows) {
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("consumerOnly")) {
+ return "true".equals(row.get("consumerOnly"));
+ }
+ }
+ return false;
+ }
+
+ public static boolean isComponentProducerOnly(List<Map<String, String>> rows) {
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("producerOnly")) {
+ return "true".equals(row.get("producerOnly"));
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPropertyConsumerOnly(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String labels = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("label")) {
+ labels = row.get("label");
+ }
+ if (found) {
+ return labels != null && labels.contains("consumer");
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPropertyProducerOnly(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String labels = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("label")) {
+ labels = row.get("label");
+ }
+ if (found) {
+ return labels != null && labels.contains("producer");
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPropertyRequired(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ boolean required = false;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("required")) {
+ required = "true".equals(row.get("required"));
+ }
+ if (found) {
+ return required;
+ }
+ }
+ return false;
+ }
+
+ public static String getPropertyKind(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String kind = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("kind")) {
+ kind = row.get("kind");
+ }
+ if (found) {
+ return kind;
+ }
+ }
+ return null;
+ }
+
+ public static boolean isPropertyBoolean(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String type = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("type")) {
+ type = row.get("type");
+ }
+ if (found) {
+ return "boolean".equals(type);
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPropertyInteger(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String type = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("type")) {
+ type = row.get("type");
+ }
+ if (found) {
+ return "integer".equals(type);
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPropertyNumber(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String type = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("type")) {
+ type = row.get("type");
+ }
+ if (found) {
+ return "number".equals(type);
+ }
+ }
+ return false;
+ }
+
+ public static boolean isPropertyObject(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String type = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("type")) {
+ type = row.get("type");
+ }
+ if (found) {
+ return "object".equals(type);
+ }
+ }
+ return false;
+ }
+
+ public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String defaultValue = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("defaultValue")) {
+ defaultValue = row.get("defaultValue");
+ }
+ if (found) {
+ return defaultValue;
+ }
+ }
+ return null;
+ }
+
+ public static String stripOptionalPrefixFromName(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String optionalPrefix = null;
+ boolean found = false;
+ if (row.containsKey("optionalPrefix")) {
+ optionalPrefix = row.get("optionalPrefix");
+ }
+ if (row.containsKey("name")) {
+ if (optionalPrefix != null && name.startsWith(optionalPrefix)) {
+ name = name.substring(optionalPrefix.length());
+ // try again
+ return stripOptionalPrefixFromName(rows, name);
+ } else {
+ found = name.equals(row.get("name"));
+ }
+ }
+ if (found) {
+ return name;
+ }
+ }
+ return name;
+ }
+
+ public static String getPropertyEnum(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String enums = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("enum")) {
+ enums = row.get("enum");
+ }
+ if (found) {
+ return enums;
+ }
+ }
+ return null;
+ }
+
+ public static String getPropertyPrefix(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String prefix = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("prefix")) {
+ prefix = row.get("prefix");
+ }
+ if (found) {
+ return prefix;
+ }
+ }
+ return null;
+ }
+
+ public static boolean isPropertyMultiValue(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ boolean multiValue = false;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ found = name.equals(row.get("name"));
+ }
+ if (row.containsKey("multiValue")) {
+ multiValue = "true".equals(row.get("multiValue"));
+ }
+ if (found) {
+ return multiValue;
+ }
+ }
+ return false;
+ }
+
+ public static String getPropertyNameFromNameWithPrefix(List<Map<String, String>> rows, String name) {
+ for (Map<String, String> row : rows) {
+ String propertyName = null;
+ boolean found = false;
+ if (row.containsKey("name")) {
+ propertyName = row.get("name");
+ }
+ if (row.containsKey("prefix")) {
+ String preifx = row.get("prefix");
+ found = name.startsWith(preifx);
+ }
+ if (found) {
+ return propertyName;
+ }
+ }
+ return null;
+ }
+
+ public static Map<String, String> getRow(List<Map<String, String>> rows, String key) {
+ for (Map<String, String> row : rows) {
+ if (key.equals(row.get("name"))) {
+ return row;
+ }
+ }
+ return null;
+ }
+
+ public static Set<String> getNames(List<Map<String, String>> rows) {
+ Set<String> answer = new LinkedHashSet<String>();
+ for (Map<String, String> row : rows) {
+ if (row.containsKey("name")) {
+ answer.add(row.get("name"));
+ }
+ }
+ return answer;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaResolver.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaResolver.java b/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaResolver.java
new file mode 100644
index 0000000..e992f95
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/JSonSchemaResolver.java
@@ -0,0 +1,64 @@
+/**
+ * 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;
+
+/**
+ * Pluggable resolver to load JSon schmea files for components, data formats, languages etc.
+ */
+public interface JSonSchemaResolver {
+
+ /**
+ * Returns the component information as JSon format.
+ *
+ * @param name the component name
+ * @return component details in JSon
+ */
+ String getComponentJSonSchema(String name);
+
+ /**
+ * Returns the data format information as JSon format.
+ *
+ * @param name the data format name
+ * @return data format details in JSon
+ */
+ String getDataFormatJSonSchema(String name);
+
+ /**
+ * Returns the language information as JSon format.
+ *
+ * @param name the language name
+ * @return language details in JSon
+ */
+ String getLanguageJSonSchema(String name);
+
+ /**
+ * Returns the other (miscellaneous) information as JSon format.
+ *
+ * @param name the other (miscellaneous) name
+ * @return other (miscellaneous) details in JSon
+ */
+ String getOtherJSonSchema(String name);
+
+ /**
+ * Returns the model information as JSon format.
+ *
+ * @param name the model name
+ * @return model details in JSon
+ */
+ String getModelJSonSchema(String name);
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java b/camel-core/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java
new file mode 100644
index 0000000..4a977eb
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/LanguageValidationResult.java
@@ -0,0 +1,65 @@
+/**
+ * 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;
+
+/**
+ * Validation result of parsing a language expression or predicate
+ */
+public class LanguageValidationResult implements Serializable {
+ private final String text;
+ private String error;
+ private String shortError;
+ private int index;
+
+ public LanguageValidationResult(String text) {
+ this.text = text;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public boolean isSuccess() {
+ return error == null;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public String getShortError() {
+ return shortError;
+ }
+
+ public void setShortError(String shortError) {
+ this.shortError = shortError;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/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
new file mode 100644
index 0000000..6de1f17
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/RuntimeCamelCatalog.java
@@ -0,0 +1,245 @@
+/**
+ * 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.net.URISyntaxException;
+import java.util.Map;
+
+/**
+ * Runtime based CamelCatalog which are included in camel-core that can provided limit CamelCatalog capabilities
+ */
+public interface RuntimeCamelCatalog {
+
+ /**
+ * Returns the component information as JSon format.
+ *
+ * @param name the component name
+ * @return component details in JSon
+ */
+ String componentJSonSchema(String name);
+
+ /**
+ * Returns the data format information as JSon format.
+ *
+ * @param name the data format name
+ * @return data format details in JSon
+ */
+ String dataFormatJSonSchema(String name);
+
+ /**
+ * Returns the language information as JSon format.
+ *
+ * @param name the language name
+ * @return language details in JSon
+ */
+ String languageJSonSchema(String name);
+
+ /**
+ * Returns the model information as JSon format.
+ *
+ * @param name the model name
+ * @return model details in JSon
+ */
+ String modelJSonSchema(String name);
+
+ /**
+ * Parses the endpoint uri and constructs a key/value properties of each option
+ *
+ * @param uri the endpoint uri
+ * @return properties as key value pairs of each endpoint option
+ */
+ Map<String, String> endpointProperties(String uri) throws URISyntaxException;
+
+ /**
+ * Parses the endpoint uri and constructs a key/value properties of only the lenient properties (eg custom options)
+ * <p/>
+ * For example using the HTTP components to provide query parameters in the endpoint uri.
+ *
+ * @param uri the endpoint uri
+ * @return properties as key value pairs of each lenient properties
+ */
+ Map<String, String> endpointLenientProperties(String uri) throws URISyntaxException;
+
+ /**
+ * Validates the pattern whether its a valid time pattern.
+ *
+ * @param pattern the pattern such as 5000, 5s, 5sec, 4min, 4m30s, 1h, etc.
+ * @return <tt>true</tt> if valid, <tt>false</tt> if invalid
+ */
+ boolean validateTimePattern(String pattern);
+
+ /**
+ * Parses and validates the endpoint uri and constructs a key/value properties of each option.
+ *
+ * @param uri the endpoint uri
+ * @return validation result
+ */
+ EndpointValidationResult validateEndpointProperties(String uri);
+
+ /**
+ * Parses and validates the endpoint uri and constructs a key/value properties of each option.
+ * <p/>
+ * The option ignoreLenientProperties can be used to ignore components that uses lenient properties.
+ * When this is true, then the uri validation is stricter but would fail on properties that are not part of the component
+ * but in the uri because of using lenient properties.
+ * For example using the HTTP components to provide query parameters in the endpoint uri.
+ *
+ * @param uri the endpoint uri
+ * @param ignoreLenientProperties whether to ignore components that uses lenient properties.
+ * @return validation result
+ */
+ EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties);
+
+ /**
+ * Parses and validates the endpoint uri and constructs a key/value properties of each option.
+ * <p/>
+ * The option ignoreLenientProperties can be used to ignore components that uses lenient properties.
+ * When this is true, then the uri validation is stricter but would fail on properties that are not part of the component
+ * but in the uri because of using lenient properties.
+ * For example using the HTTP components to provide query parameters in the endpoint uri.
+ *
+ * @param uri the endpoint uri
+ * @param ignoreLenientProperties whether to ignore components that uses lenient properties.
+ * @param consumerOnly whether the endpoint is only used as a consumer
+ * @param producerOnly whether the endpoint is only used as a producer
+ * @return validation result
+ */
+ EndpointValidationResult validateEndpointProperties(String uri, boolean ignoreLenientProperties, boolean consumerOnly, boolean producerOnly);
+
+ /**
+ * Parses and validates the simple expression.
+ * <p/>
+ * <b>Important:</b> This requires having <tt>camel-core</tt> on the classpath
+ *
+ * @param simple the simple expression
+ * @return validation result
+ * @deprecated use {@link #validateSimpleExpression(ClassLoader, String)}
+ */
+ @Deprecated
+ SimpleValidationResult validateSimpleExpression(String simple);
+
+ /**
+ * Parses and validates the simple expression.
+ * <p/>
+ * <b>Important:</b> This requires having <tt>camel-core</tt> on the classpath
+ *
+ * @param classLoader a custom classloader to use for loading the language from the classpath, or <tt>null</tt> for using default classloader
+ * @param simple the simple expression
+ * @return validation result
+ */
+ SimpleValidationResult validateSimpleExpression(ClassLoader classLoader, String simple);
+
+ /**
+ * Parses and validates the simple predicate
+ * <p/>
+ * <b>Important:</b> This requires having <tt>camel-core</tt> on the classpath
+ *
+ * @param simple the simple predicate
+ * @return validation result
+ * @deprecated use {@link #validateSimplePredicate(ClassLoader, String)}
+ */
+ @Deprecated
+ SimpleValidationResult validateSimplePredicate(String simple);
+
+ /**
+ * Parses and validates the simple predicate
+ * <p/>
+ * <b>Important:</b> This requires having <tt>camel-core</tt> on the classpath
+ *
+ * @param classLoader a custom classloader to use for loading the language from the classpath, or <tt>null</tt> for using default classloader
+ * @param simple the simple predicate
+ * @return validation result
+ */
+ SimpleValidationResult validateSimplePredicate(ClassLoader classLoader, String simple);
+
+ /**
+ * Parses and validates the language as a predicate
+ * <p/>
+ * <b>Important:</b> This requires having <tt>camel-core</tt> and the language dependencies on the classpath
+ *
+ * @param classLoader a custom classloader to use for loading the language from the classpath, or <tt>null</tt> for using default classloader
+ * @param language the name of the language
+ * @param text the predicate text
+ * @return validation result
+ */
+ LanguageValidationResult validateLanguagePredicate(ClassLoader classLoader, String language, String text);
+
+ /**
+ * Parses and validates the language as an expression
+ * <p/>
+ * <b>Important:</b> This requires having <tt>camel-core</tt> and the language dependencies on the classpath
+ *
+ * @param classLoader a custom classloader to use for loading the language from the classpath, or <tt>null</tt> for using default classloader
+ * @param language the name of the language
+ * @param text the expression text
+ * @return validation result
+ */
+ LanguageValidationResult validateLanguageExpression(ClassLoader classLoader, String language, String text);
+
+ /**
+ * Returns the component name from the given endpoint uri
+ *
+ * @param uri the endpoint uri
+ * @return the component name (aka scheme), or <tt>null</tt> if not possible to determine
+ */
+ String endpointComponentName(String uri);
+
+ /**
+ * Creates an endpoint uri in Java style from the information in the json schema
+ *
+ * @param scheme the endpoint schema
+ * @param json the json schema with the endpoint properties
+ * @param encode whether to URL encode the returned uri or not
+ * @return the constructed endpoint uri
+ * @throws java.net.URISyntaxException is thrown if there is encoding error
+ */
+ String asEndpointUri(String scheme, String json, boolean encode) throws URISyntaxException;
+
+ /**
+ * Creates an endpoint uri in XML style (eg escape & as &l;) from the information in the json schema
+ *
+ * @param scheme the endpoint schema
+ * @param json the json schema with the endpoint properties
+ * @param encode whether to URL encode the returned uri or not
+ * @return the constructed endpoint uri
+ * @throws java.net.URISyntaxException is thrown if there is encoding error
+ */
+ String asEndpointUriXml(String scheme, String json, boolean encode) throws URISyntaxException;
+
+ /**
+ * Creates an endpoint uri in Java style from the information from the properties
+ *
+ * @param scheme the endpoint schema
+ * @param properties the properties as key value pairs
+ * @param encode whether to URL encode the returned uri or not
+ * @return the constructed endpoint uri
+ * @throws java.net.URISyntaxException is thrown if there is encoding error
+ */
+ String asEndpointUri(String scheme, Map<String, String> properties, boolean encode) throws URISyntaxException;
+
+ /**
+ * Creates an endpoint uri in XML style (eg escape & as &l;) from the information from the properties
+ *
+ * @param scheme the endpoint schema
+ * @param properties the properties as key value pairs
+ * @param encode whether to URL encode the returned uri or not
+ * @return the constructed endpoint uri
+ * @throws java.net.URISyntaxException is thrown if there is encoding error
+ */
+ String asEndpointUriXml(String scheme, Map<String, String> properties, boolean encode) throws URISyntaxException;
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java b/camel-core/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java
new file mode 100644
index 0000000..5c86f23
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/SimpleValidationResult.java
@@ -0,0 +1,32 @@
+/**
+ * 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;
+
+/**
+ * To be backwards compatible, but favor using {@link LanguageValidationResult} instead.
+ */
+public class SimpleValidationResult extends LanguageValidationResult {
+
+ public SimpleValidationResult(String text) {
+ super(text);
+ }
+
+ public String getSimple() {
+ return getText();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java b/camel-core/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java
new file mode 100644
index 0000000..057d372
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/SuggestionStrategy.java
@@ -0,0 +1,34 @@
+/**
+ * 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.Set;
+
+/**
+ * Strategy to provide suggestions for unknown endpoint options
+ */
+public interface SuggestionStrategy {
+
+ /**
+ * Provides a list of valid option names for a did you mean function.
+ *
+ * @param names valid names
+ * @param unknownOption unknown option name
+ * @return a list of suggested names (did you mean)
+ */
+ String[] suggestEndpointOptions(Set<String> names, String unknownOption);
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/TimePatternConverter.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/TimePatternConverter.java b/camel-core/src/main/java/org/apache/camel/catalog/TimePatternConverter.java
new file mode 100644
index 0000000..4ed843a
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/TimePatternConverter.java
@@ -0,0 +1,120 @@
+/**
+ * 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.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is a copy from camel-core so we can use it independent to validate uris with time patterns
+ */
+public final class TimePatternConverter {
+ private static final Pattern NUMBERS_ONLY_STRING_PATTERN = Pattern.compile("^[-]?(\\d)+$", Pattern.CASE_INSENSITIVE);
+ private static final Pattern HOUR_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))h(our(s)?)?", Pattern.CASE_INSENSITIVE);
+ private static final Pattern MINUTES_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))m(in(ute(s)?)?)?", Pattern.CASE_INSENSITIVE);
+ private static final Pattern SECONDS_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))s(ec(ond)?(s)?)?", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Utility classes should not have a public constructor.
+ */
+ private TimePatternConverter() {
+ }
+
+ public static long toMilliSeconds(String source) throws IllegalArgumentException {
+ long milliseconds = 0;
+ boolean foundFlag = false;
+
+ checkCorrectnessOfPattern(source);
+ Matcher matcher;
+
+ matcher = createMatcher(NUMBERS_ONLY_STRING_PATTERN, source);
+ if (matcher.find()) {
+ // Note: This will also be used for regular numeric strings.
+ // This String -> long converter will be used for all strings.
+ milliseconds = Long.valueOf(source);
+ } else {
+ matcher = createMatcher(HOUR_REGEX_PATTERN, source);
+ if (matcher.find()) {
+ milliseconds = milliseconds + (3600000 * Long.valueOf(matcher.group(1)));
+ foundFlag = true;
+ }
+
+ matcher = createMatcher(MINUTES_REGEX_PATTERN, source);
+ if (matcher.find()) {
+ long minutes = Long.valueOf(matcher.group(1));
+ if ((minutes > 59) && foundFlag) {
+ throw new IllegalArgumentException("Minutes should contain a valid value between 0 and 59: " + source);
+ }
+ foundFlag = true;
+ milliseconds = milliseconds + (60000 * minutes);
+ }
+
+ matcher = createMatcher(SECONDS_REGEX_PATTERN, source);
+ if (matcher.find()) {
+ long seconds = Long.valueOf(matcher.group(1));
+ if ((seconds > 59) && foundFlag) {
+ throw new IllegalArgumentException("Seconds should contain a valid value between 0 and 59: " + source);
+ }
+ foundFlag = true;
+ milliseconds = milliseconds + (1000 * seconds);
+ }
+
+ // No pattern matched... initiating fallback check and conversion (if required).
+ // The source at this point may contain illegal values or special characters
+ if (!foundFlag) {
+ milliseconds = Long.valueOf(source);
+ }
+ }
+
+ return milliseconds;
+ }
+
+ private static void checkCorrectnessOfPattern(String source) {
+ //replace only numbers once
+ Matcher matcher = createMatcher(NUMBERS_ONLY_STRING_PATTERN, source);
+ String replaceSource = matcher.replaceFirst("");
+
+ //replace hour string once
+ matcher = createMatcher(HOUR_REGEX_PATTERN, replaceSource);
+ if (matcher.find() && matcher.find()) {
+ throw new IllegalArgumentException("Hours should not be specified more then once: " + source);
+ }
+ replaceSource = matcher.replaceFirst("");
+
+ //replace minutes once
+ matcher = createMatcher(MINUTES_REGEX_PATTERN, replaceSource);
+ if (matcher.find() && matcher.find()) {
+ throw new IllegalArgumentException("Minutes should not be specified more then once: " + source);
+ }
+ replaceSource = matcher.replaceFirst("");
+
+ //replace seconds once
+ matcher = createMatcher(SECONDS_REGEX_PATTERN, replaceSource);
+ if (matcher.find() && matcher.find()) {
+ throw new IllegalArgumentException("Seconds should not be specified more then once: " + source);
+ }
+ replaceSource = matcher.replaceFirst("");
+
+ if (replaceSource.length() > 0) {
+ throw new IllegalArgumentException("Illegal characters: " + source);
+ }
+ }
+
+ private static Matcher createMatcher(Pattern pattern, String source) {
+ return pattern.matcher(source);
+ }
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/URISupport.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/URISupport.java b/camel-core/src/main/java/org/apache/camel/catalog/URISupport.java
new file mode 100644
index 0000000..ea1ecd5
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/URISupport.java
@@ -0,0 +1,392 @@
+/**
+ * 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.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Copied from org.apache.camel.util.URISupport
+ */
+public final class URISupport {
+
+ public static final String RAW_TOKEN_START = "RAW(";
+ public static final String RAW_TOKEN_END = ")";
+
+ private static final String CHARSET = "UTF-8";
+
+ private URISupport() {
+ // Helper class
+ }
+
+ /**
+ * Normalizes the URI so unsafe characters is encoded
+ *
+ * @param uri the input uri
+ * @return as URI instance
+ * @throws URISyntaxException is thrown if syntax error in the input uri
+ */
+ public static URI normalizeUri(String uri) throws URISyntaxException {
+ return new URI(UnsafeUriCharactersEncoder.encode(uri, true));
+ }
+
+ public static Map<String, Object> extractProperties(Map<String, Object> properties, String optionPrefix) {
+ Map<String, Object> rc = new LinkedHashMap<String, Object>(properties.size());
+
+ for (Iterator<Map.Entry<String, Object>> it = properties.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<String, Object> entry = it.next();
+ String name = entry.getKey();
+ if (name.startsWith(optionPrefix)) {
+ Object value = properties.get(name);
+ name = name.substring(optionPrefix.length());
+ rc.put(name, value);
+ it.remove();
+ }
+ }
+
+ return rc;
+ }
+
+ /**
+ * Strips the query parameters from the uri
+ *
+ * @param uri the uri
+ * @return the uri without the query parameter
+ */
+ public static String stripQuery(String uri) {
+ int idx = uri.indexOf('?');
+ if (idx > -1) {
+ uri = uri.substring(0, idx);
+ }
+ return uri;
+ }
+
+ /**
+ * Parses the query parameters of the uri (eg the query part).
+ *
+ * @param uri the uri
+ * @return the parameters, or an empty map if no parameters (eg never null)
+ * @throws URISyntaxException is thrown if uri has invalid syntax.
+ */
+ public static Map<String, Object> parseParameters(URI uri) throws URISyntaxException {
+ String query = uri.getQuery();
+ if (query == null) {
+ String schemeSpecificPart = uri.getSchemeSpecificPart();
+ int idx = schemeSpecificPart.indexOf('?');
+ if (idx < 0) {
+ // return an empty map
+ return new LinkedHashMap<String, Object>(0);
+ } else {
+ query = schemeSpecificPart.substring(idx + 1);
+ }
+ } else {
+ query = stripPrefix(query, "?");
+ }
+ return parseQuery(query);
+ }
+
+ /**
+ * Strips the prefix from the value.
+ * <p/>
+ * Returns the value as-is if not starting with the prefix.
+ *
+ * @param value the value
+ * @param prefix the prefix to remove from value
+ * @return the value without the prefix
+ */
+ public static String stripPrefix(String value, String prefix) {
+ if (value != null && value.startsWith(prefix)) {
+ return value.substring(prefix.length());
+ }
+ return value;
+ }
+
+ /**
+ * Parses the query part of the uri (eg the parameters).
+ * <p/>
+ * The URI parameters will by default be URI encoded. However you can define a parameter
+ * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to not encode the value,
+ * and use the value as is (eg key=value) and the value has <b>not</b> been encoded.
+ *
+ * @param uri the uri
+ * @return the parameters, or an empty map if no parameters (eg never null)
+ * @throws URISyntaxException is thrown if uri has invalid syntax.
+ * @see #RAW_TOKEN_START
+ * @see #RAW_TOKEN_END
+ */
+ public static Map<String, Object> parseQuery(String uri) throws URISyntaxException {
+ return parseQuery(uri, false);
+ }
+
+ /**
+ * Parses the query part of the uri (eg the parameters).
+ * <p/>
+ * The URI parameters will by default be URI encoded. However you can define a parameter
+ * values with the syntax: <tt>key=RAW(value)</tt> which tells Camel to not encode the value,
+ * and use the value as is (eg key=value) and the value has <b>not</b> been encoded.
+ *
+ * @param uri the uri
+ * @param useRaw whether to force using raw values
+ * @return the parameters, or an empty map if no parameters (eg never null)
+ * @throws URISyntaxException is thrown if uri has invalid syntax.
+ * @see #RAW_TOKEN_START
+ * @see #RAW_TOKEN_END
+ */
+ public static Map<String, Object> parseQuery(String uri, boolean useRaw) throws URISyntaxException {
+ // must check for trailing & as the uri.split("&") will ignore those
+ if (uri != null && uri.endsWith("&")) {
+ throw new URISyntaxException(uri, "Invalid uri syntax: Trailing & marker found. "
+ + "Check the uri and remove the trailing & marker.");
+ }
+
+ if (isEmpty(uri)) {
+ // return an empty map
+ return new LinkedHashMap<String, Object>(0);
+ }
+
+ // need to parse the uri query parameters manually as we cannot rely on splitting by &,
+ // as & can be used in a parameter value as well.
+
+ try {
+ // use a linked map so the parameters is in the same order
+ Map<String, Object> rc = new LinkedHashMap<String, Object>();
+
+ boolean isKey = true;
+ boolean isValue = false;
+ boolean isRaw = false;
+ StringBuilder key = new StringBuilder();
+ StringBuilder value = new StringBuilder();
+
+ // parse the uri parameters char by char
+ for (int i = 0; i < uri.length(); i++) {
+ // current char
+ char ch = uri.charAt(i);
+ // look ahead of the next char
+ char next;
+ if (i <= uri.length() - 2) {
+ next = uri.charAt(i + 1);
+ } else {
+ next = '\u0000';
+ }
+
+ // are we a raw value
+ isRaw = value.toString().startsWith(RAW_TOKEN_START);
+
+ // if we are in raw mode, then we keep adding until we hit the end marker
+ if (isRaw) {
+ if (isKey) {
+ key.append(ch);
+ } else if (isValue) {
+ value.append(ch);
+ }
+
+ // we only end the raw marker if its )& or at the end of the value
+
+ boolean end = ch == RAW_TOKEN_END.charAt(0) && (next == '&' || next == '\u0000');
+ if (end) {
+ // raw value end, so add that as a parameter, and reset flags
+ addParameter(key.toString(), value.toString(), rc, useRaw || isRaw);
+ key.setLength(0);
+ value.setLength(0);
+ isKey = true;
+ isValue = false;
+ isRaw = false;
+ // skip to next as we are in raw mode and have already added the value
+ i++;
+ }
+ continue;
+ }
+
+ // if its a key and there is a = sign then the key ends and we are in value mode
+ if (isKey && ch == '=') {
+ isKey = false;
+ isValue = true;
+ isRaw = false;
+ continue;
+ }
+
+ // the & denote parameter is ended
+ if (ch == '&') {
+ // parameter is ended, as we hit & separator
+ String aKey = key.toString();
+ // the key may be a placeholder of options which we then do not know what is
+ boolean validKey = !aKey.startsWith("{{") && !aKey.endsWith("}}");
+ if (validKey) {
+ addParameter(aKey, value.toString(), rc, useRaw || isRaw);
+ }
+ key.setLength(0);
+ value.setLength(0);
+ isKey = true;
+ isValue = false;
+ isRaw = false;
+ continue;
+ }
+
+ // regular char so add it to the key or value
+ if (isKey) {
+ key.append(ch);
+ } else if (isValue) {
+ value.append(ch);
+ }
+ }
+
+ // any left over parameters, then add that
+ if (key.length() > 0) {
+ String aKey = key.toString();
+ // the key may be a placeholder of options which we then do not know what is
+ boolean validKey = !aKey.startsWith("{{") && !aKey.endsWith("}}");
+ if (validKey) {
+ addParameter(aKey, value.toString(), rc, useRaw || isRaw);
+ }
+ }
+
+ return rc;
+
+ } catch (UnsupportedEncodingException e) {
+ URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
+ se.initCause(e);
+ throw se;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void addParameter(String name, String value, Map<String, Object> map, boolean isRaw) throws UnsupportedEncodingException {
+ name = URLDecoder.decode(name, CHARSET);
+ if (!isRaw) {
+ // need to replace % with %25
+ value = URLDecoder.decode(value.replaceAll("%", "%25"), CHARSET);
+ }
+
+ // does the key already exist?
+ if (map.containsKey(name)) {
+ // yes it does, so make sure we can support multiple values, but using a list
+ // to hold the multiple values
+ Object existing = map.get(name);
+ List<String> list;
+ if (existing instanceof List) {
+ list = (List<String>) existing;
+ } else {
+ // create a new list to hold the multiple values
+ list = new ArrayList<String>();
+ String s = existing != null ? existing.toString() : null;
+ if (s != null) {
+ list.add(s);
+ }
+ }
+ list.add(value);
+ map.put(name, list);
+ } else {
+ map.put(name, value);
+ }
+ }
+
+ /**
+ * Assembles a query from the given map.
+ *
+ * @param options the map with the options (eg key/value pairs)
+ * @param ampersand to use & for Java code, and & for XML
+ * @return a query string with <tt>key1=value&key2=value2&...</tt>, or an empty string if there is no options.
+ * @throws URISyntaxException is thrown if uri has invalid syntax.
+ */
+ public static String createQueryString(Map<String, String> options, String ampersand, boolean encode) throws URISyntaxException {
+ try {
+ if (options.size() > 0) {
+ StringBuilder rc = new StringBuilder();
+ boolean first = true;
+ for (Object o : options.keySet()) {
+ if (first) {
+ first = false;
+ } else {
+ rc.append(ampersand);
+ }
+
+ String key = (String) o;
+ Object value = options.get(key);
+
+ // use the value as a String
+ String s = value != null ? value.toString() : null;
+ appendQueryStringParameter(key, s, rc, encode);
+ }
+ return rc.toString();
+ } else {
+ return "";
+ }
+ } catch (UnsupportedEncodingException e) {
+ URISyntaxException se = new URISyntaxException(e.toString(), "Invalid encoding");
+ se.initCause(e);
+ throw se;
+ }
+ }
+
+ private static void appendQueryStringParameter(String key, String value, StringBuilder rc, boolean encode) throws UnsupportedEncodingException {
+ if (encode) {
+ rc.append(URLEncoder.encode(key, CHARSET));
+ } else {
+ rc.append(key);
+ }
+ // only append if value is not null
+ if (value != null) {
+ rc.append("=");
+ if (value.startsWith(RAW_TOKEN_START) && value.endsWith(RAW_TOKEN_END)) {
+ // do not encode RAW parameters
+ rc.append(value);
+ } else {
+ if (encode) {
+ rc.append(URLEncoder.encode(value, CHARSET));
+ } else {
+ rc.append(value);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java b/camel-core/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java
new file mode 100644
index 0000000..563bac2
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/UnsafeUriCharactersEncoder.java
@@ -0,0 +1,206 @@
+/**
+ * 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.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Encoder for unsafe URI characters.
+ * <p/>
+ * A good source for details is <a href="http://en.wikipedia.org/wiki/Url_encode">wikipedia url encode</a> article.
+ */
+public final class UnsafeUriCharactersEncoder {
+ private static BitSet unsafeCharactersRfc1738;
+ private static BitSet unsafeCharactersHttp;
+ private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
+ 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'};
+ private static final Pattern RAW_PATTERN = Pattern.compile("RAW\\([^\\)]+\\)");
+
+ static {
+ unsafeCharactersRfc1738 = new BitSet(256);
+ unsafeCharactersRfc1738.set(' ');
+ unsafeCharactersRfc1738.set('"');
+ unsafeCharactersRfc1738.set('<');
+ unsafeCharactersRfc1738.set('>');
+ unsafeCharactersRfc1738.set('#');
+ unsafeCharactersRfc1738.set('%');
+ unsafeCharactersRfc1738.set('{');
+ unsafeCharactersRfc1738.set('}');
+ unsafeCharactersRfc1738.set('|');
+ unsafeCharactersRfc1738.set('\\');
+ unsafeCharactersRfc1738.set('^');
+ unsafeCharactersRfc1738.set('~');
+ unsafeCharactersRfc1738.set('[');
+ unsafeCharactersRfc1738.set(']');
+ unsafeCharactersRfc1738.set('`');
+ }
+
+ static {
+ unsafeCharactersHttp = new BitSet(256);
+ unsafeCharactersHttp.set(' ');
+ unsafeCharactersHttp.set('"');
+ unsafeCharactersHttp.set('<');
+ unsafeCharactersHttp.set('>');
+ unsafeCharactersHttp.set('#');
+ unsafeCharactersHttp.set('%');
+ unsafeCharactersHttp.set('{');
+ unsafeCharactersHttp.set('}');
+ unsafeCharactersHttp.set('|');
+ unsafeCharactersHttp.set('\\');
+ unsafeCharactersHttp.set('^');
+ unsafeCharactersHttp.set('~');
+ unsafeCharactersHttp.set('`');
+ }
+
+ private UnsafeUriCharactersEncoder() {
+ // util class
+ }
+
+ public static String encode(String s) {
+ return encode(s, unsafeCharactersRfc1738);
+ }
+
+ public static String encodeHttpURI(String s) {
+ return encode(s, unsafeCharactersHttp);
+ }
+
+ public static String encode(String s, BitSet unsafeCharacters) {
+ return encode(s, unsafeCharacters, false);
+ }
+
+ public static String encode(String s, boolean checkRaw) {
+ return encode(s, unsafeCharactersRfc1738, checkRaw);
+ }
+
+ public static String encodeHttpURI(String s, boolean checkRaw) {
+ return encode(s, unsafeCharactersHttp, checkRaw);
+ }
+
+ private static List<Pair> checkRAW(String s) {
+ Matcher matcher = RAW_PATTERN.matcher(s);
+ List<Pair> answer = new ArrayList<Pair>();
+ // Check all occurrences
+ while (matcher.find()) {
+ answer.add(new Pair(matcher.start(), matcher.end()));
+ }
+ return answer;
+ }
+
+ private static boolean isRaw(int index, List<Pair> pairs) {
+ for (Pair pair : pairs) {
+ if (index < pair.left) {
+ return false;
+ } else {
+ if (index >= pair.left) {
+ if (index <= pair.right) {
+ return true;
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private static class Pair {
+ int left;
+ int right;
+
+ Pair(int left, int right) {
+ this.left = left;
+ this.right = right;
+ }
+ }
+
+ // Just skip the encode for isRAW part
+ public static String encode(String s, BitSet unsafeCharacters, boolean checkRaw) {
+ List<Pair> rawPairs;
+ if (checkRaw) {
+ rawPairs = checkRAW(s);
+ } else {
+ rawPairs = new ArrayList<Pair>();
+ }
+
+ int n = s == null ? 0 : s.length();
+ if (n == 0) {
+ return s;
+ }
+
+ // First check whether we actually need to encode
+ char chars[] = s.toCharArray();
+ for (int i = 0;;) {
+ // just deal with the ascii character
+ if (chars[i] > 0 && chars[i] < 128) {
+ if (unsafeCharacters.get(chars[i])) {
+ break;
+ }
+ }
+ if (++i >= chars.length) {
+ return s;
+ }
+ }
+
+ // okay there are some unsafe characters so we do need to encode
+ // see details at: http://en.wikipedia.org/wiki/Url_encode
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < chars.length; i++) {
+ char ch = chars[i];
+ if (ch > 0 && ch < 128 && unsafeCharacters.get(ch)) {
+ // special for % sign as it may be a decimal encoded value
+ if (ch == '%') {
+ char next = i + 1 < chars.length ? chars[i + 1] : ' ';
+ char next2 = i + 2 < chars.length ? chars[i + 2] : ' ';
+
+ if (isHexDigit(next) && isHexDigit(next2) && !isRaw(i, rawPairs)) {
+ // its already encoded (decimal encoded) so just append as is
+ sb.append(ch);
+ } else {
+ // must escape then, as its an unsafe character
+ appendEscape(sb, (byte) ch);
+ }
+ } else {
+ // must escape then, as its an unsafe character
+ appendEscape(sb, (byte) ch);
+ }
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void appendEscape(StringBuilder sb, byte b) {
+ sb.append('%');
+ sb.append(HEX_DIGITS[(b >> 4) & 0x0f]);
+ sb.append(HEX_DIGITS[(b >> 0) & 0x0f]);
+ }
+
+ private static boolean isHexDigit(char ch) {
+ for (char hex : HEX_DIGITS) {
+ if (hex == ch) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/catalog/package.html
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/catalog/package.html b/camel-core/src/main/java/org/apache/camel/catalog/package.html
new file mode 100644
index 0000000..98786d1
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/catalog/package.html
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<html>
+<head>
+</head>
+<body>
+
+Camel Catalog
+
+</body>
+</html>
http://git-wip-us.apache.org/repos/asf/camel/blob/f5848e39/camel-core/src/main/java/org/apache/camel/util/EndpointHelper.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/EndpointHelper.java b/camel-core/src/main/java/org/apache/camel/util/EndpointHelper.java
index ec71ddb..d1ebff3 100644
--- a/camel-core/src/main/java/org/apache/camel/util/EndpointHelper.java
+++ b/camel-core/src/main/java/org/apache/camel/util/EndpointHelper.java
@@ -20,6 +20,7 @@ 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.List;
@@ -39,6 +40,8 @@ import org.apache.camel.PollingConsumer;
import org.apache.camel.Processor;
import org.apache.camel.ResolveEndpointFailedException;
import org.apache.camel.Route;
+import org.apache.camel.catalog.DefaultRuntimeCamelCatalog;
+import org.apache.camel.catalog.RuntimeCamelCatalog;
import org.apache.camel.spi.BrowsableEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -525,286 +528,13 @@ public final class EndpointHelper {
* @param uri the endpoint uri
* @return a map for each option in the uri with the corresponding information from the json
* @throws Exception is thrown in case of error
+ * @deprecated use {@link org.apache.camel.catalog.RuntimeCamelCatalog#endpointProperties(String)}
*/
- // CHECKSTYLE:OFF
+ @Deprecated
public static Map<String, Object> endpointProperties(CamelContext camelContext, String uri) throws Exception {
- // NOTICE: This logic is similar to org.apache.camel.util.EndpointHelper#endpointProperties
- // as the catalog also offers similar functionality (without having camel-core on classpath)
-
- // need to normalize uri first
-
- // parse the uri
- URI u = normalizeUri(uri);
- String scheme = u.getScheme();
-
- String json = camelContext.getComponentParameterJsonSchema(u.getScheme());
- 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, Object> answer = new LinkedHashMap<String, Object>();
-
- // 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;
- }
- // CHECKSTYLE:ON
-
- /**
- * Normalizes the URI so unsafe characters is encoded
- *
- * @param uri the input uri
- * @return as URI instance
- * @throws URISyntaxException is thrown if syntax error in the input uri
- */
- private static URI normalizeUri(String uri) throws URISyntaxException {
- return new URI(UnsafeUriCharactersEncoder.encode(uri, true));
- }
-
- /**
- * Strips the query parameters from the uri
- *
- * @param uri the uri
- * @return the uri without the query parameter
- */
- private static String stripQuery(String uri) {
- int idx = uri.indexOf('?');
- if (idx > -1) {
- uri = uri.substring(0, idx);
- }
- return uri;
+ RuntimeCamelCatalog catalog = new DefaultRuntimeCamelCatalog(camelContext, false);
+ Map<String, String> options = catalog.endpointProperties(uri);
+ return new HashMap<>(options);
}
}