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