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               InputStream stream = uConn.getInputStream();
    766               try {
    767                 InputSource src = new InputSource(stream);
    768                 src.setSystemId(url.toString());
    769                 return parser.parse(src);
    770               } finally {
    771                 stream.close();
    772               }
    773           }
    774           public String toString() {
    775               return "url [" + url.toString() + "]";
    776           }
    777       };
    778       doConfigure(action, repository);
    779   }
    780
    781   /**
    782      Configure log4j by reading in a log4j.dtd compliant XML
    783      configuration file.
    784
    785   */
    786   public
    787   void doConfigure(final InputStream inputStream, LoggerRepository repository)
    788                                           throws FactoryConfigurationError {
    789       ParseAction action = new ParseAction() {
    790           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    791               InputSource inputSource = new InputSource(inputStream);
    792               inputSource.setSystemId("dummy://log4j.dtd");
    793               return parser.parse(inputSource);
    794           }
    795           public String toString() {
    796               return "input stream [" + inputStream.toString() + "]";
    797           }
    798       };
    799       doConfigure(action, repository);
    800   }
    801
    802   /**
    803      Configure log4j by reading in a log4j.dtd compliant XML
    804      configuration file.
    805
    806   */
    807   public
    808   void doConfigure(final Reader reader, LoggerRepository repository)
    809                                           throws FactoryConfigurationError {
    810       ParseAction action = new ParseAction() {
    811           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    812               InputSource inputSource = new InputSource(reader);
    813               inputSource.setSystemId("dummy://log4j.dtd");
    814               return parser.parse(inputSource);
    815           }
    816           public String toString() {
    817               return "reader [" + reader.toString() + "]";
    818           }
    819       };
    820     doConfigure(action, repository);
    821   }
    822
    823   /**
    824      Configure log4j by reading in a log4j.dtd compliant XML
    825      configuration file.
    826
    827   */
    828   protected
    829   void doConfigure(final InputSource inputSource, LoggerRepository repository)
    830                                           throws FactoryConfigurationError {
    831       if (inputSource.getSystemId() == null) {
    832           inputSource.setSystemId("dummy://log4j.dtd");
    833       }
    834       ParseAction action = new ParseAction() {
    835           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
    836               return parser.parse(inputSource);
    837           }
    838           public String toString() {
    839               return "input source [" + inputSource.toString() + "]";
    840           }
    841       };
    842       doConfigure(action, repository);
    843     }
    844
    845
    846   private final void doConfigure(final ParseAction action, final LoggerRepository repository)
    847          throws FactoryConfigurationError {
    848     DocumentBuilderFactory dbf = null;
    849     this.repository = repository;
    850     try {
    851       LogLog.debug("System property is :"+
    852   	                        OptionConverter.getSystemProperty(dbfKey,
    853 								  null));
    854       dbf = DocumentBuilderFactory.newInstance();
    855       LogLog.debug("Standard DocumentBuilderFactory search succeded.");
    856       LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName());
    857     } catch(FactoryConfigurationError fce) {
    858       Exception e = fce.getException();
    859       LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
    860       throw fce;
    861     }
    862
    863     try {
    864       dbf.setValidating(true);
    865
    866       DocumentBuilder docBuilder = dbf.newDocumentBuilder();
    867
    868       docBuilder.setErrorHandler(new SAXErrorHandler());
    869       docBuilder.setEntityResolver(new Log4jEntityResolver());
    870
    871       Document doc = action.parse(docBuilder);
    872       parse(doc.getDocumentElement());
    873     } catch (Exception e) {
    874         if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
    875             Thread.currentThread().interrupt();
    876         }
    877       // I know this is miserable...
    878       LogLog.error("Could not parse "+ action.toString() + ".", e);
    879     }
    880   }
    881
    882   /**
    883      Configure by taking in an DOM element.
    884   */
    885   public void doConfigure(Element element, LoggerRepository repository) {
    886     this.repository = repository;
    887     parse(element);
    888   }
    889
    890
    891   /**
    892      A static version of {@link #doConfigure(String, LoggerRepository)}.  */
    893   static
    894   public
    895   void configure(String filename) throws FactoryConfigurationError {
    896     new DOMConfigurator().doConfigure(filename,
    897 				      LogManager.getLoggerRepository());
    898   }
    899
    900   /**
    901      A static version of {@link #doConfigure(URL, LoggerRepository)}.
    902    */
    903   static
    904   public
    905   void configure(URL url) throws FactoryConfigurationError {
    906     new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository());
    907   }
    908
    909   /**
    910      Used internally to configure the log4j framework by parsing a DOM
    911      tree of XML elements based on <a
    912      href="doc-files/log4j.dtd">log4j.dtd</a>.
    913
    914   */
    915   protected
    916   void parse(Element element) {
    917
    918     String rootElementName = element.getTagName();
    919
    920     if (!rootElementName.equals(CONFIGURATION_TAG)) {
    921       if(rootElementName.equals(OLD_CONFIGURATION_TAG)) {
    922 	LogLog.warn("The <"+OLD_CONFIGURATION_TAG+
    923 		     "> element has been deprecated.");
    924 	LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead.");
    925       } else {
    926 	LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element.");
    927 	return;
    928       }
    929     }
    930
    931
    932     String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
    933
    934     LogLog.debug("debug attribute= \"" + debugAttrib +"\".");
    935     // if the log4j.dtd is not specified in the XML file, then the
    936     // "debug" attribute is returned as the empty string.
    937     if(!debugAttrib.equals("") && !debugAttrib.equals("null")) {
    938       LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
    939     } else {
    940       LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
    941     }
    942
    943       //
    944       //   reset repository before configuration if reset="true"
    945       //       on configuration element.
    946       //
    947     String resetAttrib = subst(element.getAttribute(RESET_ATTR));
    948     LogLog.debug("reset attribute= \"" + resetAttrib +"\".");
    949     if(!("".equals(resetAttrib))) {
    950          if (OptionConverter.toBoolean(resetAttrib, false)) {
    951              repository.resetConfiguration();
    952          }
    953     }
    954
    955
    956
    957     String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
    958     if(!confDebug.equals("") && !confDebug.equals("null")) {
    959       LogLog.warn("The \""+CONFIG_DEBUG_ATTR+"\" attribute is deprecated.");
    960       LogLog.warn("Use the \""+INTERNAL_DEBUG_ATTR+"\" attribute instead.");
    961       LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
    962     }
    963
    964     String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
    965     LogLog.debug("Threshold =\"" + thresholdStr +"\".");
    966     if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
    967       repository.setThreshold(thresholdStr);
    968     }
    969
    970     //Hashtable appenderBag = new Hashtable(11);
    971
    972     /* Building Appender objects, placing them in a local namespace
    973        for future reference */
    974
    975     // First configure each category factory under the root element.
    976     // Category factories need to be configured before any of
    977     // categories they support.
    978     //
    979     String   tagName = null;
    980     Element  currentElement = null;
    981     Node     currentNode = null;
    982     NodeList children = element.getChildNodes();
    983     final int length = children.getLength();
    984
    985     for (int loop = 0; loop < length; loop++) {
    986       currentNode = children.item(loop);
    987       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    988 	currentElement = (Element) currentNode;
    989 	tagName = currentElement.getTagName();
    990
    991 	if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
    992 	  parseCategoryFactory(currentElement);
    993 	}
    994       }
    995     }
    996
    997     for (int loop = 0; loop < length; loop++) {
    998       currentNode = children.item(loop);
    999       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    1000 	currentElement = (Element) currentNode;
    1001 	tagName = currentElement.getTagName();
    1002
    1003 	if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
    1004 	  parseCategory(currentElement);
    1005 	} else if (tagName.equals(ROOT_TAG)) {
    1006 	  parseRoot(currentElement);
    1007 	} else if(tagName.equals(RENDERER_TAG)) {
    1008 	  parseRenderer(currentElement);
    1009     } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {
    1010         if (repository instanceof ThrowableRendererSupport) {
    1011             ThrowableRenderer tr = parseThrowableRenderer(currentElement);
    1012             if (tr != null) {
    1013                 ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
    1014             }
    1015         }
    1016     } else if (!(tagName.equals(APPENDER_TAG)
    1017             || tagName.equals(CATEGORY_FACTORY_TAG)
    1018             || tagName.equals(LOGGER_FACTORY_TAG))) {
    1019         quietParseUnrecognizedElement(repository, currentElement, props);
    1020     }
    1021       }
    1022     }
    1023   }
    1024
    1025
    1026   protected
    1027   String subst(final String value) {
    1028       return subst(value, props);
    1029   }
    1030
    1031     /**
    1032      * Substitutes property value for any references in expression.
    1033      *
    1034      * @param value value from configuration file, may contain
    1035      *              literal text, property references or both
    1036      * @param props properties.
    1037      * @return evaluated expression, may still contain expressions
    1038      *         if unable to expand.
    1039      * @since 1.2.15
    1040      */
    1041     public static String subst(final String value, final Properties props) {
    1042         try {
    1043             return OptionConverter.substVars(value, props);
    1044         } catch (IllegalArgumentException e) {
    1045             LogLog.warn("Could not perform variable substitution.", e);
    1046             return value;
    1047         }
    1048     }
    1049
    1050
    1051     /**
    1052      * Sets a parameter based from configuration file content.
    1053      *
    1054      * @param elem       param element, may not be null.
    1055      * @param propSetter property setter, may not be null.
    1056      * @param props      properties
    1057      * @since 1.2.15
    1058      */
    1059     public static void setParameter(final Element elem,
    1060                                     final PropertySetter propSetter,
    1061                                     final Properties props) {
    1062         String name = subst(elem.getAttribute("name"), props);
    1063         String value = (elem.getAttribute("value"));
    1064         value = subst(OptionConverter.convertSpecialChars(value), props);
    1065         propSetter.setProperty(name, value);
    1066     }
    1067
    1068     /**
    1069      * Creates an object and processes any nested param elements
    1070      * but does not call activateOptions.  If the class also supports
    1071      * UnrecognizedElementParser, the parseUnrecognizedElement method
    1072      * will be call for any child elements other than param.
    1073      *
    1074      * @param element       element, may not be null.
    1075      * @param props         properties
    1076      * @param expectedClass interface or class expected to be implemented
    1077      *                      by created class
    1078      * @return created class or null.
    1079      * @throws Exception thrown if the contain object should be abandoned.
    1080      * @since 1.2.15
    1081      */
    1082     public static Object parseElement(final Element element,
    1083                                              final Properties props,
    1084                                              final Class expectedClass) throws Exception {
    1085         String clazz = subst(element.getAttribute("class"), props);
    1086         Object instance = OptionConverter.instantiateByClassName(clazz,
    1087                 expectedClass, null);
    1088
    1089         if (instance != null) {
    1090             PropertySetter propSetter = new PropertySetter(instance);
    1091             NodeList children = element.getChildNodes();
    1092             final int length = children.getLength();
    1093
    1094             for (int loop = 0; loop < length; loop++) {
    1095                 Node currentNode = children.item(loop);
    1096                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
    1097                     Element currentElement = (Element) currentNode;
    1098                     String tagName = currentElement.getTagName();
    1099                     if (tagName.equals("param")) {
    1100                         setParameter(currentElement, propSetter, props);
    1101                     } else {
    1102                          parseUnrecognizedElement(instance, currentElement, props);
    1103                     }
    1104                 }
    1105             }
    1106             return instance;
    1107         }
    1108         return null;
    1109     }
    1110
    1111 }
    1112
    1113
    1114 class XMLWatchdog extends FileWatchdog {
    1115
    1116     XMLWatchdog(String filename) {
    1117     super(filename);
    1118   }
    1119
    1120   /**
    1121      Call {@link DOMConfigurator#configure(String)} with the
    1122      <code>filename</code> to reconfigure log4j. */
    1123   public
    1124   void doOnChange() {
    1125     new DOMConfigurator().doConfigure(filename,
    1126 				      LogManager.getLoggerRepository());
    1127   }
    1128 }