001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util; 018 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.Map; 022import java.util.Properties; 023import java.util.Set; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028/** 029 * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form 030 * {@code ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for 031 * user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or 032 * using a {@link PlaceholderResolver}. 033 * 034 * @author Juergen Hoeller 035 * @author Rob Harrop 036 * @since 3.0 037 */ 038public class PropertyPlaceholderHelper { 039 040 private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class); 041 042 private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4); 043 044 static { 045 wellKnownSimplePrefixes.put("}", "{"); 046 wellKnownSimplePrefixes.put("]", "["); 047 wellKnownSimplePrefixes.put(")", "("); 048 } 049 050 051 private final String placeholderPrefix; 052 053 private final String placeholderSuffix; 054 055 private final String simplePrefix; 056 057 private final String valueSeparator; 058 059 private final boolean ignoreUnresolvablePlaceholders; 060 061 062 /** 063 * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix. 064 * Unresolvable placeholders are ignored. 065 * @param placeholderPrefix the prefix that denotes the start of a placeholder 066 * @param placeholderSuffix the suffix that denotes the end of a placeholder 067 */ 068 public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) { 069 this(placeholderPrefix, placeholderSuffix, null, true); 070 } 071 072 /** 073 * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix. 074 * @param placeholderPrefix the prefix that denotes the start of a placeholder 075 * @param placeholderSuffix the suffix that denotes the end of a placeholder 076 * @param valueSeparator the separating character between the placeholder variable 077 * and the associated default value, if any 078 * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should 079 * be ignored ({@code true}) or cause an exception ({@code false}) 080 */ 081 public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, 082 String valueSeparator, boolean ignoreUnresolvablePlaceholders) { 083 084 Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); 085 Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); 086 this.placeholderPrefix = placeholderPrefix; 087 this.placeholderSuffix = placeholderSuffix; 088 String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); 089 if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { 090 this.simplePrefix = simplePrefixForSuffix; 091 } 092 else { 093 this.simplePrefix = this.placeholderPrefix; 094 } 095 this.valueSeparator = valueSeparator; 096 this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; 097 } 098 099 100 /** 101 * Replaces all placeholders of format {@code ${name}} with the corresponding 102 * property from the supplied {@link Properties}. 103 * @param value the value containing the placeholders to be replaced 104 * @param properties the {@code Properties} to use for replacement 105 * @return the supplied value with placeholders replaced inline 106 */ 107 public String replacePlaceholders(String value, final Properties properties) { 108 Assert.notNull(properties, "'properties' must not be null"); 109 return replacePlaceholders(value, new PlaceholderResolver() { 110 @Override 111 public String resolvePlaceholder(String placeholderName) { 112 return properties.getProperty(placeholderName); 113 } 114 }); 115 } 116 117 /** 118 * Replaces all placeholders of format {@code ${name}} with the value returned 119 * from the supplied {@link PlaceholderResolver}. 120 * @param value the value containing the placeholders to be replaced 121 * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement 122 * @return the supplied value with placeholders replaced inline 123 */ 124 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { 125 Assert.notNull(value, "'value' must not be null"); 126 return parseStringValue(value, placeholderResolver, new HashSet<String>()); 127 } 128 129 protected String parseStringValue( 130 String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { 131 132 StringBuilder result = new StringBuilder(value); 133 134 int startIndex = value.indexOf(this.placeholderPrefix); 135 while (startIndex != -1) { 136 int endIndex = findPlaceholderEndIndex(result, startIndex); 137 if (endIndex != -1) { 138 String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); 139 String originalPlaceholder = placeholder; 140 if (!visitedPlaceholders.add(originalPlaceholder)) { 141 throw new IllegalArgumentException( 142 "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); 143 } 144 // Recursive invocation, parsing placeholders contained in the placeholder key. 145 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); 146 // Now obtain the value for the fully resolved key... 147 String propVal = placeholderResolver.resolvePlaceholder(placeholder); 148 if (propVal == null && this.valueSeparator != null) { 149 int separatorIndex = placeholder.indexOf(this.valueSeparator); 150 if (separatorIndex != -1) { 151 String actualPlaceholder = placeholder.substring(0, separatorIndex); 152 String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); 153 propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); 154 if (propVal == null) { 155 propVal = defaultValue; 156 } 157 } 158 } 159 if (propVal != null) { 160 // Recursive invocation, parsing placeholders contained in the 161 // previously resolved placeholder value. 162 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); 163 result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); 164 if (logger.isTraceEnabled()) { 165 logger.trace("Resolved placeholder '" + placeholder + "'"); 166 } 167 startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); 168 } 169 else if (this.ignoreUnresolvablePlaceholders) { 170 // Proceed with unprocessed value. 171 startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); 172 } 173 else { 174 throw new IllegalArgumentException("Could not resolve placeholder '" + 175 placeholder + "'" + " in value \"" + value + "\""); 176 } 177 visitedPlaceholders.remove(originalPlaceholder); 178 } 179 else { 180 startIndex = -1; 181 } 182 } 183 184 return result.toString(); 185 } 186 187 private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { 188 int index = startIndex + this.placeholderPrefix.length(); 189 int withinNestedPlaceholder = 0; 190 while (index < buf.length()) { 191 if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { 192 if (withinNestedPlaceholder > 0) { 193 withinNestedPlaceholder--; 194 index = index + this.placeholderSuffix.length(); 195 } 196 else { 197 return index; 198 } 199 } 200 else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { 201 withinNestedPlaceholder++; 202 index = index + this.simplePrefix.length(); 203 } 204 else { 205 index++; 206 } 207 } 208 return -1; 209 } 210 211 212 /** 213 * Strategy interface used to resolve replacement values for placeholders contained in Strings. 214 */ 215 public interface PlaceholderResolver { 216 217 /** 218 * Resolve the supplied placeholder name to the replacement value. 219 * @param placeholderName the name of the placeholder to resolve 220 * @return the replacement value, or {@code null} if no replacement is to be made 221 */ 222 String resolvePlaceholder(String placeholderName); 223 } 224 225}