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 }