CAMEL-10383: Align explain endpoint options code from camel-core with working code from camel catalog which fixes some problems.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/836661ad Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/836661ad Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/836661ad Branch: refs/heads/camel-2.18.x Commit: 836661ad2c661077507c1d535ae06ce24a02cd2c Parents: 90ece12 Author: Claus Ibsen <davscl...@apache.org> Authored: Sun Oct 16 12:46:49 2016 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Sat Oct 22 11:44:34 2016 +0200 ---------------------------------------------------------------------- .../org/apache/camel/util/EndpointHelper.java | 213 ++++++++++++++++--- .../org/apache/camel/util/JsonSchemaHelper.java | 48 +++++ .../java/org/apache/camel/util/URISupport.java | 18 ++ .../jms/JmsExplainEndpointOptionsTest.java | 33 +++ .../org/apache/camel/catalog/URISupport.java | 2 +- .../apache/camel/catalog/CamelCatalogTest.java | 15 ++ 6 files changed, 294 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/836661ad/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 fb0c91e..ec71ddb 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,7 +20,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -44,6 +43,12 @@ import org.apache.camel.spi.BrowsableEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.util.JsonSchemaHelper.getPropertyDefaultValue; +import static org.apache.camel.util.JsonSchemaHelper.getPropertyPrefix; +import static org.apache.camel.util.JsonSchemaHelper.isPropertyMultiValue; +import static org.apache.camel.util.JsonSchemaHelper.isPropertyRequired; +import static org.apache.camel.util.ObjectHelper.after; + /** * Some helper methods for working with {@link Endpoint} instances */ @@ -132,14 +137,14 @@ public final class EndpointHelper { if (uri.contains("://")) { // try without :// also String scheme = ObjectHelper.before(uri, "://"); - String path = ObjectHelper.after(uri, "://"); + String path = after(uri, "://"); if (matchPattern(scheme + ":" + path, pattern)) { return true; } } else { // try with :// also String scheme = ObjectHelper.before(uri, ":"); - String path = ObjectHelper.after(uri, ":"); + String path = after(uri, ":"); if (matchPattern(scheme + "://" + path, pattern)) { return true; } @@ -521,12 +526,15 @@ public final class EndpointHelper { * @return a map for each option in the uri with the corresponding information from the json * @throws Exception is thrown in case of error */ + // CHECKSTYLE:OFF public static Map<String, Object> endpointProperties(CamelContext camelContext, String uri) throws Exception { - // NOTICE: This logic is similar to org.apache.camel.catalog.DefaultCamelCatalog#endpointProperties + // 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 = new URI(uri); + URI u = normalizeUri(uri); String scheme = u.getScheme(); String json = camelContext.getComponentParameterJsonSchema(u.getScheme()); @@ -536,18 +544,80 @@ public final class EndpointHelper { // 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"); - break; + } + 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"); } - // parse the syntax and find the same group in the uri + // 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()) { @@ -556,29 +626,51 @@ public final class EndpointHelper { word.add(s); } } + // parse the syntax and find each token between each option + String[] tokens = SYNTAX_PATTERN.split(syntax); - String uriPath = stripQuery(uri); - - // if there is only one, then use uriPath as is + // 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; + } + } - if (word.size() == 1) { - String s = uriPath; - s = URISupport.stripPrefix(s, scheme); - // strip any leading : or / after the scheme - while (s.startsWith(":") || s.startsWith("/")) { - s = s.substring(1); + for (String token : tokens) { + if (token.isEmpty()) { + continue; } - word2.add(s); - } else { - Matcher matcher2 = SYNTAX_PATTERN.matcher(uriPath); - while (matcher2.find()) { - String s = matcher2.group(1); - if (!scheme.equals(s)) { - word2.add(s); - } + + // 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); @@ -587,14 +679,19 @@ public final class EndpointHelper { // 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 = JsonSchemaHelper.isPropertyRequired(rows, key); - String defaultValue = JsonSchemaHelper.getPropertyDefaultValue(rows, key); + boolean required = isPropertyRequired(rows, key); + String defaultValue = getPropertyDefaultValue(rows, key); // we have all options so no problem if (allOptions) { @@ -603,12 +700,27 @@ public final class EndpointHelper { } else { // we have a little problem as we do not not have all options if (!required) { - String value = defaultValue; - options.put(key, value); - defaultValueAdded = true; + 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.next(); - options.put(key, value); + String value = it.hasNext() ? it.next() : null; + if (value != null) { + options.put(key, value); + } } } } @@ -621,8 +733,8 @@ public final class EndpointHelper { String value = entry.getValue(); if (defaultValueAdded) { - boolean required = JsonSchemaHelper.isPropertyRequired(rows, key); - String defaultValue = JsonSchemaHelper.getPropertyDefaultValue(rows, key); + boolean required = isPropertyRequired(rows, key); + String defaultValue = getPropertyDefaultValue(rows, key); if (!required && defaultValue != null) { if (defaultValue.equals(value)) { @@ -639,14 +751,47 @@ public final class EndpointHelper { Map<String, Object> parameters = URISupport.parseParameters(u); // and covert the values to String so its JMX friendly - for (Map.Entry<String, Object> entry : parameters.entrySet()) { + 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 http://git-wip-us.apache.org/repos/asf/camel/blob/836661ad/camel-core/src/main/java/org/apache/camel/util/JsonSchemaHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/JsonSchemaHelper.java b/camel-core/src/main/java/org/apache/camel/util/JsonSchemaHelper.java index 41dffe3..df7ff01 100644 --- a/camel-core/src/main/java/org/apache/camel/util/JsonSchemaHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/JsonSchemaHelper.java @@ -243,4 +243,52 @@ public final class JsonSchemaHelper { return null; } + /** + * Is the property multi valued + * + * @param rows the rows of properties + * @param name name of the property + * @return <tt>true</tt> if multi valued, or <tt>false</tt> if not + */ + 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; + } + + /** + * Gets the prefix value of the property + * + * @param rows the rows of properties + * @param name name of the property + * @return the prefix value or <tt>null</tt> if no prefix value exists + */ + 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; + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/836661ad/camel-core/src/main/java/org/apache/camel/util/URISupport.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/URISupport.java b/camel-core/src/main/java/org/apache/camel/util/URISupport.java index 20dd1c2..192b03b 100644 --- a/camel-core/src/main/java/org/apache/camel/util/URISupport.java +++ b/camel-core/src/main/java/org/apache/camel/util/URISupport.java @@ -600,4 +600,22 @@ public final class URISupport { // must include :// to do a correct URI all components can work with return scheme + "://" + path + (query != null ? "?" + query : ""); } + + 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; + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/836661ad/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsExplainEndpointOptionsTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsExplainEndpointOptionsTest.java b/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsExplainEndpointOptionsTest.java new file mode 100644 index 0000000..31ee5fb --- /dev/null +++ b/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsExplainEndpointOptionsTest.java @@ -0,0 +1,33 @@ +/** + * 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.component.jms; + +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class JmsExplainEndpointOptionsTest extends CamelTestSupport { + + @Test + public void testExplain() throws Exception { + String json = context.explainEndpointJson("jms:browse.me", true); + assertNotNull(json); + + assertTrue(json.contains("\"defaultValue\": \"queue\"")); + assertTrue(json.contains("\"value\": \"browse.me\"")); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/836661ad/platforms/catalog/src/main/java/org/apache/camel/catalog/URISupport.java ---------------------------------------------------------------------- diff --git a/platforms/catalog/src/main/java/org/apache/camel/catalog/URISupport.java b/platforms/catalog/src/main/java/org/apache/camel/catalog/URISupport.java index a62523e..079426a 100644 --- a/platforms/catalog/src/main/java/org/apache/camel/catalog/URISupport.java +++ b/platforms/catalog/src/main/java/org/apache/camel/catalog/URISupport.java @@ -42,7 +42,7 @@ public final class URISupport { } /** - * Normalizes the URI so unsafe charachters is encoded + * Normalizes the URI so unsafe characters is encoded * * @param uri the input uri * @return as URI instance http://git-wip-us.apache.org/repos/asf/camel/blob/836661ad/platforms/catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java ---------------------------------------------------------------------- diff --git a/platforms/catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java b/platforms/catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java index 39fe6e4..271ca3f 100644 --- a/platforms/catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java +++ b/platforms/catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java @@ -405,6 +405,21 @@ public class CamelCatalogTest { } @Test + public void testEndpointPropertiesJmsWithDotInName() throws Exception { + Map<String, String> map = catalog.endpointProperties("jms:browse.me"); + assertNotNull(map); + assertEquals(1, map.size()); + + assertEquals("browse.me", map.get("destinationName")); + + map = catalog.endpointProperties("jms:browse.me"); + assertNotNull(map); + assertEquals(1, map.size()); + + assertEquals("browse.me", map.get("destinationName")); + } + + @Test public void testEndpointPropertiesJmsRequired() throws Exception { Map<String, String> map = catalog.endpointProperties("jms:foo"); assertNotNull(map);