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