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  package org.apache.log4j.xml;
18  
19  import org.apache.log4j.Appender;
20  import org.apache.log4j.Layout;
21  import org.apache.log4j.Level;
22  import org.apache.log4j.bridge.AppenderAdapter;
23  import org.apache.log4j.bridge.AppenderWrapper;
24  import org.apache.log4j.config.Log4j1Configuration;
25  import org.apache.log4j.config.PropertySetter;
26  import org.apache.log4j.helpers.OptionConverter;
27  import org.apache.log4j.rewrite.RewritePolicy;
28  import org.apache.log4j.spi.AppenderAttachable;
29  import org.apache.log4j.spi.ErrorHandler;
30  import org.apache.log4j.spi.Filter;
31  import org.apache.logging.log4j.core.LoggerContext;
32  import org.apache.logging.log4j.core.config.ConfigurationSource;
33  import org.apache.logging.log4j.core.config.LoggerConfig;
34  import org.apache.logging.log4j.core.config.status.StatusConfiguration;
35  import org.apache.logging.log4j.status.StatusLogger;
36  import org.apache.logging.log4j.util.LoaderUtil;
37  import org.w3c.dom.Document;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.NamedNodeMap;
40  import org.w3c.dom.Node;
41  import org.w3c.dom.NodeList;
42  import org.xml.sax.InputSource;
43  import org.xml.sax.SAXException;
44  import org.xml.sax.SAXParseException;
45  
46  import javax.xml.parsers.DocumentBuilder;
47  import javax.xml.parsers.DocumentBuilderFactory;
48  import javax.xml.parsers.FactoryConfigurationError;
49  import java.io.IOException;
50  import java.io.InterruptedIOException;
51  import java.lang.reflect.Method;
52  import java.util.HashMap;
53  import java.util.Map;
54  import java.util.Properties;
55  import java.util.function.Consumer;
56  
57  /**
58   * Class Description goes here.
59   */
60  public class XmlConfiguration extends Log4j1Configuration {
61  
62      private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
63  
64      private static final String CONFIGURATION_TAG = "log4j:configuration";
65      private static final String OLD_CONFIGURATION_TAG = "configuration";
66      private static final String RENDERER_TAG = "renderer";
67      private static final String APPENDER_TAG = "appender";
68      public  static final String PARAM_TAG = "param";
69      public static final String LAYOUT_TAG = "layout";
70      private static final String CATEGORY = "category";
71      private static final String LOGGER_ELEMENT = "logger";
72      private static final String CATEGORY_FACTORY_TAG = "categoryFactory";
73      private static final String LOGGER_FACTORY_TAG = "loggerFactory";
74      public static final String NAME_ATTR = "name";
75      private static final String CLASS_ATTR = "class";
76      public static final String VALUE_ATTR = "value";
77      private static final String ROOT_TAG = "root";
78      private static final String LEVEL_TAG = "level";
79      private static final String PRIORITY_TAG = "priority";
80      public static final String FILTER_TAG = "filter";
81      private static final String ERROR_HANDLER_TAG = "errorHandler";
82      public static final String REF_ATTR = "ref";
83      private static final String ADDITIVITY_ATTR = "additivity";
84      private static final String CONFIG_DEBUG_ATTR = "configDebug";
85      private static final String INTERNAL_DEBUG_ATTR = "debug";
86      private static final String EMPTY_STR = "";
87      private static final Class[] ONE_STRING_PARAM = new Class[]{String.class};
88      private static final String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
89      private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
90  
91      public static final long DEFAULT_DELAY = 60000;
92      /**
93       * File name prefix for test configurations.
94       */
95      protected static final String TEST_PREFIX = "log4j-test";
96  
97      /**
98       * File name prefix for standard configurations.
99       */
100     protected static final String DEFAULT_PREFIX = "log4j";
101 
102     // key: appenderName, value: appender
103     private Map<String, Appender> appenderMap;
104 
105     private Properties props = null;
106 
107     public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
108             int monitorIntervalSeconds) {
109         super(loggerContext, source, monitorIntervalSeconds);
110         appenderMap = new HashMap<>();
111     }
112 
113     public void addAppenderIfAbsent(Appender appender) {
114         appenderMap.putIfAbsent(appender.getName(), appender);
115     }
116 
117     /**
118      * Configure log4j by reading in a log4j.dtd compliant XML
119      * configuration file.
120      */
121     @Override
122     public void doConfigure() throws FactoryConfigurationError {
123         ConfigurationSource source = getConfigurationSource();
124         ParseAction action = new ParseAction() {
125             public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
126                 InputSource inputSource = new InputSource(source.getInputStream());
127                 inputSource.setSystemId("dummy://log4j.dtd");
128                 return parser.parse(inputSource);
129             }
130 
131             public String toString() {
132                 return getConfigurationSource().getLocation();
133             }
134         };
135         doConfigure(action);
136     }
137 
138     private void doConfigure(final ParseAction action) throws FactoryConfigurationError {
139         DocumentBuilderFactory dbf;
140         try {
141             LOGGER.debug("System property is : {}", OptionConverter.getSystemProperty(dbfKey, null));
142             dbf = DocumentBuilderFactory.newInstance();
143             LOGGER.debug("Standard DocumentBuilderFactory search succeded.");
144             LOGGER.debug("DocumentBuilderFactory is: " + dbf.getClass().getName());
145         } catch (FactoryConfigurationError fce) {
146             Exception e = fce.getException();
147             LOGGER.debug("Could not instantiate a DocumentBuilderFactory.", e);
148             throw fce;
149         }
150 
151         try {
152             dbf.setValidating(true);
153 
154             DocumentBuilder docBuilder = dbf.newDocumentBuilder();
155 
156             docBuilder.setErrorHandler(new SAXErrorHandler());
157             docBuilder.setEntityResolver(new Log4jEntityResolver());
158 
159             Document doc = action.parse(docBuilder);
160             parse(doc.getDocumentElement());
161         } catch (Exception e) {
162             if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
163                 Thread.currentThread().interrupt();
164             }
165             // I know this is miserable...
166             LOGGER.error("Could not parse " + action.toString() + ".", e);
167         }
168     }
169 
170     /**
171      * Delegates unrecognized content to created instance if it supports UnrecognizedElementParser.
172      *
173      * @param instance instance, may be null.
174      * @param element  element, may not be null.
175      * @param props    properties
176      * @throws IOException thrown if configuration of owner object should be abandoned.
177      */
178     private void parseUnrecognizedElement(final Object instance, final Element element,
179             final Properties props) throws Exception {
180         boolean recognized = false;
181         if (instance instanceof UnrecognizedElementHandler) {
182             recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(
183                     element, props);
184         }
185         if (!recognized) {
186             LOGGER.warn("Unrecognized element {}", element.getNodeName());
187         }
188     }
189 
190     /**
191      * Delegates unrecognized content to created instance if
192      * it supports UnrecognizedElementParser and catches and
193      * logs any exception.
194      *
195      * @param instance instance, may be null.
196      * @param element  element, may not be null.
197      * @param props    properties
198      * @since 1.2.15
199      */
200     private void quietParseUnrecognizedElement(final Object instance,
201             final Element element,
202             final Properties props) {
203         try {
204             parseUnrecognizedElement(instance, element, props);
205         } catch (Exception ex) {
206             if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
207                 Thread.currentThread().interrupt();
208             }
209             LOGGER.error("Error in extension content: ", ex);
210         }
211     }
212 
213     /**
214      * Substitutes property value for any references in expression.
215      *
216      * @param value value from configuration file, may contain
217      *              literal text, property references or both
218      * @param props properties.
219      * @return evaluated expression, may still contain expressions
220      * if unable to expand.
221      */
222     public String subst(final String value, final Properties props) {
223         try {
224             return OptionConverter.substVars(value, props);
225         } catch (IllegalArgumentException e) {
226             LOGGER.warn("Could not perform variable substitution.", e);
227             return value;
228         }
229     }
230 
231     /**
232      * Sets a parameter based from configuration file content.
233      *
234      * @param elem       param element, may not be null.
235      * @param propSetter property setter, may not be null.
236      * @param props      properties
237      * @since 1.2.15
238      */
239     public void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {
240         String name = subst(elem.getAttribute("name"), props);
241         String value = (elem.getAttribute("value"));
242         value = subst(OptionConverter.convertSpecialChars(value), props);
243         propSetter.setProperty(name, value);
244     }
245 
246     /**
247      * Creates an object and processes any nested param elements
248      * but does not call activateOptions.  If the class also supports
249      * UnrecognizedElementParser, the parseUnrecognizedElement method
250      * will be call for any child elements other than param.
251      *
252      * @param element       element, may not be null.
253      * @param props         properties
254      * @param expectedClass interface or class expected to be implemented
255      *                      by created class
256      * @return created class or null.
257      * @throws Exception thrown if the contain object should be abandoned.
258      * @since 1.2.15
259      */
260     public Object parseElement(final Element element, final Properties props,
261             @SuppressWarnings("rawtypes") final Class expectedClass) throws Exception {
262         String clazz = subst(element.getAttribute("class"), props);
263         Object instance = OptionConverter.instantiateByClassName(clazz,
264                 expectedClass, null);
265 
266         if (instance != null) {
267             PropertySetter propSetter = new PropertySetter(instance);
268             NodeList children = element.getChildNodes();
269             final int length = children.getLength();
270 
271             for (int loop = 0; loop < length; loop++) {
272                 Node currentNode = children.item(loop);
273                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
274                     Element currentElement = (Element) currentNode;
275                     String tagName = currentElement.getTagName();
276                     if (tagName.equals("param")) {
277                         setParameter(currentElement, propSetter, props);
278                     } else {
279                         parseUnrecognizedElement(instance, currentElement, props);
280                     }
281                 }
282             }
283             return instance;
284         }
285         return null;
286     }
287 
288     /**
289      * Used internally to parse appenders by IDREF name.
290      */
291     private Appender findAppenderByName(Document doc, String appenderName) {
292         Appender appender = appenderMap.get(appenderName);
293 
294         if (appender != null) {
295             return appender;
296         } else {
297             // Doesn't work on DOM Level 1 :
298             // Element element = doc.getElementById(appenderName);
299 
300             // Endre's hack:
301             Element element = null;
302             NodeList list = doc.getElementsByTagName("appender");
303             for (int t = 0; t < list.getLength(); t++) {
304                 Node node = list.item(t);
305                 NamedNodeMap map = node.getAttributes();
306                 Node attrNode = map.getNamedItem("name");
307                 if (appenderName.equals(attrNode.getNodeValue())) {
308                     element = (Element) node;
309                     break;
310                 }
311             }
312             // Hack finished.
313 
314             if (element == null) {
315 
316                 LOGGER.error("No appender named [{}] could be found.", appenderName);
317                 return null;
318             } else {
319                 appender = parseAppender(element);
320                 if (appender != null) {
321                     appenderMap.put(appenderName, appender);
322                 }
323                 return appender;
324             }
325         }
326     }
327 
328     /**
329      * Used internally to parse appenders by IDREF element.
330      */
331     public Appender findAppenderByReference(Element appenderRef) {
332         String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
333         Document doc = appenderRef.getOwnerDocument();
334         return findAppenderByName(doc, appenderName);
335     }
336 
337     /**
338      * Used internally to parse an appender element.
339      */
340     public Appender parseAppender(Element appenderElement) {
341         String className = subst(appenderElement.getAttribute(CLASS_ATTR));
342         LOGGER.debug("Class name: [" + className + ']');
343         Appender appender = manager.parseAppender(className, appenderElement, this);
344         if (appender == null) {
345             appender = buildAppender(className, appenderElement);
346         }
347         return appender;
348     }
349 
350     private Appender buildAppender(String className, Element appenderElement) {
351         try {
352             Appender appender = LoaderUtil.newInstanceOf(className);
353             PropertySetter propSetter = new PropertySetter(appender);
354 
355             appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
356             forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
357                 // Parse appender parameters
358                 switch (currentElement.getTagName()) {
359                     case PARAM_TAG:
360                         setParameter(currentElement, propSetter);
361                         break;
362                     case LAYOUT_TAG:
363                         appender.setLayout(parseLayout(currentElement));
364                         break;
365                     case FILTER_TAG:
366                         Filter filter = parseFilters(currentElement);
367                         if (filter != null) {
368                             LOGGER.debug("Adding filter of type [{}] to appender named [{}]",
369                                     filter.getClass(), appender.getName());
370                             appender.addFilter(filter);
371                         }
372                         break;
373                     case ERROR_HANDLER_TAG:
374                         parseErrorHandler(currentElement, appender);
375                         break;
376                     case APPENDER_REF_TAG:
377                         String refName = subst(currentElement.getAttribute(REF_ATTR));
378                         if (appender instanceof AppenderAttachable) {
379                             AppenderAttachable aa = (AppenderAttachable) appender;
380                             Appender child = findAppenderByReference(currentElement);
381                             LOGGER.debug("Attaching appender named [{}] to appender named [{}].", refName,
382                                     appender.getName());
383                             aa.addAppender(child);
384                         } else {
385                             LOGGER.error("Requesting attachment of appender named [{}] to appender named [{}}]"
386                                             + "which does not implement org.apache.log4j.spi.AppenderAttachable.",
387                                     refName, appender.getName());
388                         }
389                         break;
390                     default:
391                         try {
392                             parseUnrecognizedElement(appender, currentElement, props);
393                         } catch (Exception ex) {
394                             throw new ConsumerException(ex);
395                         }
396                 }
397             });
398             propSetter.activate();
399             return appender;
400         } catch (ConsumerException ex) {
401             Throwable t = ex.getCause();
402             if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
403                 Thread.currentThread().interrupt();
404             }
405             LOGGER.error("Could not create an Appender. Reported error follows.", t);
406         } catch (Exception oops) {
407             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
408                 Thread.currentThread().interrupt();
409             }
410             LOGGER.error("Could not create an Appender. Reported error follows.", oops);
411         }
412         return null;
413     }
414 
415     public RewritePolicy parseRewritePolicy(Element rewritePolicyElement) {
416         String className = subst(rewritePolicyElement.getAttribute(CLASS_ATTR));
417         LOGGER.debug("Class name: [" + className + ']');
418         RewritePolicy policy = manager.parseRewritePolicy(className, rewritePolicyElement, this);
419         if (policy == null) {
420             policy = buildRewritePolicy(className, rewritePolicyElement);
421         }
422         return policy;
423     }
424 
425     private RewritePolicy buildRewritePolicy(String className, Element element) {
426         try {
427             RewritePolicy policy = LoaderUtil.newInstanceOf(className);
428             PropertySetter propSetter = new PropertySetter(policy);
429 
430             forEachElement(element.getChildNodes(), (currentElement) -> {
431                 if (currentElement.getTagName().equalsIgnoreCase(PARAM_TAG)) {
432                     setParameter(currentElement, propSetter);
433                 }
434             });
435             propSetter.activate();
436             return policy;
437         } catch (ConsumerException ex) {
438             Throwable t = ex.getCause();
439             if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
440                 Thread.currentThread().interrupt();
441             }
442             LOGGER.error("Could not create an RewritePolicy. Reported error follows.", t);
443         } catch (Exception oops) {
444             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
445                 Thread.currentThread().interrupt();
446             }
447             LOGGER.error("Could not create an RewritePolicy. Reported error follows.", oops);
448         }
449         return null;
450     }
451 
452     /**
453      * Used internally to parse an {@link ErrorHandler} element.
454      */
455     private void parseErrorHandler(Element element, Appender appender) {
456         ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
457                 subst(element.getAttribute(CLASS_ATTR)),
458                 ErrorHandler.class,
459                 null);
460 
461         if (eh != null) {
462             eh.setAppender(appender);
463 
464             PropertySetter propSetter = new PropertySetter(eh);
465             forEachElement(element.getChildNodes(), (currentElement) -> {
466                 String tagName = currentElement.getTagName();
467                 if (tagName.equals(PARAM_TAG)) {
468                     setParameter(currentElement, propSetter);
469                 }
470             });
471             propSetter.activate();
472             appender.setErrorHandler(eh);
473         }
474     }
475 
476     /**
477      * Used internally to parse a filter element.
478      */
479     public Filter parseFilters(Element filterElement) {
480         String className = subst(filterElement.getAttribute(CLASS_ATTR));
481         LOGGER.debug("Class name: [" + className + ']');
482         Filter filter = manager.parseFilter(className, filterElement, this);
483         if (filter == null) {
484             PropertySetter propSetter = new PropertySetter(filter);
485             forEachElement(filterElement.getChildNodes(), (currentElement) -> {
486                 String tagName = currentElement.getTagName();
487                 if (tagName.equals(PARAM_TAG)) {
488                     setParameter(currentElement, propSetter);
489                 } else {
490                     quietParseUnrecognizedElement(filter, currentElement, props);
491                 }
492             });
493             propSetter.activate();
494         }
495         return filter;
496     }
497 
498     /**
499      * Used internally to parse an category element.
500      */
501     private void parseCategory(Element loggerElement) {
502         // Create a new org.apache.log4j.Category object from the <category> element.
503         String catName = subst(loggerElement.getAttribute(NAME_ATTR));
504         boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), true);
505         LoggerConfig loggerConfig = getLogger(catName);
506         if (loggerConfig == null) {
507             loggerConfig = new LoggerConfig(catName, org.apache.logging.log4j.Level.ERROR, additivity);
508             addLogger(catName, loggerConfig);
509         } else {
510             loggerConfig.setAdditive(additivity);
511         }
512         parseChildrenOfLoggerElement(loggerElement, loggerConfig, false);
513     }
514 
515     /**
516      * Used internally to parse the roor category element.
517      */
518     private void parseRoot(Element rootElement) {
519         LoggerConfig root = getRootLogger();
520         parseChildrenOfLoggerElement(rootElement, root, true);
521     }
522 
523     /**
524      * Used internally to parse the children of a LoggerConfig element.
525      */
526     private void parseChildrenOfLoggerElement(Element catElement, LoggerConfig loggerConfig, boolean isRoot) {
527 
528         final PropertySetter propSetter = new PropertySetter(loggerConfig);
529         loggerConfig.getAppenderRefs().clear();
530         forEachElement(catElement.getChildNodes(), (currentElement) -> {
531             switch (currentElement.getTagName()) {
532                 case APPENDER_REF_TAG: {
533                     Appender appender = findAppenderByReference(currentElement);
534                     String refName = subst(currentElement.getAttribute(REF_ATTR));
535                     if (appender != null) {
536                         LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", refName,
537                                 loggerConfig.getName());
538                         loggerConfig.addAppender(getAppender(refName), null, null);
539                     } else {
540                         LOGGER.debug("Appender named [{}}] not found.", refName);
541                     }
542                     break;
543                 }
544                 case LEVEL_TAG: case PRIORITY_TAG: {
545                     parseLevel(currentElement, loggerConfig, isRoot);
546                     break;
547                 }
548                 case PARAM_TAG: {
549                     setParameter(currentElement, propSetter);
550                     break;
551                 }
552                 default: {
553                     quietParseUnrecognizedElement(loggerConfig, currentElement, props);
554                 }
555             }
556         });
557         propSetter.activate();
558     }
559 
560     /**
561      * Used internally to parse a layout element.
562      */
563     public Layout parseLayout(Element layoutElement) {
564         String className = subst(layoutElement.getAttribute(CLASS_ATTR));
565         LOGGER.debug("Parsing layout of class: \"{}\"", className);
566         Layout layout = manager.parseLayout(className, layoutElement, this);
567         if (layout == null) {
568             layout = buildLayout(className, layoutElement);
569         }
570         return layout;
571     }
572 
573     private Layout buildLayout(String className, Element layout_element) {
574         try {
575             Layout layout = LoaderUtil.newInstanceOf(className);
576             PropertySetter propSetter = new PropertySetter(layout);
577             forEachElement(layout_element.getChildNodes(), (currentElement) -> {
578                 String tagName = currentElement.getTagName();
579                 if (tagName.equals(PARAM_TAG)) {
580                     setParameter(currentElement, propSetter);
581                 } else {
582                     try {
583                         parseUnrecognizedElement(layout, currentElement, props);
584                     } catch (Exception ex) {
585                         throw new ConsumerException(ex);
586                     }
587                 }
588             });
589 
590             propSetter.activate();
591             return layout;
592         } catch (ConsumerException ce) {
593             Throwable cause = ce.getCause();
594             if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) {
595                 Thread.currentThread().interrupt();
596             }
597             LOGGER.error("Could not create the Layout. Reported error follows.", cause);
598         } catch (Exception oops) {
599             if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
600                 Thread.currentThread().interrupt();
601             }
602             LOGGER.error("Could not create the Layout. Reported error follows.", oops);
603         }
604         return null;
605     }
606 
607     /**
608      * Used internally to parse a level  element.
609      */
610     private void parseLevel(Element element, LoggerConfig logger, boolean isRoot) {
611         String catName = logger.getName();
612         if (isRoot) {
613             catName = "root";
614         }
615 
616         String priStr = subst(element.getAttribute(VALUE_ATTR));
617         LOGGER.debug("Level value for {} is [{}}].", catName, priStr);
618 
619         if (INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
620             if (isRoot) {
621                 LOGGER.error("Root level cannot be inherited. Ignoring directive.");
622             } else {
623                 logger.setLevel(null);
624             }
625         } else {
626             String className = subst(element.getAttribute(CLASS_ATTR));
627             if (EMPTY_STR.equals(className)) {
628                 logger.setLevel(OptionConverter.convertLevel(priStr, org.apache.logging.log4j.Level.DEBUG));
629             } else {
630                 LOGGER.debug("Desired Level sub-class: [{}]", className);
631                 try {
632                     Class<?> clazz = LoaderUtil.loadClass(className);
633                     Method toLevelMethod = clazz.getMethod("toLevel", ONE_STRING_PARAM);
634                     Level pri = (Level) toLevelMethod.invoke(null, new Object[]{priStr});
635                     logger.setLevel(OptionConverter.convertLevel(pri));
636                 } catch (Exception oops) {
637                     if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
638                         Thread.currentThread().interrupt();
639                     }
640                     LOGGER.error("Could not create level [" + priStr +
641                             "]. Reported error follows.", oops);
642                     return;
643                 }
644             }
645         }
646         LOGGER.debug("{} level set to {}", catName,  logger.getLevel());
647     }
648 
649     private void setParameter(Element elem, PropertySetter propSetter) {
650         String name = subst(elem.getAttribute(NAME_ATTR));
651         String value = (elem.getAttribute(VALUE_ATTR));
652         value = subst(OptionConverter.convertSpecialChars(value));
653         propSetter.setProperty(name, value);
654     }
655 
656     /**
657      * Used internally to configure the log4j framework by parsing a DOM
658      * tree of XML elements based on <a
659      * href="doc-files/log4j.dtd">log4j.dtd</a>.
660      */
661     private void parse(Element element) {
662         String rootElementName = element.getTagName();
663 
664         if (!rootElementName.equals(CONFIGURATION_TAG)) {
665             if (rootElementName.equals(OLD_CONFIGURATION_TAG)) {
666                 LOGGER.warn("The <" + OLD_CONFIGURATION_TAG +
667                         "> element has been deprecated.");
668                 LOGGER.warn("Use the <" + CONFIGURATION_TAG + "> element instead.");
669             } else {
670                 LOGGER.error("DOM element is - not a <" + CONFIGURATION_TAG + "> element.");
671                 return;
672             }
673         }
674 
675 
676         String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
677 
678         LOGGER.debug("debug attribute= \"" + debugAttrib + "\".");
679         // if the log4j.dtd is not specified in the XML file, then the
680         // "debug" attribute is returned as the empty string.
681         String status = "error";
682         if (!debugAttrib.equals("") && !debugAttrib.equals("null")) {
683             status = OptionConverter.toBoolean(debugAttrib, true) ? "debug" : "error";
684 
685         } else {
686             LOGGER.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
687         }
688 
689         String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
690         if (!confDebug.equals("") && !confDebug.equals("null")) {
691             LOGGER.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
692             LOGGER.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
693             status = OptionConverter.toBoolean(confDebug, true) ? "debug" : "error";
694         }
695 
696         final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status);
697         statusConfig.initialize();
698 
699         forEachElement(element.getChildNodes(), (currentElement) -> {
700             switch (currentElement.getTagName()) {
701                 case CATEGORY: case LOGGER_ELEMENT:
702                     parseCategory(currentElement);
703                     break;
704                 case ROOT_TAG:
705                     parseRoot(currentElement);
706                     break;
707                 case RENDERER_TAG:
708                     LOGGER.warn("Renderers are not supported by Log4j 2 and will be ignored.");
709                     break;
710                 case THROWABLE_RENDERER_TAG:
711                     LOGGER.warn("Throwable Renderers are not supported by Log4j 2 and will be ignored.");
712                     break;
713                 case CATEGORY_FACTORY_TAG: case LOGGER_FACTORY_TAG:
714                     LOGGER.warn("Log4j 1 Logger factories are not supported by Log4j 2 and will be ignored.");
715                     break;
716                 case APPENDER_TAG:
717                     Appender appender = parseAppender(currentElement);
718                     appenderMap.put(appender.getName(), appender);
719                     if (appender instanceof AppenderWrapper) {
720                         addAppender(((AppenderWrapper) appender).getAppender());
721                     } else {
722                         addAppender(new AppenderAdapter(appender).getAdapter());
723                     }
724                     break;
725                 default:
726                     quietParseUnrecognizedElement(null, currentElement, props);
727             }
728         });
729     }
730 
731     private String subst(final String value) {
732         return getStrSubstitutor().replace(value);
733     }
734 
735     public static void forEachElement(NodeList list, Consumer<Element> consumer) {
736         final int length = list.getLength();
737         for (int loop = 0; loop < length; loop++) {
738             Node currentNode = list.item(loop);
739 
740             if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
741                 Element currentElement = (Element) currentNode;
742                 consumer.accept(currentElement);
743             }
744         }
745     }
746 
747     private interface ParseAction {
748         Document parse(final DocumentBuilder parser) throws SAXException, IOException;
749     }
750 
751     private static class SAXErrorHandler implements org.xml.sax.ErrorHandler {
752         private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
753 
754         public void error(final SAXParseException ex) {
755             emitMessage("Continuable parsing error ", ex);
756         }
757 
758         public void fatalError(final SAXParseException ex) {
759             emitMessage("Fatal parsing error ", ex);
760         }
761 
762         public void warning(final SAXParseException ex) {
763             emitMessage("Parsing warning ", ex);
764         }
765 
766         private static void emitMessage(final String msg, final SAXParseException ex) {
767             LOGGER.warn("{} {} and column {}", msg, ex.getLineNumber(), ex.getColumnNumber());
768             LOGGER.warn(ex.getMessage(), ex.getException());
769         }
770     }
771 
772     private static class ConsumerException extends RuntimeException {
773 
774         ConsumerException(Exception ex) {
775             super(ex);
776         }
777     }
778 }