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 */ 017package org.apache.camel.component.properties; 018 019import java.util.HashSet; 020import java.util.Properties; 021import java.util.Set; 022 023import static java.lang.String.format; 024 025import org.apache.camel.util.ObjectHelper; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * A parser to parse a string which contains property placeholders. 031 */ 032public class DefaultPropertiesParser implements AugmentedPropertyNameAwarePropertiesParser { 033 private static final String GET_OR_ELSE_TOKEN = ":"; 034 035 protected final Logger log = LoggerFactory.getLogger(getClass()); 036 037 private PropertiesComponent propertiesComponent; 038 039 public DefaultPropertiesParser() { 040 } 041 042 public DefaultPropertiesParser(PropertiesComponent propertiesComponent) { 043 this.propertiesComponent = propertiesComponent; 044 } 045 046 public PropertiesComponent getPropertiesComponent() { 047 return propertiesComponent; 048 } 049 050 public void setPropertiesComponent(PropertiesComponent propertiesComponent) { 051 this.propertiesComponent = propertiesComponent; 052 } 053 054 @Override 055 public String parseUri(String text, Properties properties, String prefixToken, String suffixToken) throws IllegalArgumentException { 056 return parseUri(text, properties, prefixToken, suffixToken, null, null, false); 057 } 058 059 public String parseUri(String text, Properties properties, String prefixToken, String suffixToken, String propertyPrefix, String propertySuffix, 060 boolean fallbackToUnaugmentedProperty) throws IllegalArgumentException { 061 ParsingContext context = new ParsingContext(properties, prefixToken, suffixToken, propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty); 062 return context.parse(text); 063 } 064 065 public String parseProperty(String key, String value, Properties properties) { 066 return value; 067 } 068 069 /** 070 * This inner class helps replacing properties. 071 */ 072 private final class ParsingContext { 073 private final Properties properties; 074 private final String prefixToken; 075 private final String suffixToken; 076 private final String propertyPrefix; 077 private final String propertySuffix; 078 private final boolean fallbackToUnaugmentedProperty; 079 080 ParsingContext(Properties properties, String prefixToken, String suffixToken, String propertyPrefix, String propertySuffix, 081 boolean fallbackToUnaugmentedProperty) { 082 this.properties = properties; 083 this.prefixToken = prefixToken; 084 this.suffixToken = suffixToken; 085 this.propertyPrefix = propertyPrefix; 086 this.propertySuffix = propertySuffix; 087 this.fallbackToUnaugmentedProperty = fallbackToUnaugmentedProperty; 088 } 089 090 /** 091 * Parses the given input string and replaces all properties 092 * 093 * @param input Input string 094 * @return Evaluated string 095 */ 096 public String parse(String input) { 097 return doParse(input, new HashSet<String>()); 098 } 099 100 /** 101 * Recursively parses the given input string and replaces all properties 102 * 103 * @param input Input string 104 * @param replacedPropertyKeys Already replaced property keys used for tracking circular references 105 * @return Evaluated string 106 */ 107 private String doParse(String input, Set<String> replacedPropertyKeys) { 108 if (input == null) { 109 return null; 110 } 111 String answer = input; 112 Property property; 113 while ((property = readProperty(answer)) != null) { 114 // Check for circular references 115 if (replacedPropertyKeys.contains(property.getKey())) { 116 throw new IllegalArgumentException("Circular reference detected with key [" + property.getKey() + "] from text: " + input); 117 } 118 119 Set<String> newReplaced = new HashSet<String>(replacedPropertyKeys); 120 newReplaced.add(property.getKey()); 121 122 String before = answer.substring(0, property.getBeginIndex()); 123 String after = answer.substring(property.getEndIndex()); 124 answer = before + doParse(property.getValue(), newReplaced) + after; 125 } 126 return answer; 127 } 128 129 /** 130 * Finds a property in the given string. It returns {@code null} if there's no property defined. 131 * 132 * @param input Input string 133 * @return A property in the given string or {@code null} if not found 134 */ 135 private Property readProperty(String input) { 136 // Find the index of the first valid suffix token 137 int suffix = getSuffixIndex(input); 138 139 // If not found, ensure that there is no valid prefix token in the string 140 if (suffix == -1) { 141 if (getMatchingPrefixIndex(input, input.length()) != -1) { 142 throw new IllegalArgumentException(format("Missing %s from the text: %s", suffixToken, input)); 143 } 144 return null; 145 } 146 147 // Find the index of the prefix token that matches the suffix token 148 int prefix = getMatchingPrefixIndex(input, suffix); 149 if (prefix == -1) { 150 throw new IllegalArgumentException(format("Missing %s from the text: %s", prefixToken, input)); 151 } 152 153 String key = input.substring(prefix + prefixToken.length(), suffix); 154 String value = getPropertyValue(key, input); 155 return new Property(prefix, suffix + suffixToken.length(), key, value); 156 } 157 158 /** 159 * Gets the first index of the suffix token that is not surrounded by quotes 160 * 161 * @param input Input string 162 * @return First index of the suffix token that is not surrounded by quotes 163 */ 164 private int getSuffixIndex(String input) { 165 int index = -1; 166 do { 167 index = input.indexOf(suffixToken, index + 1); 168 } while (index != -1 && isQuoted(input, index, suffixToken)); 169 return index; 170 } 171 172 /** 173 * Gets the index of the prefix token that matches the suffix at the given index and that is not surrounded by quotes 174 * 175 * @param input Input string 176 * @param suffixIndex Index of the suffix token 177 * @return Index of the prefix token that matches the suffix at the given index and that is not surrounded by quotes 178 */ 179 private int getMatchingPrefixIndex(String input, int suffixIndex) { 180 int index = suffixIndex; 181 do { 182 index = input.lastIndexOf(prefixToken, index - 1); 183 } while (index != -1 && isQuoted(input, index, prefixToken)); 184 return index; 185 } 186 187 /** 188 * Indicates whether or not the token at the given index is surrounded by single or double quotes 189 * 190 * @param input Input string 191 * @param index Index of the token 192 * @param token Token 193 * @return {@code true} 194 */ 195 private boolean isQuoted(String input, int index, String token) { 196 int beforeIndex = index - 1; 197 int afterIndex = index + token.length(); 198 if (beforeIndex >= 0 && afterIndex < input.length()) { 199 char before = input.charAt(beforeIndex); 200 char after = input.charAt(afterIndex); 201 return (before == after) && (before == '\'' || before == '"'); 202 } 203 return false; 204 } 205 206 /** 207 * Gets the value of the property with given key 208 * 209 * @param key Key of the property 210 * @param input Input string (used for exception message if value not found) 211 * @return Value of the property with the given key 212 */ 213 private String getPropertyValue(String key, String input) { 214 215 // the key may be a function, so lets check this first 216 if (propertiesComponent != null) { 217 for (PropertiesFunction function : propertiesComponent.getFunctions().values()) { 218 String token = function.getName() + ":"; 219 if (key.startsWith(token)) { 220 String remainder = key.substring(token.length()); 221 log.debug("Property with key [{}] is applied by function [{}]", key, function.getName()); 222 String value = function.apply(remainder); 223 if (value == null) { 224 throw new IllegalArgumentException("Property with key [" + key + "] using function [" + function.getName() + "]" 225 + " returned null value which is not allowed, from input: " + input); 226 } else { 227 if (log.isDebugEnabled()) { 228 log.debug("Property with key [{}] applied by function [{}] -> {}", new Object[]{key, function.getName(), value}); 229 } 230 return value; 231 } 232 } 233 } 234 } 235 236 // they key may have a get or else expression 237 String defaultValue = null; 238 if (key.contains(GET_OR_ELSE_TOKEN)) { 239 defaultValue = ObjectHelper.after(key, GET_OR_ELSE_TOKEN); 240 key = ObjectHelper.before(key, GET_OR_ELSE_TOKEN); 241 } 242 243 String augmentedKey = getAugmentedKey(key); 244 boolean shouldFallback = fallbackToUnaugmentedProperty && !key.equals(augmentedKey); 245 246 String value = doGetPropertyValue(augmentedKey); 247 if (value == null && shouldFallback) { 248 log.debug("Property with key [{}] not found, attempting with unaugmented key: {}", augmentedKey, key); 249 value = doGetPropertyValue(key); 250 } 251 252 if (value == null && defaultValue != null) { 253 log.debug("Property with key [{}] not found, using default value: {}", augmentedKey, defaultValue); 254 value = defaultValue; 255 } 256 257 if (value == null) { 258 StringBuilder esb = new StringBuilder(); 259 if (propertiesComponent == null || propertiesComponent.isDefaultCreated()) { 260 // if the component was auto created then include more information that the end user should define it 261 esb.append("PropertiesComponent with name properties must be defined in CamelContext to support property placeholders. "); 262 } 263 esb.append("Property with key [").append(augmentedKey).append("] "); 264 if (shouldFallback) { 265 esb.append("(and original key [").append(key).append("]) "); 266 } 267 esb.append("not found in properties from text: ").append(input); 268 throw new IllegalArgumentException(esb.toString()); 269 } 270 271 return value; 272 } 273 274 /** 275 * Gets the augmented key of the given base key 276 * 277 * @param key Base key 278 * @return Augmented key 279 */ 280 private String getAugmentedKey(String key) { 281 String augmentedKey = key; 282 if (propertyPrefix != null) { 283 log.debug("Augmenting property key [{}] with prefix: {}", key, propertyPrefix); 284 augmentedKey = propertyPrefix + augmentedKey; 285 } 286 if (propertySuffix != null) { 287 log.debug("Augmenting property key [{}] with suffix: {}", key, propertySuffix); 288 augmentedKey = augmentedKey + propertySuffix; 289 } 290 return augmentedKey; 291 } 292 293 /** 294 * Gets the property with the given key, it returns {@code null} if the property is not found 295 * 296 * @param key Key of the property 297 * @return Value of the property or {@code null} if not found 298 */ 299 private String doGetPropertyValue(String key) { 300 String value = null; 301 302 // override is the default mode 303 int mode = propertiesComponent != null ? propertiesComponent.getSystemPropertiesMode() : PropertiesComponent.SYSTEM_PROPERTIES_MODE_OVERRIDE; 304 305 if (mode == PropertiesComponent.SYSTEM_PROPERTIES_MODE_OVERRIDE) { 306 value = System.getProperty(key); 307 if (value != null) { 308 log.debug("Found a JVM system property: {} with value: {} to be used.", key, value); 309 } 310 } 311 312 if (value == null && properties != null) { 313 value = properties.getProperty(key); 314 if (value != null) { 315 log.debug("Found property: {} with value: {} to be used.", key, value); 316 } 317 } 318 319 if (value == null && mode == PropertiesComponent.SYSTEM_PROPERTIES_MODE_FALLBACK) { 320 value = System.getProperty(key); 321 if (value != null) { 322 log.debug("Found a JVM system property: {} with value: {} to be used.", key, value); 323 } 324 } 325 326 return parseProperty(key, value, properties); 327 } 328 } 329 330 /** 331 * This inner class is the definition of a property used in a string 332 */ 333 private static final class Property { 334 private final int beginIndex; 335 private final int endIndex; 336 private final String key; 337 private final String value; 338 339 private Property(int beginIndex, int endIndex, String key, String value) { 340 this.beginIndex = beginIndex; 341 this.endIndex = endIndex; 342 this.key = key; 343 this.value = value; 344 } 345 346 /** 347 * Gets the begin index of the property (including the prefix token). 348 */ 349 public int getBeginIndex() { 350 return beginIndex; 351 } 352 353 /** 354 * Gets the end index of the property (including the suffix token). 355 */ 356 public int getEndIndex() { 357 return endIndex; 358 } 359 360 /** 361 * Gets the key of the property. 362 */ 363 public String getKey() { 364 return key; 365 } 366 367 /** 368 * Gets the value of the property. 369 */ 370 public String getValue() { 371 return value; 372 } 373 } 374}