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 2014/06/19 09:17:42 UTC
[5/6] git commit: CAMEL-7429 Camel Properties Component concatenation
issue
CAMEL-7429 Camel Properties Component concatenation issue
Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/43956f93
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/43956f93
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/43956f93
Branch: refs/heads/master
Commit: 43956f9368c0950a75fdb39923c1c1d8e2b26e5d
Parents: 5ba767e
Author: Antoine DESSAIGNE <an...@gmail.com>
Authored: Wed Jun 18 13:31:39 2014 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Thu Jun 19 09:17:28 2014 +0200
----------------------------------------------------------------------
.../properties/DefaultPropertiesParser.java | 381 ++++++++++++-------
...rtiesComponentConcatenatePropertiesTest.java | 74 ++++
.../properties/concatenation.properties | 24 ++
3 files changed, 333 insertions(+), 146 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/camel/blob/43956f93/camel-core/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java b/camel-core/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
index f7bffa7..1ee227a 100644
--- a/camel-core/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
+++ b/camel-core/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
@@ -16,189 +16,278 @@
*/
package org.apache.camel.component.properties;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
import java.util.Properties;
+import java.util.Set;
-import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static java.lang.String.format;
+
/**
* A parser to parse a string which contains property placeholders
- *
- * @version
*/
public class DefaultPropertiesParser implements AugmentedPropertyNameAwarePropertiesParser {
protected final Logger log = LoggerFactory.getLogger(getClass());
-
+
@Override
public String parseUri(String text, Properties properties, String prefixToken, String suffixToken) throws IllegalArgumentException {
return parseUri(text, properties, prefixToken, suffixToken, null, null, false);
}
- public String parseUri(String text, Properties properties, String prefixToken, String suffixToken,
- String propertyPrefix, String propertySuffix, boolean fallbackToUnaugmentedProperty) throws IllegalArgumentException {
- String answer = text;
- boolean done = false;
-
- // the placeholders can contain nested placeholders so we need to do recursive parsing
- // we must therefore also do circular reference check and must keep a list of visited keys
- List<String> visited = new ArrayList<String>();
- while (!done) {
- List<String> replaced = new ArrayList<String>();
- answer = doParseUri(answer, properties, replaced, prefixToken, suffixToken, propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty);
-
- // check the replaced with the visited to avoid circular reference
- for (String replace : replaced) {
- if (visited.contains(replace)) {
- throw new IllegalArgumentException("Circular reference detected with key [" + replace + "] from text: " + text);
- }
- }
- // okay all okay so add the replaced as visited
- visited.addAll(replaced);
-
- // we are done when we can no longer find any prefix tokens in the answer
- done = findTokenPosition(answer, 0, prefixToken) == -1;
- }
- return answer;
+ public String parseUri(String text, Properties properties, String prefixToken, String suffixToken, String propertyPrefix, String propertySuffix,
+ boolean fallbackToUnaugmentedProperty) throws IllegalArgumentException {
+ ParsingContext context = new ParsingContext(properties, prefixToken, suffixToken, propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty);
+ return context.parse(text);
}
public String parseProperty(String key, String value, Properties properties) {
return value;
}
- private String doParseUri(String uri, Properties properties, List<String> replaced, String prefixToken, String suffixToken,
- String propertyPrefix, String propertySuffix, boolean fallbackToUnaugmentedProperty) {
- StringBuilder sb = new StringBuilder();
-
- int pivot = 0;
- int size = uri.length();
- while (pivot < size) {
- int idx = findTokenPosition(uri, pivot, prefixToken);
- if (idx < 0) {
- sb.append(createConstantPart(uri, pivot, size));
- break;
- } else {
- if (pivot < idx) {
- sb.append(createConstantPart(uri, pivot, idx));
- }
- pivot = idx + prefixToken.length();
- int endIdx = findTokenPosition(uri, pivot, suffixToken);
- if (endIdx < 0) {
- throw new IllegalArgumentException("Expecting " + suffixToken + " but found end of string from text: " + uri);
- }
- String key = uri.substring(pivot, endIdx);
- String augmentedKey = key;
-
- if (propertyPrefix != null) {
- log.debug("Augmenting property key [{}] with prefix: {}", key, propertyPrefix);
- augmentedKey = propertyPrefix + augmentedKey;
- }
-
- if (propertySuffix != null) {
- log.debug("Augmenting property key [{}] with suffix: {}", key, propertySuffix);
- augmentedKey = augmentedKey + propertySuffix;
- }
+ /**
+ * This inner class helps replacing properties.
+ */
+ private final class ParsingContext {
+ private final Properties properties;
+ private final String prefixToken;
+ private final String suffixToken;
+ private final String propertyPrefix;
+ private final String propertySuffix;
+ private final boolean fallbackToUnaugmentedProperty;
- String part = createPlaceholderPart(augmentedKey, properties, replaced, prefixToken, suffixToken);
-
- // Note: Only fallback to unaugmented when the original key was actually augmented
- if (part == null && fallbackToUnaugmentedProperty && (propertyPrefix != null || propertySuffix != null)) {
- log.debug("Property wth key [{}] not found, attempting with unaugmented key: {}", augmentedKey, key);
- part = createPlaceholderPart(key, properties, replaced, prefixToken, suffixToken);
- }
-
- if (part == null) {
- StringBuilder esb = new StringBuilder();
- esb.append("Property with key [").append(augmentedKey).append("] ");
- if (fallbackToUnaugmentedProperty && (propertyPrefix != null || propertySuffix != null)) {
- esb.append("(and original key [").append(key).append("]) ");
- }
- esb.append("not found in properties from text: ").append(uri);
- throw new IllegalArgumentException(esb.toString());
+ public ParsingContext(Properties properties, String prefixToken, String suffixToken, String propertyPrefix, String propertySuffix,
+ boolean fallbackToUnaugmentedProperty) {
+ this.properties = properties;
+ this.prefixToken = prefixToken;
+ this.suffixToken = suffixToken;
+ this.propertyPrefix = propertyPrefix;
+ this.propertySuffix = propertySuffix;
+ this.fallbackToUnaugmentedProperty = fallbackToUnaugmentedProperty;
+ }
+
+ /**
+ * Parses the given input string and replaces all properties
+ *
+ * @param input Input string
+ * @return Evaluated string
+ */
+ public String parse(String input) {
+ return doParse(input, new HashSet<String>());
+ }
+
+ /**
+ * Recursively parses the given input string and replaces all properties
+ *
+ * @param input Input string
+ * @param replacedPropertyKeys Already replaced property keys used for tracking circular references
+ * @return Evaluated string
+ */
+ private String doParse(String input, Set<String> replacedPropertyKeys) {
+ String answer = input;
+ Property property;
+ while ((property = readProperty(answer)) != null) {
+ // Check for circular references
+ if (replacedPropertyKeys.contains(property.getKey())) {
+ throw new IllegalArgumentException("Circular reference detected with key [" + property.getKey() + "] from text: " + input);
}
- sb.append(part);
- pivot = endIdx + suffixToken.length();
+
+ Set<String> newReplaced = new HashSet<String>(replacedPropertyKeys);
+ newReplaced.add(property.getKey());
+
+ String before = answer.substring(0, property.getBeginIndex());
+ String after = answer.substring(property.getEndIndex());
+ answer = before + doParse(property.getValue(), newReplaced) + after;
}
+ return answer;
}
- return sb.toString();
- }
-
- private int findTokenPosition(String uri, int pivot, String token) {
- int idx = uri.indexOf(token, pivot);
- while (idx > 0) {
- // grab part as the previous char + token + next char, to test if the token is quoted
- String part = null;
- int len = idx + token.length() + 1;
- if (uri.length() >= len) {
- part = uri.substring(idx - 1, len);
+
+ /**
+ * Finds a property in the given string. It returns {@code null} if there's no property defined.
+ *
+ * @param input Input string
+ * @return A property in the given string or {@code null} if not found
+ */
+ private Property readProperty(String input) {
+ // Find the index of the first valid suffix token
+ int suffix = getSuffixIndex(input);
+
+ // If not found, ensure that there is no valid prefix token in the string
+ if (suffix == -1) {
+ if (getMatchingPrefixIndex(input, input.length()) != -1) {
+ throw new IllegalArgumentException(format("Missing %s from the text: %s", suffixToken, input));
+ }
+ return null;
}
- if (StringHelper.isQuoted(part)) {
- // the token was quoted, so regard it as a literal
- // and then try to find from next position
- pivot = idx + token.length() + 1;
- idx = uri.indexOf(token, pivot);
- } else {
- // found token
- return idx;
+
+ // Find the index of the prefix token that matches the suffix token
+ int prefix = getMatchingPrefixIndex(input, suffix);
+ if (prefix == -1) {
+ throw new IllegalArgumentException(format("Missing %s from the text: %s", prefixToken, input));
}
+
+ String key = input.substring(prefix + prefixToken.length(), suffix);
+ String value = getPropertyValue(key, input);
+ return new Property(prefix, suffix + suffixToken.length(), key, value);
}
- return idx;
- }
-
- private boolean isNestProperty(String uri, String prefixToken, String suffixToken) {
- if (ObjectHelper.isNotEmpty(uri)) {
- uri = uri.trim();
- if (uri.startsWith(prefixToken) && uri.endsWith(suffixToken)) {
- return true;
+
+ /**
+ * Gets the first index of the suffix token that is not surrounded by quotes
+ *
+ * @param input Input string
+ * @return First index of the suffix token that is not surrounded by quotes
+ */
+ private int getSuffixIndex(String input) {
+ int index = -1;
+ do {
+ index = input.indexOf(suffixToken, index + 1);
+ } while (index != -1 && isQuoted(input, index, suffixToken));
+ return index;
+ }
+
+ /**
+ * Gets the index of the prefix token that matches the suffix at the given index and that is not surrounded by quotes
+ *
+ * @param input Input string
+ * @param suffixIndex Index of the suffix token
+ * @return Index of the prefix token that matches the suffix at the given index and that is not surrounded by quotes
+ */
+ private int getMatchingPrefixIndex(String input, int suffixIndex) {
+ int index = suffixIndex;
+ do {
+ index = input.lastIndexOf(prefixToken, index - 1);
+ } while (index != -1 && isQuoted(input, index, prefixToken));
+ return index;
+ }
+
+ /**
+ * Indicates whether or not the token at the given index is surrounded by single or double quotes
+ *
+ * @param input Input string
+ * @param index Index of the token
+ * @param token Token
+ * @return {@code true}
+ */
+ private boolean isQuoted(String input, int index, String token) {
+ int beforeIndex = index - 1;
+ int afterIndex = index + token.length();
+ if (beforeIndex >= 0 && afterIndex < input.length()) {
+ char before = input.charAt(beforeIndex);
+ char after = input.charAt(afterIndex);
+ return (before == after) && (before == '\'' || before == '"');
}
+ return false;
}
- return false;
- }
-
- private String takeOffNestTokes(String uri, String prefixToken, String suffixToken) {
- int start = prefixToken.length();
- int end = uri.length() - suffixToken.length();
- return uri.substring(start, end);
- }
- private String createConstantPart(String uri, int start, int end) {
- return uri.substring(start, end);
- }
+ /**
+ * Gets the value of the property with given key
+ *
+ * @param key Key of the property
+ * @param input Input string (used for exception message if value not found)
+ * @return Value of the property with the given key
+ */
+ private String getPropertyValue(String key, String input) {
+ String augmentedKey = getAugmentedKey(key);
+ boolean shouldFallback = fallbackToUnaugmentedProperty && !key.equals(augmentedKey);
+
+ String value = doGetPropertyValue(augmentedKey);
+ if (value == null && shouldFallback) {
+ log.debug("Property with key [{}] not found, attempting with unaugmented key: {}", augmentedKey, key);
+ value = doGetPropertyValue(key);
+ }
+
+ if (value == null) {
+ StringBuilder esb = new StringBuilder();
+ esb.append("Property with key [").append(augmentedKey).append("] ");
+ if (shouldFallback) {
+ esb.append("(and original key [").append(key).append("]) ");
+ }
+ esb.append("not found in properties from text: ").append(input);
+ throw new IllegalArgumentException(esb.toString());
+ }
- private String createPlaceholderPart(String key, Properties properties, List<String> replaced, String prefixToken, String suffixToken) {
- // keep track of which parts we have replaced
- replaced.add(key);
-
- String propertyValue = System.getProperty(key);
- if (propertyValue != null) {
- log.debug("Found a JVM system property: {} with value: {} to be used.", key, propertyValue);
- } else if (properties != null) {
- propertyValue = properties.getProperty(key);
+ return value;
}
-
- // we need to check if the propertyValue is nested
- // we need to check if there is cycle dependency of the nested properties
- List<String> visited = new ArrayList<String>();
- while (isNestProperty(propertyValue, prefixToken, suffixToken)) {
- visited.add(key);
- // need to take off the token first
- String value = takeOffNestTokes(propertyValue, prefixToken, suffixToken);
- key = parseUri(value, properties, prefixToken, suffixToken);
- if (visited.contains(key)) {
- throw new IllegalArgumentException("Circular reference detected with key [" + key + "] from text: " + propertyValue);
+
+ /**
+ * Gets the augmented key of the given base key
+ *
+ * @param key Base key
+ * @return Augmented key
+ */
+ private String getAugmentedKey(String key) {
+ String augmentedKey = key;
+ if (propertyPrefix != null) {
+ log.debug("Augmenting property key [{}] with prefix: {}", key, propertyPrefix);
+ augmentedKey = propertyPrefix + augmentedKey;
}
- propertyValue = System.getProperty(key);
- if (propertyValue != null) {
- log.debug("Found a JVM system property: {} with value: {} to be used.", key, propertyValue);
- } else if (properties != null) {
- propertyValue = properties.getProperty(key);
+ if (propertySuffix != null) {
+ log.debug("Augmenting property key [{}] with suffix: {}", key, propertySuffix);
+ augmentedKey = augmentedKey + propertySuffix;
}
+ return augmentedKey;
}
- return parseProperty(key, propertyValue, properties);
+ /**
+ * Gets the property with the given key, it returns {@code null} if the property is not found
+ *
+ * @param key Key of the property
+ * @return Value of the property or {@code null} if not found
+ */
+ private String doGetPropertyValue(String key) {
+ String value = System.getProperty(key);
+ if (value != null) {
+ log.debug("Found a JVM system property: {} with value: {} to be used.", key, value);
+ } else if (properties != null) {
+ value = properties.getProperty(key);
+ }
+ return value;
+ }
}
+ /**
+ * This inner class is the definition of a property used in a string
+ */
+ private static final class Property {
+ private final int beginIndex;
+ private final int endIndex;
+ private final String key;
+ private final String value;
+
+ private Property(int beginIndex, int endIndex, String key, String value) {
+ this.beginIndex = beginIndex;
+ this.endIndex = endIndex;
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Gets the begin index of the property (including the prefix token).
+ */
+ public int getBeginIndex() {
+ return beginIndex;
+ }
+
+ /**
+ * Gets the end index of the property (including the suffix token).
+ */
+ public int getEndIndex() {
+ return endIndex;
+ }
+
+ /**
+ * Gets the key of the property.
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Gets the value of the property.
+ */
+ public String getValue() {
+ return value;
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/camel/blob/43956f93/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentConcatenatePropertiesTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentConcatenatePropertiesTest.java b/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentConcatenatePropertiesTest.java
new file mode 100644
index 0000000..07952b2
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/component/properties/PropertiesComponentConcatenatePropertiesTest.java
@@ -0,0 +1,74 @@
+/**
+ * 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.properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+
+public class PropertiesComponentConcatenatePropertiesTest extends ContextTestSupport {
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ context.addComponent("properties", new PropertiesComponent("classpath:org/apache/camel/component/properties/concatenation.properties"));
+ return context;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ System.setProperty("environment", "junit");
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ System.clearProperty("environment");
+ super.tearDown();
+ }
+
+ public void testConcatPropertiesComponentDefault() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").setBody(simple("${properties:concat.property}")).to("mock:result");
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedBodiesReceived("file:dirname");
+
+ template.sendBody("direct:start", "Test");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ public void testWithoutConcatPropertiesComponentDefault() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").setBody(simple("${properties:property.complete}")).to("mock:result");
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedBodiesReceived("file:dirname");
+
+ template.sendBody("direct:start", "Test");
+
+ assertMockEndpointsSatisfied();
+ }
+}
http://git-wip-us.apache.org/repos/asf/camel/blob/43956f93/camel-core/src/test/resources/org/apache/camel/component/properties/concatenation.properties
----------------------------------------------------------------------
diff --git a/camel-core/src/test/resources/org/apache/camel/component/properties/concatenation.properties b/camel-core/src/test/resources/org/apache/camel/component/properties/concatenation.properties
new file mode 100644
index 0000000..3105ddc
--- /dev/null
+++ b/camel-core/src/test/resources/org/apache/camel/component/properties/concatenation.properties
@@ -0,0 +1,24 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+#PROPERTIES CONCATENATION
+prop1=file:
+prop2=dirname
+concat.property={{prop1}}{{prop2}}
+
+#PROPERTIES WITHOUT CONCATENATION
+property.complete=file:dirname