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