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