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