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