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