001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j.helpers;
019
020import java.io.InputStream;
021import java.io.InterruptedIOException;
022import java.net.URL;
023import java.util.Properties;
024
025import org.apache.log4j.Level;
026import org.apache.log4j.PropertyConfigurator;
027import org.apache.log4j.spi.Configurator;
028import org.apache.log4j.spi.LoggerRepository;
029
030// Contributors:   Avy Sharell (sharell@online.fr)
031//                 Matthieu Verbert (mve@zurich.ibm.com)
032//                 Colin Sampaleanu
033
034/**
035   A convenience class to convert property values to specific types.
036
037   @author Ceki Gülcü
038   @author Simon Kitching;
039   @author Anders Kristensen
040*/
041public class OptionConverter {
042
043  static String DELIM_START = "${";
044  static char   DELIM_STOP  = '}';
045  static int DELIM_START_LEN = 2;
046  static int DELIM_STOP_LEN  = 1;
047
048  /** OptionConverter is a static class. */
049  private OptionConverter() {}
050
051  public
052  static
053  String[] concatanateArrays(String[] l, String[] r) {
054    int len = l.length + r.length;
055    String[] a = new String[len];
056
057    System.arraycopy(l, 0, a, 0, l.length);
058    System.arraycopy(r, 0, a, l.length, r.length);
059
060    return a;
061  }
062
063  public
064  static
065  String convertSpecialChars(String s) {
066    char c;
067    int len = s.length();
068    StringBuffer sbuf = new StringBuffer(len);
069
070    int i = 0;
071    while(i < len) {
072      c = s.charAt(i++);
073      if (c == '\\') {
074        c =  s.charAt(i++);
075        if(c == 'n')      c = '\n';
076        else if(c == 'r') c = '\r';
077        else if(c == 't') c = '\t';
078        else if(c == 'f') c = '\f';
079        else if(c == '\b') c = '\b';
080        else if(c == '\"') c = '\"';
081        else if(c == '\'') c = '\'';
082        else if(c == '\\') c = '\\';
083      }
084      sbuf.append(c);
085    }
086    return sbuf.toString();
087  }
088
089
090  /**
091     Very similar to <code>System.getProperty</code> except
092     that the {@link SecurityException} is hidden.
093
094     @param key The key to search for.
095     @param def The default value to return.
096     @return the string value of the system property, or the default
097     value if there is no property with that key.
098
099     @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
460static
461public
462void selectAndConfigure(InputStream inputStream, String clazz, LoggerRepository hierarchy) {
463Configurator configurator = null;
464
465if(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
478configurator.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}