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> properties = new ConcurrentHashMap<>();
126    private final StrLookup tempLookup = new Interpolator(properties);
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, properties);
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 properties;
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 (T) appenders.get(appenderName);
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        appenders.putIfAbsent(appender.getName(), appender);
670    }
671
672    @Override
673    public StrSubstitutor getStrSubstitutor() {
674        return subst;
675    }
676
677    @Override
678    public void setAdvertiser(final Advertiser advertiser) {
679        this.advertiser = advertiser;
680    }
681
682    @Override
683    public Advertiser getAdvertiser() {
684        return advertiser;
685    }
686
687    /*
688     * (non-Javadoc)
689     *
690     * @see org.apache.logging.log4j.core.config.ReliabilityStrategyFactory#getReliabilityStrategy(org.apache.logging.log4j
691     * .core.config.LoggerConfig)
692     */
693    @Override
694    public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) {
695        return ReliabilityStrategyFactory.getReliabilityStrategy(loggerConfig);
696    }
697
698    /**
699     * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is
700     * being updated at the same time.
701     *
702     * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
703     *
704     * @param logger The Logger the Appender will be associated with.
705     * @param appender The Appender.
706     */
707    @Override
708    public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
709            final Appender appender) {
710        final String loggerName = logger.getName();
711        appenders.putIfAbsent(appender.getName(), appender);
712        final LoggerConfig lc = getLoggerConfig(loggerName);
713        if (lc.getName().equals(loggerName)) {
714            lc.addAppender(appender, null, null);
715        } else {
716            final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
717            nlc.addAppender(appender, null, null);
718            nlc.setParent(lc);
719            loggerConfigs.putIfAbsent(loggerName, nlc);
720            setParents();
721            logger.getContext().updateLoggers();
722        }
723    }
724
725    /**
726     * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being
727     * updated at the same time.
728     *
729     * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
730     *
731     * @param logger The Logger the Footer will be associated with.
732     * @param filter The Filter.
733     */
734    @Override
735    public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
736        final String loggerName = logger.getName();
737        final LoggerConfig lc = getLoggerConfig(loggerName);
738        if (lc.getName().equals(loggerName)) {
739            lc.addFilter(filter);
740        } else {
741            final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
742            nlc.addFilter(filter);
743            nlc.setParent(lc);
744            loggerConfigs.putIfAbsent(loggerName, nlc);
745            setParents();
746            logger.getContext().updateLoggers();
747        }
748    }
749
750    /**
751     * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being
752     * updated at the same time.
753     *
754     * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
755     *
756     * @param logger The Logger the Appender will be associated with.
757     * @param additive True if the LoggerConfig should be additive, false otherwise.
758     */
759    @Override
760    public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) {
761        final String loggerName = logger.getName();
762        final LoggerConfig lc = getLoggerConfig(loggerName);
763        if (lc.getName().equals(loggerName)) {
764            lc.setAdditive(additive);
765        } else {
766            final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive);
767            nlc.setParent(lc);
768            loggerConfigs.putIfAbsent(loggerName, nlc);
769            setParents();
770            logger.getContext().updateLoggers();
771        }
772    }
773
774    /**
775     * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes the Appender
776     * from this appender list and then stops the appender. This method is synchronized in case an Appender with the
777     * same name is being added during the removal.
778     *
779     * @param appenderName the name of the appender to remove.
780     */
781    public synchronized void removeAppender(final String appenderName) {
782        for (final LoggerConfig logger : loggerConfigs.values()) {
783            logger.removeAppender(appenderName);
784        }
785        final Appender app = appenders.remove(appenderName);
786
787        if (app != null) {
788            app.stop();
789        }
790    }
791
792    /*
793     * (non-Javadoc)
794     *
795     * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels()
796     */
797    @Override
798    public List<CustomLevelConfig> getCustomLevels() {
799        return Collections.unmodifiableList(customLevels);
800    }
801
802    /**
803     * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the package name as
804     * necessary or return the root LoggerConfig if no other matches were found.
805     *
806     * @param loggerName The Logger name.
807     * @return The located LoggerConfig.
808     */
809    @Override
810    public LoggerConfig getLoggerConfig(final String loggerName) {
811        LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
812        if (loggerConfig != null) {
813            return loggerConfig;
814        }
815        String substr = loggerName;
816        while ((substr = NameUtil.getSubName(substr)) != null) {
817            loggerConfig = loggerConfigs.get(substr);
818            if (loggerConfig != null) {
819                return loggerConfig;
820            }
821        }
822        return root;
823    }
824
825    @Override
826    public LoggerContext getLoggerContext() {
827        return loggerContext.get();
828    }
829
830    /**
831     * Returns the root Logger.
832     *
833     * @return the root Logger.
834     */
835    @Override
836    public LoggerConfig getRootLogger() {
837        return root;
838    }
839
840    /**
841     * Returns a Map of all the LoggerConfigs.
842     *
843     * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
844     */
845    @Override
846    public Map<String, LoggerConfig> getLoggers() {
847        return Collections.unmodifiableMap(loggerConfigs);
848    }
849
850    /**
851     * Returns the LoggerConfig with the specified name.
852     *
853     * @param loggerName The Logger name.
854     * @return The LoggerConfig or null if no match was found.
855     */
856    public LoggerConfig getLogger(final String loggerName) {
857        return loggerConfigs.get(loggerName);
858    }
859
860    /**
861     * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc. After addLogger is
862     * called LoggerContext.updateLoggers must be called.
863     *
864     * @param loggerName The name of the Logger.
865     * @param loggerConfig The LoggerConfig.
866     */
867    @Override
868    public synchronized void addLogger(final String loggerName, final LoggerConfig loggerConfig) {
869        loggerConfigs.putIfAbsent(loggerName, loggerConfig);
870        setParents();
871    }
872
873    /**
874     * Remove a LoggerConfig.
875     *
876     * @param loggerName The name of the Logger.
877     */
878    @Override
879    public synchronized void removeLogger(final String loggerName) {
880        loggerConfigs.remove(loggerName);
881        setParents();
882    }
883
884    @Override
885    public void createConfiguration(final Node node, final LogEvent event) {
886        final PluginType<?> type = node.getType();
887        if (type != null && type.isDeferChildren()) {
888            node.setObject(createPluginObject(type, node, event));
889        } else {
890            for (final Node child : node.getChildren()) {
891                createConfiguration(child, event);
892            }
893
894            if (type == null) {
895                if (node.getParent() != null) {
896                    LOGGER.error("Unable to locate plugin for {}", node.getName());
897                }
898            } else {
899                node.setObject(createPluginObject(type, node, event));
900            }
901        }
902    }
903
904    /**
905     * Invokes a static factory method to either create the desired object or to create a builder object that creates
906     * the desired object. In the case of a factory method, it should be annotated with
907     * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with
908     * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
909     * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a
910     * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters}
911     * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or
912     * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
913     * called can create these from an array.
914     *
915     * Plugins can also be created using a builder class that implements
916     * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with
917     * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and
918     * the various fields in the builder class should be annotated similarly to the method parameters. However, instead
919     * of using PluginAttribute, one should use
920     * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
921     * specified as the default field value instead of as an additional annotation parameter.
922     *
923     * In either case, there are also annotations for specifying a
924     * {@link org.apache.logging.log4j.core.config.Configuration} (
925     * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
926     * {@link org.apache.logging.log4j.core.config.Node} (
927     * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}).
928     *
929     * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally
930     * result in unhelpful InvocationTargetExceptions.
931     *
932     * @param type the type of plugin to create.
933     * @param node the corresponding configuration node for this plugin to create.
934     * @param event the LogEvent that spurred the creation of this plugin
935     * @return the created plugin object or {@code null} if there was an error setting it up.
936     * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
937     * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor
938     * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter
939     */
940    private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
941        final Class<?> clazz = type.getPluginClass();
942
943        if (Map.class.isAssignableFrom(clazz)) {
944            try {
945                return createPluginMap(node);
946            } catch (final Exception e) {
947                LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e);
948            }
949        }
950
951        if (Collection.class.isAssignableFrom(clazz)) {
952            try {
953                return createPluginCollection(node);
954            } catch (final Exception e) {
955                LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e);
956            }
957        }
958
959        return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build();
960    }
961
962    private static Map<String, ?> createPluginMap(final Node node) {
963        final Map<String, Object> map = new LinkedHashMap<>();
964        for (final Node child : node.getChildren()) {
965            final Object object = child.getObject();
966            map.put(child.getName(), object);
967        }
968        return map;
969    }
970
971    private static Collection<?> createPluginCollection(final Node node) {
972        final List<Node> children = node.getChildren();
973        final Collection<Object> list = new ArrayList<>(children.size());
974        for (final Node child : children) {
975            final Object object = child.getObject();
976            list.add(object);
977        }
978        return list;
979    }
980
981    private void setParents() {
982        for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
983            final LoggerConfig logger = entry.getValue();
984            String key = entry.getKey();
985            if (!key.isEmpty()) {
986                final int i = key.lastIndexOf('.');
987                if (i > 0) {
988                    key = key.substring(0, i);
989                    LoggerConfig parent = getLoggerConfig(key);
990                    if (parent == null) {
991                        parent = root;
992                    }
993                    logger.setParent(parent);
994                } else {
995                    logger.setParent(root);
996                }
997            }
998        }
999    }
1000
1001    /**
1002     * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open after
1003     * invocation of this method.
1004     *
1005     * @param is the InputStream to read into a byte array buffer.
1006     * @return a byte array of the InputStream contents.
1007     * @throws IOException if the {@code read} method of the provided InputStream throws this exception.
1008     */
1009    protected static byte[] toByteArray(final InputStream is) throws IOException {
1010        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1011
1012        int nRead;
1013        final byte[] data = new byte[BUF_SIZE];
1014
1015        while ((nRead = is.read(data, 0, data.length)) != -1) {
1016            buffer.write(data, 0, nRead);
1017        }
1018
1019        return buffer.toByteArray();
1020    }
1021
1022    @Override
1023    public NanoClock getNanoClock() {
1024        return nanoClock;
1025    }
1026
1027    @Override
1028    public void setNanoClock(final NanoClock nanoClock) {
1029        this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock");
1030    }
1031}