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 java.io.InputStream;
21  import java.io.InterruptedIOException;
22  import java.net.URL;
23  import java.util.Properties;
24  
25  import org.apache.log4j.Level;
26  import org.apache.log4j.PropertyConfigurator;
27  import org.apache.log4j.spi.Configurator;
28  import org.apache.log4j.spi.LoggerRepository;
29  
30  // Contributors:   Avy Sharell (sharell@online.fr)
31  //                 Matthieu Verbert (mve@zurich.ibm.com)
32  //                 Colin Sampaleanu
33  
34  /**
35     A convenience class to convert property values to specific types.
36  
37     @author Ceki Gülcü
38     @author Simon Kitching;
39     @author Anders Kristensen
40  */
41  public class OptionConverter {
42  
43    static String DELIM_START = "${";
44    static char   DELIM_STOP  = '}';
45    static int DELIM_START_LEN = 2;
46    static int DELIM_STOP_LEN  = 1;
47  
48    /** OptionConverter is a static class. */
49    private OptionConverter() {}
50  
51    public
52    static
53    String[] concatanateArrays(String[] l, String[] r) {
54      int len = l.length + r.length;
55      String[] a = new String[len];
56  
57      System.arraycopy(l, 0, a, 0, l.length);
58      System.arraycopy(r, 0, a, l.length, r.length);
59  
60      return a;
61    }
62  
63    public
64    static
65    String convertSpecialChars(String s) {
66      char c;
67      int len = s.length();
68      StringBuffer sbuf = new StringBuffer(len);
69  
70      int i = 0;
71      while(i < len) {
72        c = s.charAt(i++);
73        if (c == '\\') {
74  	c =  s.charAt(i++);
75  	if(c == 'n')      c = '\n';
76  	else if(c == 'r') c = '\r';
77  	else if(c == 't') c = '\t';
78  	else if(c == 'f') c = '\f';
79  	else if(c == '\b') c = '\b';
80  	else if(c == '\"') c = '\"';
81  	else if(c == '\'') c = '\'';
82  	else if(c == '\\') c = '\\';
83        }
84        sbuf.append(c);
85      }
86      return sbuf.toString();
87    }
88  
89  
90    /**
91       Very similar to <code>System.getProperty</code> except
92       that the {@link SecurityException} is hidden.
93  
94       @param key The key to search for.
95       @param def The default value to return.
96       @return the string value of the system property, or the default
97       value if there is no property with that key.
98  
99       @since 1.1 */
100   public
101   static
102   String getSystemProperty(String key, String def) {
103     try {
104       return System.getProperty(key, def);
105     } catch(Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
106       LogLog.debug("Was not allowed to read system property \""+key+"\".");
107       return def;
108     }
109   }
110 
111 
112   public
113   static
114   Object instantiateByKey(Properties props, String key, Class superClass,
115 				Object defaultValue) {
116 
117     // Get the value of the property in string form
118     String className = findAndSubst(key, props);
119     if(className == null) {
120       LogLog.error("Could not find value for key " + key);
121       return defaultValue;
122     }
123     // Trim className to avoid trailing spaces that cause problems.
124     return OptionConverter.instantiateByClassName(className.trim(), superClass,
125 						  defaultValue);
126   }
127 
128   /**
129      If <code>value</code> is "true", then <code>true</code> is
130      returned. If <code>value</code> is "false", then
131      <code>true</code> is returned. Otherwise, <code>default</code> is
132      returned.
133 
134      <p>Case of value is unimportant.  */
135   public
136   static
137   boolean toBoolean(String value, boolean dEfault) {
138     if(value == null)
139       return dEfault;
140     String trimmedVal = value.trim();
141     if("true".equalsIgnoreCase(trimmedVal))
142       return true;
143     if("false".equalsIgnoreCase(trimmedVal))
144       return false;
145     return dEfault;
146   }
147 
148   public
149   static
150   int toInt(String value, int dEfault) {
151     if(value != null) {
152       String s = value.trim();
153       try {
154 	return Integer.valueOf(s).intValue();
155       }
156       catch (NumberFormatException e) {
157 	 LogLog.error("[" + s + "] is not in proper int form.");
158 	e.printStackTrace();
159       }
160     }
161     return dEfault;
162   }
163 
164   /**
165      Converts a standard or custom priority level to a Level
166      object.  <p> If <code>value</code> is of form
167      "level#classname", then the specified class' toLevel method
168      is called to process the specified level string; if no '#'
169      character is present, then the default {@link org.apache.log4j.Level}
170      class is used to process the level value.
171 
172      <p>As a special case, if the <code>value</code> parameter is
173      equal to the string "NULL", then the value <code>null</code> will
174      be returned.
175 
176      <p> If any error occurs while converting the value to a level,
177      the <code>defaultValue</code> parameter, which may be
178      <code>null</code>, is returned.
179 
180      <p> Case of <code>value</code> is insignificant for the level level, but is
181      significant for the class name part, if present.
182 
183      @since 1.1 */
184   public
185   static
186   Level toLevel(String value, Level defaultValue) {
187     if(value == null)
188       return defaultValue;
189       
190     value = value.trim();
191 
192     int hashIndex = value.indexOf('#');
193     if (hashIndex == -1) {
194       if("NULL".equalsIgnoreCase(value)) {
195 	return null;
196       } else {
197 	// no class name specified : use standard Level class
198 	return(Level) Level.toLevel(value, defaultValue);
199       }
200     }
201 
202     Level result = defaultValue;
203 
204     String clazz = value.substring(hashIndex+1);
205     String levelName = value.substring(0, hashIndex);
206 
207     // This is degenerate case but you never know.
208     if("NULL".equalsIgnoreCase(levelName)) {
209 	return null;
210     }
211 
212     LogLog.debug("toLevel" + ":class=[" + clazz + "]"
213 		 + ":pri=[" + levelName + "]");
214 
215     try {
216       Class customLevel = Loader.loadClass(clazz);
217 
218       // get a ref to the specified class' static method
219       // toLevel(String, org.apache.log4j.Level)
220       Class[] paramTypes = new Class[] { String.class,
221 					 org.apache.log4j.Level.class
222                                        };
223       java.lang.reflect.Method toLevelMethod =
224                       customLevel.getMethod("toLevel", paramTypes);
225 
226       // now call the toLevel method, passing level string + default
227       Object[] params = new Object[] {levelName, defaultValue};
228       Object o = toLevelMethod.invoke(null, params);
229 
230       result = (Level) o;
231     } catch(ClassNotFoundException e) {
232       LogLog.warn("custom level class [" + clazz + "] not found.");
233     } catch(NoSuchMethodException e) {
234       LogLog.warn("custom level class [" + clazz + "]"
235         + " does not have a class function toLevel(String, Level)", e);
236     } catch(java.lang.reflect.InvocationTargetException e) {
237         if (e.getTargetException() instanceof InterruptedException
238                 || e.getTargetException() instanceof InterruptedIOException) {
239             Thread.currentThread().interrupt();
240         }
241       LogLog.warn("custom level class [" + clazz + "]"
242 		   + " could not be instantiated", e);
243     } catch(ClassCastException e) {
244       LogLog.warn("class [" + clazz
245         + "] is not a subclass of org.apache.log4j.Level", e);
246     } catch(IllegalAccessException e) {
247       LogLog.warn("class ["+clazz+
248 		   "] cannot be instantiated due to access restrictions", e);
249     } catch(RuntimeException e) {
250       LogLog.warn("class ["+clazz+"], level ["+levelName+
251 		   "] conversion failed.", e);
252     }
253     return result;
254    }
255 
256   public
257   static
258   long toFileSize(String value, long dEfault) {
259     if(value == null)
260       return dEfault;
261 
262     String s = value.trim().toUpperCase();
263     long multiplier = 1;
264     int index;
265 
266     if((index = s.indexOf("KB")) != -1) {
267       multiplier = 1024;
268       s = s.substring(0, index);
269     }
270     else if((index = s.indexOf("MB")) != -1) {
271       multiplier = 1024*1024;
272       s = s.substring(0, index);
273     }
274     else if((index = s.indexOf("GB")) != -1) {
275       multiplier = 1024*1024*1024;
276       s = s.substring(0, index);
277     }
278     if(s != null) {
279       try {
280 	return Long.valueOf(s).longValue() * multiplier;
281       }
282       catch (NumberFormatException e) {
283 	LogLog.error("[" + s + "] is not in proper int form.");
284 	LogLog.error("[" + value + "] not in expected format.", e);
285       }
286     }
287     return dEfault;
288   }
289 
290   /**
291      Find the value corresponding to <code>key</code> in
292      <code>props</code>. Then perform variable substitution on the
293      found value.
294 
295  */
296   public
297   static
298   String findAndSubst(String key, Properties props) {
299     String value = props.getProperty(key);
300     if(value == null)
301       return null;
302 
303     try {
304       return substVars(value, props);
305     } catch(IllegalArgumentException e) {
306       LogLog.error("Bad option value ["+value+"].", e);
307       return value;
308     }
309   }
310 
311   /**
312      Instantiate an object given a class name. Check that the
313      <code>className</code> is a subclass of
314      <code>superClass</code>. If that test fails or the object could
315      not be instantiated, then <code>defaultValue</code> is returned.
316 
317      @param className The fully qualified class name of the object to instantiate.
318      @param superClass The class to which the new object should belong.
319      @param defaultValue The object to return in case of non-fulfillment
320    */
321   public
322   static
323   Object instantiateByClassName(String className, Class superClass,
324 				Object defaultValue) {
325     if(className != null) {
326       try {
327 	Class classObj = Loader.loadClass(className);
328 	if(!superClass.isAssignableFrom(classObj)) {
329 	  LogLog.error("A \""+className+"\" object is not assignable to a \""+
330 		       superClass.getName() + "\" variable.");
331 	  LogLog.error("The class \""+ superClass.getName()+"\" was loaded by ");
332 	  LogLog.error("["+superClass.getClassLoader()+"] whereas object of type ");
333 	  LogLog.error("\"" +classObj.getName()+"\" was loaded by ["
334 		       +classObj.getClassLoader()+"].");
335 	  return defaultValue;
336 	}
337 	return classObj.newInstance();
338       } catch (ClassNotFoundException e) {
339 	    LogLog.error("Could not instantiate class [" + className + "].", e);
340       } catch (IllegalAccessException e) {
341 	    LogLog.error("Could not instantiate class [" + className + "].", e);
342       } catch (InstantiationException e) {
343         LogLog.error("Could not instantiate class [" + className + "].", e);
344       } catch (RuntimeException e) {
345 	    LogLog.error("Could not instantiate class [" + className + "].", e);
346       }
347     }
348     return defaultValue;
349   }
350 
351 
352   /**
353      Perform variable substitution in string <code>val</code> from the
354      values of keys found in the system propeties.
355 
356      <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
357 
358      <p>For example, if the System properties contains "key=value", then
359      the call
360      <pre>
361      String s = OptionConverter.substituteVars("Value of key is ${key}.");
362      </pre>
363 
364      will set the variable <code>s</code> to "Value of key is value.".
365 
366      <p>If no value could be found for the specified key, then the
367      <code>props</code> parameter is searched, if the value could not
368      be found there, then substitution defaults to the empty string.
369 
370      <p>For example, if system propeties contains no value for the key
371      "inexistentKey", then the call
372 
373      <pre>
374      String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
375      </pre>
376      will set <code>s</code> to "Value of inexistentKey is []"
377 
378      <p>An {@link java.lang.IllegalArgumentException} is thrown if
379      <code>val</code> contains a start delimeter "${" which is not
380      balanced by a stop delimeter "}". </p>
381 
382      <p><b>Author</b> Avy Sharell</a></p>
383 
384      @param val The string on which variable substitution is performed.
385      @throws IllegalArgumentException if <code>val</code> is malformed.
386 
387   */
388   public static
389   String substVars(String val, Properties props) throws
390                         IllegalArgumentException {
391 
392     StringBuffer sbuf = new StringBuffer();
393 
394     int i = 0;
395     int j, k;
396 
397     while(true) {
398       j=val.indexOf(DELIM_START, i);
399       if(j == -1) {
400 	// no more variables
401 	if(i==0) { // this is a simple string
402 	  return val;
403 	} else { // add the tail string which contails no variables and return the result.
404 	  sbuf.append(val.substring(i, val.length()));
405 	  return sbuf.toString();
406 	}
407       } else {
408 	sbuf.append(val.substring(i, j));
409 	k = val.indexOf(DELIM_STOP, j);
410 	if(k == -1) {
411 	  throw new IllegalArgumentException('"'+val+
412 		      "\" has no closing brace. Opening brace at position " + j
413 					     + '.');
414 	} else {
415 	  j += DELIM_START_LEN;
416 	  String key = val.substring(j, k);
417 	  // first try in System properties
418 	  String replacement = getSystemProperty(key, null);
419 	  // then try props parameter
420 	  if(replacement == null && props != null) {
421 	    replacement =  props.getProperty(key);
422 	  }
423 
424 	  if(replacement != null) {
425 	    // Do variable substitution on the replacement string
426 	    // such that we can solve "Hello ${x2}" as "Hello p1" 
427             // the where the properties are
428 	    // x1=p1
429             // x2=${x1}
430 	    String recursiveReplacement = substVars(replacement, props);
431 	    sbuf.append(recursiveReplacement);
432 	  }
433 	  i = k + DELIM_STOP_LEN;
434 	}
435       }
436     }
437   }
438 
439     /**
440      * Configure log4j given an {@link InputStream}.
441      * 
442      * <p>
443      * The InputStream will be interpreted by a new instance of a log4j configurator.
444      * </p>
445      * <p>
446      * All configurations steps are taken on the <code>hierarchy</code> passed as a parameter.
447      * </p>
448      * 
449      * @param inputStream
450      *            The configuration input stream.
451      * @param clazz
452      *            The class name, of the log4j configurator which will parse the <code>inputStream</code>. This must be a
453      *            subclass of {@link Configurator}, or null. If this value is null then a default configurator of
454      *            {@link PropertyConfigurator} is used.
455      * @param hierarchy
456      *            The {@link org.apache.log4j.Hierarchy} to act on.
457      * @since 1.2.17
458      */
459 
460 static
461 public
462 void selectAndConfigure(InputStream inputStream, String clazz, LoggerRepository hierarchy) {
463 Configurator configurator = null;
464 
465 if(clazz != null) {
466   LogLog.debug("Preferred configurator class: " + clazz);
467   configurator = (Configurator) instantiateByClassName(clazz,
468                            Configurator.class,
469                            null);
470   if(configurator == null) {
471    LogLog.error("Could not instantiate configurator ["+clazz+"].");
472    return;
473   }
474 } else {
475   configurator = new PropertyConfigurator();
476 }
477 
478 configurator.doConfigure(inputStream, hierarchy);
479 }
480 
481 
482   /**
483      Configure log4j given a URL.
484 
485      <p>The url must point to a file or resource which will be interpreted by
486      a new instance of a log4j configurator.
487 
488      <p>All configurations steps are taken on the
489      <code>hierarchy</code> passed as a parameter.
490 
491      <p>
492      @param url The location of the configuration file or resource.
493      @param clazz The classname, of the log4j configurator which will parse
494      the file or resource at <code>url</code>. This must be a subclass of
495      {@link Configurator}, or null. If this value is null then a default
496      configurator of {@link PropertyConfigurator} is used, unless the
497      filename pointed to by <code>url</code> ends in '.xml', in which case
498      {@link org.apache.log4j.xml.DOMConfigurator} is used.
499      @param hierarchy The {@link org.apache.log4j.Hierarchy} to act on.
500 
501      @since 1.1.4 */
502 
503   static
504   public
505   void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
506    Configurator configurator = null;
507    String filename = url.getFile();
508 
509    if(clazz == null && filename != null && filename.endsWith(".xml")) {
510      clazz = "org.apache.log4j.xml.DOMConfigurator";
511    }
512 
513    if(clazz != null) {
514      LogLog.debug("Preferred configurator class: " + clazz);
515      configurator = (Configurator) instantiateByClassName(clazz,
516 							  Configurator.class,
517 							  null);
518      if(configurator == null) {
519    	  LogLog.error("Could not instantiate configurator ["+clazz+"].");
520    	  return;
521      }
522    } else {
523      configurator = new PropertyConfigurator();
524    }
525 
526    configurator.doConfigure(url, hierarchy);
527   }
528 }