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:38 UTC

[1/6] git commit: CAMEL-7429 Fix issue preventing from redefining property values

Repository: camel
Updated Branches:
  refs/heads/camel-2.12.x 23bb1758a -> 76784069f
  refs/heads/camel-2.13.x 5b4cceb0b -> 7f78ed9ff
  refs/heads/master 5ba767e1b -> 9b7cc6b5c


CAMEL-7429 Fix issue preventing from redefining property values


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/76784069
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/76784069
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/76784069

Branch: refs/heads/camel-2.12.x
Commit: 76784069fce0e041affa0b011b25bb8cc085b265
Parents: 893a0e5
Author: Antoine DESSAIGNE <an...@gmail.com>
Authored: Thu Jun 19 09:01:44 2014 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Thu Jun 19 09:17:06 2014 +0200

----------------------------------------------------------------------
 .../apache/camel/component/properties/DefaultPropertiesParser.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/76784069/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 1ee227a..7d25e8d 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
@@ -242,7 +242,7 @@ public class DefaultPropertiesParser implements AugmentedPropertyNameAwareProper
             } else if (properties != null) {
                 value = properties.getProperty(key);
             }
-            return value;
+            return parseProperty(key, value, properties);
         }
     }
 


[4/6] git commit: CAMEL-7429 Camel Properties Component concatenation issue

Posted by da...@apache.org.
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/c8a324e9
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/c8a324e9
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/c8a324e9

Branch: refs/heads/camel-2.13.x
Commit: c8a324e999ced55f3ea5ab6ebf0bce040e2ad3b5
Parents: 5b4cceb
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:21 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/c8a324e9/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/c8a324e9/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/c8a324e9/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


[5/6] git commit: CAMEL-7429 Camel Properties Component concatenation issue

Posted by da...@apache.org.
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


[2/6] git commit: CAMEL-7429 Camel Properties Component concatenation issue

Posted by da...@apache.org.
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/893a0e5f
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/893a0e5f
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/893a0e5f

Branch: refs/heads/camel-2.12.x
Commit: 893a0e5f4c2527832ad360862f1bedc7b618fde9
Parents: 23bb175
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:06 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/893a0e5f/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/893a0e5f/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/893a0e5f/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


[6/6] git commit: CAMEL-7429 Fix issue preventing from redefining property values

Posted by da...@apache.org.
CAMEL-7429 Fix issue preventing from redefining property values


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/9b7cc6b5
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/9b7cc6b5
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/9b7cc6b5

Branch: refs/heads/master
Commit: 9b7cc6b5ca0412367149fabd5a51e33c2716c66b
Parents: 43956f9
Author: Antoine DESSAIGNE <an...@gmail.com>
Authored: Thu Jun 19 09:01:44 2014 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Thu Jun 19 09:17:29 2014 +0200

----------------------------------------------------------------------
 .../apache/camel/component/properties/DefaultPropertiesParser.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/9b7cc6b5/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 1ee227a..7d25e8d 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
@@ -242,7 +242,7 @@ public class DefaultPropertiesParser implements AugmentedPropertyNameAwareProper
             } else if (properties != null) {
                 value = properties.getProperty(key);
             }
-            return value;
+            return parseProperty(key, value, properties);
         }
     }
 


[3/6] git commit: CAMEL-7429 Fix issue preventing from redefining property values

Posted by da...@apache.org.
CAMEL-7429 Fix issue preventing from redefining property values


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/7f78ed9f
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/7f78ed9f
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/7f78ed9f

Branch: refs/heads/camel-2.13.x
Commit: 7f78ed9ffd9bad64bbe2a6d4a762909aacbef80f
Parents: c8a324e
Author: Antoine DESSAIGNE <an...@gmail.com>
Authored: Thu Jun 19 09:01:44 2014 +0200
Committer: Claus Ibsen <da...@apache.org>
Committed: Thu Jun 19 09:17:21 2014 +0200

----------------------------------------------------------------------
 .../apache/camel/component/properties/DefaultPropertiesParser.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/7f78ed9f/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 1ee227a..7d25e8d 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
@@ -242,7 +242,7 @@ public class DefaultPropertiesParser implements AugmentedPropertyNameAwareProper
             } else if (properties != null) {
                 value = properties.getProperty(key);
             }
-            return value;
+            return parseProperty(key, value, properties);
         }
     }