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 }