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    }