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