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.net.URL;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Map;
025import java.util.Properties;
026import java.util.concurrent.ConcurrentHashMap;
027
028/**
029 * <em>Consider this class private.</em>
030 * <p>
031 * Helps access properties. This utility provides a method to override system properties by specifying properties in a
032 * properties file.
033 * </p>
034 */
035public final class PropertiesUtil {
036
037    private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil("log4j2.component.properties");
038
039    private final Properties props;
040
041    /**
042     * Constructs a PropertiesUtil using a given Properties object as its source of defined properties.
043     *
044     * @param props the Properties to use by default
045     */
046    public PropertiesUtil(final Properties props) {
047        this.props = props;
048    }
049
050    /**
051     * Constructs a PropertiesUtil for a given properties file name on the classpath. The properties specified in this
052     * file are used by default. If a property is not defined in this file, then the equivalent system property is used.
053     *
054     * @param propertiesFileName the location of properties file to load
055     */
056    public PropertiesUtil(final String propertiesFileName) {
057        final Properties properties = new Properties();
058        for (final URL url : LoaderUtil.findResources(propertiesFileName)) {
059            try (final InputStream in = url.openStream()) {
060                properties.load(in);
061            } catch (final IOException ioe) {
062                LowLevelLogUtil.logException("Unable to read " + url.toString(), ioe);
063            }
064        }
065        this.props = properties;
066    }
067
068    /**
069     * Loads and closes the given property input stream. If an error occurs, log to the status logger.
070     *
071     * @param in a property input stream.
072     * @param source a source object describing the source, like a resource string or a URL.
073     * @return a new Properties object
074     */
075    static Properties loadClose(final InputStream in, final Object source) {
076        final Properties props = new Properties();
077        if (null != in) {
078            try {
079                props.load(in);
080            } catch (final IOException e) {
081                LowLevelLogUtil.logException("Unable to read " + source, e);
082            } finally {
083                try {
084                    in.close();
085                } catch (final IOException e) {
086                    LowLevelLogUtil.logException("Unable to close " + source, e);
087                }
088            }
089        }
090        return props;
091    }
092
093    /**
094     * Returns the PropertiesUtil used by Log4j.
095     *
096     * @return the main Log4j PropertiesUtil instance.
097     */
098    public static PropertiesUtil getProperties() {
099        return LOG4J_PROPERTIES;
100    }
101
102    /**
103     * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive),
104     * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is
105     * considered {@code false}.
106     *
107     * @param name the name of the property to look up
108     * @return the boolean value of the property or {@code false} if undefined.
109     */
110    public boolean getBooleanProperty(final String name) {
111        return getBooleanProperty(name, false);
112    }
113
114    /**
115     * Gets the named property as a boolean value.
116     *
117     * @param name the name of the property to look up
118     * @param defaultValue the default value to use if the property is undefined
119     * @return the boolean value of the property or {@code defaultValue} if undefined.
120     */
121    public boolean getBooleanProperty(final String name, final boolean defaultValue) {
122        final String prop = getStringProperty(name);
123        return (prop == null) ? defaultValue : "true".equalsIgnoreCase(prop);
124    }
125
126    /**
127     * Gets the named property as a double.
128     *
129     * @param name the name of the property to look up
130     * @param defaultValue the default value to use if the property is undefined
131     * @return the parsed double value of the property or {@code defaultValue} if it was undefined or could not be parsed.
132     */
133    public double getDoubleProperty(final String name, final double defaultValue) {
134        final String prop = getStringProperty(name);
135        if (prop != null) {
136            try {
137                return Double.parseDouble(prop);
138            } catch (final Exception ignored) {
139                return defaultValue;
140            }
141        }
142        return defaultValue;
143    }
144
145    /**
146     * Gets the named property as an integer.
147     *
148     * @param name the name of the property to look up
149     * @param defaultValue the default value to use if the property is undefined
150     * @return the parsed integer value of the property or {@code defaultValue} if it was undefined or could not be
151     *         parsed.
152     */
153    public int getIntegerProperty(final String name, final int defaultValue) {
154        final String prop = getStringProperty(name);
155        if (prop != null) {
156            try {
157                return Integer.parseInt(prop);
158            } catch (final Exception ignored) {
159                return defaultValue;
160            }
161        }
162        return defaultValue;
163    }
164
165    /**
166     * Gets the named property as a long.
167     *
168     * @param name the name of the property to look up
169     * @param defaultValue the default value to use if the property is undefined
170     * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed.
171     */
172    public long getLongProperty(final String name, final long defaultValue) {
173        final String prop = getStringProperty(name);
174        if (prop != null) {
175            try {
176                return Long.parseLong(prop);
177            } catch (final Exception ignored) {
178                return defaultValue;
179            }
180        }
181        return defaultValue;
182    }
183
184    /**
185     * Gets the named property as a String.
186     *
187     * @param name the name of the property to look up
188     * @return the String value of the property or {@code null} if undefined.
189     */
190    public String getStringProperty(final String name) {
191        String prop = null;
192        try {
193            prop = System.getProperty(name);
194        } catch (final SecurityException ignored) {
195            // Ignore
196        }
197        return prop == null ? props.getProperty(name) : prop;
198    }
199
200    /**
201     * Gets the named property as a String.
202     *
203     * @param name the name of the property to look up
204     * @param defaultValue the default value to use if the property is undefined
205     * @return the String value of the property or {@code defaultValue} if undefined.
206     */
207    public String getStringProperty(final String name, final String defaultValue) {
208        final String prop = getStringProperty(name);
209        return (prop == null) ? defaultValue : prop;
210    }
211
212    /**
213     * Return the system properties or an empty Properties object if an error occurs.
214     *
215     * @return The system properties.
216     */
217    public static Properties getSystemProperties() {
218        try {
219            return new Properties(System.getProperties());
220        } catch (final SecurityException ex) {
221            LowLevelLogUtil.logException("Unable to access system properties.", ex);
222            // Sandboxed - can't read System Properties
223            return new Properties();
224        }
225    }
226
227    /**
228     * Extracts properties that start with or are equals to the specific prefix and returns them in a new Properties
229     * object with the prefix removed.
230     *
231     * @param properties The Properties to evaluate.
232     * @param prefix The prefix to extract.
233     * @return The subset of properties.
234     */
235    public static Properties extractSubset(final Properties properties, final String prefix) {
236        final Properties subset = new Properties();
237
238        if (prefix == null || prefix.length() == 0) {
239            return subset;
240        }
241
242        final String prefixToMatch = prefix.charAt(prefix.length() - 1) != '.' ? prefix + '.' : prefix;
243
244        final List<String> keys = new ArrayList<>();
245
246        for (final String key : properties.stringPropertyNames()) {
247            if (key.startsWith(prefixToMatch)) {
248                subset.setProperty(key.substring(prefixToMatch.length()), properties.getProperty(key));
249                keys.add(key);
250            }
251        }
252        for (final String key : keys) {
253            properties.remove(key);
254        }
255
256        return subset;
257    }
258
259    /**
260     * Partitions a properties map based on common key prefixes up to the first period.
261     *
262     * @param properties properties to partition
263     * @return the partitioned properties where each key is the common prefix (minus the period) and the values are
264     * new property maps without the prefix and period in the key
265     * @since 2.6
266     */
267    public static Map<String, Properties> partitionOnCommonPrefixes(final Properties properties) {
268        final Map<String, Properties> parts = new ConcurrentHashMap<>();
269        for (final String key : properties.stringPropertyNames()) {
270            final String prefix = key.substring(0, key.indexOf('.'));
271            if (!parts.containsKey(prefix)) {
272                parts.put(prefix, new Properties());
273            }
274            parts.get(prefix).setProperty(key.substring(key.indexOf('.') + 1), properties.getProperty(key));
275        }
276        return parts;
277    }
278
279    /**
280     * Returns true if system properties tell us we are running on Windows.
281     * @return true if system properties tell us we are running on Windows.
282     */
283    public boolean isOsWindows() {
284        return getStringProperty("os.name").startsWith("Windows");
285    }
286
287}