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 }