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