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