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