d3b664468f05c2cef6f8f70c245d8b9b6d19b549
[jalview.git] / srcjar2 / org / apache / log4j / xml / DOMConfigurator.java
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             
532           cat.addAppender(appender);
533           
534         } else if(tagName.equals(LEVEL_TAG)) {
535           parseLevel(currentElement, cat, isRoot);      
536         } else if(tagName.equals(PRIORITY_TAG)) {
537           parseLevel(currentElement, cat, isRoot);
538         } else if(tagName.equals(PARAM_TAG)) {
539           setParameter(currentElement, propSetter);
540         } else {
541         quietParseUnrecognizedElement(cat, currentElement, props);
542     }
543       }
544     }
545     propSetter.activate();
546   }
547
548   /**
549      Used internally to parse a layout element.
550   */  
551   protected
552   Layout parseLayout (Element layout_element) {
553     String className = subst(layout_element.getAttribute(CLASS_ATTR));
554     LogLog.debug("Parsing layout of class: \""+className+"\"");          
555     try {
556       Object instance   = Loader.loadClass(className).newInstance();
557       Layout layout     = (Layout)instance;
558       PropertySetter propSetter = new PropertySetter(layout);
559       
560       NodeList params   = layout_element.getChildNodes();
561       final int length  = params.getLength();
562
563       for (int loop = 0; loop < length; loop++) {
564         Node currentNode = params.item(loop);
565         if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
566           Element currentElement = (Element) currentNode;
567           String tagName = currentElement.getTagName();
568           if(tagName.equals(PARAM_TAG)) {
569             setParameter(currentElement, propSetter);
570           } else {
571           parseUnrecognizedElement(instance, currentElement, props);
572       }
573         }
574       }
575       
576       propSetter.activate();
577       return layout;
578     }
579     catch (Exception oops) {
580         if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
581             Thread.currentThread().interrupt();
582         }
583       LogLog.error("Could not create the Layout. Reported error follows.",
584                    oops);
585       return null;
586     }
587   }
588
589   protected 
590   void parseRenderer(Element element) {
591     String renderingClass = subst(element.getAttribute(RENDERING_CLASS_ATTR));
592     String renderedClass = subst(element.getAttribute(RENDERED_CLASS_ATTR));
593     if(repository instanceof RendererSupport) {
594       RendererMap.addRenderer((RendererSupport) repository, renderedClass, 
595                               renderingClass);
596     }
597   }
598
599     /**
600      * Parses throwable renderer.
601      * @param element throwableRenderer element.
602      * @return configured throwable renderer.
603      * @since 1.2.16.
604      */
605     protected ThrowableRenderer parseThrowableRenderer(final Element element) {
606         String className = subst(element.getAttribute(CLASS_ATTR));
607         LogLog.debug("Parsing throwableRenderer of class: \""+className+"\"");
608         try {
609           Object instance       = Loader.loadClass(className).newInstance();
610           ThrowableRenderer tr          = (ThrowableRenderer)instance;
611           PropertySetter propSetter = new PropertySetter(tr);
612
613           NodeList params       = element.getChildNodes();
614           final int length      = params.getLength();
615
616           for (int loop = 0; loop < length; loop++) {
617                 Node currentNode = params.item(loop);
618                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
619                     Element currentElement = (Element) currentNode;
620                     String tagName = currentElement.getTagName();
621                     if(tagName.equals(PARAM_TAG)) {
622                         setParameter(currentElement, propSetter);
623                     } else {
624                         parseUnrecognizedElement(instance, currentElement, props);
625                     }
626                 }
627           }
628
629           propSetter.activate();
630           return tr;
631         }
632         catch (Exception oops) {
633             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
634                 Thread.currentThread().interrupt();
635             }
636             LogLog.error("Could not create the ThrowableRenderer. Reported error follows.",
637                oops);
638           return null;
639         }
640     }
641
642   /**
643      Used internally to parse a level  element.
644   */
645   protected
646   void parseLevel(Element element, Logger logger, boolean isRoot) {
647     String catName = logger.getName();
648     if(isRoot) {
649       catName = "root";
650     }
651
652     String priStr = subst(element.getAttribute(VALUE_ATTR));
653     LogLog.debug("Level value for "+catName+" is  ["+priStr+"].");
654     
655     if(INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
656       if(isRoot) {
657         LogLog.error("Root level cannot be inherited. Ignoring directive.");
658       } else {
659         logger.setLevel(null);
660       }
661     } else {
662       String className = subst(element.getAttribute(CLASS_ATTR));      
663       if(EMPTY_STR.equals(className)) { 
664         logger.setLevel(OptionConverter.toLevel(priStr, Level.DEBUG));
665       } else {
666         LogLog.debug("Desired Level sub-class: ["+className+']');
667         try {    
668           Class clazz = Loader.loadClass(className);
669           Method toLevelMethod = clazz.getMethod("toLevel", 
670                                                     ONE_STRING_PARAM);
671           Level pri = (Level) toLevelMethod.invoke(null, 
672                                                     new Object[] {priStr});
673           logger.setLevel(pri);
674         } catch (Exception oops) {
675         if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
676             Thread.currentThread().interrupt();
677         }
678           LogLog.error("Could not create level ["+priStr+
679                        "]. Reported error follows.", oops);
680           return;
681         }
682       }
683     }
684     LogLog.debug(catName + " level set to " + logger.getLevel());    
685   }
686
687   protected
688   void setParameter(Element elem, PropertySetter propSetter) {
689       String name = subst(elem.getAttribute(NAME_ATTR));
690       String value = (elem.getAttribute(VALUE_ATTR));
691       value = subst(OptionConverter.convertSpecialChars(value));
692       propSetter.setProperty(name, value);
693   }
694
695
696   /**
697      Configure log4j using a <code>configuration</code> element as
698      defined in the log4j.dtd. 
699
700   */
701   static
702   public
703   void configure (Element element) {
704     DOMConfigurator configurator = new DOMConfigurator();
705     configurator.doConfigure(element,  LogManager.getLoggerRepository());
706   }
707
708  /**
709      Like {@link #configureAndWatch(String, long)} except that the
710      default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
711      used. 
712
713      @param configFilename A log4j configuration file in XML format.
714
715   */
716   static
717   public
718   void configureAndWatch(String configFilename) {
719     configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
720   }
721
722   /**
723      Read the configuration file <code>configFilename</code> if it
724      exists. Moreover, a thread will be created that will periodically
725      check if <code>configFilename</code> has been created or
726      modified. The period is determined by the <code>delay</code>
727      argument. If a change or file creation is detected, then
728      <code>configFilename</code> is read to configure log4j.  
729
730       @param configFilename A log4j configuration file in XML format.
731       @param delay The delay in milliseconds to wait between each check.
732   */
733   static
734   public
735   void configureAndWatch(String configFilename, long delay) {
736     XMLWatchdog xdog = new XMLWatchdog(configFilename);
737     xdog.setDelay(delay);
738     xdog.start();
739   }
740   
741   private interface ParseAction {
742       Document parse(final DocumentBuilder parser) throws SAXException, IOException;
743   }
744
745
746   public
747   void doConfigure(final String filename, LoggerRepository repository) {
748     ParseAction action = new ParseAction() {
749           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
750               return parser.parse(new File(filename));
751           }
752           public String toString() { 
753               return "file [" + filename + "]"; 
754           }
755     };
756     doConfigure(action, repository);
757   }
758   
759
760   public
761   void doConfigure(final URL url, LoggerRepository repository) {
762       ParseAction action = new ParseAction() {
763           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
764               URLConnection uConn = url.openConnection();
765               uConn.setUseCaches(false);
766               InputStream stream = uConn.getInputStream();
767               try {
768                 InputSource src = new InputSource(stream);
769                 src.setSystemId(url.toString());
770                 return parser.parse(src);
771               } finally {
772                 stream.close();
773               }
774           }
775           public String toString() { 
776               return "url [" + url.toString() + "]"; 
777           }
778       };
779       doConfigure(action, repository);
780   }
781
782   /**
783      Configure log4j by reading in a log4j.dtd compliant XML
784      configuration file.
785
786   */
787   public
788   void doConfigure(final InputStream inputStream, LoggerRepository repository) 
789                                           throws FactoryConfigurationError {
790       ParseAction action = new ParseAction() {
791           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
792               InputSource inputSource = new InputSource(inputStream);
793               inputSource.setSystemId("dummy://log4j.dtd");
794               return parser.parse(inputSource);
795           }
796           public String toString() { 
797               return "input stream [" + inputStream.toString() + "]"; 
798           }
799       };
800       doConfigure(action, repository);
801   }
802
803   /**
804      Configure log4j by reading in a log4j.dtd compliant XML
805      configuration file.
806
807   */
808   public
809   void doConfigure(final Reader reader, LoggerRepository repository) 
810                                           throws FactoryConfigurationError {
811       ParseAction action = new ParseAction() {
812           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
813               InputSource inputSource = new InputSource(reader);
814               inputSource.setSystemId("dummy://log4j.dtd");
815               return parser.parse(inputSource);
816           }
817           public String toString() { 
818               return "reader [" + reader.toString() + "]"; 
819           }
820       };
821     doConfigure(action, repository);
822   }
823
824   /**
825      Configure log4j by reading in a log4j.dtd compliant XML
826      configuration file.
827
828   */
829   protected
830   void doConfigure(final InputSource inputSource, LoggerRepository repository) 
831                                           throws FactoryConfigurationError {
832       if (inputSource.getSystemId() == null) {
833           inputSource.setSystemId("dummy://log4j.dtd");
834       }
835       ParseAction action = new ParseAction() {
836           public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
837               return parser.parse(inputSource);
838           }
839           public String toString() { 
840               return "input source [" + inputSource.toString() + "]"; 
841           }
842       };
843       doConfigure(action, repository);
844     }
845     
846     
847   private final void doConfigure(final ParseAction action, final LoggerRepository repository)
848          throws FactoryConfigurationError {
849     DocumentBuilderFactory dbf = null;
850     this.repository = repository;
851     try { 
852       LogLog.debug("System property is :"+
853                                 OptionConverter.getSystemProperty(dbfKey, 
854                                                                   null)); 
855       dbf = DocumentBuilderFactory.newInstance();
856       LogLog.debug("Standard DocumentBuilderFactory search succeded.");
857       LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName());
858     } catch(FactoryConfigurationError fce) {
859       Exception e = fce.getException();
860       LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
861       throw fce;
862     }
863       
864     try {
865       dbf.setValidating(true);
866
867       DocumentBuilder docBuilder = dbf.newDocumentBuilder();
868
869       docBuilder.setErrorHandler(new SAXErrorHandler());      
870       docBuilder.setEntityResolver(new Log4jEntityResolver());
871          
872       Document doc = action.parse(docBuilder);     
873       parse(doc.getDocumentElement());
874     } catch (Exception e) {
875         if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
876             Thread.currentThread().interrupt();
877         }
878       // I know this is miserable...
879       LogLog.error("Could not parse "+ action.toString() + ".", e);
880     }
881   }
882
883   /**
884      Configure by taking in an DOM element. 
885   */
886   public void doConfigure(Element element, LoggerRepository repository) {
887     this.repository = repository;
888     parse(element);
889   }
890
891   
892   /**
893      A static version of {@link #doConfigure(String, LoggerRepository)}.  */
894   static
895   public
896   void configure(String filename) throws FactoryConfigurationError {
897     new DOMConfigurator().doConfigure(filename, 
898                                       LogManager.getLoggerRepository());
899   }
900
901   /**
902      A static version of {@link #doConfigure(URL, LoggerRepository)}.
903    */
904   static
905   public
906   void configure(URL url) throws FactoryConfigurationError {
907     new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository());
908   }
909
910   /**
911      Used internally to configure the log4j framework by parsing a DOM
912      tree of XML elements based on <a
913      href="doc-files/log4j.dtd">log4j.dtd</a>.
914      
915   */
916   protected
917   void parse(Element element) {
918
919     String rootElementName = element.getTagName();
920
921     if (!rootElementName.equals(CONFIGURATION_TAG)) {
922       if(rootElementName.equals(OLD_CONFIGURATION_TAG)) {
923         LogLog.warn("The <"+OLD_CONFIGURATION_TAG+
924                      "> element has been deprecated.");
925         LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead.");
926       } else {
927         LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element.");
928         return;
929       }
930     }
931
932
933     String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
934       
935     LogLog.debug("debug attribute= \"" + debugAttrib +"\".");
936     // if the log4j.dtd is not specified in the XML file, then the
937     // "debug" attribute is returned as the empty string.
938     if(!debugAttrib.equals("") && !debugAttrib.equals("null")) {      
939       LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
940     } else {
941       LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
942     }
943
944       //
945       //   reset repository before configuration if reset="true"
946       //       on configuration element.
947       //
948     String resetAttrib = subst(element.getAttribute(RESET_ATTR));
949     LogLog.debug("reset attribute= \"" + resetAttrib +"\".");
950     if(!("".equals(resetAttrib))) {
951          if (OptionConverter.toBoolean(resetAttrib, false)) {
952              repository.resetConfiguration();
953          }
954     }
955
956
957
958     String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
959     if(!confDebug.equals("") && !confDebug.equals("null")) {      
960       LogLog.warn("The \""+CONFIG_DEBUG_ATTR+"\" attribute is deprecated.");
961       LogLog.warn("Use the \""+INTERNAL_DEBUG_ATTR+"\" attribute instead.");
962       LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
963     }
964
965     String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
966     LogLog.debug("Threshold =\"" + thresholdStr +"\".");
967     if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
968       repository.setThreshold(thresholdStr);
969     }
970
971     //Hashtable appenderBag = new Hashtable(11);
972
973     /* Building Appender objects, placing them in a local namespace
974        for future reference */
975
976     // First configure each category factory under the root element.
977     // Category factories need to be configured before any of
978     // categories they support.
979     //
980     String   tagName = null;
981     Element  currentElement = null;
982     Node     currentNode = null;
983     NodeList children = element.getChildNodes();
984     final int length = children.getLength();
985
986     for (int loop = 0; loop < length; loop++) {
987       currentNode = children.item(loop);
988       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
989         currentElement = (Element) currentNode;
990         tagName = currentElement.getTagName();
991
992         if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
993           parseCategoryFactory(currentElement);
994         }
995       }
996     }
997     
998     for (int loop = 0; loop < length; loop++) {
999       currentNode = children.item(loop);
1000       if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
1001         currentElement = (Element) currentNode;
1002         tagName = currentElement.getTagName();
1003
1004         if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
1005           parseCategory(currentElement);
1006         } else if (tagName.equals(ROOT_TAG)) {
1007           parseRoot(currentElement);
1008         } else if(tagName.equals(RENDERER_TAG)) {
1009           parseRenderer(currentElement);
1010     } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {
1011         if (repository instanceof ThrowableRendererSupport) {
1012             ThrowableRenderer tr = parseThrowableRenderer(currentElement);
1013             if (tr != null) {
1014                 ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
1015             }
1016         }
1017     } else if (!(tagName.equals(APPENDER_TAG)
1018             || tagName.equals(CATEGORY_FACTORY_TAG)
1019             || tagName.equals(LOGGER_FACTORY_TAG))) {
1020         quietParseUnrecognizedElement(repository, currentElement, props);
1021     }
1022       }
1023     }
1024   }
1025
1026   
1027   protected
1028   String subst(final String value) {
1029       return subst(value, props);
1030   }
1031
1032     /**
1033      * Substitutes property value for any references in expression.
1034      *
1035      * @param value value from configuration file, may contain
1036      *              literal text, property references or both
1037      * @param props properties.
1038      * @return evaluated expression, may still contain expressions
1039      *         if unable to expand.
1040      * @since 1.2.15
1041      */
1042     public static String subst(final String value, final Properties props) {
1043         try {
1044             return OptionConverter.substVars(value, props);
1045         } catch (IllegalArgumentException e) {
1046             LogLog.warn("Could not perform variable substitution.", e);
1047             return value;
1048         }
1049     }
1050
1051
1052     /**
1053      * Sets a parameter based from configuration file content.
1054      *
1055      * @param elem       param element, may not be null.
1056      * @param propSetter property setter, may not be null.
1057      * @param props      properties
1058      * @since 1.2.15
1059      */
1060     public static void setParameter(final Element elem,
1061                                     final PropertySetter propSetter,
1062                                     final Properties props) {
1063         String name = subst(elem.getAttribute("name"), props);
1064         String value = (elem.getAttribute("value"));
1065         value = subst(OptionConverter.convertSpecialChars(value), props);
1066         propSetter.setProperty(name, value);
1067     }
1068
1069     /**
1070      * Creates an object and processes any nested param elements
1071      * but does not call activateOptions.  If the class also supports
1072      * UnrecognizedElementParser, the parseUnrecognizedElement method
1073      * will be call for any child elements other than param.
1074      *
1075      * @param element       element, may not be null.
1076      * @param props         properties
1077      * @param expectedClass interface or class expected to be implemented
1078      *                      by created class
1079      * @return created class or null.
1080      * @throws Exception thrown if the contain object should be abandoned.
1081      * @since 1.2.15
1082      */
1083     public static Object parseElement(final Element element,
1084                                              final Properties props,
1085                                              final Class expectedClass) throws Exception {
1086         String clazz = subst(element.getAttribute("class"), props);
1087         Object instance = OptionConverter.instantiateByClassName(clazz,
1088                 expectedClass, null);
1089
1090         if (instance != null) {
1091             PropertySetter propSetter = new PropertySetter(instance);
1092             NodeList children = element.getChildNodes();
1093             final int length = children.getLength();
1094
1095             for (int loop = 0; loop < length; loop++) {
1096                 Node currentNode = children.item(loop);
1097                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
1098                     Element currentElement = (Element) currentNode;
1099                     String tagName = currentElement.getTagName();
1100                     if (tagName.equals("param")) {
1101                         setParameter(currentElement, propSetter, props);
1102                     } else {
1103                          parseUnrecognizedElement(instance, currentElement, props);
1104                     }
1105                 }
1106             }
1107             return instance;
1108         }
1109         return null;
1110     }
1111
1112 }
1113
1114
1115 class XMLWatchdog extends FileWatchdog {
1116
1117     XMLWatchdog(String filename) {
1118     super(filename);
1119   }
1120
1121   /**
1122      Call {@link DOMConfigurator#configure(String)} with the
1123      <code>filename</code> to reconfigure log4j. */
1124   public
1125   void doOnChange() {
1126     new DOMConfigurator().doConfigure(filename, 
1127                                       LogManager.getLoggerRepository());
1128   }
1129 }