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.xml;
    19
    20  import org.apache.log4j.Appender;
    21  import org.apache.log4j.Layout;
    22  import org.apache.log4j.Level;
    23  import org.apache.log4j.LogManager;
    24  import org.apache.log4j.Logger;
    25  import org.apache.log4j.config.PropertySetter;
    26  import org.apache.log4j.helpers.FileWatchdog;
    27  import org.apache.log4j.helpers.Loader;
    28  import org.apache.log4j.helpers.LogLog;
    29  import org.apache.log4j.helpers.OptionConverter;
    30  import org.apache.log4j.or.RendererMap;
    31  import org.apache.log4j.spi.AppenderAttachable;
    32  import org.apache.log4j.spi.Configurator;
    33  import org.apache.log4j.spi.ErrorHandler;
    34  import org.apache.log4j.spi.Filter;
    35  import org.apache.log4j.spi.LoggerFactory;
    36  import org.apache.log4j.spi.LoggerRepository;
    37  import org.apache.log4j.spi.RendererSupport;
    38  import org.apache.log4j.spi.ThrowableRenderer;
    39  import org.apache.log4j.spi.ThrowableRendererSupport;
    40  import org.w3c.dom.Document;
    41  import org.w3c.dom.Element;
    42  import org.w3c.dom.NamedNodeMap;
    43  import org.w3c.dom.Node;
    44  import org.w3c.dom.NodeList;
    45  import org.xml.sax.InputSource;
    46  import org.xml.sax.SAXException;
    47
    48  import javax.xml.parsers.DocumentBuilder;
    49  import javax.xml.parsers.DocumentBuilderFactory;
    50  import javax.xml.parsers.FactoryConfigurationError;
    51  import java.io.File;
    52  import java.io.IOException;
    53  import java.io.InputStream;
    54  import java.io.InterruptedIOException;
    55  import java.io.Reader;
    56  import java.lang.reflect.Method;
    57  import java.lang.reflect.InvocationTargetException;
    58  import java.net.URL;
    59  import java.net.URLConnection;
    60  import java.util.Hashtable;
    61  import java.util.Properties;
    62
    63  // Contributors:   Mark Womack
    64  //                 Arun Katkere
    65
    66  /**
    67     Use this class to initialize the log4j environment using a DOM tree.
    68
    69     <p>The DTD is specified in <a
    70     href="doc-files/log4j.dtd"><b>log4j.dtd</b></a>.
    71
    72     <p>Sometimes it is useful to see how log4j is reading configuration
    73     files. You can enable log4j internal logging by defining the
    74     <b>log4j.debug</b> variable on the java command
    75     line. Alternatively, set the <code>debug</code> attribute in the
    76     <code>log4j:configuration</code> element. As in
    77  <pre>
    78     &lt;log4j:configuration <b>debug="true"</b> xmlns:log4j="http://jakarta.apache.org/log4j/">
    79     ...
    80     &lt;/log4j:configuration>
    81  </pre>
    82
    83     <p>There are sample XML files included in the package.
    84
    85     @author Christopher Taylor
    86     @author Ceki G&uuml;lc&uuml;
    87     @author Anders Kristensen
    88
    89     @since 0.8.3 */
    90  public class DOMConfigurator implements Configurator {
    91
    92    static final String CONFIGURATION_TAG = "log4j:configuration";
    93    static final String OLD_CONFIGURATION_TAG = "configuration";
    94    static final String RENDERER_TAG      = "renderer";
    95    private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
    96    static final String APPENDER_TAG 	= "appender";
    97    static final String APPENDER_REF_TAG 	= "appender-ref";
    98    static final String PARAM_TAG    	= "param";
    99    static final String LAYOUT_TAG	= "layout";
    100   static final String CATEGORY		= "category";
    101   static final String LOGGER		= "logger";
    102   static final String LOGGER_REF	= "logger-ref";
    103   static final String CATEGORY_FACTORY_TAG  = "categoryFactory";
    104   static final String LOGGER_FACTORY_TAG  = "loggerFactory";
    105   static final String NAME_ATTR		= "name";
    106   static final String CLASS_ATTR        = "class";
    107   static final String VALUE_ATTR	= "value";
    108   static final String ROOT_TAG		= "root";
    109   static final String ROOT_REF		= "root-ref";
    110   static final String LEVEL_TAG	        = "level";
    111   static final String PRIORITY_TAG      = "priority";
    112   static final String FILTER_TAG	= "filter";
    113   static final String ERROR_HANDLER_TAG	= "errorHandler";
    114   static final String REF_ATTR		= "ref";
    115   static final String ADDITIVITY_ATTR    = "additivity";
    116   static final String THRESHOLD_ATTR       = "threshold";
    117   static final String CONFIG_DEBUG_ATTR  = "configDebug";
    118   static final String INTERNAL_DEBUG_ATTR  = "debug";
    119   private static final String RESET_ATTR  = "reset";
    120   static final String RENDERING_CLASS_ATTR = "renderingClass";
    121   static final String RENDERED_CLASS_ATTR = "renderedClass";
    122
    123   static final String EMPTY_STR = "";
    124   static final Class[] ONE_STRING_PARAM = new Class[] {String.class};
    125
    126   final static String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
    127
    128
    129   // key: appenderName, value: appender
    130   Hashtable appenderBag;
    131
    132   Properties props;
    133   LoggerRepository repository;
    134
    135   protected LoggerFactory catFactory = null;
    136
    137   /**
    138      No argument constructor.
    139   */
    140   public
    141   DOMConfigurator () {
    142     appenderBag = new Hashtable();
    143   }
    144
    145   /**
    146      Used internally to parse appenders by IDREF name.
    147   */
    148   protected
    149   Appender findAppenderByName(Document doc, String appenderName)  {
    150     Appender appender = (Appender) appenderBag.get(appenderName);
    151
    152     if(appender != null) {
    153       return appender;
    154     } else {
    155       // Doesn't work on DOM Level 1 :
    156       // Element element = doc.getElementById(appenderName);
    157
    158       // Endre's hack:
    159       Element element = null;
    160       NodeList list = doc.getElementsByTagName("appender");
    161       for (int t=0; t < list.getLength(); t++) {
    162 	Node node = list.item(t);
    163 	NamedNodeMap map= node.getAttributes();
    164 	Node attrNode = map.getNamedItem("name");
    165 	if (appenderName.equals(attrNode.getNodeValue())) {
    166 	  element = (Element) node;
    167 	  break;
    168 	}
    169       }
    170       // Hack finished.
    171
    172       if(element == null) {
    173 	LogLog.error("No appender named ["+appenderName+"] could be found.");
    174 	return null;
    175       } else {
    176 	      appender = parseAppender(element);
    177           if (appender != null) {
    178             appenderBag.put(appenderName, appender);
    179           }
    180     return appender;
    181       }
    182     }
    183   }
    184   /**
    185      Used internally to parse appenders by IDREF element.
    186    */
    187   protected
    188   Appender findAppenderByReference(Element appenderRef) {
    189     String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
    190     Document doc = appenderRef.getOwnerDocument();
    191     return findAppenderByName(doc, appenderName);
    192   }
    193
    194     /**
    195      * Delegates unrecognized content to created instance if
    196      * it supports UnrecognizedElementParser.
    197      * @since 1.2.15
    198      * @param instance instance, may be null.
    199      * @param element element, may not be null.
    200      * @param props properties
    201      * @throws IOException thrown if configuration of owner object
    202      * should be abandoned.
    203      */
    204   private static void parseUnrecognizedElement(final Object instance,
    205                                         final Element element,
    206                                         final Properties props) throws Exception {
    207       boolean recognized = false;
    208       if (instance instanceof UnrecognizedElementHandler) {
    209           recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(
    210                   element, props);
    211       }
    212       if (!recognized) {
    213           LogLog.warn("Unrecognized element " + element.getNodeName());
    214       }
    215   }
    216
    217     /**
    218       * Delegates unrecognized content to created instance if
    219       * it supports UnrecognizedElementParser and catches and
    220      *  logs any exception.
    221       * @since 1.2.15
    222       * @param instance instance, may be null.
    223       * @param element element, may not be null.
    224       * @param props properties
    225       */
    226    private static void quietParseUnrecognizedElement(final Object instance,
    227                                           final Element element,
    228                                           final Properties props) {
    229       try {
    230           parseUnrecognizedElement(instance, element, props);
    231       } catch (Exception ex) {
    232           if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
    233               Thread.currentThread().interrupt();
    234           }
    235           LogLog.error("Error in extension content: ", ex);
    236       }
    237   }
    238
    239   /**
    240      Used internally to parse an appender element.
    241    */
    242   protected
    243   Appender parseAppender (Element appenderElement) {
    244     String className = subst(appenderElement.getAttribute(CLASS_ATTR));
    245     LogLog.debug("Class name: [" + className+']');
    246     try {
    247       Object instance 	= Loader.loadClass(className).newInstance();
    248       Appender appender	= (Appender)instance;
    249       PropertySetter propSetter = new PropertySetter(appender);
    250
    251       appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
    252
    253       NodeList children	= appenderElement.getChildNodes();
    254       final int length 	= children.getLength();
    255
    256       for (int loop = 0; loop < length; loop++) {
    257 	Node currentNode = children.item(loop);
    258
    259 	/* We're only interested in Elements */
    260 	if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    261 	  Element currentElement = (Element)currentNode;
    262
    263 	  // Parse appender parameters
    264 	  if (currentElement.getTagName().equals(PARAM_TAG)) {
    265             setParameter(currentElement, propSetter);
    266 	  }
    267 	  // Set appender layout
    268 	  else if (currentElement.getTagName().equals(LAYOUT_TAG)) {
    269 	    appender.setLayout(parseLayout(currentElement));
    270 	  }
    271 	  // Add filters
    272 	  else if (currentElement.getTagName().equals(FILTER_TAG)) {
    273 	    parseFilters(currentElement, appender);
    274 	  }
    275 	  else if (currentElement.getTagName().equals(ERROR_HANDLER_TAG)) {
    276 	    parseErrorHandler(currentElement, appender);
    277 	  }
    278 	  else if (currentElement.getTagName().equals(APPENDER_REF_TAG)) {
    279 	    String refName = subst(currentElement.getAttribute(REF_ATTR));
    280 	    if(appender instanceof AppenderAttachable) {
    281 	      AppenderAttachable aa = (AppenderAttachable) appender;
    282 	      LogLog.debug("Attaching appender named ["+ refName+
    283 			   "] to appender named ["+ appender.getName()+"].");
    284 	      aa.addAppender(findAppenderByReference(currentElement));
    285 	    } else {
    286 	      LogLog.error("Requesting attachment of appender named ["+
    287 			   refName+ "] to appender named ["+ appender.getName()+
    288                 "] which does not implement org.apache.log4j.spi.AppenderAttachable.");
    289 	    }
    290 	  } else {
    291           parseUnrecognizedElement(instance, currentElement, props);
    292       }
    293 	}
    294       }
    295       propSetter.activate();
    296       return appender;
    297     }
    298     /* Yes, it's ugly.  But all of these exceptions point to the same
    299        problem: we can't create an Appender */
    300     catch (Exception oops) {
    301         if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
    302             Thread.currentThread().interrupt();
    303         }
    304       LogLog.error("Could not create an Appender. Reported error follows.",
    305 		   oops);
    306       return null;
    307     }
    308   }
    309
    310   /**
    311      Used internally to parse an {@link ErrorHandler} element.
    312    */
    313   protected
    314   void parseErrorHandler(Element element, Appender appender) {
    315     ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
    316                                        subst(element.getAttribute(CLASS_ATTR)),
    317                                        org.apache.log4j.spi.ErrorHandler.class,
    318  				       null);
    319
    320     if(eh != null) {
    321       eh.setAppender(appender);
    322
    323       PropertySetter propSetter = new PropertySetter(eh);
    324       NodeList children = element.getChildNodes();
    325       final int length 	= children.getLength();
    326
    327       for (int loop = 0; loop < length; loop++) {
    328 	Node currentNode = children.item(loop);
    329 	if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    330 	  Element currentElement = (Element) currentNode;
    331 	  String tagName = currentElement.getTagName();
    332 	  if(tagName.equals(PARAM_TAG)) {
    333             setParameter(currentElement, propSetter);
    334 	  } else if(tagName.equals(APPENDER_REF_TAG)) {
    335 	    eh.setBackupAppender(findAppenderByReference(currentElement));
    336 	  } else if(tagName.equals(LOGGER_REF)) {
    337 	    String loggerName = currentElement.getAttribute(REF_ATTR);
    338 	    Logger logger = (catFactory == null) ? repository.getLogger(loggerName)
    339                 : repository.getLogger(loggerName, catFactory);
    340 	    eh.setLogger(logger);
    341 	  } else if(tagName.equals(ROOT_REF)) {
    342 	    Logger root = repository.getRootLogger();
    343 	    eh.setLogger(root);
    344 	  } else {
    345           quietParseUnrecognizedElement(eh, currentElement, props);
    346       }
    347 	}
    348       }
    349       propSetter.activate();
    350       appender.setErrorHandler(eh);
    351     }
    352   }
    353
    354   /**
    355      Used internally to parse a filter element.
    356    */
    357   protected
    358   void parseFilters(Element element, Appender appender) {
    359     String clazz = subst(element.getAttribute(CLASS_ATTR));
    360     Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz,
    361                                                 Filter.class, null);
    362
    363     if(filter != null) {
    364       PropertySetter propSetter = new PropertySetter(filter);
    365       NodeList children = element.getChildNodes();
    366       final int length 	= children.getLength();
    367
    368       for (int loop = 0; loop < length; loop++) {
    369 	Node currentNode = children.item(loop);
    370 	if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    371 	  Element currentElement = (Element) currentNode;
    372 	  String tagName = currentElement.getTagName();
    373 	  if(tagName.equals(PARAM_TAG)) {
    374             setParameter(currentElement, propSetter);
    375 	  } else {
    376             quietParseUnrecognizedElement(filter, currentElement, props);
    377       }
    378 	}
    379       }
    380       propSetter.activate();
    381       LogLog.debug("Adding filter of type ["+filter.getClass()
    382 		   +"] to appender named ["+appender.getName()+"].");
    383       appender.addFilter(filter);
    384     }
    385   }
    386
    387   /**
    388      Used internally to parse an category element.
    389   */
    390   protected
    391   void parseCategory (Element loggerElement) {
    392     // Create a new org.apache.log4j.Category object from the <category> element.
    393     String catName = subst(loggerElement.getAttribute(NAME_ATTR));
    394
    395     Logger cat;
    396
    397     String className = subst(loggerElement.getAttribute(CLASS_ATTR));
    398
    399
    400     if(EMPTY_STR.equals(className)) {
    401       LogLog.debug("Retreiving an instance of org.apache.log4j.Logger.");
    402       cat = (catFactory == null) ? repository.getLogger(catName) : repository.getLogger(catName, catFactory);
    403     }
    404     else {
    405       LogLog.debug("Desired logger sub-class: ["+className+']');
    406        try {
    407 	 Class clazz = Loader.loadClass(className);
    408 	 Method getInstanceMethod = clazz.getMethod("getLogger",
    409 						    ONE_STRING_PARAM);
    410 	 cat = (Logger) getInstanceMethod.invoke(null, new Object[] {catName});
    411        } catch (InvocationTargetException oops) {
    412           if (oops.getTargetException() instanceof InterruptedException
    413                   || oops.getTargetException() instanceof InterruptedIOException) {
    414               Thread.currentThread().interrupt();
    415           }
    416           LogLog.error("Could not retrieve category ["+catName+
    417 		      "]. Reported error follows.", oops);
    418 	      return;
    419        } catch (Exception oops) {
    420 	      LogLog.error("Could not retrieve category ["+catName+
    421 		      "]. Reported error follows.", oops);
    422 	      return;
    423        }
    424     }
    425
    426     // Setting up a category needs to be an atomic operation, in order
    427     // to protect potential log operations while category
    428     // configuration is in progress.
    429     synchronized(cat) {
    430       boolean additivity = OptionConverter.toBoolean(
    431                            subst(loggerElement.getAttribute(ADDITIVITY_ATTR)),
    432 			   true);
    433
    434       LogLog.debug("Setting ["+cat.getName()+"] additivity to ["+additivity+"].");
    435       cat.setAdditivity(additivity);
    436       parseChildrenOfLoggerElement(loggerElement, cat, false);
    437     }
    438   }
    439
    440
    441   /**
    442      Used internally to parse the category factory element.
    443   */
    444   protected
    445   void parseCategoryFactory(Element factoryElement) {
    446     String className = subst(factoryElement.getAttribute(CLASS_ATTR));
    447
    448     if(EMPTY_STR.equals(className)) {
    449       LogLog.error("Category Factory tag " + CLASS_ATTR + " attribute not found.");
    450       LogLog.debug("No Category Factory configured.");
    451     }
    452     else {
    453       LogLog.debug("Desired category factory: ["+className+']');
    454       Object factory = OptionConverter.instantiateByClassName(className,
    455                                                                  LoggerFactory.class,
    456                                                                  null);
    457       if (factory instanceof LoggerFactory) {
    458           catFactory = (LoggerFactory) factory;
    459       } else {
    460           LogLog.error("Category Factory class " + className + " does not implement org.apache.log4j.LoggerFactory");
    461       }
    462       PropertySetter propSetter = new PropertySetter(factory);
    463
    464       Element  currentElement = null;
    465       Node     currentNode    = null;
    466       NodeList children       = factoryElement.getChildNodes();
    467       final int length        = children.getLength();
    468
    469       for (int loop=0; loop < length; loop++) {
    470         currentNode = children.item(loop);
    471 	if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    472 	  currentElement = (Element)currentNode;
    473 	  if (currentElement.getTagName().equals(PARAM_TAG)) {
    474 	    setParameter(currentElement, propSetter);
    475 	  } else {
    476            quietParseUnrecognizedElement(factory, currentElement, props);
    477       }
    478 	}
    479       }
    480     }
    481   }
    482
    483
    484   /**
    485      Used internally to parse the roor category element.
    486   */
    487   protected
    488   void parseRoot (Element rootElement) {
    489     Logger root = repository.getRootLogger();
    490     // category configuration needs to be atomic
    491     synchronized(root) {
    492       parseChildrenOfLoggerElement(rootElement, root, true);
    493     }
    494   }
    495
    496
    497   /**
    498      Used internally to parse the children of a category element.
    499   */
    500   protected
    501   void parseChildrenOfLoggerElement(Element catElement,
    502 				      Logger cat, boolean isRoot) {
    503
    504     PropertySetter propSetter = new PropertySetter(cat);
    505
    506     // Remove all existing appenders from cat. They will be
    507     // reconstructed if need be.
    508     cat.removeAllAppenders();
    509
    510
    511     NodeList children 	= catElement.getChildNodes();
    512     final int length 	= children.getLength();
    513
    514     for (int loop = 0; loop < length; loop++) {
    515       Node currentNode = children.item(loop);
    516
    517       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    518 	Element currentElement = (Element) currentNode;
    519 	String tagName = currentElement.getTagName();
    520
    521 	if (tagName.equals(APPENDER_REF_TAG)) {
    522 	  Element appenderRef = (Element) currentNode;
    523 	  Appender appender = findAppenderByReference(appenderRef);
    524 	  String refName =  subst(appenderRef.getAttribute(REF_ATTR));
    525 	  if(appender != null)
    526 	    LogLog.debug("Adding appender named ["+ refName+
    527 			 "] to category ["+cat.getName()+"].");
    528 	  else
    529 	    LogLog.debug("Appender named ["+ refName + "] not found.");
    530
    531 	  cat.addAppender(appender);
    532
    533 	} else if(tagName.equals(LEVEL_TAG)) {
    534 	  parseLevel(currentElement, cat, isRoot);
    535 	} else if(tagName.equals(PRIORITY_TAG)) {
    536 	  parseLevel(currentElement, cat, isRoot);
    537 	} else if(tagName.equals(PARAM_TAG)) {
    538           setParameter(currentElement, propSetter);
    539 	} else {
    540         quietParseUnrecognizedElement(cat, currentElement, props);
    541     }
    542       }
    543     }
    544     propSetter.activate();
    545   }
    546
    547   /**
    548      Used internally to parse a layout element.
    549   */
    550   protected
    551   Layout parseLayout (Element layout_element) {
    552     String className = subst(layout_element.getAttribute(CLASS_ATTR));
    553     LogLog.debug("Parsing layout of class: \""+className+"\"");
    554     try {
    555       Object instance 	= Loader.loadClass(className).newInstance();
    556       Layout layout   	= (Layout)instance;
    557       PropertySetter propSetter = new PropertySetter(layout);
    558
    559       NodeList params 	= layout_element.getChildNodes();
    560       final int length 	= params.getLength();
    561
    562       for (int loop = 0; loop < length; loop++) {
    563 	Node currentNode = (Node)params.item(loop);
    564 	if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    565 	  Element currentElement = (Element) currentNode;
    566 	  String tagName = currentElement.getTagName();
    567 	  if(tagName.equals(PARAM_TAG)) {
    568             setParameter(currentElement, propSetter);
    569 	  } else {
    570           parseUnrecognizedElement(instance, currentElement, props);
    571       }
    572 	}
    573       }
    574
    575       propSetter.activate();
    576       return layout;
    577     }
    578     catch (Exception oops) {
    579         if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
    580             Thread.currentThread().interrupt();
    581         }
    582       LogLog.error("Could not create the Layout. Reported error follows.",
    583 		   oops);
    584       return null;
    585     }
    586   }
    587
    588   protected
    589   void parseRenderer(Element element) {
    590     String renderingClass = subst(element.getAttribute(RENDERING_CLASS_ATTR));
    591     String renderedClass = subst(element.getAttribute(RENDERED_CLASS_ATTR));
    592     if(repository instanceof RendererSupport) {
    593       RendererMap.addRenderer((RendererSupport) repository, renderedClass,
    594 			      renderingClass);
    595     }
    596   }
    597
    598     /**
    599      * Parses throwable renderer.
    600      * @param element throwableRenderer element.
    601      * @return configured throwable renderer.
    602      * @since 1.2.16.
    603      */
    604     protected ThrowableRenderer parseThrowableRenderer(final Element element) {
    605         String className = subst(element.getAttribute(CLASS_ATTR));
    606         LogLog.debug("Parsing throwableRenderer of class: \""+className+"\"");
    607         try {
    608           Object instance 	= Loader.loadClass(className).newInstance();
    609           ThrowableRenderer tr   	= (ThrowableRenderer)instance;
    610           PropertySetter propSetter = new PropertySetter(tr);
    611
    612           NodeList params 	= element.getChildNodes();
    613           final int length 	= params.getLength();
    614
    615           for (int loop = 0; loop < length; loop++) {
    616                 Node currentNode = (Node)params.item(loop);
    617                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    618                     Element currentElement = (Element) currentNode;
    619                     String tagName = currentElement.getTagName();
    620                     if(tagName.equals(PARAM_TAG)) {
    621                         setParameter(currentElement, propSetter);
    622                     } else {
    623                         parseUnrecognizedElement(instance, currentElement, props);
    624                     }
    625                 }
    626           }
    627
    628           propSetter.activate();
    629           return tr;
    630         }
    631         catch (Exception oops) {
    632             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
    633                 Thread.currentThread().interrupt();
    634             }
    635             LogLog.error("Could not create the ThrowableRenderer. Reported error follows.",
    636                oops);
    637           return null;
    638         }
    639     }
    640
    641   /**
    642      Used internally to parse a level  element.
    643   */
    644   protected
    645   void parseLevel(Element element, Logger logger, boolean isRoot) {
    646     String catName = logger.getName();
    647     if(isRoot) {
    648       catName = "root";
    649     }
    650
    651     String priStr = subst(element.getAttribute(VALUE_ATTR));
    652     LogLog.debug("Level value for "+catName+" is  ["+priStr+"].");
    653
    654     if(INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
    655       if(isRoot) {
    656 	LogLog.error("Root level cannot be inherited. Ignoring directive.");
    657       } else {
    658 	logger.setLevel(null);
    659       }
    660     } else {
    661       String className = subst(element.getAttribute(CLASS_ATTR));
    662       if(EMPTY_STR.equals(className)) {
    663 	logger.setLevel(OptionConverter.toLevel(priStr, Level.DEBUG));
    664       } else {
    665 	LogLog.debug("Desired Level sub-class: ["+className+']');
    666 	try {
    667 	  Class clazz = Loader.loadClass(className);
    668 	  Method toLevelMethod = clazz.getMethod("toLevel",
    669 						    ONE_STRING_PARAM);
    670 	  Level pri = (Level) toLevelMethod.invoke(null,
    671 						    new Object[] {priStr});
    672 	  logger.setLevel(pri);
    673 	} catch (Exception oops) {
    674         if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
    675             Thread.currentThread().interrupt();
    676         }
    677 	  LogLog.error("Could not create level ["+priStr+
    678 		       "]. Reported error follows.", oops);
    679 	  return;
    680 	}
    681       }
    682     }
    683     LogLog.debug(catName + " level set to " + logger.getLevel());
    684   }
    685
    686   protected
    687   void setParameter(Element elem, PropertySetter propSetter) {
    688       String name = subst(elem.getAttribute(NAME_ATTR));
    689       String value = (elem.getAttribute(VALUE_ATTR));
    690       value = subst(OptionConverter.convertSpecialChars(value));
    691       propSetter.setProperty(name, value);
    692   }
    693
    694
    695   /**
    696      Configure log4j using a <code>configuration</code> element as
    697      defined in the log4j.dtd.
    698
    699   */
    700   static
    701   public
    702   void configure (Element element) {
    703     DOMConfigurator configurator = new DOMConfigurator();
    704     configurator.doConfigure(element,  LogManager.getLoggerRepository());
    705   }
    706
    707  /**
    708      Like {@link #configureAndWatch(String, long)} except that the
    709      default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
    710      used.
    711
    712      @param configFilename A log4j configuration file in XML format.
    713
    714   */
    715   static
    716   public
    717   void configureAndWatch(String configFilename) {
    718     configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
    719   }
    720
    721   /**
    722      Read the configuration file <code>configFilename</code> if it
    723      exists. Moreover, a thread will be created that will periodically
    724      check if <code>configFilename</code> has been created or
    725      modified. The period is determined by the <code>delay</code>
    726      argument. If a change or file creation is detected, then
    727      <code>configFilename</code> is read to configure log4j.
    728
    729       @param configFilename A log4j configuration file in XML format.
    730       @param delay The delay in milliseconds to wait between each check.
    731   */
    732   static
    733   public
    734   void configureAndWatch(String configFilename, long delay) {
    735     XMLWatchdog xdog = new XMLWatchdog(configFilename);
    736     xdog.setDelay(delay);
    737     xdog.start();
    738   }
    739
    740   private interface ParseAction {
    741       Document parse(final DocumentBuilder parser) throws SAXException, IOException;
    742   }
    743
    744
    745   public
    746   void doConfigure(final String filename, LoggerRepository repository) {
    747     ParseAction action = new ParseAction() {
    748           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    749               return parser.parse(new File(filename));
    750           }
    751           public String toString() {
    752               return "file [" + filename + "]";
    753           }
    754     };
    755     doConfigure(action, repository);
    756   }
    757
    758
    759   public
    760   void doConfigure(final URL url, LoggerRepository repository) {
    761       ParseAction action = new ParseAction() {
    762           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    763               URLConnection uConn = url.openConnection();
    764               uConn.setUseCaches(false);
    765               InputSource src = new InputSource(uConn.getInputStream());
    766               src.setSystemId(url.toString());
    767               return parser.parse(src);
    768           }
    769           public String toString() {
    770               return "url [" + url.toString() + "]";
    771           }
    772       };
    773       doConfigure(action, repository);
    774   }
    775
    776   /**
    777      Configure log4j by reading in a log4j.dtd compliant XML
    778      configuration file.
    779
    780   */
    781   public
    782   void doConfigure(final InputStream inputStream, LoggerRepository repository)
    783                                           throws FactoryConfigurationError {
    784       ParseAction action = new ParseAction() {
    785           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    786               InputSource inputSource = new InputSource(inputStream);
    787               inputSource.setSystemId("dummy://log4j.dtd");
    788               return parser.parse(inputSource);
    789           }
    790           public String toString() {
    791               return "input stream [" + inputStream.toString() + "]";
    792           }
    793       };
    794       doConfigure(action, repository);
    795   }
    796
    797   /**
    798      Configure log4j by reading in a log4j.dtd compliant XML
    799      configuration file.
    800
    801   */
    802   public
    803   void doConfigure(final Reader reader, LoggerRepository repository)
    804                                           throws FactoryConfigurationError {
    805       ParseAction action = new ParseAction() {
    806           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    807               InputSource inputSource = new InputSource(reader);
    808               inputSource.setSystemId("dummy://log4j.dtd");
    809               return parser.parse(inputSource);
    810           }
    811           public String toString() {
    812               return "reader [" + reader.toString() + "]";
    813           }
    814       };
    815     doConfigure(action, repository);
    816   }
    817
    818   /**
    819      Configure log4j by reading in a log4j.dtd compliant XML
    820      configuration file.
    821
    822   */
    823   protected
    824   void doConfigure(final InputSource inputSource, LoggerRepository repository)
    825                                           throws FactoryConfigurationError {
    826       if (inputSource.getSystemId() == null) {
    827           inputSource.setSystemId("dummy://log4j.dtd");
    828       }
    829       ParseAction action = new ParseAction() {
    830           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    831               return parser.parse(inputSource);
    832           }
    833           public String toString() {
    834               return "input source [" + inputSource.toString() + "]";
    835           }
    836       };
    837       doConfigure(action, repository);
    838     }
    839
    840
    841   private final void doConfigure(final ParseAction action, final LoggerRepository repository)
    842          throws FactoryConfigurationError {
    843     DocumentBuilderFactory dbf = null;
    844     this.repository = repository;
    845     try {
    846       LogLog.debug("System property is :"+
    847   	                        OptionConverter.getSystemProperty(dbfKey,
    848 								  null));
    849       dbf = DocumentBuilderFactory.newInstance();
    850       LogLog.debug("Standard DocumentBuilderFactory search succeded.");
    851       LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName());
    852     } catch(FactoryConfigurationError fce) {
    853       Exception e = fce.getException();
    854       LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
    855       throw fce;
    856     }
    857
    858     try {
    859       dbf.setValidating(true);
    860
    861       DocumentBuilder docBuilder = dbf.newDocumentBuilder();
    862
    863       docBuilder.setErrorHandler(new SAXErrorHandler());
    864       docBuilder.setEntityResolver(new Log4jEntityResolver());
    865
    866       Document doc = action.parse(docBuilder);
    867       parse(doc.getDocumentElement());
    868     } catch (Exception e) {
    869         if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
    870             Thread.currentThread().interrupt();
    871         }
    872       // I know this is miserable...
    873       LogLog.error("Could not parse "+ action.toString() + ".", e);
    874     }
    875   }
    876
    877   /**
    878      Configure by taking in an DOM element.
    879   */
    880   public void doConfigure(Element element, LoggerRepository repository) {
    881     this.repository = repository;
    882     parse(element);
    883   }
    884
    885
    886   /**
    887      A static version of {@link #doConfigure(String, LoggerRepository)}.  */
    888   static
    889   public
    890   void configure(String filename) throws FactoryConfigurationError {
    891     new DOMConfigurator().doConfigure(filename,
    892 				      LogManager.getLoggerRepository());
    893   }
    894
    895   /**
    896      A static version of {@link #doConfigure(URL, LoggerRepository)}.
    897    */
    898   static
    899   public
    900   void configure(URL url) throws FactoryConfigurationError {
    901     new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository());
    902   }
    903
    904   /**
    905      Used internally to configure the log4j framework by parsing a DOM
    906      tree of XML elements based on <a
    907      href="doc-files/log4j.dtd">log4j.dtd</a>.
    908
    909   */
    910   protected
    911   void parse(Element element) {
    912
    913     String rootElementName = element.getTagName();
    914
    915     if (!rootElementName.equals(CONFIGURATION_TAG)) {
    916       if(rootElementName.equals(OLD_CONFIGURATION_TAG)) {
    917 	LogLog.warn("The <"+OLD_CONFIGURATION_TAG+
    918 		     "> element has been deprecated.");
    919 	LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead.");
    920       } else {
    921 	LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element.");
    922 	return;
    923       }
    924     }
    925
    926
    927     String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
    928
    929     LogLog.debug("debug attribute= \"" + debugAttrib +"\".");
    930     // if the log4j.dtd is not specified in the XML file, then the
    931     // "debug" attribute is returned as the empty string.
    932     if(!debugAttrib.equals("") && !debugAttrib.equals("null")) {
    933       LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
    934     } else {
    935       LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
    936     }
    937
    938       //
    939       //   reset repository before configuration if reset="true"
    940       //       on configuration element.
    941       //
    942     String resetAttrib = subst(element.getAttribute(RESET_ATTR));
    943     LogLog.debug("reset attribute= \"" + resetAttrib +"\".");
    944     if(!("".equals(resetAttrib))) {
    945          if (OptionConverter.toBoolean(resetAttrib, false)) {
    946              repository.resetConfiguration();
    947          }
    948     }
    949
    950
    951
    952     String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
    953     if(!confDebug.equals("") && !confDebug.equals("null")) {
    954       LogLog.warn("The \""+CONFIG_DEBUG_ATTR+"\" attribute is deprecated.");
    955       LogLog.warn("Use the \""+INTERNAL_DEBUG_ATTR+"\" attribute instead.");
    956       LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
    957     }
    958
    959     String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
    960     LogLog.debug("Threshold =\"" + thresholdStr +"\".");
    961     if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
    962       repository.setThreshold(thresholdStr);
    963     }
    964
    965     //Hashtable appenderBag = new Hashtable(11);
    966
    967     /* Building Appender objects, placing them in a local namespace
    968        for future reference */
    969
    970     // First configure each category factory under the root element.
    971     // Category factories need to be configured before any of
    972     // categories they support.
    973     //
    974     String   tagName = null;
    975     Element  currentElement = null;
    976     Node     currentNode = null;
    977     NodeList children = element.getChildNodes();
    978     final int length = children.getLength();
    979
    980     for (int loop = 0; loop < length; loop++) {
    981       currentNode = children.item(loop);
    982       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    983 	currentElement = (Element) currentNode;
    984 	tagName = currentElement.getTagName();
    985
    986 	if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
    987 	  parseCategoryFactory(currentElement);
    988 	}
    989       }
    990     }
    991
    992     for (int loop = 0; loop < length; loop++) {
    993       currentNode = children.item(loop);
    994       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    995 	currentElement = (Element) currentNode;
    996 	tagName = currentElement.getTagName();
    997
    998 	if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
    999 	  parseCategory(currentElement);
    1000 	} else if (tagName.equals(ROOT_TAG)) {
    1001 	  parseRoot(currentElement);
    1002 	} else if(tagName.equals(RENDERER_TAG)) {
    1003 	  parseRenderer(currentElement);
    1004     } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {
    1005         if (repository instanceof ThrowableRendererSupport) {
    1006             ThrowableRenderer tr = parseThrowableRenderer(currentElement);
    1007             if (tr != null) {
    1008                 ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
    1009             }
    1010         }
    1011     } else if (!(tagName.equals(APPENDER_TAG)
    1012             || tagName.equals(CATEGORY_FACTORY_TAG)
    1013             || tagName.equals(LOGGER_FACTORY_TAG))) {
    1014         quietParseUnrecognizedElement(repository, currentElement, props);
    1015     }
    1016       }
    1017     }
    1018   }
    1019
    1020
    1021   protected
    1022   String subst(final String value) {
    1023       return subst(value, props);
    1024   }
    1025
    1026     /**
    1027      * Substitutes property value for any references in expression.
    1028      *
    1029      * @param value value from configuration file, may contain
    1030      *              literal text, property references or both
    1031      * @param props properties.
    1032      * @return evaluated expression, may still contain expressions
    1033      *         if unable to expand.
    1034      * @since 1.2.15
    1035      */
    1036     public static String subst(final String value, final Properties props) {
    1037         try {
    1038             return OptionConverter.substVars(value, props);
    1039         } catch (IllegalArgumentException e) {
    1040             LogLog.warn("Could not perform variable substitution.", e);
    1041             return value;
    1042         }
    1043     }
    1044
    1045
    1046     /**
    1047      * Sets a parameter based from configuration file content.
    1048      *
    1049      * @param elem       param element, may not be null.
    1050      * @param propSetter property setter, may not be null.
    1051      * @param props      properties
    1052      * @since 1.2.15
    1053      */
    1054     public static void setParameter(final Element elem,
    1055                                     final PropertySetter propSetter,
    1056                                     final Properties props) {
    1057         String name = subst(elem.getAttribute("name"), props);
    1058         String value = (elem.getAttribute("value"));
    1059         value = subst(OptionConverter.convertSpecialChars(value), props);
    1060         propSetter.setProperty(name, value);
    1061     }
    1062
    1063     /**
    1064      * Creates an object and processes any nested param elements
    1065      * but does not call activateOptions.  If the class also supports
    1066      * UnrecognizedElementParser, the parseUnrecognizedElement method
    1067      * will be call for any child elements other than param.
    1068      *
    1069      * @param element       element, may not be null.
    1070      * @param props         properties
    1071      * @param expectedClass interface or class expected to be implemented
    1072      *                      by created class
    1073      * @return created class or null.
    1074      * @throws Exception thrown if the contain object should be abandoned.
    1075      * @since 1.2.15
    1076      */
    1077     public static Object parseElement(final Element element,
    1078                                              final Properties props,
    1079                                              final Class expectedClass) throws Exception {
    1080         String clazz = subst(element.getAttribute("class"), props);
    1081         Object instance = OptionConverter.instantiateByClassName(clazz,
    1082                 expectedClass, null);
    1083
    1084         if (instance != null) {
    1085             PropertySetter propSetter = new PropertySetter(instance);
    1086             NodeList children = element.getChildNodes();
    1087             final int length = children.getLength();
    1088
    1089             for (int loop = 0; loop < length; loop++) {
    1090                 Node currentNode = children.item(loop);
    1091                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    1092                     Element currentElement = (Element) currentNode;
    1093                     String tagName = currentElement.getTagName();
    1094                     if (tagName.equals("param")) {
    1095                         setParameter(currentElement, propSetter, props);
    1096                     } else {
    1097                          parseUnrecognizedElement(instance, currentElement, props);
    1098                     }
    1099                 }
    1100             }
    1101             return instance;
    1102         }
    1103         return null;
    1104     }
    1105
    1106 }
    1107
    1108
    1109 class XMLWatchdog extends FileWatchdog {
    1110
    1111     XMLWatchdog(String filename) {
    1112     super(filename);
    1113   }
    1114
    1115   /**
    1116      Call {@link DOMConfigurator#configure(String)} with the
    1117      <code>filename</code> to reconfigure log4j. */
    1118   public
    1119   void doOnChange() {
    1120     new DOMConfigurator().doConfigure(filename,
    1121 				      LogManager.getLoggerRepository());
    1122   }
    1123 }