View Javadoc
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.net.URL;
22  import java.nio.charset.Charset;
23  import java.nio.charset.UnsupportedCharsetException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  /**
31   * <em>Consider this class private.</em>
32   * <p>
33   * Helps access properties. This utility provides a method to override system properties by specifying properties in a
34   * properties file.
35   * </p>
36   */
37  public final class PropertiesUtil {
38  
39      private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil("log4j2.component.properties");
40  
41      private final Properties props;
42  
43      /**
44       * Constructs a PropertiesUtil using a given Properties object as its source of defined properties.
45       *
46       * @param props the Properties to use by default
47       */
48      public PropertiesUtil(final Properties props) {
49          this.props = props;
50      }
51  
52      /**
53       * Constructs a PropertiesUtil for a given properties file name on the classpath. The properties specified in this
54       * file are used by default. If a property is not defined in this file, then the equivalent system property is used.
55       *
56       * @param propertiesFileName the location of properties file to load
57       */
58      public PropertiesUtil(final String propertiesFileName) {
59          final Properties properties = new Properties();
60          for (final URL url : LoaderUtil.findResources(propertiesFileName)) {
61              try (final InputStream in = url.openStream()) {
62                  properties.load(in);
63              } catch (final IOException ioe) {
64                  LowLevelLogUtil.logException("Unable to read " + url.toString(), ioe);
65              }
66          }
67          this.props = properties;
68      }
69  
70      /**
71       * Loads and closes the given property input stream. If an error occurs, log to the status logger.
72       *
73       * @param in a property input stream.
74       * @param source a source object describing the source, like a resource string or a URL.
75       * @return a new Properties object
76       */
77      static Properties loadClose(final InputStream in, final Object source) {
78          final Properties props = new Properties();
79          if (null != in) {
80              try {
81                  props.load(in);
82              } catch (final IOException e) {
83                  LowLevelLogUtil.logException("Unable to read " + source, e);
84              } finally {
85                  try {
86                      in.close();
87                  } catch (final IOException e) {
88                      LowLevelLogUtil.logException("Unable to close " + source, e);
89                  }
90              }
91          }
92          return props;
93      }
94  
95      /**
96       * Returns the PropertiesUtil used by Log4j.
97       *
98       * @return the main Log4j PropertiesUtil instance.
99       */
100     public static PropertiesUtil getProperties() {
101         return LOG4J_PROPERTIES;
102     }
103 
104     /**
105      * Returns {@code true} if the specified property is defined, regardless of its value (it may not have a value).
106      * @param name the name of the property to verify
107      * @return {@code true} if the specified property is defined, regardless of its value
108      */
109     public boolean hasProperty(final String name) {
110         return System.getProperties().containsKey(name) || props.containsKey(name);
111     }
112 
113     /**
114      * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive),
115      * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is
116      * considered {@code false}.
117      *
118      * @param name the name of the property to look up
119      * @return the boolean value of the property or {@code false} if undefined.
120      */
121     public boolean getBooleanProperty(final String name) {
122         return getBooleanProperty(name, false);
123     }
124 
125     /**
126      * Gets the named property as a boolean value.
127      *
128      * @param name the name of the property to look up
129      * @param defaultValue the default value to use if the property is undefined
130      * @return the boolean value of the property or {@code defaultValue} if undefined.
131      */
132     public boolean getBooleanProperty(final String name, final boolean defaultValue) {
133         final String prop = getStringProperty(name);
134         return prop == null ? defaultValue : "true".equalsIgnoreCase(prop);
135     }
136 
137     /**
138      * Gets the named property as a boolean value.
139      *
140      * @param name the name of the property to look up
141      * @param defaultValueIfAbsent the default value to use if the property is undefined
142      * @param defaultValueIfPresent the default value to use if the property is defined but not assigned
143      * @return the boolean value of the property or {@code defaultValue} if undefined.
144      */
145     public boolean getBooleanProperty(final String name, final boolean defaultValueIfAbsent,
146             final boolean defaultValueIfPresent) {
147         final String prop = getStringProperty(name);
148         return prop == null ? defaultValueIfAbsent
149                 : prop.isEmpty() ? defaultValueIfPresent : "true".equalsIgnoreCase(prop);
150     }
151 
152     /**
153      * Gets the named property as a Charset value.
154      *
155      * @param name the name of the property to look up
156      * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined.
157      */
158     public Charset getCharsetProperty(final String name) {
159         return getCharsetProperty(name, Charset.defaultCharset());
160     }
161 
162     /**
163      * Gets the named property as a Charset value.
164      *
165      * @param name the name of the property to look up
166      * @param defaultValue the default value to use if the property is undefined
167      * @return the Charset value of the property or {@code defaultValue} if undefined.
168      */
169     public Charset getCharsetProperty(final String name, final Charset defaultValue) {
170         final String prop = getStringProperty(name);
171         try {
172             return prop == null ? defaultValue : Charset.forName(prop);
173         } catch (UnsupportedCharsetException e) {
174             LowLevelLogUtil.logException("Unable to get Charset '" + name + "', using default " + defaultValue, e);
175             return defaultValue;
176         }
177     }
178 
179     /**
180      * Gets the named property as a double.
181      *
182      * @param name the name of the property to look up
183      * @param defaultValue the default value to use if the property is undefined
184      * @return the parsed double value of the property or {@code defaultValue} if it was undefined or could not be parsed.
185      */
186     public double getDoubleProperty(final String name, final double defaultValue) {
187         final String prop = getStringProperty(name);
188         if (prop != null) {
189             try {
190                 return Double.parseDouble(prop);
191             } catch (final Exception ignored) {
192                 return defaultValue;
193             }
194         }
195         return defaultValue;
196     }
197 
198     /**
199      * Gets the named property as an integer.
200      *
201      * @param name the name of the property to look up
202      * @param defaultValue the default value to use if the property is undefined
203      * @return the parsed integer value of the property or {@code defaultValue} if it was undefined or could not be
204      *         parsed.
205      */
206     public int getIntegerProperty(final String name, final int defaultValue) {
207         final String prop = getStringProperty(name);
208         if (prop != null) {
209             try {
210                 return Integer.parseInt(prop);
211             } catch (final Exception ignored) {
212                 return defaultValue;
213             }
214         }
215         return defaultValue;
216     }
217 
218     /**
219      * Gets the named property as a long.
220      *
221      * @param name the name of the property to look up
222      * @param defaultValue the default value to use if the property is undefined
223      * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed.
224      */
225     public long getLongProperty(final String name, final long defaultValue) {
226         final String prop = getStringProperty(name);
227         if (prop != null) {
228             try {
229                 return Long.parseLong(prop);
230             } catch (final Exception ignored) {
231                 return defaultValue;
232             }
233         }
234         return defaultValue;
235     }
236 
237     /**
238      * Gets the named property as a String.
239      *
240      * @param name the name of the property to look up
241      * @return the String value of the property or {@code null} if undefined.
242      */
243     public String getStringProperty(final String name) {
244         String prop = null;
245         try {
246             prop = System.getProperty(name);
247         } catch (final SecurityException ignored) {
248             // Ignore
249         }
250         return prop == null ? props.getProperty(name) : prop;
251     }
252 
253     /**
254      * Gets the named property as a String.
255      *
256      * @param name the name of the property to look up
257      * @param defaultValue the default value to use if the property is undefined
258      * @return the String value of the property or {@code defaultValue} if undefined.
259      */
260     public String getStringProperty(final String name, final String defaultValue) {
261         final String prop = getStringProperty(name);
262         return (prop == null) ? defaultValue : prop;
263     }
264 
265     /**
266      * Return the system properties or an empty Properties object if an error occurs.
267      *
268      * @return The system properties.
269      */
270     public static Properties getSystemProperties() {
271         try {
272             return new Properties(System.getProperties());
273         } catch (final SecurityException ex) {
274             LowLevelLogUtil.logException("Unable to access system properties.", ex);
275             // Sandboxed - can't read System Properties
276             return new Properties();
277         }
278     }
279 
280     /**
281      * Extracts properties that start with or are equals to the specific prefix and returns them in a new Properties
282      * object with the prefix removed.
283      *
284      * @param properties The Properties to evaluate.
285      * @param prefix The prefix to extract.
286      * @return The subset of properties.
287      */
288     public static Properties extractSubset(final Properties properties, final String prefix) {
289         final Properties subset = new Properties();
290 
291         if (prefix == null || prefix.length() == 0) {
292             return subset;
293         }
294 
295         final String prefixToMatch = prefix.charAt(prefix.length() - 1) != '.' ? prefix + '.' : prefix;
296 
297         final List<String> keys = new ArrayList<>();
298 
299         for (final String key : properties.stringPropertyNames()) {
300             if (key.startsWith(prefixToMatch)) {
301                 subset.setProperty(key.substring(prefixToMatch.length()), properties.getProperty(key));
302                 keys.add(key);
303             }
304         }
305         for (final String key : keys) {
306             properties.remove(key);
307         }
308 
309         return subset;
310     }
311 
312     /**
313      * Partitions a properties map based on common key prefixes up to the first period.
314      *
315      * @param properties properties to partition
316      * @return the partitioned properties where each key is the common prefix (minus the period) and the values are
317      * new property maps without the prefix and period in the key
318      * @since 2.6
319      */
320     public static Map<String, Properties> partitionOnCommonPrefixes(final Properties properties) {
321         final Map<String, Properties> parts = new ConcurrentHashMap<>();
322         for (final String key : properties.stringPropertyNames()) {
323             final String prefix = key.substring(0, key.indexOf('.'));
324             if (!parts.containsKey(prefix)) {
325                 parts.put(prefix, new Properties());
326             }
327             parts.get(prefix).setProperty(key.substring(key.indexOf('.') + 1), properties.getProperty(key));
328         }
329         return parts;
330     }
331 
332     /**
333      * Returns true if system properties tell us we are running on Windows.
334      * @return true if system properties tell us we are running on Windows.
335      */
336     public boolean isOsWindows() {
337         return getStringProperty("os.name").startsWith("Windows");
338     }
339 
340 }