001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.properties;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.Properties;
022    
023    import org.apache.camel.util.ObjectHelper;
024    import org.apache.camel.util.StringHelper;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    
028    /**
029     * A parser to parse a string which contains property placeholders
030     *
031     * @version 
032     */
033    public class DefaultPropertiesParser implements AugmentedPropertyNameAwarePropertiesParser {
034        protected final Logger log = LoggerFactory.getLogger(getClass());
035        
036        @Override
037        public String parseUri(String text, Properties properties, String prefixToken, String suffixToken) throws IllegalArgumentException {
038            return parseUri(text, properties, prefixToken, suffixToken, null, null, false);
039        }
040    
041        public String parseUri(String text, Properties properties, String prefixToken, String suffixToken,
042                               String propertyPrefix, String propertySuffix, boolean fallbackToUnaugmentedProperty) throws IllegalArgumentException {
043            String answer = text;
044            boolean done = false;
045    
046            // the placeholders can contain nested placeholders so we need to do recursive parsing
047            // we must therefore also do circular reference check and must keep a list of visited keys
048            List<String> visited = new ArrayList<String>();
049            while (!done) {
050                List<String> replaced = new ArrayList<String>();
051                answer = doParseUri(answer, properties, replaced, prefixToken, suffixToken, propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty);
052    
053                // check the replaced with the visited to avoid circular reference
054                for (String replace : replaced) {
055                    if (visited.contains(replace)) {
056                        throw new IllegalArgumentException("Circular reference detected with key [" + replace + "] from text: " + text);
057                    }
058                }
059                // okay all okay so add the replaced as visited
060                visited.addAll(replaced);
061    
062                // we are done when we can no longer find any prefix tokens in the answer
063                done = findTokenPosition(answer, 0, prefixToken) == -1;
064            }
065            return answer;
066        }
067    
068        public String parseProperty(String key, String value, Properties properties) {
069            return value;
070        }
071    
072        private String doParseUri(String uri, Properties properties, List<String> replaced, String prefixToken, String suffixToken,
073                                  String propertyPrefix, String propertySuffix, boolean fallbackToUnaugmentedProperty) {
074            StringBuilder sb = new StringBuilder();
075    
076            int pivot = 0;
077            int size = uri.length();
078            while (pivot < size) {
079                int idx = findTokenPosition(uri, pivot, prefixToken);
080                if (idx < 0) {
081                    sb.append(createConstantPart(uri, pivot, size));
082                    break;
083                } else {
084                    if (pivot < idx) {
085                        sb.append(createConstantPart(uri, pivot, idx));
086                    }
087                    pivot = idx + prefixToken.length();
088                    int endIdx = findTokenPosition(uri, pivot, suffixToken);
089                    if (endIdx < 0) {
090                        throw new IllegalArgumentException("Expecting " + suffixToken + " but found end of string from text: " + uri);
091                    }
092                    String key = uri.substring(pivot, endIdx);
093                    String augmentedKey = key;
094                    
095                    if (propertyPrefix != null) {
096                        log.debug("Augmenting property key [{}] with prefix: {}", key, propertyPrefix);
097                        augmentedKey = propertyPrefix + augmentedKey;
098                    }
099                    
100                    if (propertySuffix != null) {
101                        log.debug("Augmenting property key [{}] with suffix: {}", key, propertySuffix);
102                        augmentedKey = augmentedKey + propertySuffix;
103                    }
104    
105                    String part = createPlaceholderPart(augmentedKey, properties, replaced, prefixToken, suffixToken);
106                    
107                    // Note: Only fallback to unaugmented when the original key was actually augmented
108                    if (part == null && fallbackToUnaugmentedProperty && (propertyPrefix != null || propertySuffix != null)) {
109                        log.debug("Property wth key [{}] not found, attempting with unaugmented key: {}", augmentedKey, key);
110                        part = createPlaceholderPart(key, properties, replaced, prefixToken, suffixToken);
111                    }
112                    
113                    if (part == null) {
114                        StringBuilder esb = new StringBuilder();
115                        esb.append("Property with key [").append(augmentedKey).append("] ");
116                        if (fallbackToUnaugmentedProperty && (propertyPrefix != null || propertySuffix != null)) {
117                            esb.append("(and original key [").append(key).append("]) ");
118                        }
119                        esb.append("not found in properties from text: ").append(uri);
120                        throw new IllegalArgumentException(esb.toString());
121                    }
122                    sb.append(part);
123                    pivot = endIdx + suffixToken.length();
124                }
125            }
126            return sb.toString();
127        }
128        
129        private int findTokenPosition(String uri, int pivot, String token) {
130            int idx = uri.indexOf(token, pivot);
131            while (idx > 0) {
132                // grab part as the previous char + token + next char, to test if the token is quoted
133                String part = null;
134                int len = idx + token.length() + 1;
135                if (uri.length() >= len) {
136                    part = uri.substring(idx - 1, len);
137                }
138                if (StringHelper.isQuoted(part)) {
139                    // the token was quoted, so regard it as a literal
140                    // and then try to find from next position
141                    pivot = idx + token.length() + 1;
142                    idx = uri.indexOf(token, pivot);
143                } else {
144                    // found token
145                    return idx;
146                }
147            }
148            return idx;
149        }
150        
151        private boolean isNestProperty(String uri, String prefixToken, String suffixToken) {
152            if (ObjectHelper.isNotEmpty(uri)) {
153                uri = uri.trim();
154                if (uri.startsWith(prefixToken) && uri.endsWith(suffixToken)) {
155                    return true;
156                }
157            }
158            return false;
159        }
160        
161        private String takeOffNestTokes(String uri, String prefixToken, String suffixToken) {
162            int start = prefixToken.length(); 
163            int end = uri.length() - suffixToken.length();
164            return uri.substring(start, end); 
165        }
166    
167        private String createConstantPart(String uri, int start, int end) {
168            return uri.substring(start, end);
169        }
170    
171        private String createPlaceholderPart(String key, Properties properties, List<String> replaced, String prefixToken, String suffixToken) {
172            // keep track of which parts we have replaced
173            replaced.add(key);
174            
175            String propertyValue = System.getProperty(key);
176            if (propertyValue != null) {
177                log.debug("Found a JVM system property: {} with value: {} to be used.", key, propertyValue);
178            } else if (properties != null) {
179                propertyValue = properties.getProperty(key);
180            }
181            
182            // we need to check if the propertyValue is nested
183            // we need to check if there is cycle dependency of the nested properties
184            List<String> visited = new ArrayList<String>();
185            while (isNestProperty(propertyValue, prefixToken, suffixToken)) {
186                visited.add(key);
187                // need to take off the token first
188                String value = takeOffNestTokes(propertyValue, prefixToken, suffixToken);
189                key = parseUri(value, properties, prefixToken, suffixToken);
190                if (visited.contains(key)) {
191                    throw new IllegalArgumentException("Circular reference detected with key [" + key + "] from text: " + propertyValue);
192                }
193                propertyValue = System.getProperty(key);
194                if (propertyValue != null) {
195                    log.debug("Found a JVM system property: {} with value: {} to be used.", key, propertyValue);
196                } else if (properties != null) {
197                    propertyValue = properties.getProperty(key);
198                }
199            }
200    
201            return parseProperty(key, propertyValue, properties);
202        }
203    
204    }