001 /**
002 * Copyright 2010-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php
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 package org.kuali.common.util.property;
017
018 import java.util.HashMap;
019 import java.util.Map;
020 import java.util.Set;
021
022 import org.apache.commons.lang3.StringUtils;
023 import org.slf4j.Logger;
024 import org.slf4j.LoggerFactory;
025
026 /**
027 *******************************************************************************************************************
028 * WARNING
029 *******************************************************************************************************************
030 * This class needs extensive testing. It does not work right. Do not use this in any real sort of way.
031 *******************************************************************************************************************
032 *
033 * Support the equivalent of <code>escapeString</code> from the maven-resources-plugin.
034 *
035 * This allows you to place <code>\</code> in front of placeholders you don't want replaced.
036 *
037 * The string <code>The sky over ${city} is \${color}</code> is resolved to <code>The sky over Phoenix is ${color}</code>
038 *
039 * You can also have default values that are themselves placeholders - <code>${foo:${bar}}</code>
040 */
041 public class EscapingPropertyPlaceholderHelper extends org.springframework.util.PropertyPlaceholderHelper {
042
043 private static final Logger logger = LoggerFactory.getLogger(EscapingPropertyPlaceholderHelper.class);
044 private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
045
046 static {
047 wellKnownSimplePrefixes.put("}", "{");
048 wellKnownSimplePrefixes.put("]", "[");
049 wellKnownSimplePrefixes.put(")", "(");
050 }
051
052 String placeholderPrefix;
053 String placeholderSuffix;
054 String valueSeparator;
055 boolean ignoreUnresolvablePlaceholders;
056 String simplePrefix;
057 String escapeString = Constants.DEFAULT_ESCAPE_STRING;
058 String skipString;
059
060 public EscapingPropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
061 this(placeholderPrefix, placeholderSuffix, Constants.DEFAULT_VALUE_SEPARATOR, Constants.DEFAULT_ESCAPE_STRING, Constants.DEFAULT_IGNORE_UNRESOLVABLE_PLACEHOLDERS);
062 }
063
064 public EscapingPropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, String escapeString, boolean ignoreUnresolvablePlaceholders) {
065 super(placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
066 this.placeholderPrefix = placeholderPrefix;
067 this.placeholderSuffix = placeholderSuffix;
068 this.valueSeparator = valueSeparator;
069 this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
070 String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
071 if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
072 this.simplePrefix = simplePrefixForSuffix;
073 } else {
074 this.simplePrefix = this.placeholderPrefix;
075 }
076 this.escapeString = StringUtils.trimToNull(escapeString);
077 if (this.escapeString != null) {
078 this.skipString = this.escapeString + this.placeholderPrefix;
079 }
080 }
081
082 // ::JC:: New method
083 protected int getStartIndex(String s, String prefix, String skipString, int fromIndex) {
084 if (skipString == null) {
085 return StringUtils.indexOf(s, prefix, fromIndex);
086 }
087 int pos = StringUtils.indexOf(s, skipString, fromIndex);
088 int len = StringUtils.length(s);
089 fromIndex = getNewFromIndex(fromIndex, pos, skipString);
090 while (pos != -1 && fromIndex < len) {
091 pos = StringUtils.indexOf(s, skipString, fromIndex);
092 fromIndex = getNewFromIndex(fromIndex, pos, skipString);
093 }
094 if (fromIndex >= len) {
095 return -1;
096 } else {
097 return StringUtils.indexOf(s, prefix, fromIndex);
098 }
099 }
100
101 // ::JC:: New method
102 protected int getNewFromIndex(int fromIndex, int pos, String s) {
103 return pos == -1 ? fromIndex : pos + s.length();
104 }
105
106 @Override
107 protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
108 StringBuilder buf = new StringBuilder(strVal);
109
110 // ::JC:: Replaced this line with the line below it
111 // int startIndex = strVal.indexOf(this.placeholderPrefix);
112 int startIndex = getStartIndex(strVal, placeholderPrefix, skipString, 0);
113 while (startIndex != -1) {
114 int endIndex = findPlaceholderEndIndex(buf, startIndex);
115 if (endIndex != -1) {
116 String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
117 String originalPlaceholder = placeholder;
118 if (!visitedPlaceholders.add(originalPlaceholder)) {
119 throw new IllegalArgumentException("Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
120 }
121 // Recursive invocation, parsing placeholders contained in the placeholder key.
122 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
123 // Now obtain the value for the fully resolved key...
124 String propVal = placeholderResolver.resolvePlaceholder(placeholder);
125 if (propVal == null && this.valueSeparator != null) {
126 int separatorIndex = placeholder.indexOf(this.valueSeparator);
127 if (separatorIndex != -1) {
128 String actualPlaceholder = placeholder.substring(0, separatorIndex);
129 String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
130 propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
131 if (propVal == null) {
132 // Replaced this line with the line below it
133 // propVal = defaultValue;
134 propVal = parseStringValue(defaultValue, placeholderResolver, visitedPlaceholders);
135 }
136 }
137 }
138 if (propVal != null) {
139 // Recursive invocation, parsing placeholders contained in the
140 // previously resolved placeholder value.
141 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
142 buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
143 if (logger.isTraceEnabled()) {
144 logger.trace("Resolved placeholder '" + placeholder + "'");
145 }
146 // ::JC:: Replaced this line with the line below it
147 // startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
148 int fromIndex = startIndex + propVal.length();
149 startIndex = getStartIndex(buf.toString(), placeholderPrefix, skipString, fromIndex);
150 } else if (this.ignoreUnresolvablePlaceholders) {
151 // Proceed with unprocessed value.
152 // ::JC:: Replaced this line with the line below it
153 // startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
154 int fromIndex = endIndex + placeholderSuffix.length();
155 startIndex = getStartIndex(buf.toString(), placeholderPrefix, skipString, fromIndex);
156 } else {
157 throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in string value \"" + strVal + "\"");
158 }
159 visitedPlaceholders.remove(originalPlaceholder);
160 } else {
161 startIndex = -1;
162 }
163 }
164 // ::JC:: Added this block
165 String s = buf.toString();
166 if (skipString != null) {
167 // This isn't right, but I *think* it works unless a string contains the sequence "\${" without a matching "}"
168 return StringUtils.replace(s, skipString, placeholderPrefix);
169 } else {
170 return s;
171 }
172 }
173
174 private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
175 int index = startIndex + this.placeholderPrefix.length();
176 int withinNestedPlaceholder = 0;
177 while (index < buf.length()) {
178 if (org.springframework.util.StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
179 if (withinNestedPlaceholder > 0) {
180 withinNestedPlaceholder--;
181 index = index + this.placeholderSuffix.length();
182 } else {
183 return index;
184 }
185 } else if (org.springframework.util.StringUtils.substringMatch(buf, index, this.simplePrefix)) {
186 withinNestedPlaceholder++;
187 index = index + this.simplePrefix.length();
188 } else {
189 index++;
190 }
191 }
192 return -1;
193 }
194
195 public String getPlaceholderPrefix() {
196 return placeholderPrefix;
197 }
198
199 public String getPlaceholderSuffix() {
200 return placeholderSuffix;
201 }
202
203 public String getValueSeparator() {
204 return valueSeparator;
205 }
206
207 public boolean isIgnoreUnresolvablePlaceholders() {
208 return ignoreUnresolvablePlaceholders;
209 }
210
211 public String getSimplePrefix() {
212 return simplePrefix;
213 }
214
215 public String getEscapeString() {
216 return escapeString;
217 }
218
219 public String getSkipString() {
220 return skipString;
221 }
222
223 }