blob: f6c3aba70cde1e6c64207e07294f3350b4dcf5f1 [file] [log] [blame]
/**
* 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 java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import static java.lang.String.format;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A parser to parse a string which contains property placeholders
*/
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 {
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;
}
/**
* 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;
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);
}
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;
}
/**
* 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;
}
// 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);
}
/**
* 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;
}
/**
* 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());
}
return value;
}
/**
* 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;
}
if (propertySuffix != null) {
log.debug("Augmenting property key [{}] with suffix: {}", key, propertySuffix);
augmentedKey = augmentedKey + propertySuffix;
}
return augmentedKey;
}
/**
* 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 parseProperty(key, value, properties);
}
}
/**
* 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;
}
}
}