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