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