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 PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);
50
51 private final Environment environment;
52
53 /**
54 * Constructs a PropertiesUtil using a given Properties object as its source of defined properties.
55 *
56 * @param props the Properties to use by default
57 */
58 public PropertiesUtil(final Properties props) {
59 this.environment = new Environment(new PropertiesPropertySource(props));
60 }
61
62 /**
63 * Constructs a PropertiesUtil for a given properties file name on the classpath. The properties specified in this
64 * file are used by default. If a property is not defined in this file, then the equivalent system property is used.
65 *
66 * @param propertiesFileName the location of properties file to load
67 */
68 public PropertiesUtil(final String propertiesFileName) {
69 this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName));
70 }
71
72 /**
73 * Loads and closes the given property input stream. If an error occurs, log to the status logger.
74 *
75 * @param in a property input stream.
76 * @param source a source object describing the source, like a resource string or a URL.
77 * @return a new Properties object
78 */
79 static Properties loadClose(final InputStream in, final Object source) {
80 final Properties props = new Properties();
81 if (null != in) {
82 try {
83 props.load(in);
84 } catch (final IOException e) {
85 LowLevelLogUtil.logException("Unable to read " + source, e);
86 } finally {
87 try {
88 in.close();
89 } catch (final IOException e) {
90 LowLevelLogUtil.logException("Unable to close " + source, e);
91 }
92 }
93 }
94 return props;
95 }
96
97 /**
98 * Returns the PropertiesUtil used by Log4j.
99 *
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 }