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