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 */
017
018package org.apache.log4j.helpers;
019
020import org.apache.log4j.Level;
021import org.apache.logging.log4j.LogManager;
022import org.apache.logging.log4j.Logger;
023import org.apache.logging.log4j.core.lookup.StrSubstitutor;
024import org.apache.logging.log4j.util.LoaderUtil;
025
026import java.io.InterruptedIOException;
027import java.lang.reflect.InvocationTargetException;
028import java.util.Properties;
029
030/**
031 * A convenience class to convert property values to specific types.
032 */
033public class OptionConverter {
034
035    static String DELIM_START = "${";
036    static char DELIM_STOP = '}';
037    static int DELIM_START_LEN = 2;
038    static int DELIM_STOP_LEN = 1;
039    private static final Logger LOGGER = LogManager.getLogger(OptionConverter.class);
040    private static final CharMap[] charMap = new CharMap[] {
041        new CharMap('n', '\n'),
042        new CharMap('r', '\r'),
043        new CharMap('t', '\t'),
044        new CharMap('f', '\f'),
045        new CharMap('\b', '\b'),
046        new CharMap('\"', '\"'),
047        new CharMap('\'', '\''),
048        new CharMap('\\', '\\')
049    };
050
051    /**
052     * OptionConverter is a static class.
053     */
054    private OptionConverter() {
055    }
056
057    public static String[] concatanateArrays(String[] l, String[] r) {
058        int len = l.length + r.length;
059        String[] a = new String[len];
060
061        System.arraycopy(l, 0, a, 0, l.length);
062        System.arraycopy(r, 0, a, l.length, r.length);
063
064        return a;
065    }
066
067    public static String convertSpecialChars(String s) {
068        char c;
069        int len = s.length();
070        StringBuilder sbuf = new StringBuilder(len);
071
072        int i = 0;
073        while (i < len) {
074            c = s.charAt(i++);
075            if (c == '\\') {
076                c = s.charAt(i++);
077                for (CharMap entry : charMap) {
078                    if (entry.key == c) {
079                        c = entry.replacement;
080                    }
081                }
082            }
083            sbuf.append(c);
084        }
085        return sbuf.toString();
086    }
087
088
089    /**
090     * Very similar to <code>System.getProperty</code> except
091     * that the {@link SecurityException} is hidden.
092     *
093     * @param key The key to search for.
094     * @param def The default value to return.
095     * @return the string value of the system property, or the default
096     * value if there is no property with that key.
097     * @since 1.1
098     */
099    public static String getSystemProperty(String key, String def) {
100        try {
101            return System.getProperty(key, def);
102        } catch (Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
103            LOGGER.debug("Was not allowed to read system property \"{}\".", key);
104            return def;
105        }
106    }
107
108    /**
109     * If <code>value</code> is "true", then <code>true</code> is
110     * returned. If <code>value</code> is "false", then
111     * <code>true</code> is returned. Otherwise, <code>default</code> is
112     * returned.
113     *
114     * <p>Case of value is unimportant.
115     * @param value The value to convert.
116     * @param dEfault The default value.
117     * @return the value of the result.
118     */
119    public static boolean toBoolean(String value, boolean dEfault) {
120        if (value == null) {
121            return dEfault;
122        }
123        String trimmedVal = value.trim();
124        if ("true".equalsIgnoreCase(trimmedVal)) {
125            return true;
126        }
127        if ("false".equalsIgnoreCase(trimmedVal)) {
128            return false;
129        }
130        return dEfault;
131    }
132
133    /**
134     * Converts a standard or custom priority level to a Level
135     * object.  <p> If <code>value</code> is of form
136     * "level#classname", then the specified class' toLevel method
137     * is called to process the specified level string; if no '#'
138     * character is present, then the default {@link org.apache.log4j.Level}
139     * class is used to process the level value.
140     *
141     * <p>As a special case, if the <code>value</code> parameter is
142     * equal to the string "NULL", then the value <code>null</code> will
143     * be returned.
144     *
145     * <p> If any error occurs while converting the value to a level,
146     * the <code>defaultValue</code> parameter, which may be
147     * <code>null</code>, is returned.
148     *
149     * <p> Case of <code>value</code> is insignificant for the level level, but is
150     * significant for the class name part, if present.
151     * @param value The value to convert.
152     * @param defaultValue The default value.
153     * @return the value of the result.
154     *
155     * @since 1.1
156     */
157    public static Level toLevel(String value, Level defaultValue) {
158        if (value == null) {
159            return defaultValue;
160        }
161
162        value = value.trim();
163
164        int hashIndex = value.indexOf('#');
165        if (hashIndex == -1) {
166            if ("NULL".equalsIgnoreCase(value)) {
167                return null;
168            }
169            // no class name specified : use standard Level class
170            return Level.toLevel(value, defaultValue);
171        }
172
173        Level result = defaultValue;
174
175        String clazz = value.substring(hashIndex + 1);
176        String levelName = value.substring(0, hashIndex);
177
178        // This is degenerate case but you never know.
179        if ("NULL".equalsIgnoreCase(levelName)) {
180            return null;
181        }
182
183        LOGGER.debug("toLevel" + ":class=[" + clazz + "]"
184                + ":pri=[" + levelName + "]");
185
186        try {
187            Class<?> customLevel = LoaderUtil.loadClass(clazz);
188
189            // get a ref to the specified class' static method
190            // toLevel(String, org.apache.log4j.Level)
191            Class<?>[] paramTypes = new Class[] { String.class, org.apache.log4j.Level.class };
192            java.lang.reflect.Method toLevelMethod =
193                    customLevel.getMethod("toLevel", paramTypes);
194
195            // now call the toLevel method, passing level string + default
196            Object[] params = new Object[]{levelName, defaultValue};
197            Object o = toLevelMethod.invoke(null, params);
198
199            result = (Level) o;
200        } catch (ClassNotFoundException e) {
201            LOGGER.warn("custom level class [" + clazz + "] not found.");
202        } catch (NoSuchMethodException e) {
203            LOGGER.warn("custom level class [" + clazz + "]"
204                    + " does not have a class function toLevel(String, Level)", e);
205        } catch (java.lang.reflect.InvocationTargetException e) {
206            if (e.getTargetException() instanceof InterruptedException
207                    || e.getTargetException() instanceof InterruptedIOException) {
208                Thread.currentThread().interrupt();
209            }
210            LOGGER.warn("custom level class [" + clazz + "]"
211                    + " could not be instantiated", e);
212        } catch (ClassCastException e) {
213            LOGGER.warn("class [" + clazz
214                    + "] is not a subclass of org.apache.log4j.Level", e);
215        } catch (IllegalAccessException e) {
216            LOGGER.warn("class [" + clazz +
217                    "] cannot be instantiated due to access restrictions", e);
218        } catch (RuntimeException e) {
219            LOGGER.warn("class [" + clazz + "], level [" + levelName +
220                    "] conversion failed.", e);
221        }
222        return result;
223    }
224
225    /**
226     * Instantiate an object given a class name. Check that the
227     * <code>className</code> is a subclass of
228     * <code>superClass</code>. If that test fails or the object could
229     * not be instantiated, then <code>defaultValue</code> is returned.
230     *
231     * @param className    The fully qualified class name of the object to instantiate.
232     * @param superClass   The class to which the new object should belong.
233     * @param defaultValue The object to return in case of non-fulfillment
234     * @return The created object.
235     */
236    public static Object instantiateByClassName(String className, Class<?> superClass,
237            Object defaultValue) {
238        if (className != null) {
239            try {
240                Object obj = LoaderUtil.newInstanceOf(className);
241                if (!superClass.isAssignableFrom(obj.getClass())) {
242                    LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable", className,
243                            superClass.getName());
244                    return defaultValue;
245                }
246                return obj;
247            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
248                    | InstantiationException | InvocationTargetException e) {
249                LOGGER.error("Could not instantiate class [" + className + "].", e);
250            }
251        }
252        return defaultValue;
253    }
254
255
256    /**
257     * Perform variable substitution in string <code>val</code> from the
258     * values of keys found in the system propeties.
259     *
260     * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
261     *
262     * <p>For example, if the System properties contains "key=value", then
263     * the call
264     * <pre>
265     * String s = OptionConverter.substituteVars("Value of key is ${key}.");
266     * </pre>
267     * <p>
268     * will set the variable <code>s</code> to "Value of key is value.".
269     *
270     * <p>If no value could be found for the specified key, then the
271     * <code>props</code> parameter is searched, if the value could not
272     * be found there, then substitution defaults to the empty string.
273     *
274     * <p>For example, if system propeties contains no value for the key
275     * "inexistentKey", then the call
276     *
277     * <pre>
278     * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
279     * </pre>
280     * will set <code>s</code> to "Value of inexistentKey is []"
281     *
282     * <p>An {@link IllegalArgumentException} is thrown if
283     * <code>val</code> contains a start delimeter "${" which is not
284     * balanced by a stop delimeter "}". </p>
285     *
286     * <p><b>Author</b> Avy Sharell</p>
287     *
288     * @param val The string on which variable substitution is performed.
289     * @param props The properties to use for the substitution.
290     * @return The substituted string.
291     * @throws IllegalArgumentException if <code>val</code> is malformed.
292     */
293    public static String substVars(String val, Properties props) throws IllegalArgumentException {
294        return StrSubstitutor.replace(val, props);
295    }
296
297    public static org.apache.logging.log4j.Level convertLevel(String level,
298            org.apache.logging.log4j.Level defaultLevel) {
299        Level l = toLevel(level, null);
300        return l != null ? convertLevel(l) : defaultLevel;
301    }
302
303    public static  org.apache.logging.log4j.Level convertLevel(Level level) {
304        if (level == null) {
305            return org.apache.logging.log4j.Level.ERROR;
306        }
307        if (level.isGreaterOrEqual(Level.FATAL)) {
308            return org.apache.logging.log4j.Level.FATAL;
309        } else if (level.isGreaterOrEqual(Level.ERROR)) {
310            return org.apache.logging.log4j.Level.ERROR;
311        } else if (level.isGreaterOrEqual(Level.WARN)) {
312            return org.apache.logging.log4j.Level.WARN;
313        } else if (level.isGreaterOrEqual(Level.INFO)) {
314            return org.apache.logging.log4j.Level.INFO;
315        } else if (level.isGreaterOrEqual(Level.DEBUG)) {
316            return org.apache.logging.log4j.Level.DEBUG;
317        } else if (level.isGreaterOrEqual(Level.TRACE)) {
318            return org.apache.logging.log4j.Level.TRACE;
319        }
320        return org.apache.logging.log4j.Level.ALL;
321    }
322
323    public static Level convertLevel(org.apache.logging.log4j.Level level) {
324        if (level == null) {
325            return Level.ERROR;
326        }
327        switch (level.getStandardLevel()) {
328            case FATAL:
329                return Level.FATAL;
330            case WARN:
331                return Level.WARN;
332            case INFO:
333                return Level.INFO;
334            case DEBUG:
335                return Level.DEBUG;
336            case TRACE:
337                return Level.TRACE;
338            case ALL:
339                return Level.ALL;
340            case OFF:
341                return Level.OFF;
342            default:
343                return Level.ERROR;
344        }
345    }
346
347    /**
348     * Find the value corresponding to <code>key</code> in
349     * <code>props</code>. Then perform variable substitution on the
350     * found value.
351     * @param key The key used to locate the substitution string.
352     * @param props The properties to use in the substitution.
353     * @return The substituted string.
354     */
355    public static String findAndSubst(String key, Properties props) {
356        String value = props.getProperty(key);
357        if (value == null) {
358            return null;
359        }
360
361        try {
362            return substVars(value, props);
363        } catch (IllegalArgumentException e) {
364            LOGGER.error("Bad option value [{}].", value, e);
365            return value;
366        }
367    }
368
369    private static class CharMap {
370        final char key;
371        final char replacement;
372
373        public CharMap(char key, char replacement) {
374            this.key = key;
375            this.replacement = replacement;
376        }
377    }
378}