001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.util; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.nio.charset.Charset; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026import java.util.Properties; 027import java.util.ResourceBundle; 028import java.util.ServiceLoader; 029import java.util.Set; 030import java.util.TreeSet; 031import java.util.concurrent.ConcurrentHashMap; 032 033/** 034 * <em>Consider this class private.</em> 035 * <p> 036 * Provides utility methods for managing {@link Properties} instances as well as access to the global configuration 037 * system. Properties by default are loaded from the system properties, system environment, and a classpath resource 038 * file named {@value #LOG4J_PROPERTIES_FILE_NAME}. Additional properties can be loaded by implementing a custom 039 * {@link PropertySource} service and specifying it via a {@link ServiceLoader} file called 040 * {@code META-INF/services/org.apache.logging.log4j.util.PropertySource} with a list of fully qualified class names 041 * implementing that interface. 042 * </p> 043 * 044 * @see PropertySource 045 */ 046public final class PropertiesUtil { 047 048 private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties"; 049 private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties"; 050 private static final String SYSTEM = "system:"; 051 private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME); 052 053 private final Environment environment; 054 055 /** 056 * Constructs a PropertiesUtil using a given Properties object as its source of defined properties. 057 * 058 * @param props the Properties to use by default 059 */ 060 public PropertiesUtil(final Properties props) { 061 this.environment = new Environment(new PropertiesPropertySource(props)); 062 } 063 064 /** 065 * Constructs a PropertiesUtil for a given properties file name on the classpath. The properties specified in this 066 * file are used by default. If a property is not defined in this file, then the equivalent system property is used. 067 * 068 * @param propertiesFileName the location of properties file to load 069 */ 070 public PropertiesUtil(final String propertiesFileName) { 071 this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName)); 072 } 073 074 /** 075 * Loads and closes the given property input stream. If an error occurs, log to the status logger. 076 * 077 * @param in a property input stream. 078 * @param source a source object describing the source, like a resource string or a URL. 079 * @return a new Properties object 080 */ 081 static Properties loadClose(final InputStream in, final Object source) { 082 final Properties props = new Properties(); 083 if (null != in) { 084 try { 085 props.load(in); 086 } catch (final IOException e) { 087 LowLevelLogUtil.logException("Unable to read " + source, e); 088 } finally { 089 try { 090 in.close(); 091 } catch (final IOException e) { 092 LowLevelLogUtil.logException("Unable to close " + source, e); 093 } 094 } 095 } 096 return props; 097 } 098 099 /** 100 * Returns the PropertiesUtil used by Log4j. 101 * 102 * @return the main Log4j PropertiesUtil instance. 103 */ 104 public static PropertiesUtil getProperties() { 105 return LOG4J_PROPERTIES; 106 } 107 108 /** 109 * Returns {@code true} if the specified property is defined, regardless of its value (it may not have a value). 110 * 111 * @param name the name of the property to verify 112 * @return {@code true} if the specified property is defined, regardless of its value 113 */ 114 public boolean hasProperty(final String name) { 115 return environment.containsKey(name); 116 } 117 118 /** 119 * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive), 120 * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is 121 * considered {@code false}. 122 * 123 * @param name the name of the property to look up 124 * @return the boolean value of the property or {@code false} if undefined. 125 */ 126 public boolean getBooleanProperty(final String name) { 127 return getBooleanProperty(name, false); 128 } 129 130 /** 131 * Gets the named property as a boolean value. 132 * 133 * @param name the name of the property to look up 134 * @param defaultValue the default value to use if the property is undefined 135 * @return the boolean value of the property or {@code defaultValue} if undefined. 136 */ 137 public boolean getBooleanProperty(final String name, final boolean defaultValue) { 138 final String prop = getStringProperty(name); 139 return prop == null ? defaultValue : "true".equalsIgnoreCase(prop); 140 } 141 142 /** 143 * Gets the named property as a boolean value. 144 * 145 * @param name the name of the property to look up 146 * @param defaultValueIfAbsent the default value to use if the property is undefined 147 * @param defaultValueIfPresent the default value to use if the property is defined but not assigned 148 * @return the boolean value of the property or {@code defaultValue} if undefined. 149 */ 150 public boolean getBooleanProperty(final String name, final boolean defaultValueIfAbsent, 151 final boolean defaultValueIfPresent) { 152 final String prop = getStringProperty(name); 153 return prop == null ? defaultValueIfAbsent 154 : prop.isEmpty() ? defaultValueIfPresent : "true".equalsIgnoreCase(prop); 155 } 156 157 /** 158 * Gets the named property as a Charset value. 159 * 160 * @param name the name of the property to look up 161 * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined. 162 */ 163 public Charset getCharsetProperty(final String name) { 164 return getCharsetProperty(name, Charset.defaultCharset()); 165 } 166 167 /** 168 * Gets the named property as a Charset value. If we cannot find the named Charset, see if it is mapped in 169 * file {@code Log4j-charsets.properties} on the class path. 170 * 171 * @param name the name of the property to look up 172 * @param defaultValue the default value to use if the property is undefined 173 * @return the Charset value of the property or {@code defaultValue} if undefined. 174 */ 175 public Charset getCharsetProperty(final String name, final Charset defaultValue) { 176 final String charsetName = getStringProperty(name); 177 if (charsetName == null) { 178 return defaultValue; 179 } 180 if (Charset.isSupported(charsetName)) { 181 return Charset.forName(charsetName); 182 } 183 final ResourceBundle bundle = getCharsetsResourceBundle(); 184 if (bundle.containsKey(name)) { 185 final String mapped = bundle.getString(name); 186 if (Charset.isSupported(mapped)) { 187 return Charset.forName(mapped); 188 } 189 } 190 LowLevelLogUtil.log("Unable to get Charset '" + charsetName + "' for property '" + name + "', using default " 191 + defaultValue + " and continuing."); 192 return defaultValue; 193 } 194 195 /** 196 * Gets the named property as a double. 197 * 198 * @param name the name of the property to look up 199 * @param defaultValue the default value to use if the property is undefined 200 * @return the parsed double value of the property or {@code defaultValue} if it was undefined or could not be parsed. 201 */ 202 public double getDoubleProperty(final String name, final double defaultValue) { 203 final String prop = getStringProperty(name); 204 if (prop != null) { 205 try { 206 return Double.parseDouble(prop); 207 } catch (final Exception ignored) { 208 return defaultValue; 209 } 210 } 211 return defaultValue; 212 } 213 214 /** 215 * Gets the named property as an integer. 216 * 217 * @param name the name of the property to look up 218 * @param defaultValue the default value to use if the property is undefined 219 * @return the parsed integer value of the property or {@code defaultValue} if it was undefined or could not be 220 * parsed. 221 */ 222 public int getIntegerProperty(final String name, final int defaultValue) { 223 final String prop = getStringProperty(name); 224 if (prop != null) { 225 try { 226 return Integer.parseInt(prop); 227 } catch (final Exception ignored) { 228 return defaultValue; 229 } 230 } 231 return defaultValue; 232 } 233 234 /** 235 * Gets the named property as a long. 236 * 237 * @param name the name of the property to look up 238 * @param defaultValue the default value to use if the property is undefined 239 * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed. 240 */ 241 public long getLongProperty(final String name, final long defaultValue) { 242 final String prop = getStringProperty(name); 243 if (prop != null) { 244 try { 245 return Long.parseLong(prop); 246 } catch (final Exception ignored) { 247 return defaultValue; 248 } 249 } 250 return defaultValue; 251 } 252 253 /** 254 * Gets the named property as a String. 255 * 256 * @param name the name of the property to look up 257 * @return the String value of the property or {@code null} if undefined. 258 */ 259 public String getStringProperty(final String name) { 260 return environment.get(name); 261 } 262 263 /** 264 * Gets the named property as a String. 265 * 266 * @param name the name of the property to look up 267 * @param defaultValue the default value to use if the property is undefined 268 * @return the String value of the property or {@code defaultValue} if undefined. 269 */ 270 public String getStringProperty(final String name, final String defaultValue) { 271 final String prop = getStringProperty(name); 272 return (prop == null) ? defaultValue : prop; 273 } 274 275 /** 276 * Return the system properties or an empty Properties object if an error occurs. 277 * 278 * @return The system properties. 279 */ 280 public static Properties getSystemProperties() { 281 try { 282 return new Properties(System.getProperties()); 283 } catch (final SecurityException ex) { 284 LowLevelLogUtil.logException("Unable to access system properties.", ex); 285 // Sandboxed - can't read System Properties 286 return new Properties(); 287 } 288 } 289 290 /** 291 * Reloads all properties. This is primarily useful for unit tests. 292 * 293 * @since 2.10.0 294 */ 295 public void reload() { 296 environment.reload(); 297 } 298 299 /** 300 * Provides support for looking up global configuration properties via environment variables, property files, 301 * and system properties, in three variations: 302 * <p> 303 * Normalized: all log4j-related prefixes removed, remaining property is camelCased with a log4j2 prefix for 304 * property files and system properties, or follows a LOG4J_FOO_BAR format for environment variables. 305 * <p> 306 * Legacy: the original property name as defined in the source pre-2.10.0. 307 * <p> 308 * Tokenized: loose matching based on word boundaries. 309 * 310 * @since 2.10.0 311 */ 312 private static class Environment { 313 314 private final Set<PropertySource> sources = new TreeSet<>(new PropertySource.Comparator()); 315 private final Map<CharSequence, String> literal = new ConcurrentHashMap<>(); 316 private final Map<CharSequence, String> normalized = new ConcurrentHashMap<>(); 317 private final Map<List<CharSequence>, String> tokenized = new ConcurrentHashMap<>(); 318 319 private Environment(final PropertySource propertySource) { 320 PropertyFilePropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME); 321 try { 322 sysProps.forEach(new BiConsumer<String, String>() { 323 @Override 324 public void accept(String key, String value) { 325 if (System.getProperty(key) == null) { 326 System.setProperty(key, value); 327 } 328 } 329 }); 330 } catch (SecurityException ex) { 331 // Access to System Properties is restricted so just skip it. 332 } 333 sources.add(propertySource); 334 for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) { 335 try { 336 for (final PropertySource source : ServiceLoader.load(PropertySource.class, classLoader)) { 337 sources.add(source); 338 } 339 } catch (final Throwable ex) { 340 /* Don't log anything to the console. It may not be a problem that a PropertySource 341 * isn't accessible. 342 */ 343 } 344 } 345 346 reload(); 347 } 348 349 private synchronized void reload() { 350 literal.clear(); 351 normalized.clear(); 352 tokenized.clear(); 353 for (final PropertySource source : sources) { 354 source.forEach(new BiConsumer<String, String>() { 355 @Override 356 public void accept(final String key, final String value) { 357 if (key != null && value != null) { 358 literal.put(key, value); 359 final List<CharSequence> tokens = PropertySource.Util.tokenize(key); 360 if (tokens.isEmpty()) { 361 normalized.put(source.getNormalForm(Collections.singleton(key)), value); 362 } else { 363 normalized.put(source.getNormalForm(tokens), value); 364 tokenized.put(tokens, value); 365 } 366 } 367 } 368 }); 369 } 370 } 371 372 private static boolean hasSystemProperty(final String key) { 373 try { 374 return System.getProperties().containsKey(key); 375 } catch (final SecurityException ignored) { 376 return false; 377 } 378 } 379 380 private String get(final String key) { 381 if (normalized.containsKey(key)) { 382 return normalized.get(key); 383 } 384 if (literal.containsKey(key)) { 385 return literal.get(key); 386 } 387 if (hasSystemProperty(key)) { 388 return System.getProperty(key); 389 } 390 return tokenized.get(PropertySource.Util.tokenize(key)); 391 } 392 393 private boolean containsKey(final String key) { 394 return normalized.containsKey(key) || 395 literal.containsKey(key) || 396 hasSystemProperty(key) || 397 tokenized.containsKey(PropertySource.Util.tokenize(key)); 398 } 399 } 400 401 /** 402 * Extracts properties that start with or are equals to the specific prefix and returns them in a new Properties 403 * object with the prefix removed. 404 * 405 * @param properties The Properties to evaluate. 406 * @param prefix The prefix to extract. 407 * @return The subset of properties. 408 */ 409 public static Properties extractSubset(final Properties properties, final String prefix) { 410 final Properties subset = new Properties(); 411 412 if (prefix == null || prefix.length() == 0) { 413 return subset; 414 } 415 416 final String prefixToMatch = prefix.charAt(prefix.length() - 1) != '.' ? prefix + '.' : prefix; 417 418 final List<String> keys = new ArrayList<>(); 419 420 for (final String key : properties.stringPropertyNames()) { 421 if (key.startsWith(prefixToMatch)) { 422 subset.setProperty(key.substring(prefixToMatch.length()), properties.getProperty(key)); 423 keys.add(key); 424 } 425 } 426 for (final String key : keys) { 427 properties.remove(key); 428 } 429 430 return subset; 431 } 432 433 static ResourceBundle getCharsetsResourceBundle() { 434 return ResourceBundle.getBundle("Log4j-charsets"); 435 } 436 437 /** 438 * Partitions a properties map based on common key prefixes up to the first period. 439 * 440 * @param properties properties to partition 441 * @return the partitioned properties where each key is the common prefix (minus the period) and the values are 442 * new property maps without the prefix and period in the key 443 * @since 2.6 444 */ 445 public static Map<String, Properties> partitionOnCommonPrefixes(final Properties properties) { 446 final Map<String, Properties> parts = new ConcurrentHashMap<>(); 447 for (final String key : properties.stringPropertyNames()) { 448 final String prefix = key.substring(0, key.indexOf('.')); 449 if (!parts.containsKey(prefix)) { 450 parts.put(prefix, new Properties()); 451 } 452 parts.get(prefix).setProperty(key.substring(key.indexOf('.') + 1), properties.getProperty(key)); 453 } 454 return parts; 455 } 456 457 /** 458 * Returns true if system properties tell us we are running on Windows. 459 * 460 * @return true if system properties tell us we are running on Windows. 461 */ 462 public boolean isOsWindows() { 463 return getStringProperty("os.name", "").startsWith("Windows"); 464 } 465 466}