1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.logging.log4j.util;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.nio.charset.Charset;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Properties;
27 import java.util.ResourceBundle;
28 import java.util.ServiceLoader;
29 import java.util.Set;
30 import java.util.TreeSet;
31 import java.util.concurrent.ConcurrentHashMap;
32
33 /**
34 * <em>Consider this class private.</em>
35 * <p>
36 * Provides utility methods for managing {@link Properties} instances as well as access to the global configuration
37 * system. Properties by default are loaded from the system properties, system environment, and a classpath resource
38 * file named {@value #LOG4J_PROPERTIES_FILE_NAME}. Additional properties can be loaded by implementing a custom
39 * {@link PropertySource} service and specifying it via a {@link ServiceLoader} file called
40 * {@code META-INF/services/org.apache.logging.log4j.util.PropertySource} with a list of fully qualified class names
41 * implementing that interface.
42 * </p>
43 *
44 * @see PropertySource
45 */
46 public final class PropertiesUtil {
47
48 private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties";
49 private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties";
50 private static final String SYSTEM = "system:";
51 private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);
52
53 private final Environment environment;
54
55 /**
56 * Constructs a PropertiesUtil using a given Properties object as its source of defined properties.
57 *
58 * @param props the Properties to use by default
59 */
60 public PropertiesUtil(final Properties props) {
61 this.environment = new Environment(new PropertiesPropertySource(props));
62 }
63
64 /**
65 * Constructs a PropertiesUtil for a given properties file name on the classpath. The properties specified in this
66 * file are used by default. If a property is not defined in this file, then the equivalent system property is used.
67 *
68 * @param propertiesFileName the location of properties file to load
69 */
70 public PropertiesUtil(final String propertiesFileName) {
71 this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName));
72 }
73
74 /**
75 * Loads and closes the given property input stream. If an error occurs, log to the status logger.
76 *
77 * @param in a property input stream.
78 * @param source a source object describing the source, like a resource string or a URL.
79 * @return a new Properties object
80 */
81 static Properties loadClose(final InputStream in, final Object source) {
82 final Properties props = new Properties();
83 if (null != in) {
84 try {
85 props.load(in);
86 } catch (final IOException e) {
87 LowLevelLogUtil.logException("Unable to read " + source, e);
88 } finally {
89 try {
90 in.close();
91 } catch (final IOException e) {
92 LowLevelLogUtil.logException("Unable to close " + source, e);
93 }
94 }
95 }
96 return props;
97 }
98
99 /**
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 }