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&uuml;lc&uuml;
    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 }