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.util.LoaderUtil;
24  
25  import java.io.InterruptedIOException;
26  import java.lang.reflect.InvocationTargetException;
27  import java.util.Properties;
28  
29  /**
30   * A convenience class to convert property values to specific types.
31   */
32  public class OptionConverter {
33      
34      static String DELIM_START = "${";
35      static char DELIM_STOP = '}';
36      static int DELIM_START_LEN = 2;
37      static int DELIM_STOP_LEN = 1;
38      private static final Logger LOGGER = LogManager.getLogger(OptionConverter.class);
39      private static final CharMap[] charMap = new CharMap[] {
40          new CharMap('n', '\n'),
41          new CharMap('r', '\r'),
42          new CharMap('t', '\t'),
43          new CharMap('f', '\f'),
44          new CharMap('\b', '\b'),
45          new CharMap('\"', '\"'),
46          new CharMap('\'', '\''),
47          new CharMap('\\', '\\')    
48      };
49  
50      /**
51       * OptionConverter is a static class.
52       */
53      private OptionConverter() {
54      }
55  
56      public static String[] concatanateArrays(String[] l, String[] r) {
57          int len = l.length + r.length;
58          String[] a = new String[len];
59  
60          System.arraycopy(l, 0, a, 0, l.length);
61          System.arraycopy(r, 0, a, l.length, r.length);
62  
63          return a;
64      }
65  
66      public static String convertSpecialChars(String s) {
67          char c;
68          int len = s.length();
69          StringBuilder sbuf = new StringBuilder(len);
70  
71          int i = 0;
72          while (i < len) {
73              c = s.charAt(i++);
74              if (c == '\\') {
75                  c = s.charAt(i++);
76                  for (CharMap entry : charMap) {
77                      if (entry.key == c) {
78                          c = entry.replacement;
79                      }
80                  }
81              }
82              sbuf.append(c);
83          }
84          return sbuf.toString();
85      }
86  
87  
88      /**
89       * Very similar to <code>System.getProperty</code> except
90       * that the {@link SecurityException} is hidden.
91       *
92       * @param key The key to search for.
93       * @param def The default value to return.
94       * @return the string value of the system property, or the default
95       * value if there is no property with that key.
96       * @since 1.1
97       */
98      public static String getSystemProperty(String key, String def) {
99          try {
100             return System.getProperty(key, def);
101         } catch (Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
102             LOGGER.debug("Was not allowed to read system property \"{}\".", key);
103             return def;
104         }
105     }
106 
107     /**
108      * If <code>value</code> is "true", then <code>true</code> is
109      * returned. If <code>value</code> is "false", then
110      * <code>true</code> is returned. Otherwise, <code>default</code> is
111      * returned.
112      *
113      * <p>Case of value is unimportant.
114      * @param value The value to convert.
115      * @param dEfault The default value.
116      * @return the value of the result.
117      */
118     public static boolean toBoolean(String value, boolean dEfault) {
119         if (value == null) {
120             return dEfault;
121         }
122         String trimmedVal = value.trim();
123         if ("true".equalsIgnoreCase(trimmedVal)) {
124             return true;
125         }
126         if ("false".equalsIgnoreCase(trimmedVal)) {
127             return false;
128         }
129         return dEfault;
130     }
131 
132     /**
133      * Converts a standard or custom priority level to a Level
134      * object.  <p> If <code>value</code> is of form
135      * "level#classname", then the specified class' toLevel method
136      * is called to process the specified level string; if no '#'
137      * character is present, then the default {@link org.apache.log4j.Level}
138      * class is used to process the level value.
139      *
140      * <p>As a special case, if the <code>value</code> parameter is
141      * equal to the string "NULL", then the value <code>null</code> will
142      * be returned.
143      *
144      * <p> If any error occurs while converting the value to a level,
145      * the <code>defaultValue</code> parameter, which may be
146      * <code>null</code>, is returned.
147      *
148      * <p> Case of <code>value</code> is insignificant for the level level, but is
149      * significant for the class name part, if present.
150      * @param value The value to convert.
151      * @param defaultValue The default value.
152      * @return the value of the result.
153      *
154      * @since 1.1
155      */
156     public static Level toLevel(String value, Level defaultValue) {
157         if (value == null) {
158             return defaultValue;
159         }
160 
161         value = value.trim();
162 
163         int hashIndex = value.indexOf('#');
164         if (hashIndex == -1) {
165             if ("NULL".equalsIgnoreCase(value)) {
166                 return null;
167             } else {
168                 // no class name specified : use standard Level class
169                 return Level.toLevel(value, defaultValue);
170             }
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,
192                     org.apache.log4j.Level.class
193             };
194             java.lang.reflect.Method toLevelMethod =
195                     customLevel.getMethod("toLevel", paramTypes);
196 
197             // now call the toLevel method, passing level string + default
198             Object[] params = new Object[]{levelName, defaultValue};
199             Object o = toLevelMethod.invoke(null, params);
200 
201             result = (Level) o;
202         } catch (ClassNotFoundException e) {
203             LOGGER.warn("custom level class [" + clazz + "] not found.");
204         } catch (NoSuchMethodException e) {
205             LOGGER.warn("custom level class [" + clazz + "]"
206                     + " does not have a class function toLevel(String, Level)", e);
207         } catch (java.lang.reflect.InvocationTargetException e) {
208             if (e.getTargetException() instanceof InterruptedException
209                     || e.getTargetException() instanceof InterruptedIOException) {
210                 Thread.currentThread().interrupt();
211             }
212             LOGGER.warn("custom level class [" + clazz + "]"
213                     + " could not be instantiated", e);
214         } catch (ClassCastException e) {
215             LOGGER.warn("class [" + clazz
216                     + "] is not a subclass of org.apache.log4j.Level", e);
217         } catch (IllegalAccessException e) {
218             LOGGER.warn("class [" + clazz +
219                     "] cannot be instantiated due to access restrictions", e);
220         } catch (RuntimeException e) {
221             LOGGER.warn("class [" + clazz + "], level [" + levelName +
222                     "] conversion failed.", e);
223         }
224         return result;
225     }
226 
227     /**
228      * Instantiate an object given a class name. Check that the
229      * <code>className</code> is a subclass of
230      * <code>superClass</code>. If that test fails or the object could
231      * not be instantiated, then <code>defaultValue</code> is returned.
232      *
233      * @param className    The fully qualified class name of the object to instantiate.
234      * @param superClass   The class to which the new object should belong.
235      * @param defaultValue The object to return in case of non-fulfillment
236      * @return The created object.
237      */
238     public static Object instantiateByClassName(String className, Class<?> superClass,
239             Object defaultValue) {
240         if (className != null) {
241             try {
242                 Object obj = LoaderUtil.newInstanceOf(className);
243                 if (!superClass.isAssignableFrom(obj.getClass())) {
244                     LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable", className,
245                             superClass.getName());
246                     return defaultValue;
247                 }
248                 return obj;
249             } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
250                     | InstantiationException | InvocationTargetException e) {
251                 LOGGER.error("Could not instantiate class [" + className + "].", e);
252             }
253         }
254         return defaultValue;
255     }
256 
257 
258     /**
259      * Perform variable substitution in string <code>val</code> from the
260      * values of keys found in the system propeties.
261      *
262      * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
263      *
264      * <p>For example, if the System properties contains "key=value", then
265      * the call
266      * <pre>
267      * String s = OptionConverter.substituteVars("Value of key is ${key}.");
268      * </pre>
269      * <p>
270      * will set the variable <code>s</code> to "Value of key is value.".
271      *
272      * <p>If no value could be found for the specified key, then the
273      * <code>props</code> parameter is searched, if the value could not
274      * be found there, then substitution defaults to the empty string.
275      *
276      * <p>For example, if system propeties contains no value for the key
277      * "inexistentKey", then the call
278      *
279      * <pre>
280      * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
281      * </pre>
282      * will set <code>s</code> to "Value of inexistentKey is []"
283      *
284      * <p>An {@link IllegalArgumentException} is thrown if
285      * <code>val</code> contains a start delimeter "${" which is not
286      * balanced by a stop delimeter "}". </p>
287      *
288      * <p><b>Author</b> Avy Sharell</p>
289      *
290      * @param val The string on which variable substitution is performed.
291      * @param props The properties to use for the substitution.
292      * @return The substituted string.
293      * @throws IllegalArgumentException if <code>val</code> is malformed.
294      */
295     public static String substVars(String val, Properties props) throws IllegalArgumentException {
296 
297         StringBuilder sbuf = new StringBuilder();
298 
299         int i = 0;
300         int j, k;
301 
302         while (true) {
303             j = val.indexOf(DELIM_START, i);
304             if (j == -1) {
305                 // no more variables
306                 if (i == 0) { // this is a simple string
307                     return val;
308                 } else { // add the tail string which contails no variables and return the result.
309                     sbuf.append(val.substring(i, val.length()));
310                     return sbuf.toString();
311                 }
312             } else {
313                 sbuf.append(val.substring(i, j));
314                 k = val.indexOf(DELIM_STOP, j);
315                 if (k == -1) {
316                     throw new IllegalArgumentException('"' + val +
317                             "\" has no closing brace. Opening brace at position " + j
318                             + '.');
319                 } else {
320                     j += DELIM_START_LEN;
321                     String key = val.substring(j, k);
322                     // first try in System properties
323                     String replacement = getSystemProperty(key, null);
324                     // then try props parameter
325                     if (replacement == null && props != null) {
326                         replacement = props.getProperty(key);
327                     }
328 
329                     if (replacement != null) {
330                         // Do variable substitution on the replacement string
331                         // such that we can solve "Hello ${x2}" as "Hello p1"
332                         // the where the properties are
333                         // x1=p1
334                         // x2=${x1}
335                         String recursiveReplacement = substVars(replacement, props);
336                         sbuf.append(recursiveReplacement);
337                     }
338                     i = k + DELIM_STOP_LEN;
339                 }
340             }
341         }
342     }
343 
344     public static org.apache.logging.log4j.Level convertLevel(String level,
345             org.apache.logging.log4j.Level defaultLevel) {
346         Level l = toLevel(level, null);
347         return l != null ? convertLevel(l) : defaultLevel;
348     }
349 
350     public static  org.apache.logging.log4j.Level convertLevel(Level level) {
351         if (level == null) {
352             return org.apache.logging.log4j.Level.ERROR;
353         }
354         if (level.isGreaterOrEqual(Level.FATAL)) {
355             return org.apache.logging.log4j.Level.FATAL;
356         } else if (level.isGreaterOrEqual(Level.ERROR)) {
357             return org.apache.logging.log4j.Level.ERROR;
358         } else if (level.isGreaterOrEqual(Level.WARN)) {
359             return org.apache.logging.log4j.Level.WARN;
360         } else if (level.isGreaterOrEqual(Level.INFO)) {
361             return org.apache.logging.log4j.Level.INFO;
362         } else if (level.isGreaterOrEqual(Level.DEBUG)) {
363             return org.apache.logging.log4j.Level.DEBUG;
364         } else if (level.isGreaterOrEqual(Level.TRACE)) {
365             return org.apache.logging.log4j.Level.TRACE;
366         }
367         return org.apache.logging.log4j.Level.ALL;
368     }
369 
370     public static Level convertLevel(org.apache.logging.log4j.Level level) {
371         if (level == null) {
372             return Level.ERROR;
373         }
374         switch (level.getStandardLevel()) {
375             case FATAL:
376                 return Level.FATAL;
377             case WARN:
378                 return Level.WARN;
379             case INFO:
380                 return Level.INFO;
381             case DEBUG:
382                 return Level.DEBUG;
383             case TRACE:
384                 return Level.TRACE;
385             case ALL:
386                 return Level.ALL;
387             case OFF:
388                 return Level.OFF;
389             default:
390                 return Level.ERROR;
391         }
392     }
393 
394     /**
395      * Find the value corresponding to <code>key</code> in
396      * <code>props</code>. Then perform variable substitution on the
397      * found value.
398      * @param key The key used to locate the substitution string.
399      * @param props The properties to use in the substitution.
400      * @return The substituted string.
401      */
402     public static String findAndSubst(String key, Properties props) {
403         String value = props.getProperty(key);
404         if (value == null) {
405             return null;
406         }
407 
408         try {
409             return substVars(value, props);
410         } catch (IllegalArgumentException e) {
411             LOGGER.error("Bad option value [{}].", value, e);
412             return value;
413         }
414     }
415     
416     private static class CharMap {
417         final char key;
418         final char replacement;
419         
420         public CharMap(char key, char replacement) {
421             this.key = key;
422             this.replacement = replacement;
423         }
424     }
425 }