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