View Javadoc

1   /**
2    * Copyright 2010-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util.property;
17  
18  import java.util.HashMap;
19  import java.util.Map;
20  import java.util.Set;
21  
22  import org.apache.commons.lang3.StringUtils;
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  /**
27   *******************************************************************************************************************
28   * WARNING
29   *******************************************************************************************************************
30   * This class needs extensive testing. It does not work right. Do not use this in any real sort of way.
31   *******************************************************************************************************************
32   *
33   * Support the equivalent of <code>escapeString</code> from the maven-resources-plugin.
34   *
35   * This allows you to place <code>\</code> in front of placeholders you don't want replaced.
36   *
37   * The string <code>The sky over ${city} is \${color}</code> is resolved to <code>The sky over Phoenix is ${color}</code>
38   *
39   * You can also have default values that are themselves placeholders - <code>${foo:${bar}}</code>
40   */
41  public class EscapingPropertyPlaceholderHelper extends org.springframework.util.PropertyPlaceholderHelper {
42  
43  	private static final Logger logger = LoggerFactory.getLogger(EscapingPropertyPlaceholderHelper.class);
44  	private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
45  
46  	static {
47  		wellKnownSimplePrefixes.put("}", "{");
48  		wellKnownSimplePrefixes.put("]", "[");
49  		wellKnownSimplePrefixes.put(")", "(");
50  	}
51  
52  	String placeholderPrefix;
53  	String placeholderSuffix;
54  	String valueSeparator;
55  	boolean ignoreUnresolvablePlaceholders;
56  	String simplePrefix;
57  	String escapeString = Constants.DEFAULT_ESCAPE_STRING;
58  	String skipString;
59  
60  	public EscapingPropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
61  		this(placeholderPrefix, placeholderSuffix, Constants.DEFAULT_VALUE_SEPARATOR, Constants.DEFAULT_ESCAPE_STRING, Constants.DEFAULT_IGNORE_UNRESOLVABLE_PLACEHOLDERS);
62  	}
63  
64  	public EscapingPropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, String escapeString, boolean ignoreUnresolvablePlaceholders) {
65  		super(placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
66  		this.placeholderPrefix = placeholderPrefix;
67  		this.placeholderSuffix = placeholderSuffix;
68  		this.valueSeparator = valueSeparator;
69  		this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
70  		String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
71  		if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
72  			this.simplePrefix = simplePrefixForSuffix;
73  		} else {
74  			this.simplePrefix = this.placeholderPrefix;
75  		}
76  		this.escapeString = StringUtils.trimToNull(escapeString);
77  		if (this.escapeString != null) {
78  			this.skipString = this.escapeString + this.placeholderPrefix;
79  		}
80  	}
81  
82  	// ::JC:: New method
83  	protected int getStartIndex(String s, String prefix, String skipString, int fromIndex) {
84  		if (skipString == null) {
85  			return StringUtils.indexOf(s, prefix, fromIndex);
86  		}
87  		int pos = StringUtils.indexOf(s, skipString, fromIndex);
88  		int len = StringUtils.length(s);
89  		fromIndex = getNewFromIndex(fromIndex, pos, skipString);
90  		while (pos != -1 && fromIndex < len) {
91  			pos = StringUtils.indexOf(s, skipString, fromIndex);
92  			fromIndex = getNewFromIndex(fromIndex, pos, skipString);
93  		}
94  		if (fromIndex >= len) {
95  			return -1;
96  		} else {
97  			return StringUtils.indexOf(s, prefix, fromIndex);
98  		}
99  	}
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 }