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.time.Duration; 023import java.time.temporal.ChronoUnit; 024import java.time.temporal.TemporalUnit; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030import java.util.ResourceBundle; 031import java.util.ServiceLoader; 032import java.util.Set; 033import java.util.TreeSet; 034import java.util.concurrent.ConcurrentHashMap; 035 036/** 037 * <em>Consider this class private.</em> 038 * <p> 039 * Provides utility methods for managing {@link Properties} instances as well as access to the global configuration 040 * system. Properties by default are loaded from the system properties, system environment, and a classpath resource 041 * file named {@value #LOG4J_PROPERTIES_FILE_NAME}. Additional properties can be loaded by implementing a custom 042 * {@link PropertySource} service and specifying it via a {@link ServiceLoader} file called 043 * {@code META-INF/services/org.apache.logging.log4j.util.PropertySource} with a list of fully qualified class names 044 * implementing that interface. 045 * </p> 046 * 047 * @see PropertySource 048 */ 049public final class PropertiesUtil { 050 051 private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties"; 052 private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties"; 053 private static final String SYSTEM = "system:"; 054 private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME); 055 056 private final Environment environment; 057 058 /** 059 * Constructs a PropertiesUtil using a given Properties object as its source of defined properties. 060 * 061 * @param props the Properties to use by default 062 */ 063 public PropertiesUtil(final Properties props) { 064 this.environment = new Environment(new PropertiesPropertySource(props)); 065 } 066 067 /** 068 * Constructs a PropertiesUtil for a given properties file name on the classpath. The properties specified in this 069 * file are used by default. If a property is not defined in this file, then the equivalent system property is used. 070 * 071 * @param propertiesFileName the location of properties file to load 072 */ 073 public PropertiesUtil(final String propertiesFileName) { 074 this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName)); 075 } 076 077 /** 078 * Loads and closes the given property input stream. If an error occurs, log to the status logger. 079 * 080 * @param in a property input stream. 081 * @param source a source object describing the source, like a resource string or a URL. 082 * @return a new Properties object 083 */ 084 static Properties loadClose(final InputStream in, final Object source) { 085 final Properties props = new Properties(); 086 if (null != in) { 087 try { 088 props.load(in); 089 } catch (final IOException e) { 090 LowLevelLogUtil.logException("Unable to read " + source, e); 091 } finally { 092 try { 093 in.close(); 094 } catch (final IOException e) { 095 LowLevelLogUtil.logException("Unable to close " + source, e); 096 } 097 } 098 } 099 return props; 100 } 101 102 /** 103 * Returns the PropertiesUtil used by Log4j. 104 * 105 * @return the main Log4j PropertiesUtil instance. 106 */ 107 public static PropertiesUtil getProperties() { 108 return LOG4J_PROPERTIES; 109 } 110 111 /** 112 * Returns {@code true} if the specified property is defined, regardless of its value (it may not have a value). 113 * 114 * @param name the name of the property to verify 115 * @return {@code true} if the specified property is defined, regardless of its value 116 */ 117 public boolean hasProperty(final String name) { 118 return environment.containsKey(name); 119 } 120 121 /** 122 * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive), 123 * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is 124 * considered {@code false}. 125 * 126 * @param name the name of the property to look up 127 * @return the boolean value of the property or {@code false} if undefined. 128 */ 129 public boolean getBooleanProperty(final String name) { 130 return getBooleanProperty(name, false); 131 } 132 133 /** 134 * Gets the named property as a boolean value. 135 * 136 * @param name the name of the property to look up 137 * @param defaultValue the default value to use if the property is undefined 138 * @return the boolean value of the property or {@code defaultValue} if undefined. 139 */ 140 public boolean getBooleanProperty(final String name, final boolean defaultValue) { 141 final String prop = getStringProperty(name); 142 return prop == null ? defaultValue : "true".equalsIgnoreCase(prop); 143 } 144 145 /** 146 * Gets the named property as a boolean value. 147 * 148 * @param name the name of the property to look up 149 * @param defaultValueIfAbsent the default value to use if the property is undefined 150 * @param defaultValueIfPresent the default value to use if the property is defined but not assigned 151 * @return the boolean value of the property or {@code defaultValue} if undefined. 152 */ 153 public boolean getBooleanProperty(final String name, final boolean defaultValueIfAbsent, 154 final boolean defaultValueIfPresent) { 155 final String prop = getStringProperty(name); 156 return prop == null ? defaultValueIfAbsent 157 : prop.isEmpty() ? defaultValueIfPresent : "true".equalsIgnoreCase(prop); 158 } 159 160 /** 161 * Retrieves a property that may be prefixed by more than one string. 162 * @param prefixes The array of prefixes. 163 * @param key The key to locate. 164 * @param supplier The method to call to derive the default value. If the value is null, null will be returned 165 * if no property is found. 166 * @return The value or null if it is not found. 167 * @since 2.13.0 168 */ 169 public Boolean getBooleanProperty(final String[] prefixes, String key, Supplier<Boolean> supplier) { 170 for (String prefix : prefixes) { 171 if (hasProperty(prefix + key)) { 172 return getBooleanProperty(prefix + key); 173 } 174 } 175 return supplier != null ? supplier.get() : null; 176 } 177 178 /** 179 * Gets the named property as a Charset value. 180 * 181 * @param name the name of the property to look up 182 * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined. 183 */ 184 public Charset getCharsetProperty(final String name) { 185 return getCharsetProperty(name, Charset.defaultCharset()); 186 } 187 188 /** 189 * Gets the named property as a Charset value. If we cannot find the named Charset, see if it is mapped in 190 * file {@code Log4j-charsets.properties} on the class path. 191 * 192 * @param name the name of the property to look up 193 * @param defaultValue the default value to use if the property is undefined 194 * @return the Charset value of the property or {@code defaultValue} if undefined. 195 */ 196 public Charset getCharsetProperty(final String name, final Charset defaultValue) { 197 final String charsetName = getStringProperty(name); 198 if (charsetName == null) { 199 return defaultValue; 200 } 201 if (Charset.isSupported(charsetName)) { 202 return Charset.forName(charsetName); 203 } 204 final ResourceBundle bundle = getCharsetsResourceBundle(); 205 if (bundle.containsKey(name)) { 206 final String mapped = bundle.getString(name); 207 if (Charset.isSupported(mapped)) { 208 return Charset.forName(mapped); 209 } 210 } 211 LowLevelLogUtil.log("Unable to get Charset '" + charsetName + "' for property '" + name + "', using default " 212 + defaultValue + " and continuing."); 213 return defaultValue; 214 } 215 216 /** 217 * Gets the named property as a double. 218 * 219 * @param name the name of the property to look up 220 * @param defaultValue the default value to use if the property is undefined 221 * @return the parsed double value of the property or {@code defaultValue} if it was undefined or could not be parsed. 222 */ 223 public double getDoubleProperty(final String name, final double defaultValue) { 224 final String prop = getStringProperty(name); 225 if (prop != null) { 226 try { 227 return Double.parseDouble(prop); 228 } catch (final Exception ignored) { 229 } 230 } 231 return defaultValue; 232 } 233 234 /** 235 * Gets the named property as an integer. 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 integer value of the property or {@code defaultValue} if it was undefined or could not be 240 * parsed. 241 */ 242 public int getIntegerProperty(final String name, final int defaultValue) { 243 final String prop = getStringProperty(name); 244 if (prop != null) { 245 try { 246 return Integer.parseInt(prop); 247 } catch (final Exception ignored) { 248 // ignore 249 } 250 } 251 return defaultValue; 252 } 253 254 /** 255 * Retrieves a property that may be prefixed by more than one string. 256 * @param prefixes The array of prefixes. 257 * @param key The key to locate. 258 * @param supplier The method to call to derive the default value. If the value is null, null will be returned 259 * if no property is found. 260 * @return The value or null if it is not found. 261 * @since 2.13.0 262 */ 263 public Integer getIntegerProperty(final String[] prefixes, String key, Supplier<Integer> supplier) { 264 for (String prefix : prefixes) { 265 if (hasProperty(prefix + key)) { 266 return getIntegerProperty(prefix + key, 0); 267 } 268 } 269 return supplier != null ? supplier.get() : null; 270 } 271 272 /** 273 * Gets the named property as a long. 274 * 275 * @param name the name of the property to look up 276 * @param defaultValue the default value to use if the property is undefined 277 * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed. 278 */ 279 public long getLongProperty(final String name, final long defaultValue) { 280 final String prop = getStringProperty(name); 281 if (prop != null) { 282 try { 283 return Long.parseLong(prop); 284 } catch (final Exception ignored) { 285 } 286 } 287 return defaultValue; 288 } 289 290 /** 291 * Retrieves a property that may be prefixed by more than one string. 292 * @param prefixes The array of prefixes. 293 * @param key The key to locate. 294 * @param supplier The method to call to derive the default value. If the value is null, null will be returned 295 * if no property is found. 296 * @return The value or null if it is not found. 297 * @since 2.13.0 298 */ 299 public Long getLongProperty(final String[] prefixes, String key, Supplier<Long> supplier) { 300 for (String prefix : prefixes) { 301 if (hasProperty(prefix + key)) { 302 return getLongProperty(prefix + key, 0); 303 } 304 } 305 return supplier != null ? supplier.get() : null; 306 } 307 308 /** 309 * Retrieves a Duration where the String is of the format nnn[unit] where nnn represents an integer value 310 * and unit represents a time unit. 311 * @param name The property name. 312 * @param defaultValue The default value. 313 * @return The value of the String as a Duration or the default value, which may be null. 314 * @since 2.13.0 315 */ 316 public Duration getDurationProperty(final String name, Duration defaultValue) { 317 final String prop = getStringProperty(name); 318 if (prop != null) { 319 return TimeUnit.getDuration(prop); 320 } 321 return defaultValue; 322 } 323 324 /** 325 * Retrieves a property that may be prefixed by more than one string. 326 * @param prefixes The array of prefixes. 327 * @param key The key to locate. 328 * @param supplier The method to call to derive the default value. If the value is null, null will be returned 329 * if no property is found. 330 * @return The value or null if it is not found. 331 * @since 2.13.0 332 */ 333 public Duration getDurationProperty(final String[] prefixes, String key, Supplier<Duration> supplier) { 334 for (String prefix : prefixes) { 335 if (hasProperty(prefix + key)) { 336 return getDurationProperty(prefix + key, null); 337 } 338 } 339 return supplier != null ? supplier.get() : null; 340 } 341 342 /** 343 * Retrieves a property that may be prefixed by more than one string. 344 * @param prefixes The array of prefixes. 345 * @param key The key to locate. 346 * @param supplier The method to call to derive the default value. If the value is null, null will be returned 347 * if no property is found. 348 * @return The value or null if it is not found. 349 * @since 2.13.0 350 */ 351 public String getStringProperty(final String[] prefixes, String key, Supplier<String> supplier) { 352 for (String prefix : prefixes) { 353 String result = getStringProperty(prefix + key); 354 if (result != null) { 355 return result; 356 } 357 } 358 return supplier != null ? supplier.get() : null; 359 } 360 361 /** 362 * Gets the named property as a String. 363 * 364 * @param name the name of the property to look up 365 * @return the String value of the property or {@code null} if undefined. 366 */ 367 public String getStringProperty(final String name) { 368 return environment.get(name); 369 } 370 371 /** 372 * Gets the named property as a String. 373 * 374 * @param name the name of the property to look up 375 * @param defaultValue the default value to use if the property is undefined 376 * @return the String value of the property or {@code defaultValue} if undefined. 377 */ 378 public String getStringProperty(final String name, final String defaultValue) { 379 final String prop = getStringProperty(name); 380 return (prop == null) ? defaultValue : prop; 381 } 382 383 /** 384 * Return the system properties or an empty Properties object if an error occurs. 385 * 386 * @return The system properties. 387 */ 388 public static Properties getSystemProperties() { 389 try { 390 return new Properties(System.getProperties()); 391 } catch (final SecurityException ex) { 392 LowLevelLogUtil.logException("Unable to access system properties.", ex); 393 // Sandboxed - can't read System Properties 394 return new Properties(); 395 } 396 } 397 398 /** 399 * Reloads all properties. This is primarily useful for unit tests. 400 * 401 * @since 2.10.0 402 */ 403 public void reload() { 404 environment.reload(); 405 } 406 407 /** 408 * Provides support for looking up global configuration properties via environment variables, property files, 409 * and system properties, in three variations: 410 * <p> 411 * Normalized: all log4j-related prefixes removed, remaining property is camelCased with a log4j2 prefix for 412 * property files and system properties, or follows a LOG4J_FOO_BAR format for environment variables. 413 * <p> 414 * Legacy: the original property name as defined in the source pre-2.10.0. 415 * <p> 416 * Tokenized: loose matching based on word boundaries. 417 * 418 * @since 2.10.0 419 */ 420 private static class Environment { 421 422 private final Set<PropertySource> sources = new TreeSet<>(new PropertySource.Comparator()); 423 private final Map<CharSequence, String> literal = new ConcurrentHashMap<>(); 424 private final Map<CharSequence, String> normalized = new ConcurrentHashMap<>(); 425 private final Map<List<CharSequence>, String> tokenized = new ConcurrentHashMap<>(); 426 427 private Environment(final PropertySource propertySource) { 428 PropertyFilePropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME); 429 try { 430 sysProps.forEach((key, value) -> { 431 if (System.getProperty(key) == null) { 432 System.setProperty(key, value); 433 } 434 }); 435 } catch (SecurityException ex) { 436 // Access to System Properties is restricted so just skip it. 437 } 438 sources.add(propertySource); 439 for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) { 440 try { 441 for (final PropertySource source : ServiceLoader.load(PropertySource.class, classLoader)) { 442 sources.add(source); 443 } 444 } catch (final Throwable ex) { 445 /* Don't log anything to the console. It may not be a problem that a PropertySource 446 * isn't accessible. 447 */ 448 } 449 } 450 451 reload(); 452 } 453 454 private synchronized void reload() { 455 literal.clear(); 456 normalized.clear(); 457 tokenized.clear(); 458 for (final PropertySource source : sources) { 459 source.forEach((key, value) -> { 460 if (key != null && value != null) { 461 literal.put(key, value); 462 final List<CharSequence> tokens = PropertySource.Util.tokenize(key); 463 if (tokens.isEmpty()) { 464 normalized.put(source.getNormalForm(Collections.singleton(key)), value); 465 } else { 466 normalized.put(source.getNormalForm(tokens), value); 467 tokenized.put(tokens, value); 468 } 469 } 470 }); 471 } 472 } 473 474 private static boolean hasSystemProperty(final String key) { 475 try { 476 return System.getProperties().containsKey(key); 477 } catch (final SecurityException ignored) { 478 return false; 479 } 480 } 481 482 private String get(final String key) { 483 if (normalized.containsKey(key)) { 484 return normalized.get(key); 485 } 486 if (literal.containsKey(key)) { 487 return literal.get(key); 488 } 489 if (hasSystemProperty(key)) { 490 return System.getProperty(key); 491 } 492 for (final PropertySource source : sources) { 493 if (source.containsProperty(key)) { 494 return source.getProperty(key); 495 } 496 } 497 return tokenized.get(PropertySource.Util.tokenize(key)); 498 } 499 500 private boolean containsKey(final String key) { 501 return normalized.containsKey(key) || 502 literal.containsKey(key) || 503 hasSystemProperty(key) || 504 tokenized.containsKey(PropertySource.Util.tokenize(key)); 505 } 506 } 507 508 /** 509 * Extracts properties that start with or are equals to the specific prefix and returns them in a new Properties 510 * object with the prefix removed. 511 * 512 * @param properties The Properties to evaluate. 513 * @param prefix The prefix to extract. 514 * @return The subset of properties. 515 */ 516 public static Properties extractSubset(final Properties properties, final String prefix) { 517 final Properties subset = new Properties(); 518 519 if (prefix == null || prefix.length() == 0) { 520 return subset; 521 } 522 523 final String prefixToMatch = prefix.charAt(prefix.length() - 1) != '.' ? prefix + '.' : prefix; 524 525 final List<String> keys = new ArrayList<>(); 526 527 for (final String key : properties.stringPropertyNames()) { 528 if (key.startsWith(prefixToMatch)) { 529 subset.setProperty(key.substring(prefixToMatch.length()), properties.getProperty(key)); 530 keys.add(key); 531 } 532 } 533 for (final String key : keys) { 534 properties.remove(key); 535 } 536 537 return subset; 538 } 539 540 static ResourceBundle getCharsetsResourceBundle() { 541 return ResourceBundle.getBundle("Log4j-charsets"); 542 } 543 544 /** 545 * Partitions a properties map based on common key prefixes up to the first period. 546 * 547 * @param properties properties to partition 548 * @return the partitioned properties where each key is the common prefix (minus the period) and the values are 549 * new property maps without the prefix and period in the key 550 * @since 2.6 551 */ 552 public static Map<String, Properties> partitionOnCommonPrefixes(final Properties properties) { 553 final Map<String, Properties> parts = new ConcurrentHashMap<>(); 554 for (final String key : properties.stringPropertyNames()) { 555 final String prefix = key.substring(0, key.indexOf('.')); 556 if (!parts.containsKey(prefix)) { 557 parts.put(prefix, new Properties()); 558 } 559 parts.get(prefix).setProperty(key.substring(key.indexOf('.') + 1), properties.getProperty(key)); 560 } 561 return parts; 562 } 563 564 /** 565 * Returns true if system properties tell us we are running on Windows. 566 * 567 * @return true if system properties tell us we are running on Windows. 568 */ 569 public boolean isOsWindows() { 570 return getStringProperty("os.name", "").startsWith("Windows"); 571 } 572 573 private enum TimeUnit { 574 NANOS("ns,nano,nanos,nanosecond,nanoseconds", ChronoUnit.NANOS), 575 MICROS("us,micro,micros,microsecond,microseconds", ChronoUnit.MICROS), 576 MILLIS("ms,milli,millis,millsecond,milliseconds", ChronoUnit.MILLIS), 577 SECONDS("s,second,seconds", ChronoUnit.SECONDS), 578 MINUTES("m,minute,minutes", ChronoUnit.MINUTES), 579 HOURS("h,hour,hours", ChronoUnit.HOURS), 580 DAYS("d,day,days", ChronoUnit.DAYS); 581 582 private final String[] descriptions; 583 private final ChronoUnit timeUnit; 584 585 TimeUnit(String descriptions, ChronoUnit timeUnit) { 586 this.descriptions = descriptions.split(","); 587 this.timeUnit = timeUnit; 588 } 589 590 ChronoUnit getTimeUnit() { 591 return this.timeUnit; 592 } 593 594 static Duration getDuration(String time) { 595 String value = time.trim(); 596 TemporalUnit temporalUnit = ChronoUnit.MILLIS; 597 long timeVal = 0; 598 for (TimeUnit timeUnit : values()) { 599 for (String suffix : timeUnit.descriptions) { 600 if (value.endsWith(suffix)) { 601 temporalUnit = timeUnit.timeUnit; 602 timeVal = Long.parseLong(value.substring(0, value.length() - suffix.length())); 603 } 604 } 605 } 606 return Duration.of(timeVal, temporalUnit); 607 } 608 } 609}