001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j.xml;
019
020import org.apache.log4j.Appender;
021import org.apache.log4j.Layout;
022import org.apache.log4j.Level;
023import org.apache.log4j.LogManager;
024import org.apache.log4j.Logger;
025import org.apache.log4j.config.PropertySetter;
026import org.apache.log4j.helpers.FileWatchdog;
027import org.apache.log4j.helpers.Loader;
028import org.apache.log4j.helpers.LogLog;
029import org.apache.log4j.helpers.OptionConverter;
030import org.apache.log4j.or.RendererMap;
031import org.apache.log4j.spi.AppenderAttachable;
032import org.apache.log4j.spi.Configurator;
033import org.apache.log4j.spi.ErrorHandler;
034import org.apache.log4j.spi.Filter;
035import org.apache.log4j.spi.LoggerFactory;
036import org.apache.log4j.spi.LoggerRepository;
037import org.apache.log4j.spi.RendererSupport;
038import org.apache.log4j.spi.ThrowableRenderer;
039import org.apache.log4j.spi.ThrowableRendererSupport;
040import org.w3c.dom.Document;
041import org.w3c.dom.Element;
042import org.w3c.dom.NamedNodeMap;
043import org.w3c.dom.Node;
044import org.w3c.dom.NodeList;
045import org.xml.sax.InputSource;
046import org.xml.sax.SAXException;
047
048import javax.xml.parsers.DocumentBuilder;
049import javax.xml.parsers.DocumentBuilderFactory;
050import javax.xml.parsers.FactoryConfigurationError;
051import java.io.File;
052import java.io.IOException;
053import java.io.InputStream;
054import java.io.InterruptedIOException;
055import java.io.Reader;
056import java.lang.reflect.Method;
057import java.lang.reflect.InvocationTargetException;
058import java.net.URL;
059import java.net.URLConnection;
060import java.util.Hashtable;
061import java.util.Properties;
062
063// Contributors:   Mark Womack
064//                 Arun Katkere 
065
066/**
067   Use this class to initialize the log4j environment using a DOM tree.
068
069   <p>The DTD is specified in <a
070   href="doc-files/log4j.dtd"><b>log4j.dtd</b></a>.
071
072   <p>Sometimes it is useful to see how log4j is reading configuration
073   files. You can enable log4j internal logging by defining the
074   <b>log4j.debug</b> variable on the java command
075   line. Alternatively, set the <code>debug</code> attribute in the
076   <code>log4j:configuration</code> element. As in
077<pre>
078   &lt;log4j:configuration <b>debug="true"</b> xmlns:log4j="http://jakarta.apache.org/log4j/">
079   ...
080   &lt;/log4j:configuration>
081</pre>
082
083   <p>There are sample XML files included in the package.
084   
085   @author Christopher Taylor
086   @author Ceki G&uuml;lc&uuml;
087   @author Anders Kristensen
088
089   @since 0.8.3 */
090public class DOMConfigurator implements Configurator {
091
092  static final String CONFIGURATION_TAG = "log4j:configuration";
093  static final String OLD_CONFIGURATION_TAG = "configuration";
094  static final String RENDERER_TAG      = "renderer";
095  private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
096  static final String APPENDER_TAG      = "appender";
097  static final String APPENDER_REF_TAG  = "appender-ref";  
098  static final String PARAM_TAG         = "param";
099  static final String LAYOUT_TAG        = "layout";
100  static final String CATEGORY          = "category";
101  static final String LOGGER            = "logger";
102  static final String LOGGER_REF        = "logger-ref";
103  static final String CATEGORY_FACTORY_TAG  = "categoryFactory";
104  static final String LOGGER_FACTORY_TAG  = "loggerFactory";
105  static final String NAME_ATTR         = "name";
106  static final String CLASS_ATTR        = "class";
107  static final String VALUE_ATTR        = "value";
108  static final String ROOT_TAG          = "root";
109  static final String ROOT_REF          = "root-ref";
110  static final String LEVEL_TAG         = "level";
111  static final String PRIORITY_TAG      = "priority";
112  static final String FILTER_TAG        = "filter";
113  static final String ERROR_HANDLER_TAG = "errorHandler";
114  static final String REF_ATTR          = "ref";
115  static final String ADDITIVITY_ATTR    = "additivity";  
116  static final String THRESHOLD_ATTR       = "threshold";
117  static final String CONFIG_DEBUG_ATTR  = "configDebug";
118  static final String INTERNAL_DEBUG_ATTR  = "debug";
119  private static final String RESET_ATTR  = "reset";
120  static final String RENDERING_CLASS_ATTR = "renderingClass";
121  static final String RENDERED_CLASS_ATTR = "renderedClass";
122
123  static final String EMPTY_STR = "";
124  static final Class[] ONE_STRING_PARAM = new Class[] {String.class};
125
126  final static String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
127
128  
129  // key: appenderName, value: appender
130  Hashtable appenderBag;
131
132  Properties props;
133  LoggerRepository repository;
134
135  protected LoggerFactory catFactory = null;
136
137  /**
138     No argument constructor.
139  */
140  public
141  DOMConfigurator () { 
142    appenderBag = new Hashtable();
143  }
144
145  /**
146     Used internally to parse appenders by IDREF name.
147  */
148  protected
149  Appender findAppenderByName(Document doc, String appenderName)  {      
150    Appender appender = (Appender) appenderBag.get(appenderName);
151
152    if(appender != null) {
153      return appender;
154    } else {
155      // Doesn't work on DOM Level 1 :
156      // Element element = doc.getElementById(appenderName);
157                        
158      // Endre's hack:
159      Element element = null;
160      NodeList list = doc.getElementsByTagName("appender");
161      for (int t=0; t < list.getLength(); t++) {
162        Node node = list.item(t);
163        NamedNodeMap map= node.getAttributes();
164        Node attrNode = map.getNamedItem("name");
165        if (appenderName.equals(attrNode.getNodeValue())) {
166          element = (Element) node;
167          break;
168        }
169      }
170      // Hack finished.
171
172      if(element == null) {
173        LogLog.error("No appender named ["+appenderName+"] could be found."); 
174        return null;
175      } else {
176              appender = parseAppender(element);
177          if (appender != null) {
178            appenderBag.put(appenderName, appender);
179          }
180    return appender;
181      }
182    } 
183  }
184  /**
185     Used internally to parse appenders by IDREF element.
186   */
187  protected
188  Appender findAppenderByReference(Element appenderRef) {    
189    String appenderName = subst(appenderRef.getAttribute(REF_ATTR));    
190    Document doc = appenderRef.getOwnerDocument();
191    return findAppenderByName(doc, appenderName);
192  }
193
194    /**
195     * Delegates unrecognized content to created instance if
196     * it supports UnrecognizedElementParser.
197     * @since 1.2.15
198     * @param instance instance, may be null.
199     * @param element element, may not be null.
200     * @param props properties
201     * @throws IOException thrown if configuration of owner object
202     * should be abandoned.
203     */
204  private static void parseUnrecognizedElement(final Object instance,
205                                        final Element element,
206                                        final Properties props) throws Exception {
207      boolean recognized = false;
208      if (instance instanceof UnrecognizedElementHandler) {
209          recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(
210                  element, props);
211      }
212      if (!recognized) {
213          LogLog.warn("Unrecognized element " + element.getNodeName());
214      }
215  }
216
217    /**
218      * Delegates unrecognized content to created instance if
219      * it supports UnrecognizedElementParser and catches and
220     *  logs any exception.
221      * @since 1.2.15
222      * @param instance instance, may be null.
223      * @param element element, may not be null.
224      * @param props properties
225      */
226   private static void quietParseUnrecognizedElement(final Object instance,
227                                          final Element element,
228                                          final Properties props) {
229      try {
230          parseUnrecognizedElement(instance, element, props);
231      } catch (Exception ex) {
232          if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
233              Thread.currentThread().interrupt();
234          }
235          LogLog.error("Error in extension content: ", ex);
236      }
237  }
238
239  /**
240     Used internally to parse an appender element.
241   */
242  protected
243  Appender parseAppender (Element appenderElement) {
244    String className = subst(appenderElement.getAttribute(CLASS_ATTR));
245    LogLog.debug("Class name: [" + className+']');    
246    try {
247      Object instance   = Loader.loadClass(className).newInstance();
248      Appender appender = (Appender)instance;
249      PropertySetter propSetter = new PropertySetter(appender);
250
251      appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
252      
253      NodeList children = appenderElement.getChildNodes();
254      final int length  = children.getLength();
255
256      for (int loop = 0; loop < length; loop++) {
257        Node currentNode = children.item(loop);
258
259        /* We're only interested in Elements */
260        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
261          Element currentElement = (Element)currentNode;
262
263          // Parse appender parameters 
264          if (currentElement.getTagName().equals(PARAM_TAG)) {
265            setParameter(currentElement, propSetter);
266          }
267          // Set appender layout
268          else if (currentElement.getTagName().equals(LAYOUT_TAG)) {
269            appender.setLayout(parseLayout(currentElement));
270          }
271          // Add filters
272          else if (currentElement.getTagName().equals(FILTER_TAG)) {
273            parseFilters(currentElement, appender);
274          }
275          else if (currentElement.getTagName().equals(ERROR_HANDLER_TAG)) {
276            parseErrorHandler(currentElement, appender);
277          }
278          else if (currentElement.getTagName().equals(APPENDER_REF_TAG)) {
279            String refName = subst(currentElement.getAttribute(REF_ATTR));
280            if(appender instanceof AppenderAttachable) {
281              AppenderAttachable aa = (AppenderAttachable) appender;
282              LogLog.debug("Attaching appender named ["+ refName+
283                           "] to appender named ["+ appender.getName()+"].");
284              aa.addAppender(findAppenderByReference(currentElement));
285            } else {
286              LogLog.error("Requesting attachment of appender named ["+
287                           refName+ "] to appender named ["+ appender.getName()+
288                "] which does not implement org.apache.log4j.spi.AppenderAttachable.");
289            }
290          } else {
291          parseUnrecognizedElement(instance, currentElement, props);
292      }
293        }
294      }
295      propSetter.activate();
296      return appender;
297    }
298    /* Yes, it's ugly.  But all of these exceptions point to the same
299       problem: we can't create an Appender */
300    catch (Exception oops) {
301        if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
302            Thread.currentThread().interrupt();
303        }
304      LogLog.error("Could not create an Appender. Reported error follows.",
305                   oops);
306      return null;
307    }
308  }
309
310  /**
311     Used internally to parse an {@link ErrorHandler} element.
312   */
313  protected
314  void parseErrorHandler(Element element, Appender appender) {
315    ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
316                                       subst(element.getAttribute(CLASS_ATTR)),
317                                       org.apache.log4j.spi.ErrorHandler.class, 
318                                       null);
319    
320    if(eh != null) {
321      eh.setAppender(appender);
322
323      PropertySetter propSetter = new PropertySetter(eh);
324      NodeList children = element.getChildNodes();
325      final int length  = children.getLength();
326
327      for (int loop = 0; loop < length; loop++) {
328        Node currentNode = children.item(loop);
329        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
330          Element currentElement = (Element) currentNode;
331          String tagName = currentElement.getTagName();
332          if(tagName.equals(PARAM_TAG)) {
333            setParameter(currentElement, propSetter);
334          } else if(tagName.equals(APPENDER_REF_TAG)) {
335            eh.setBackupAppender(findAppenderByReference(currentElement));
336          } else if(tagName.equals(LOGGER_REF)) {
337            String loggerName = currentElement.getAttribute(REF_ATTR);      
338            Logger logger = (catFactory == null) ? repository.getLogger(loggerName)
339                : repository.getLogger(loggerName, catFactory);
340            eh.setLogger(logger);
341          } else if(tagName.equals(ROOT_REF)) {
342            Logger root = repository.getRootLogger();
343            eh.setLogger(root);
344          } else {
345          quietParseUnrecognizedElement(eh, currentElement, props);
346      }
347        }
348      }
349      propSetter.activate();
350      appender.setErrorHandler(eh);
351    }
352  }
353  
354  /**
355     Used internally to parse a filter element.
356   */
357  protected
358  void parseFilters(Element element, Appender appender) {
359    String clazz = subst(element.getAttribute(CLASS_ATTR));
360    Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz,
361                                                Filter.class, null);
362    
363    if(filter != null) {
364      PropertySetter propSetter = new PropertySetter(filter);
365      NodeList children = element.getChildNodes();
366      final int length  = children.getLength();
367
368      for (int loop = 0; loop < length; loop++) {
369        Node currentNode = children.item(loop);
370        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
371          Element currentElement = (Element) currentNode;
372          String tagName = currentElement.getTagName();
373          if(tagName.equals(PARAM_TAG)) {
374            setParameter(currentElement, propSetter);
375          } else {
376            quietParseUnrecognizedElement(filter, currentElement, props);
377      }
378        }
379      }
380      propSetter.activate();
381      LogLog.debug("Adding filter of type ["+filter.getClass()
382                   +"] to appender named ["+appender.getName()+"].");
383      appender.addFilter(filter);
384    }    
385  }
386  
387  /**
388     Used internally to parse an category element.
389  */
390  protected
391  void parseCategory (Element loggerElement) {
392    // Create a new org.apache.log4j.Category object from the <category> element.
393    String catName = subst(loggerElement.getAttribute(NAME_ATTR));
394
395    Logger cat;    
396
397    String className = subst(loggerElement.getAttribute(CLASS_ATTR));
398
399
400    if(EMPTY_STR.equals(className)) {
401      LogLog.debug("Retreiving an instance of org.apache.log4j.Logger.");
402      cat = (catFactory == null) ? repository.getLogger(catName) : repository.getLogger(catName, catFactory);
403    }
404    else {
405      LogLog.debug("Desired logger sub-class: ["+className+']');
406       try {     
407         Class clazz = Loader.loadClass(className);
408         Method getInstanceMethod = clazz.getMethod("getLogger", 
409                                                    ONE_STRING_PARAM);
410         cat = (Logger) getInstanceMethod.invoke(null, new Object[] {catName});
411       } catch (InvocationTargetException oops) {
412          if (oops.getTargetException() instanceof InterruptedException
413                  || oops.getTargetException() instanceof InterruptedIOException) {
414              Thread.currentThread().interrupt();
415          }
416          LogLog.error("Could not retrieve category ["+catName+
417                      "]. Reported error follows.", oops);
418              return;
419       } catch (Exception oops) {
420              LogLog.error("Could not retrieve category ["+catName+
421                      "]. Reported error follows.", oops);
422              return;
423       }
424    }
425
426    // Setting up a category needs to be an atomic operation, in order
427    // to protect potential log operations while category
428    // configuration is in progress.
429    synchronized(cat) {
430      boolean additivity = OptionConverter.toBoolean(
431                           subst(loggerElement.getAttribute(ADDITIVITY_ATTR)),
432                           true);
433    
434      LogLog.debug("Setting ["+cat.getName()+"] additivity to ["+additivity+"].");
435      cat.setAdditivity(additivity);
436      parseChildrenOfLoggerElement(loggerElement, cat, false);
437    }
438  }
439
440
441  /**
442     Used internally to parse the category factory element.
443  */
444  protected
445  void parseCategoryFactory(Element factoryElement) {
446    String className = subst(factoryElement.getAttribute(CLASS_ATTR));
447
448    if(EMPTY_STR.equals(className)) {
449      LogLog.error("Category Factory tag " + CLASS_ATTR + " attribute not found.");
450      LogLog.debug("No Category Factory configured.");
451    }
452    else {
453      LogLog.debug("Desired category factory: ["+className+']');
454      Object factory = OptionConverter.instantiateByClassName(className,
455                                                                 LoggerFactory.class, 
456                                                                 null);
457      if (factory instanceof LoggerFactory) {
458          catFactory = (LoggerFactory) factory;
459      } else {
460          LogLog.error("Category Factory class " + className + " does not implement org.apache.log4j.LoggerFactory");
461      }
462      PropertySetter propSetter = new PropertySetter(factory);
463
464      Element  currentElement = null;
465      Node     currentNode    = null;
466      NodeList children       = factoryElement.getChildNodes();
467      final int length        = children.getLength();
468
469      for (int loop=0; loop < length; loop++) {
470        currentNode = children.item(loop);
471        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
472          currentElement = (Element)currentNode;
473          if (currentElement.getTagName().equals(PARAM_TAG)) {
474            setParameter(currentElement, propSetter);
475          } else {
476           quietParseUnrecognizedElement(factory, currentElement, props);
477      }
478        }
479      }
480    }
481  }
482
483
484  /**
485     Used internally to parse the roor category element.
486  */
487  protected
488  void parseRoot (Element rootElement) {
489    Logger root = repository.getRootLogger();
490    // category configuration needs to be atomic
491    synchronized(root) {    
492      parseChildrenOfLoggerElement(rootElement, root, true);
493    }
494  }
495
496
497  /**
498     Used internally to parse the children of a category element.
499  */
500  protected
501  void parseChildrenOfLoggerElement(Element catElement,
502                                      Logger cat, boolean isRoot) {
503    
504    PropertySetter propSetter = new PropertySetter(cat);
505    
506    // Remove all existing appenders from cat. They will be
507    // reconstructed if need be.
508    cat.removeAllAppenders();
509
510
511    NodeList children   = catElement.getChildNodes();
512    final int length    = children.getLength();
513    
514    for (int loop = 0; loop < length; loop++) {
515      Node currentNode = children.item(loop);
516
517      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
518        Element currentElement = (Element) currentNode;
519        String tagName = currentElement.getTagName();
520        
521        if (tagName.equals(APPENDER_REF_TAG)) {
522          Element appenderRef = (Element) currentNode;
523          Appender appender = findAppenderByReference(appenderRef);
524          String refName =  subst(appenderRef.getAttribute(REF_ATTR));
525          if(appender != null)
526            LogLog.debug("Adding appender named ["+ refName+ 
527                         "] to category ["+cat.getName()+"].");
528          else 
529            LogLog.debug("Appender named ["+ refName + "] not found.");
530            
531          cat.addAppender(appender);
532          
533        } else if(tagName.equals(LEVEL_TAG)) {
534          parseLevel(currentElement, cat, isRoot);      
535        } else if(tagName.equals(PRIORITY_TAG)) {
536          parseLevel(currentElement, cat, isRoot);
537        } else if(tagName.equals(PARAM_TAG)) {
538          setParameter(currentElement, propSetter);
539        } else {
540        quietParseUnrecognizedElement(cat, currentElement, props);
541    }
542      }
543    }
544    propSetter.activate();
545  }
546
547  /**
548     Used internally to parse a layout element.
549  */  
550  protected
551  Layout parseLayout (Element layout_element) {
552    String className = subst(layout_element.getAttribute(CLASS_ATTR));
553    LogLog.debug("Parsing layout of class: \""+className+"\"");          
554    try {
555      Object instance   = Loader.loadClass(className).newInstance();
556      Layout layout     = (Layout)instance;
557      PropertySetter propSetter = new PropertySetter(layout);
558      
559      NodeList params   = layout_element.getChildNodes();
560      final int length  = params.getLength();
561
562      for (int loop = 0; loop < length; loop++) {
563        Node currentNode = (Node)params.item(loop);
564        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
565          Element currentElement = (Element) currentNode;
566          String tagName = currentElement.getTagName();
567          if(tagName.equals(PARAM_TAG)) {
568            setParameter(currentElement, propSetter);
569          } else {
570          parseUnrecognizedElement(instance, currentElement, props);
571      }
572        }
573      }
574      
575      propSetter.activate();
576      return layout;
577    }
578    catch (Exception oops) {
579        if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
580            Thread.currentThread().interrupt();
581        }
582      LogLog.error("Could not create the Layout. Reported error follows.",
583                   oops);
584      return null;
585    }
586  }
587
588  protected 
589  void parseRenderer(Element element) {
590    String renderingClass = subst(element.getAttribute(RENDERING_CLASS_ATTR));
591    String renderedClass = subst(element.getAttribute(RENDERED_CLASS_ATTR));
592    if(repository instanceof RendererSupport) {
593      RendererMap.addRenderer((RendererSupport) repository, renderedClass, 
594                              renderingClass);
595    }
596  }
597
598    /**
599     * Parses throwable renderer.
600     * @param element throwableRenderer element.
601     * @return configured throwable renderer.
602     * @since 1.2.16.
603     */
604    protected ThrowableRenderer parseThrowableRenderer(final Element element) {
605        String className = subst(element.getAttribute(CLASS_ATTR));
606        LogLog.debug("Parsing throwableRenderer of class: \""+className+"\"");
607        try {
608          Object instance       = Loader.loadClass(className).newInstance();
609          ThrowableRenderer tr          = (ThrowableRenderer)instance;
610          PropertySetter propSetter = new PropertySetter(tr);
611
612          NodeList params       = element.getChildNodes();
613          final int length      = params.getLength();
614
615          for (int loop = 0; loop < length; loop++) {
616                Node currentNode = (Node)params.item(loop);
617                if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
618                    Element currentElement = (Element) currentNode;
619                    String tagName = currentElement.getTagName();
620                    if(tagName.equals(PARAM_TAG)) {
621                        setParameter(currentElement, propSetter);
622                    } else {
623                        parseUnrecognizedElement(instance, currentElement, props);
624                    }
625                }
626          }
627
628          propSetter.activate();
629          return tr;
630        }
631        catch (Exception oops) {
632            if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
633                Thread.currentThread().interrupt();
634            }
635            LogLog.error("Could not create the ThrowableRenderer. Reported error follows.",
636               oops);
637          return null;
638        }
639    }
640
641  /**
642     Used internally to parse a level  element.
643  */
644  protected
645  void parseLevel(Element element, Logger logger, boolean isRoot) {
646    String catName = logger.getName();
647    if(isRoot) {
648      catName = "root";
649    }
650
651    String priStr = subst(element.getAttribute(VALUE_ATTR));
652    LogLog.debug("Level value for "+catName+" is  ["+priStr+"].");
653    
654    if(INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
655      if(isRoot) {
656        LogLog.error("Root level cannot be inherited. Ignoring directive.");
657      } else {
658        logger.setLevel(null);
659      }
660    } else {
661      String className = subst(element.getAttribute(CLASS_ATTR));      
662      if(EMPTY_STR.equals(className)) { 
663        logger.setLevel(OptionConverter.toLevel(priStr, Level.DEBUG));
664      } else {
665        LogLog.debug("Desired Level sub-class: ["+className+']');
666        try {    
667          Class clazz = Loader.loadClass(className);
668          Method toLevelMethod = clazz.getMethod("toLevel", 
669                                                    ONE_STRING_PARAM);
670          Level pri = (Level) toLevelMethod.invoke(null, 
671                                                    new Object[] {priStr});
672          logger.setLevel(pri);
673        } catch (Exception oops) {
674        if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
675            Thread.currentThread().interrupt();
676        }
677          LogLog.error("Could not create level ["+priStr+
678                       "]. Reported error follows.", oops);
679          return;
680        }
681      }
682    }
683    LogLog.debug(catName + " level set to " + logger.getLevel());    
684  }
685
686  protected
687  void setParameter(Element elem, PropertySetter propSetter) {
688      String name = subst(elem.getAttribute(NAME_ATTR));
689      String value = (elem.getAttribute(VALUE_ATTR));
690      value = subst(OptionConverter.convertSpecialChars(value));
691      propSetter.setProperty(name, value);
692  }
693
694
695  /**
696     Configure log4j using a <code>configuration</code> element as
697     defined in the log4j.dtd. 
698
699  */
700  static
701  public
702  void configure (Element element) {
703    DOMConfigurator configurator = new DOMConfigurator();
704    configurator.doConfigure(element,  LogManager.getLoggerRepository());
705  }
706
707 /**
708     Like {@link #configureAndWatch(String, long)} except that the
709     default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
710     used. 
711
712     @param configFilename A log4j configuration file in XML format.
713
714  */
715  static
716  public
717  void configureAndWatch(String configFilename) {
718    configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
719  }
720
721  /**
722     Read the configuration file <code>configFilename</code> if it
723     exists. Moreover, a thread will be created that will periodically
724     check if <code>configFilename</code> has been created or
725     modified. The period is determined by the <code>delay</code>
726     argument. If a change or file creation is detected, then
727     <code>configFilename</code> is read to configure log4j.  
728
729      @param configFilename A log4j configuration file in XML format.
730      @param delay The delay in milliseconds to wait between each check.
731  */
732  static
733  public
734  void configureAndWatch(String configFilename, long delay) {
735    XMLWatchdog xdog = new XMLWatchdog(configFilename);
736    xdog.setDelay(delay);
737    xdog.start();
738  }
739  
740  private interface ParseAction {
741      Document parse(final DocumentBuilder parser) throws SAXException, IOException;
742  }
743
744
745  public
746  void doConfigure(final String filename, LoggerRepository repository) {
747    ParseAction action = new ParseAction() {
748          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
749              return parser.parse(new File(filename));
750          }
751          public String toString() { 
752              return "file [" + filename + "]"; 
753          }
754    };
755    doConfigure(action, repository);
756  }
757  
758
759  public
760  void doConfigure(final URL url, LoggerRepository repository) {
761      ParseAction action = new ParseAction() {
762          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
763              URLConnection uConn = url.openConnection();
764              uConn.setUseCaches(false);
765              InputStream stream = uConn.getInputStream();
766              try {
767                InputSource src = new InputSource(stream);
768                src.setSystemId(url.toString());
769                return parser.parse(src);
770              } finally {
771                stream.close();
772              }
773          }
774          public String toString() { 
775              return "url [" + url.toString() + "]"; 
776          }
777      };
778      doConfigure(action, repository);
779  }
780
781  /**
782     Configure log4j by reading in a log4j.dtd compliant XML
783     configuration file.
784
785  */
786  public
787  void doConfigure(final InputStream inputStream, LoggerRepository repository) 
788                                          throws FactoryConfigurationError {
789      ParseAction action = new ParseAction() {
790          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
791              InputSource inputSource = new InputSource(inputStream);
792              inputSource.setSystemId("dummy://log4j.dtd");
793              return parser.parse(inputSource);
794          }
795          public String toString() { 
796              return "input stream [" + inputStream.toString() + "]"; 
797          }
798      };
799      doConfigure(action, repository);
800  }
801
802  /**
803     Configure log4j by reading in a log4j.dtd compliant XML
804     configuration file.
805
806  */
807  public
808  void doConfigure(final Reader reader, LoggerRepository repository) 
809                                          throws FactoryConfigurationError {
810      ParseAction action = new ParseAction() {
811          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
812              InputSource inputSource = new InputSource(reader);
813              inputSource.setSystemId("dummy://log4j.dtd");
814              return parser.parse(inputSource);
815          }
816          public String toString() { 
817              return "reader [" + reader.toString() + "]"; 
818          }
819      };
820    doConfigure(action, repository);
821  }
822
823  /**
824     Configure log4j by reading in a log4j.dtd compliant XML
825     configuration file.
826
827  */
828  protected
829  void doConfigure(final InputSource inputSource, LoggerRepository repository) 
830                                          throws FactoryConfigurationError {
831      if (inputSource.getSystemId() == null) {
832          inputSource.setSystemId("dummy://log4j.dtd");
833      }
834      ParseAction action = new ParseAction() {
835          public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
836              return parser.parse(inputSource);
837          }
838          public String toString() { 
839              return "input source [" + inputSource.toString() + "]"; 
840          }
841      };
842      doConfigure(action, repository);
843    }
844    
845    
846  private final void doConfigure(final ParseAction action, final LoggerRepository repository)
847         throws FactoryConfigurationError {
848    DocumentBuilderFactory dbf = null;
849    this.repository = repository;
850    try { 
851      LogLog.debug("System property is :"+
852                                OptionConverter.getSystemProperty(dbfKey, 
853                                                                  null)); 
854      dbf = DocumentBuilderFactory.newInstance();
855      LogLog.debug("Standard DocumentBuilderFactory search succeded.");
856      LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName());
857    } catch(FactoryConfigurationError fce) {
858      Exception e = fce.getException();
859      LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e);
860      throw fce;
861    }
862      
863    try {
864      dbf.setValidating(true);
865
866      DocumentBuilder docBuilder = dbf.newDocumentBuilder();
867
868      docBuilder.setErrorHandler(new SAXErrorHandler());      
869      docBuilder.setEntityResolver(new Log4jEntityResolver());
870         
871      Document doc = action.parse(docBuilder);     
872      parse(doc.getDocumentElement());
873    } catch (Exception e) {
874        if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
875            Thread.currentThread().interrupt();
876        }
877      // I know this is miserable...
878      LogLog.error("Could not parse "+ action.toString() + ".", e);
879    }
880  }
881
882  /**
883     Configure by taking in an DOM element. 
884  */
885  public void doConfigure(Element element, LoggerRepository repository) {
886    this.repository = repository;
887    parse(element);
888  }
889
890  
891  /**
892     A static version of {@link #doConfigure(String, LoggerRepository)}.  */
893  static
894  public
895  void configure(String filename) throws FactoryConfigurationError {
896    new DOMConfigurator().doConfigure(filename, 
897                                      LogManager.getLoggerRepository());
898  }
899
900  /**
901     A static version of {@link #doConfigure(URL, LoggerRepository)}.
902   */
903  static
904  public
905  void configure(URL url) throws FactoryConfigurationError {
906    new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository());
907  }
908
909  /**
910     Used internally to configure the log4j framework by parsing a DOM
911     tree of XML elements based on <a
912     href="doc-files/log4j.dtd">log4j.dtd</a>.
913     
914  */
915  protected
916  void parse(Element element) {
917
918    String rootElementName = element.getTagName();
919
920    if (!rootElementName.equals(CONFIGURATION_TAG)) {
921      if(rootElementName.equals(OLD_CONFIGURATION_TAG)) {
922        LogLog.warn("The <"+OLD_CONFIGURATION_TAG+
923                     "> element has been deprecated.");
924        LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead.");
925      } else {
926        LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element.");
927        return;
928      }
929    }
930
931
932    String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
933      
934    LogLog.debug("debug attribute= \"" + debugAttrib +"\".");
935    // if the log4j.dtd is not specified in the XML file, then the
936    // "debug" attribute is returned as the empty string.
937    if(!debugAttrib.equals("") && !debugAttrib.equals("null")) {      
938      LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true));
939    } else {
940      LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
941    }
942
943      //
944      //   reset repository before configuration if reset="true"
945      //       on configuration element.
946      //
947    String resetAttrib = subst(element.getAttribute(RESET_ATTR));
948    LogLog.debug("reset attribute= \"" + resetAttrib +"\".");
949    if(!("".equals(resetAttrib))) {
950         if (OptionConverter.toBoolean(resetAttrib, false)) {
951             repository.resetConfiguration();
952         }
953    }
954
955
956
957    String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
958    if(!confDebug.equals("") && !confDebug.equals("null")) {      
959      LogLog.warn("The \""+CONFIG_DEBUG_ATTR+"\" attribute is deprecated.");
960      LogLog.warn("Use the \""+INTERNAL_DEBUG_ATTR+"\" attribute instead.");
961      LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
962    }
963
964    String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR));
965    LogLog.debug("Threshold =\"" + thresholdStr +"\".");
966    if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) {
967      repository.setThreshold(thresholdStr);
968    }
969
970    //Hashtable appenderBag = new Hashtable(11);
971
972    /* Building Appender objects, placing them in a local namespace
973       for future reference */
974
975    // First configure each category factory under the root element.
976    // Category factories need to be configured before any of
977    // categories they support.
978    //
979    String   tagName = null;
980    Element  currentElement = null;
981    Node     currentNode = null;
982    NodeList children = element.getChildNodes();
983    final int length = children.getLength();
984
985    for (int loop = 0; loop < length; loop++) {
986      currentNode = children.item(loop);
987      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
988        currentElement = (Element) currentNode;
989        tagName = currentElement.getTagName();
990
991        if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) {
992          parseCategoryFactory(currentElement);
993        }
994      }
995    }
996    
997    for (int loop = 0; loop < length; loop++) {
998      currentNode = children.item(loop);
999      if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
1000        currentElement = (Element) currentNode;
1001        tagName = currentElement.getTagName();
1002
1003        if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {
1004          parseCategory(currentElement);
1005        } else if (tagName.equals(ROOT_TAG)) {
1006          parseRoot(currentElement);
1007        } else if(tagName.equals(RENDERER_TAG)) {
1008          parseRenderer(currentElement);
1009    } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {
1010        if (repository instanceof ThrowableRendererSupport) {
1011            ThrowableRenderer tr = parseThrowableRenderer(currentElement);
1012            if (tr != null) {
1013                ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);
1014            }
1015        }
1016    } else if (!(tagName.equals(APPENDER_TAG)
1017            || tagName.equals(CATEGORY_FACTORY_TAG)
1018            || tagName.equals(LOGGER_FACTORY_TAG))) {
1019        quietParseUnrecognizedElement(repository, currentElement, props);
1020    }
1021      }
1022    }
1023  }
1024
1025  
1026  protected
1027  String subst(final String value) {
1028      return subst(value, props);
1029  }
1030
1031    /**
1032     * Substitutes property value for any references in expression.
1033     *
1034     * @param value value from configuration file, may contain
1035     *              literal text, property references or both
1036     * @param props properties.
1037     * @return evaluated expression, may still contain expressions
1038     *         if unable to expand.
1039     * @since 1.2.15
1040     */
1041    public static String subst(final String value, final Properties props) {
1042        try {
1043            return OptionConverter.substVars(value, props);
1044        } catch (IllegalArgumentException e) {
1045            LogLog.warn("Could not perform variable substitution.", e);
1046            return value;
1047        }
1048    }
1049
1050
1051    /**
1052     * Sets a parameter based from configuration file content.
1053     *
1054     * @param elem       param element, may not be null.
1055     * @param propSetter property setter, may not be null.
1056     * @param props      properties
1057     * @since 1.2.15
1058     */
1059    public static void setParameter(final Element elem,
1060                                    final PropertySetter propSetter,
1061                                    final Properties props) {
1062        String name = subst(elem.getAttribute("name"), props);
1063        String value = (elem.getAttribute("value"));
1064        value = subst(OptionConverter.convertSpecialChars(value), props);
1065        propSetter.setProperty(name, value);
1066    }
1067
1068    /**
1069     * Creates an object and processes any nested param elements
1070     * but does not call activateOptions.  If the class also supports
1071     * UnrecognizedElementParser, the parseUnrecognizedElement method
1072     * will be call for any child elements other than param.
1073     *
1074     * @param element       element, may not be null.
1075     * @param props         properties
1076     * @param expectedClass interface or class expected to be implemented
1077     *                      by created class
1078     * @return created class or null.
1079     * @throws Exception thrown if the contain object should be abandoned.
1080     * @since 1.2.15
1081     */
1082    public static Object parseElement(final Element element,
1083                                             final Properties props,
1084                                             final Class expectedClass) throws Exception {
1085        String clazz = subst(element.getAttribute("class"), props);
1086        Object instance = OptionConverter.instantiateByClassName(clazz,
1087                expectedClass, null);
1088
1089        if (instance != null) {
1090            PropertySetter propSetter = new PropertySetter(instance);
1091            NodeList children = element.getChildNodes();
1092            final int length = children.getLength();
1093
1094            for (int loop = 0; loop < length; loop++) {
1095                Node currentNode = children.item(loop);
1096                if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
1097                    Element currentElement = (Element) currentNode;
1098                    String tagName = currentElement.getTagName();
1099                    if (tagName.equals("param")) {
1100                        setParameter(currentElement, propSetter, props);
1101                    } else {
1102                         parseUnrecognizedElement(instance, currentElement, props);
1103                    }
1104                }
1105            }
1106            return instance;
1107        }
1108        return null;
1109    }
1110
1111}
1112
1113
1114class XMLWatchdog extends FileWatchdog {
1115
1116    XMLWatchdog(String filename) {
1117    super(filename);
1118  }
1119
1120  /**
1121     Call {@link DOMConfigurator#configure(String)} with the
1122     <code>filename</code> to reconfigure log4j. */
1123  public
1124  void doOnChange() {
1125    new DOMConfigurator().doConfigure(filename, 
1126                                      LogManager.getLoggerRepository());
1127  }
1128}