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.logging.log4j.core.config;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.Serializable;
023import java.lang.ref.WeakReference;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Objects;
033import java.util.Set;
034import java.util.concurrent.ConcurrentHashMap;
035import java.util.concurrent.ConcurrentMap;
036import java.util.concurrent.CopyOnWriteArrayList;
037import java.util.concurrent.TimeUnit;
038
039import org.apache.logging.log4j.Level;
040import org.apache.logging.log4j.core.Appender;
041import org.apache.logging.log4j.core.Filter;
042import org.apache.logging.log4j.core.Layout;
043import org.apache.logging.log4j.core.LifeCycle2;
044import org.apache.logging.log4j.core.LogEvent;
045import org.apache.logging.log4j.core.LoggerContext;
046import org.apache.logging.log4j.core.Version;
047import org.apache.logging.log4j.core.appender.AsyncAppender;
048import org.apache.logging.log4j.core.appender.ConsoleAppender;
049import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
050import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate;
051import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor;
052import org.apache.logging.log4j.core.config.arbiters.Arbiter;
053import org.apache.logging.log4j.core.config.arbiters.SelectArbiter;
054import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
055import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
056import org.apache.logging.log4j.core.config.plugins.util.PluginType;
057import org.apache.logging.log4j.core.filter.AbstractFilterable;
058import org.apache.logging.log4j.core.layout.PatternLayout;
059import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
060import org.apache.logging.log4j.core.lookup.Interpolator;
061import org.apache.logging.log4j.core.lookup.PropertiesLookup;
062import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor;
063import org.apache.logging.log4j.core.lookup.StrLookup;
064import org.apache.logging.log4j.core.lookup.StrSubstitutor;
065import org.apache.logging.log4j.core.net.Advertiser;
066import org.apache.logging.log4j.core.script.AbstractScript;
067import org.apache.logging.log4j.core.script.ScriptManager;
068import org.apache.logging.log4j.core.script.ScriptRef;
069import org.apache.logging.log4j.core.util.Constants;
070import org.apache.logging.log4j.core.util.DummyNanoClock;
071import org.apache.logging.log4j.core.util.Loader;
072import org.apache.logging.log4j.core.util.NameUtil;
073import org.apache.logging.log4j.core.util.NanoClock;
074import org.apache.logging.log4j.core.util.Source;
075import org.apache.logging.log4j.core.util.WatchManager;
076import org.apache.logging.log4j.core.util.Watcher;
077import org.apache.logging.log4j.core.util.WatcherFactory;
078import org.apache.logging.log4j.util.PropertiesUtil;
079
080/**
081 * The base Configuration. Many configuration implementations will extend this class.
082 */
083public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
084
085    private static final int BUF_SIZE = 16384;
086
087    /**
088     * The root node of the configuration.
089     */
090    protected Node rootNode;
091
092    /**
093     * Listeners for configuration changes.
094     */
095    protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<>();
096
097    /**
098     * Packages found in configuration "packages" attribute.
099     */
100    protected final List<String> pluginPackages = new ArrayList<>();
101
102    /**
103     * The plugin manager.
104     */
105    protected PluginManager pluginManager;
106
107    /**
108     * Shutdown hook is enabled by default.
109     */
110    protected boolean isShutdownHookEnabled = true;
111
112    /**
113     * Shutdown timeout in milliseconds.
114     */
115    protected long shutdownTimeoutMillis = 0;
116
117    /**
118     * The Script manager.
119     */
120    protected ScriptManager scriptManager;
121
122    /**
123     * The Advertiser which exposes appender configurations to external systems.
124     */
125    private Advertiser advertiser = new DefaultAdvertiser();
126    private Node advertiserNode = null;
127    private Object advertisement;
128    private String name;
129    private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<>();
130    private ConcurrentMap<String, LoggerConfig> loggerConfigs = new ConcurrentHashMap<>();
131    private List<CustomLevelConfig> customLevels = Collections.emptyList();
132    private final ConcurrentMap<String, String> propertyMap = new ConcurrentHashMap<>();
133    private final StrLookup tempLookup = new Interpolator(propertyMap);
134    private final StrSubstitutor subst = new RuntimeStrSubstitutor(tempLookup);
135    private final StrSubstitutor configurationStrSubstitutor = new ConfigurationStrSubstitutor(subst);
136    private LoggerConfig root = new LoggerConfig();
137    private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<>();
138    private final ConfigurationSource configurationSource;
139    private final ConfigurationScheduler configurationScheduler = new ConfigurationScheduler();
140    private final WatchManager watchManager = new WatchManager(configurationScheduler);
141    private AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor;
142    private NanoClock nanoClock = new DummyNanoClock();
143    private final WeakReference<LoggerContext> loggerContext;
144
145    /**
146     * Constructor.
147     */
148    protected AbstractConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) {
149        this.loggerContext = new WeakReference<>(loggerContext);
150        // The loggerContext is null for the NullConfiguration class.
151        // this.loggerContext = new WeakReference(Objects.requireNonNull(loggerContext, "loggerContext is null"));
152        this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null");
153        componentMap.put(Configuration.CONTEXT_PROPERTIES, propertyMap);
154        pluginManager = new PluginManager(Node.CATEGORY);
155        rootNode = new Node();
156        setState(State.INITIALIZING);
157
158    }
159
160    @Override
161    public ConfigurationSource getConfigurationSource() {
162        return configurationSource;
163    }
164
165    @Override
166    public List<String> getPluginPackages() {
167        return pluginPackages;
168    }
169
170    @Override
171    public Map<String, String> getProperties() {
172        return propertyMap;
173    }
174
175    @Override
176    public ScriptManager getScriptManager() {
177        return scriptManager;
178    }
179
180    public void setScriptManager(final ScriptManager scriptManager) {
181        this.scriptManager = scriptManager;
182    }
183
184    public PluginManager getPluginManager() {
185        return pluginManager;
186    }
187
188    public void setPluginManager(final PluginManager pluginManager) {
189        this.pluginManager = pluginManager;
190    }
191
192    @Override
193    public WatchManager getWatchManager() {
194        return watchManager;
195    }
196
197    @Override
198    public ConfigurationScheduler getScheduler() {
199        return configurationScheduler;
200    }
201
202    public Node getRootNode() {
203        return rootNode;
204    }
205
206    @Override
207    public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() {
208        // lazily instantiate only when requested by AsyncLoggers:
209        // loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath
210        if (asyncLoggerConfigDisruptor == null) {
211            asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor();
212        }
213        return asyncLoggerConfigDisruptor;
214    }
215
216    /**
217     * Initialize the configuration.
218     */
219    @Override
220    public void initialize() {
221        LOGGER.debug(Version.getProductString() + " initializing configuration {}", this);
222        subst.setConfiguration(this);
223        configurationStrSubstitutor.setConfiguration(this);
224        try {
225            scriptManager = new ScriptManager(this, watchManager);
226        } catch (final LinkageError | Exception e) {
227            // LOG4J2-1920 ScriptEngineManager is not available in Android
228            LOGGER.info("Cannot initialize scripting support because this JRE does not support it.", e);
229        }
230        pluginManager.collectPlugins(pluginPackages);
231        final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
232        levelPlugins.collectPlugins(pluginPackages);
233        final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
234        if (plugins != null) {
235            for (final PluginType<?> type : plugins.values()) {
236                try {
237                    // Cause the class to be initialized if it isn't already.
238                    Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
239                } catch (final Exception e) {
240                    LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
241                            .getSimpleName(), e);
242                }
243            }
244        }
245        setup();
246        setupAdvertisement();
247        doConfigure();
248        setState(State.INITIALIZED);
249        LOGGER.debug("Configuration {} initialized", this);
250    }
251
252    protected void initializeWatchers(Reconfigurable reconfigurable, ConfigurationSource configSource,
253        int monitorIntervalSeconds) {
254        if (configSource.getFile() != null || configSource.getURL() != null) {
255            if (monitorIntervalSeconds > 0) {
256                watchManager.setIntervalSeconds(monitorIntervalSeconds);
257                if (configSource.getFile() != null) {
258                    final Source cfgSource = new Source(configSource);
259                    final long lastModifeid = configSource.getFile().lastModified();
260                    final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable,
261                        listeners, lastModifeid);
262                    watchManager.watch(cfgSource, watcher);
263                } else if (configSource.getURL() != null) {
264                    monitorSource(reconfigurable, configSource);
265                }
266            } else if (watchManager.hasEventListeners() && configSource.getURL() != null
267                && monitorIntervalSeconds >= 0) {
268                monitorSource(reconfigurable, configSource);
269            }
270        }
271    }
272
273    private void monitorSource(Reconfigurable reconfigurable, ConfigurationSource configSource) {
274                if (configSource.getLastModified() > 0) {
275                        final Source cfgSource = new Source(configSource);
276                        final Watcher watcher = WatcherFactory.getInstance(pluginPackages)
277                                .newWatcher(cfgSource, this, reconfigurable, listeners, configSource.getLastModified());
278                        if (watcher != null) {
279                                watchManager.watch(cfgSource, watcher);
280                        }
281                } else {
282                        LOGGER.info("{} does not support dynamic reconfiguration", configSource.getURI());
283                }
284        }
285
286        /**
287     * Start the configuration.
288     */
289    @Override
290    public void start() {
291        // Preserve the prior behavior of initializing during start if not initialized.
292        if (getState().equals(State.INITIALIZING)) {
293            initialize();
294        }
295        LOGGER.debug("Starting configuration {}", this);
296        this.setStarting();
297        if (watchManager.getIntervalSeconds() >= 0) {
298            watchManager.start();
299        }
300        if (hasAsyncLoggers()) {
301            asyncLoggerConfigDisruptor.start();
302        }
303        final Set<LoggerConfig> alreadyStarted = new HashSet<>();
304        for (final LoggerConfig logger : loggerConfigs.values()) {
305            logger.start();
306            alreadyStarted.add(logger);
307        }
308        for (final Appender appender : appenders.values()) {
309            appender.start();
310        }
311        if (!alreadyStarted.contains(root)) { // LOG4J2-392
312            root.start(); // LOG4J2-336
313        }
314        super.start();
315        LOGGER.debug("Started configuration {} OK.", this);
316    }
317
318    private boolean hasAsyncLoggers() {
319        if (root instanceof AsyncLoggerConfig) {
320            return true;
321        }
322        for (final LoggerConfig logger : loggerConfigs.values()) {
323            if (logger instanceof AsyncLoggerConfig) {
324                return true;
325            }
326        }
327        return false;
328    }
329
330    /**
331     * Tear down the configuration.
332     */
333    @Override
334    public boolean stop(final long timeout, final TimeUnit timeUnit) {
335        this.setStopping();
336        super.stop(timeout, timeUnit, false);
337        LOGGER.trace("Stopping {}...", this);
338
339        // Stop the components that are closest to the application first:
340        // 1. Notify all LoggerConfigs' ReliabilityStrategy that the configuration will be stopped.
341        // 2. Stop the LoggerConfig objects (this may stop nested Filters)
342        // 3. Stop the AsyncLoggerConfigDelegate. This shuts down the AsyncLoggerConfig Disruptor
343        //    and waits until all events in the RingBuffer have been processed.
344        // 4. Stop all AsyncAppenders. This shuts down the associated thread and
345        //    waits until all events in the queue have been processed. (With optional timeout.)
346        // 5. Notify all LoggerConfigs' ReliabilityStrategy that appenders will be stopped.
347        //    This guarantees that any event received by a LoggerConfig before reconfiguration
348        //    are passed on to the Appenders before the Appenders are stopped.
349        // 6. Stop the remaining running Appenders. (It should now be safe to do so.)
350        // 7. Notify all LoggerConfigs that their Appenders can be cleaned up.
351
352        for (final LoggerConfig loggerConfig : loggerConfigs.values()) {
353            loggerConfig.getReliabilityStrategy().beforeStopConfiguration(this);
354        }
355        root.getReliabilityStrategy().beforeStopConfiguration(this);
356
357        final String cls = getClass().getSimpleName();
358        LOGGER.trace("{} notified {} ReliabilityStrategies that config will be stopped.", cls, loggerConfigs.size()
359                + 1);
360
361        if (!loggerConfigs.isEmpty()) {
362            LOGGER.trace("{} stopping {} LoggerConfigs.", cls, loggerConfigs.size());
363            for (final LoggerConfig logger : loggerConfigs.values()) {
364                logger.stop(timeout, timeUnit);
365            }
366        }
367        LOGGER.trace("{} stopping root LoggerConfig.", cls);
368        if (!root.isStopped()) {
369            root.stop(timeout, timeUnit);
370        }
371
372        if (hasAsyncLoggers()) {
373            LOGGER.trace("{} stopping AsyncLoggerConfigDisruptor.", cls);
374            asyncLoggerConfigDisruptor.stop(timeout, timeUnit);
375        }
376
377        LOGGER.trace("{} notifying ReliabilityStrategies that appenders will be stopped.", cls);
378        for (final LoggerConfig loggerConfig : loggerConfigs.values()) {
379            loggerConfig.getReliabilityStrategy().beforeStopAppenders();
380        }
381        root.getReliabilityStrategy().beforeStopAppenders();
382
383        // Stop the appenders in reverse order in case they still have activity.
384        final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]);
385        final List<Appender> async = getAsyncAppenders(array);
386        if (!async.isEmpty()) {
387            // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first
388            LOGGER.trace("{} stopping {} AsyncAppenders.", cls, async.size());
389            for (final Appender appender : async) {
390                if (appender instanceof LifeCycle2) {
391                    ((LifeCycle2) appender).stop(timeout, timeUnit);
392                } else {
393                    appender.stop();
394                }
395            }
396        }
397
398        LOGGER.trace("{} stopping remaining Appenders.", cls);
399        int appenderCount = 0;
400        for (int i = array.length - 1; i >= 0; --i) {
401            if (array[i].isStarted()) { // then stop remaining Appenders
402                if (array[i] instanceof LifeCycle2) {
403                    ((LifeCycle2) array[i]).stop(timeout, timeUnit);
404                } else {
405                    array[i].stop();
406                }
407                appenderCount++;
408            }
409        }
410        LOGGER.trace("{} stopped {} remaining Appenders.", cls, appenderCount);
411
412        LOGGER.trace("{} cleaning Appenders from {} LoggerConfigs.", cls, loggerConfigs.size() + 1);
413        for (final LoggerConfig loggerConfig : loggerConfigs.values()) {
414
415            // LOG4J2-520, LOG4J2-392:
416            // Important: do not clear appenders until after all AsyncLoggerConfigs
417            // have been stopped! Stopping the last AsyncLoggerConfig will
418            // shut down the disruptor and wait for all enqueued events to be processed.
419            // Only *after this* the appenders can be cleared or events will be lost.
420            loggerConfig.clearAppenders();
421        }
422        root.clearAppenders();
423
424        if (watchManager.isStarted()) {
425            watchManager.stop(timeout, timeUnit);
426        }
427        configurationScheduler.stop(timeout, timeUnit);
428
429        if (advertiser != null && advertisement != null) {
430            advertiser.unadvertise(advertisement);
431        }
432        setStopped();
433        LOGGER.debug("Stopped {} OK", this);
434        return true;
435    }
436
437    private List<Appender> getAsyncAppenders(final Appender[] all) {
438        final List<Appender> result = new ArrayList<>();
439        for (int i = all.length - 1; i >= 0; --i) {
440            if (all[i] instanceof AsyncAppender) {
441                result.add(all[i]);
442            }
443        }
444        return result;
445    }
446
447    @Override
448    public boolean isShutdownHookEnabled() {
449        return isShutdownHookEnabled;
450    }
451
452    @Override
453    public long getShutdownTimeoutMillis() {
454        return shutdownTimeoutMillis;
455    }
456
457    public void setup() {
458        // default does nothing, subclasses do work.
459    }
460
461    protected Level getDefaultStatus() {
462        final String statusLevel = PropertiesUtil.getProperties().getStringProperty(
463                Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name());
464        try {
465            return Level.toLevel(statusLevel);
466        } catch (final Exception ex) {
467            return Level.ERROR;
468        }
469    }
470
471    protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource,
472            final byte[] buffer, final String contentType) {
473        if (advertiserString != null) {
474            final Node node = new Node(null, advertiserString, null);
475            final Map<String, String> attributes = node.getAttributes();
476            attributes.put("content", new String(buffer));
477            attributes.put("contentType", contentType);
478            attributes.put("name", "configuration");
479            if (configSource.getLocation() != null) {
480                attributes.put("location", configSource.getLocation());
481            }
482            advertiserNode = node;
483        }
484    }
485
486    private void setupAdvertisement() {
487        if (advertiserNode != null) {
488            final String nodeName = advertiserNode.getName();
489            final PluginType<?> type = pluginManager.getPluginType(nodeName);
490            if (type != null) {
491                final Class<? extends Advertiser> clazz = type.getPluginClass().asSubclass(Advertiser.class);
492                try {
493                    advertiser = clazz.newInstance();
494                    advertisement = advertiser.advertise(advertiserNode.getAttributes());
495                } catch (final InstantiationException e) {
496                    LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", nodeName, e);
497                } catch (final IllegalAccessException e) {
498                    LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", nodeName, e);
499                }
500            }
501        }
502    }
503
504    @SuppressWarnings("unchecked")
505    @Override
506    public <T> T getComponent(final String componentName) {
507        return (T) componentMap.get(componentName);
508    }
509
510    @Override
511    public void addComponent(final String componentName, final Object obj) {
512        componentMap.putIfAbsent(componentName, obj);
513    }
514
515    protected void preConfigure(final Node node) {
516        try {
517            for (final Node child : node.getChildren()) {
518                if (child.getType() == null) {
519                    LOGGER.error("Unable to locate plugin type for " + child.getName());
520                    continue;
521                }
522                final Class<?> clazz = child.getType().getPluginClass();
523                if (clazz.isAnnotationPresent(Scheduled.class)) {
524                    configurationScheduler.incrementScheduledItems();
525                }
526                preConfigure(child);
527            }
528        } catch (final Exception ex) {
529            LOGGER.error("Error capturing node data for node " + node.getName(), ex);
530        }
531    }
532
533    /**
534     * Process conditions by evaluating them and including the children of conditions that are true
535     * and discarding those that are not.
536     * @param node The node to evaluate.
537     */
538    protected void processConditionals(final Node node) {
539        try {
540            List<Node> addList = new ArrayList<>();
541            List<Node> removeList = new ArrayList<>();
542            for (final Node child : node.getChildren()) {
543                final PluginType<?> type = child.getType();
544                if (type != null && Arbiter.ELEMENT_TYPE.equals(type.getElementName())) {
545                    final Class<?> clazz = type.getPluginClass();
546                    if (SelectArbiter.class.isAssignableFrom(clazz)) {
547                        removeList.add(child);
548                        addList.addAll(processSelect(child, type));
549                    } else if (Arbiter.class.isAssignableFrom(clazz)) {
550                        removeList.add(child);
551                        try {
552                            Arbiter condition = (Arbiter) createPluginObject(type, child, null);
553                            if (condition.isCondition()) {
554                                addList.addAll(child.getChildren());
555                                processConditionals(child);
556                            }
557                        } catch (final Exception inner) {
558                            LOGGER.error("Exception processing {}: Ignoring and including children",
559                                    type.getPluginClass());
560                            processConditionals(child);
561                        }
562                    } else {
563                        LOGGER.error("Encountered Condition Plugin that does not implement Condition: {}",
564                                child.getName());
565                        processConditionals(child);
566                    }
567                } else {
568                    processConditionals(child);
569                }
570            }
571            if (!removeList.isEmpty()) {
572                List<Node> children = node.getChildren();
573                children.removeAll(removeList);
574                children.addAll(addList);
575                for (Node grandChild : addList) {
576                    grandChild.setParent(node);
577                }
578            }
579        } catch (final Exception ex) {
580            LOGGER.error("Error capturing node data for node " + node.getName(), ex);
581        }
582    }
583
584    /**
585     * Handle Select nodes. This finds the first child condition that returns true and attaches its children
586     * to the parent of the Select Node. Other Nodes are discarded.
587     * @param selectNode The Select Node.
588     * @param type The PluginType of the Select Node.
589     * @return The list of Nodes to be added to the parent.
590     */
591    protected List<Node> processSelect(Node selectNode, PluginType<?> type) {
592        List<Node> addList = new ArrayList<>();
593        SelectArbiter select = (SelectArbiter) createPluginObject(type, selectNode, null);
594        List<Arbiter> conditions = new ArrayList<>();
595        for (Node child : selectNode.getChildren()) {
596            PluginType<?> nodeType = child.getType();
597            if (nodeType != null) {
598                if (Arbiter.class.isAssignableFrom(nodeType.getPluginClass())) {
599                    Arbiter condition = (Arbiter) createPluginObject(nodeType, child, null);
600                    conditions.add(condition);
601                    child.setObject(condition);
602                } else {
603                    LOGGER.error("Invalid Node {} for Select. Must be a Condition",
604                            child.getName());
605                }
606            } else {
607                LOGGER.error("No PluginType for node {}", child.getName());
608            }
609        }
610        Arbiter condition = select.evaluateConditions(conditions);
611        if (condition != null) {
612            for (Node child : selectNode.getChildren()) {
613                if (condition == child.getObject()) {
614                    addList.addAll(child.getChildren());
615                    processConditionals(child);
616                }
617            }
618        }
619        return addList;
620    }
621
622    protected void doConfigure() {
623        processConditionals(rootNode);
624        preConfigure(rootNode);
625        configurationScheduler.start();
626        if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
627            final Node first = rootNode.getChildren().get(0);
628            createConfiguration(first, null);
629            if (first.getObject() != null) {
630                StrLookup lookup = (StrLookup) first.getObject();
631                subst.setVariableResolver(lookup);
632                configurationStrSubstitutor.setVariableResolver(lookup);
633            }
634        } else {
635            final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
636            final StrLookup lookup = map == null ? null : new PropertiesLookup(map);
637            Interpolator interpolator = new Interpolator(lookup, pluginPackages);
638            subst.setVariableResolver(interpolator);
639            configurationStrSubstitutor.setVariableResolver(interpolator);
640        }
641
642        boolean setLoggers = false;
643        boolean setRoot = false;
644        for (final Node child : rootNode.getChildren()) {
645            if (child.getName().equalsIgnoreCase("Properties")) {
646                if (tempLookup == subst.getVariableResolver()) {
647                    LOGGER.error("Properties declaration must be the first element in the configuration");
648                }
649                continue;
650            }
651            createConfiguration(child, null);
652            if (child.getObject() == null) {
653                continue;
654            }
655            if (child.getName().equalsIgnoreCase("Scripts")) {
656                for (final AbstractScript script : child.getObject(AbstractScript[].class)) {
657                    if (script instanceof ScriptRef) {
658                        LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references",
659                                script.getName());
660                    } else if (scriptManager != null) {
661                        scriptManager.addScript(script);
662                    }
663                }
664            } else if (child.getName().equalsIgnoreCase("Appenders")) {
665                appenders = child.getObject();
666            } else if (child.isInstanceOf(Filter.class)) {
667                addFilter(child.getObject(Filter.class));
668            } else if (child.getName().equalsIgnoreCase("Loggers")) {
669                final Loggers l = child.getObject();
670                loggerConfigs = l.getMap();
671                setLoggers = true;
672                if (l.getRoot() != null) {
673                    root = l.getRoot();
674                    setRoot = true;
675                }
676            } else if (child.getName().equalsIgnoreCase("CustomLevels")) {
677                customLevels = child.getObject(CustomLevels.class).getCustomLevels();
678            } else if (child.isInstanceOf(CustomLevelConfig.class)) {
679                final List<CustomLevelConfig> copy = new ArrayList<>(customLevels);
680                copy.add(child.getObject(CustomLevelConfig.class));
681                customLevels = copy;
682            } else {
683                final List<String> expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"",
684                        "\"Scripts\"", "\"CustomLevels\"");
685                LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.",
686                        child.getName(), child.getObject().getClass().getName(), expected);
687            }
688        }
689
690        if (!setLoggers) {
691            LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
692            setToDefault();
693            return;
694        } else if (!setRoot) {
695            LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
696            setToDefault();
697            // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
698        }
699
700        for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
701            final LoggerConfig loggerConfig = entry.getValue();
702            for (final AppenderRef ref : loggerConfig.getAppenderRefs()) {
703                final Appender app = appenders.get(ref.getRef());
704                if (app != null) {
705                    loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter());
706                } else {
707                    LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(),
708                            loggerConfig);
709                }
710            }
711
712        }
713
714        setParents();
715    }
716
717    protected void setToDefault() {
718        // LOG4J2-1176 facilitate memory leak investigation
719        setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
720        final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
721                .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
722                .withConfiguration(this)
723                .build();
724        final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
725        appender.start();
726        addAppender(appender);
727        final LoggerConfig rootLoggerConfig = getRootLogger();
728        rootLoggerConfig.addAppender(appender, null, null);
729
730        final Level defaultLevel = Level.ERROR;
731        final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL,
732                defaultLevel.name());
733        final Level level = Level.valueOf(levelName);
734        rootLoggerConfig.setLevel(level != null ? level : defaultLevel);
735    }
736
737    /**
738     * Set the name of the configuration.
739     *
740     * @param name The name.
741     */
742    public void setName(final String name) {
743        this.name = name;
744    }
745
746    /**
747     * Returns the name of the configuration.
748     *
749     * @return the name of the configuration.
750     */
751    @Override
752    public String getName() {
753        return name;
754    }
755
756    /**
757     * Add a listener for changes on the configuration.
758     *
759     * @param listener The ConfigurationListener to add.
760     */
761    @Override
762    public void addListener(final ConfigurationListener listener) {
763        listeners.add(listener);
764    }
765
766    /**
767     * Remove a ConfigurationListener.
768     *
769     * @param listener The ConfigurationListener to remove.
770     */
771    @Override
772    public void removeListener(final ConfigurationListener listener) {
773        listeners.remove(listener);
774    }
775
776    /**
777     * Returns the Appender with the specified name.
778     *
779     * @param appenderName The name of the Appender.
780     * @return the Appender with the specified name or null if the Appender cannot be located.
781     */
782    @Override
783    @SuppressWarnings("unchecked")
784    public <T extends Appender> T getAppender(final String appenderName) {
785        return appenderName != null ? (T) appenders.get(appenderName) : null;
786    }
787
788    /**
789     * Returns a Map containing all the Appenders and their name.
790     *
791     * @return A Map containing each Appender's name and the Appender object.
792     */
793    @Override
794    public Map<String, Appender> getAppenders() {
795        return appenders;
796    }
797
798    /**
799     * Adds an Appender to the configuration.
800     *
801     * @param appender The Appender to add.
802     */
803    @Override
804    public void addAppender(final Appender appender) {
805        if (appender != null) {
806            appenders.putIfAbsent(appender.getName(), appender);
807        }
808    }
809
810    @Override
811    public StrSubstitutor getStrSubstitutor() {
812        return subst;
813    }
814
815    @Override
816    public StrSubstitutor getConfigurationStrSubstitutor() {
817        return configurationStrSubstitutor;
818    }
819
820    @Override
821    public void setAdvertiser(final Advertiser advertiser) {
822        this.advertiser = advertiser;
823    }
824
825    @Override
826    public Advertiser getAdvertiser() {
827        return advertiser;
828    }
829
830    /*
831     * (non-Javadoc)
832     *
833     * @see org.apache.logging.log4j.core.config.ReliabilityStrategyFactory#getReliabilityStrategy(org.apache.logging.log4j
834     * .core.config.LoggerConfig)
835     */
836    @Override
837    public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) {
838        return ReliabilityStrategyFactory.getReliabilityStrategy(loggerConfig);
839    }
840
841    /**
842     * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is
843     * being updated at the same time.
844     *
845     * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
846     *
847     * @param logger The Logger the Appender will be associated with.
848     * @param appender The Appender.
849     */
850    @Override
851    public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
852            final Appender appender) {
853        if (appender == null || logger == null) {
854            return;
855        }
856        final String loggerName = logger.getName();
857        appenders.putIfAbsent(appender.getName(), appender);
858        final LoggerConfig lc = getLoggerConfig(loggerName);
859        if (lc.getName().equals(loggerName)) {
860            lc.addAppender(appender, null, null);
861        } else {
862            final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
863            nlc.addAppender(appender, null, null);
864            nlc.setParent(lc);
865            loggerConfigs.putIfAbsent(loggerName, nlc);
866            setParents();
867            logger.getContext().updateLoggers();
868        }
869    }
870
871    /**
872     * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being
873     * updated at the same time.
874     *
875     * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
876     *
877     * @param logger The Logger the Footer will be associated with.
878     * @param filter The Filter.
879     */
880    @Override
881    public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
882        final String loggerName = logger.getName();
883        final LoggerConfig lc = getLoggerConfig(loggerName);
884        if (lc.getName().equals(loggerName)) {
885            lc.addFilter(filter);
886        } else {
887            final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
888            nlc.addFilter(filter);
889            nlc.setParent(lc);
890            loggerConfigs.putIfAbsent(loggerName, nlc);
891            setParents();
892            logger.getContext().updateLoggers();
893        }
894    }
895
896    /**
897     * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being
898     * updated at the same time.
899     *
900     * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
901     *
902     * @param logger The Logger the Appender will be associated with.
903     * @param additive True if the LoggerConfig should be additive, false otherwise.
904     */
905    @Override
906    public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) {
907        final String loggerName = logger.getName();
908        final LoggerConfig lc = getLoggerConfig(loggerName);
909        if (lc.getName().equals(loggerName)) {
910            lc.setAdditive(additive);
911        } else {
912            final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive);
913            nlc.setParent(lc);
914            loggerConfigs.putIfAbsent(loggerName, nlc);
915            setParents();
916            logger.getContext().updateLoggers();
917        }
918    }
919
920    /**
921     * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes the Appender
922     * from this appender list and then stops the appender. This method is synchronized in case an Appender with the
923     * same name is being added during the removal.
924     *
925     * @param appenderName the name of the appender to remove.
926     */
927    public synchronized void removeAppender(final String appenderName) {
928        for (final LoggerConfig logger : loggerConfigs.values()) {
929            logger.removeAppender(appenderName);
930        }
931        final Appender app = appenderName != null ? appenders.remove(appenderName) : null;
932
933        if (app != null) {
934            app.stop();
935        }
936    }
937
938    /*
939     * (non-Javadoc)
940     *
941     * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels()
942     */
943    @Override
944    public List<CustomLevelConfig> getCustomLevels() {
945        return Collections.unmodifiableList(customLevels);
946    }
947
948    /**
949     * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the package name as
950     * necessary or return the root LoggerConfig if no other matches were found.
951     *
952     * @param loggerName The Logger name.
953     * @return The located LoggerConfig.
954     */
955    @Override
956    public LoggerConfig getLoggerConfig(final String loggerName) {
957        LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
958        if (loggerConfig != null) {
959            return loggerConfig;
960        }
961        String substr = loggerName;
962        while ((substr = NameUtil.getSubName(substr)) != null) {
963            loggerConfig = loggerConfigs.get(substr);
964            if (loggerConfig != null) {
965                return loggerConfig;
966            }
967        }
968        return root;
969    }
970
971    @Override
972    public LoggerContext getLoggerContext() {
973        return loggerContext.get();
974    }
975
976    /**
977     * Returns the root Logger.
978     *
979     * @return the root Logger.
980     */
981    @Override
982    public LoggerConfig getRootLogger() {
983        return root;
984    }
985
986    /**
987     * Returns a Map of all the LoggerConfigs.
988     *
989     * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
990     */
991    @Override
992    public Map<String, LoggerConfig> getLoggers() {
993        return Collections.unmodifiableMap(loggerConfigs);
994    }
995
996    /**
997     * Returns the LoggerConfig with the specified name.
998     *
999     * @param loggerName The Logger name.
1000     * @return The LoggerConfig or null if no match was found.
1001     */
1002    public LoggerConfig getLogger(final String loggerName) {
1003        return loggerConfigs.get(loggerName);
1004    }
1005
1006    /**
1007     * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc. After addLogger is
1008     * called LoggerContext.updateLoggers must be called.
1009     *
1010     * @param loggerName The name of the Logger.
1011     * @param loggerConfig The LoggerConfig.
1012     */
1013    @Override
1014    public synchronized void addLogger(final String loggerName, final LoggerConfig loggerConfig) {
1015        loggerConfigs.putIfAbsent(loggerName, loggerConfig);
1016        setParents();
1017    }
1018
1019    /**
1020     * Remove a LoggerConfig.
1021     *
1022     * @param loggerName The name of the Logger.
1023     */
1024    @Override
1025    public synchronized void removeLogger(final String loggerName) {
1026        loggerConfigs.remove(loggerName);
1027        setParents();
1028    }
1029
1030    @Override
1031    public void createConfiguration(final Node node, final LogEvent event) {
1032        final PluginType<?> type = node.getType();
1033        if (type != null && type.isDeferChildren()) {
1034            node.setObject(createPluginObject(type, node, event));
1035        } else {
1036            for (final Node child : node.getChildren()) {
1037                createConfiguration(child, event);
1038            }
1039
1040            if (type == null) {
1041                if (node.getParent() != null) {
1042                    LOGGER.error("Unable to locate plugin for {}", node.getName());
1043                }
1044            } else {
1045                node.setObject(createPluginObject(type, node, event));
1046            }
1047        }
1048    }
1049
1050    /**
1051     * This method is used by Arbiters to create specific children.
1052     * @param type The PluginType.
1053     * @param node The Node.
1054     * @return The created object or null;
1055     */
1056    public Object createPluginObject(final PluginType<?> type, final Node node) {
1057        if (this.getState().equals(State.INITIALIZING)) {
1058            return createPluginObject(type, node, null);
1059        } else {
1060            LOGGER.warn("Plugin Object creation is not allowed after initialization");
1061            return null;
1062        }
1063    }
1064
1065    /**
1066     * Invokes a static factory method to either create the desired object or to create a builder object that creates
1067     * the desired object. In the case of a factory method, it should be annotated with
1068     * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with
1069     * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
1070     * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a
1071     * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters}
1072     * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or
1073     * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
1074     * called can create these from an array.
1075     *
1076     * Plugins can also be created using a builder class that implements
1077     * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with
1078     * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and
1079     * the various fields in the builder class should be annotated similarly to the method parameters. However, instead
1080     * of using PluginAttribute, one should use
1081     * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
1082     * specified as the default field value instead of as an additional annotation parameter.
1083     *
1084     * In either case, there are also annotations for specifying a
1085     * {@link org.apache.logging.log4j.core.config.Configuration} (
1086     * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
1087     * {@link org.apache.logging.log4j.core.config.Node} (
1088     * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}).
1089     *
1090     * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally
1091     * result in unhelpful InvocationTargetExceptions.
1092     *
1093     * @param type the type of plugin to create.
1094     * @param node the corresponding configuration node for this plugin to create.
1095     * @param event the LogEvent that spurred the creation of this plugin
1096     * @return the created plugin object or {@code null} if there was an error setting it up.
1097     * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
1098     * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor
1099     * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter
1100     */
1101    private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
1102        final Class<?> clazz = type.getPluginClass();
1103
1104        if (Map.class.isAssignableFrom(clazz)) {
1105            try {
1106                return createPluginMap(node);
1107            } catch (final Exception e) {
1108                LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e);
1109            }
1110        }
1111
1112        if (Collection.class.isAssignableFrom(clazz)) {
1113            try {
1114                return createPluginCollection(node);
1115            } catch (final Exception e) {
1116                LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e);
1117            }
1118        }
1119
1120        return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build();
1121    }
1122
1123    private static Map<String, ?> createPluginMap(final Node node) {
1124        final Map<String, Object> map = new LinkedHashMap<>();
1125        for (final Node child : node.getChildren()) {
1126            final Object object = child.getObject();
1127            map.put(child.getName(), object);
1128        }
1129        return map;
1130    }
1131
1132    private static Collection<?> createPluginCollection(final Node node) {
1133        final List<Node> children = node.getChildren();
1134        final Collection<Object> list = new ArrayList<>(children.size());
1135        for (final Node child : children) {
1136            final Object object = child.getObject();
1137            list.add(object);
1138        }
1139        return list;
1140    }
1141
1142    private void setParents() {
1143        for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
1144            final LoggerConfig logger = entry.getValue();
1145            String key = entry.getKey();
1146            if (!key.isEmpty()) {
1147                final int i = key.lastIndexOf('.');
1148                if (i > 0) {
1149                    key = key.substring(0, i);
1150                    LoggerConfig parent = getLoggerConfig(key);
1151                    if (parent == null) {
1152                        parent = root;
1153                    }
1154                    logger.setParent(parent);
1155                } else {
1156                    logger.setParent(root);
1157                }
1158            }
1159        }
1160    }
1161
1162    /**
1163     * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open after
1164     * invocation of this method.
1165     *
1166     * @param is the InputStream to read into a byte array buffer.
1167     * @return a byte array of the InputStream contents.
1168     * @throws IOException if the {@code read} method of the provided InputStream throws this exception.
1169     */
1170    protected static byte[] toByteArray(final InputStream is) throws IOException {
1171        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1172
1173        int nRead;
1174        final byte[] data = new byte[BUF_SIZE];
1175
1176        while ((nRead = is.read(data, 0, data.length)) != -1) {
1177            buffer.write(data, 0, nRead);
1178        }
1179
1180        return buffer.toByteArray();
1181    }
1182
1183    @Override
1184    public NanoClock getNanoClock() {
1185        return nanoClock;
1186    }
1187
1188    @Override
1189    public void setNanoClock(final NanoClock nanoClock) {
1190        this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock");
1191    }
1192}