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  
18  package org.apache.log4j.helpers;
19  
20  import org.apache.log4j.Level;
21  import org.apache.logging.log4j.LogManager;
22  import org.apache.logging.log4j.Logger;
23  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
24  import org.apache.logging.log4j.util.LoaderUtil;
25  
26  import java.io.InterruptedIOException;
27  import java.lang.reflect.InvocationTargetException;
28  import java.util.Properties;
29  
30  /**
31   * A convenience class to convert property values to specific types.
32   */
33  public class OptionConverter {
34  
35      static String DELIM_START = "${";
36      static char DELIM_STOP = '}';
37      static int DELIM_START_LEN = 2;
38      static int DELIM_STOP_LEN = 1;
39      private static final Logger LOGGER = LogManager.getLogger(OptionConverter.class);
40      private static final CharMap[] charMap = new CharMap[] {
41          new CharMap('n', '\n'),
42          new CharMap('r', '\r'),
43          new CharMap('t', '\t'),
44          new CharMap('f', '\f'),
45          new CharMap('\b', '\b'),
46          new CharMap('\"', '\"'),
47          new CharMap('\'', '\''),
48          new CharMap('\\', '\\')
49      };
50  
51      /**
52       * OptionConverter is a static class.
53       */
54      private OptionConverter() {
55      }
56  
57      public static String[] concatanateArrays(String[] l, String[] r) {
58          int len = l.length + r.length;
59          String[] a = new String[len];
60  
61          System.arraycopy(l, 0, a, 0, l.length);
62          System.arraycopy(r, 0, a, l.length, r.length);
63  
64          return a;
65      }
66  
67      public static String convertSpecialChars(String s) {
68          char c;
69          int len = s.length();
70          StringBuilder sbuf = new StringBuilder(len);
71  
72          int i = 0;
73          while (i < len) {
74              c = s.charAt(i++);
75              if (c == '\\') {
76                  c = s.charAt(i++);
77                  for (CharMap entry : charMap) {
78                      if (entry.key == c) {
79                          c = entry.replacement;
80                      }
81                  }
82              }
83              sbuf.append(c);
84          }
85          return sbuf.toString();
86      }
87  
88  
89      /**
90       * Very similar to <code>System.getProperty</code> except
91       * that the {@link SecurityException} is hidden.
92       *
93       * @param key The key to search for.
94       * @param def The default value to return.
95       * @return the string value of the system property, or the default
96       * value if there is no property with that key.
97       * @since 1.1
98       */
99      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 }