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}