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