001/*
002 * Copyright 2012-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 *      http://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.boot.loader.util;
018
019import java.util.HashSet;
020import java.util.Properties;
021import java.util.Set;
022
023/**
024 * Helper class for resolving placeholders in texts. Usually applied to file paths.
025 * <p>
026 * A text may contain {@code $ ...} placeholders, to be resolved as system properties:
027 * e.g. {@code $ user.dir} . Default values can be supplied using the ":" separator
028 * between key and value.
029 * <p>
030 * Adapted from Spring.
031 *
032 * @author Juergen Hoeller
033 * @author Rob Harrop
034 * @author Dave Syer
035 * @see System#getProperty(String)
036 */
037public abstract class SystemPropertyUtils {
038
039        /**
040         * Prefix for system property placeholders: "${".
041         */
042        public static final String PLACEHOLDER_PREFIX = "${";
043
044        /**
045         * Suffix for system property placeholders: "}".
046         */
047        public static final String PLACEHOLDER_SUFFIX = "}";
048
049        /**
050         * Value separator for system property placeholders: ":".
051         */
052        public static final String VALUE_SEPARATOR = ":";
053
054        private static final String SIMPLE_PREFIX = PLACEHOLDER_PREFIX.substring(1);
055
056        /**
057         * Resolve ${...} placeholders in the given text, replacing them with corresponding
058         * system property values.
059         * @param text the String to resolve
060         * @return the resolved String
061         * @throws IllegalArgumentException if there is an unresolvable placeholder
062         * @see #PLACEHOLDER_PREFIX
063         * @see #PLACEHOLDER_SUFFIX
064         */
065        public static String resolvePlaceholders(String text) {
066                if (text == null) {
067                        return text;
068                }
069                return parseStringValue(null, text, text, new HashSet<String>());
070        }
071
072        /**
073         * Resolve ${...} placeholders in the given text, replacing them with corresponding
074         * system property values.
075         * @param properties a properties instance to use in addition to System
076         * @param text the String to resolve
077         * @return the resolved String
078         * @throws IllegalArgumentException if there is an unresolvable placeholder
079         * @see #PLACEHOLDER_PREFIX
080         * @see #PLACEHOLDER_SUFFIX
081         */
082        public static String resolvePlaceholders(Properties properties, String text) {
083                if (text == null) {
084                        return text;
085                }
086                return parseStringValue(properties, text, text, new HashSet<String>());
087        }
088
089        private static String parseStringValue(Properties properties, String value,
090                        String current, Set<String> visitedPlaceholders) {
091
092                StringBuilder buf = new StringBuilder(current);
093
094                int startIndex = current.indexOf(PLACEHOLDER_PREFIX);
095                while (startIndex != -1) {
096                        int endIndex = findPlaceholderEndIndex(buf, startIndex);
097                        if (endIndex != -1) {
098                                String placeholder = buf
099                                                .substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
100                                String originalPlaceholder = placeholder;
101                                if (!visitedPlaceholders.add(originalPlaceholder)) {
102                                        throw new IllegalArgumentException("Circular placeholder reference '"
103                                                        + originalPlaceholder + "' in property definitions");
104                                }
105                                // Recursive invocation, parsing placeholders contained in the
106                                // placeholder
107                                // key.
108                                placeholder = parseStringValue(properties, value, placeholder,
109                                                visitedPlaceholders);
110                                // Now obtain the value for the fully resolved key...
111                                String propVal = resolvePlaceholder(properties, value, placeholder);
112                                if (propVal == null && VALUE_SEPARATOR != null) {
113                                        int separatorIndex = placeholder.indexOf(VALUE_SEPARATOR);
114                                        if (separatorIndex != -1) {
115                                                String actualPlaceholder = placeholder.substring(0,
116                                                                separatorIndex);
117                                                String defaultValue = placeholder
118                                                                .substring(separatorIndex + VALUE_SEPARATOR.length());
119                                                propVal = resolvePlaceholder(properties, value,
120                                                                actualPlaceholder);
121                                                if (propVal == null) {
122                                                        propVal = defaultValue;
123                                                }
124                                        }
125                                }
126                                if (propVal != null) {
127                                        // Recursive invocation, parsing placeholders contained in the
128                                        // previously resolved placeholder value.
129                                        propVal = parseStringValue(properties, value, propVal,
130                                                        visitedPlaceholders);
131                                        buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(),
132                                                        propVal);
133                                        startIndex = buf.indexOf(PLACEHOLDER_PREFIX,
134                                                        startIndex + propVal.length());
135                                }
136                                else {
137                                        // Proceed with unprocessed value.
138                                        startIndex = buf.indexOf(PLACEHOLDER_PREFIX,
139                                                        endIndex + PLACEHOLDER_SUFFIX.length());
140                                }
141                                visitedPlaceholders.remove(originalPlaceholder);
142                        }
143                        else {
144                                startIndex = -1;
145                        }
146                }
147
148                return buf.toString();
149        }
150
151        private static String resolvePlaceholder(Properties properties, String text,
152                        String placeholderName) {
153                String propVal = getProperty(placeholderName, null, text);
154                if (propVal != null) {
155                        return propVal;
156                }
157                return properties == null ? null : properties.getProperty(placeholderName);
158        }
159
160        public static String getProperty(String key) {
161                return getProperty(key, null, "");
162        }
163
164        public static String getProperty(String key, String defaultValue) {
165                return getProperty(key, defaultValue, "");
166        }
167
168        /**
169         * Search the System properties and environment variables for a value with the
170         * provided key. Environment variables in {@code UPPER_CASE} style are allowed where
171         * System properties would normally be {@code lower.case}.
172         * @param key the key to resolve
173         * @param defaultValue the default value
174         * @param text optional extra context for an error message if the key resolution fails
175         * (e.g. if System properties are not accessible)
176         * @return a static property value or null of not found
177         */
178        public static String getProperty(String key, String defaultValue, String text) {
179                try {
180                        String propVal = System.getProperty(key);
181                        if (propVal == null) {
182                                // Fall back to searching the system environment.
183                                propVal = System.getenv(key);
184                        }
185                        if (propVal == null) {
186                                // Try with underscores.
187                                propVal = System.getenv(key.replace('.', '_'));
188                        }
189                        if (propVal == null) {
190                                // Try uppercase with underscores as well.
191                                propVal = System.getenv(key.toUpperCase().replace('.', '_'));
192                        }
193                        if (propVal != null) {
194                                return propVal;
195                        }
196                }
197                catch (Throwable ex) {
198                        System.err.println("Could not resolve key '" + key + "' in '" + text
199                                        + "' as system property or in environment: " + ex);
200                }
201                return defaultValue;
202        }
203
204        private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
205                int index = startIndex + PLACEHOLDER_PREFIX.length();
206                int withinNestedPlaceholder = 0;
207                while (index < buf.length()) {
208                        if (substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
209                                if (withinNestedPlaceholder > 0) {
210                                        withinNestedPlaceholder--;
211                                        index = index + PLACEHOLDER_SUFFIX.length();
212                                }
213                                else {
214                                        return index;
215                                }
216                        }
217                        else if (substringMatch(buf, index, SIMPLE_PREFIX)) {
218                                withinNestedPlaceholder++;
219                                index = index + SIMPLE_PREFIX.length();
220                        }
221                        else {
222                                index++;
223                        }
224                }
225                return -1;
226        }
227
228        private static boolean substringMatch(CharSequence str, int index,
229                        CharSequence substring) {
230                for (int j = 0; j < substring.length(); j++) {
231                        int i = index + j;
232                        if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
233                                return false;
234                        }
235                }
236                return true;
237        }
238
239}