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