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.Serializable;
020import java.lang.annotation.Annotation;
021import java.lang.reflect.Array;
022import java.lang.reflect.Method;
023import java.lang.reflect.Modifier;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ConcurrentMap;
032import java.util.concurrent.CopyOnWriteArrayList;
033
034import org.apache.logging.log4j.Level;
035import org.apache.logging.log4j.LogManager;
036import org.apache.logging.log4j.Logger;
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.PluginAliases;
046import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
047import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
048import org.apache.logging.log4j.core.config.plugins.PluginElement;
049import org.apache.logging.log4j.core.config.plugins.PluginFactory;
050import org.apache.logging.log4j.core.config.plugins.PluginManager;
051import org.apache.logging.log4j.core.config.plugins.PluginNode;
052import org.apache.logging.log4j.core.config.plugins.PluginType;
053import org.apache.logging.log4j.core.config.plugins.PluginValue;
054import org.apache.logging.log4j.core.filter.AbstractFilterable;
055import org.apache.logging.log4j.core.helpers.Constants;
056import org.apache.logging.log4j.core.helpers.NameUtil;
057import org.apache.logging.log4j.core.impl.Log4jContextFactory;
058import org.apache.logging.log4j.core.layout.PatternLayout;
059import org.apache.logging.log4j.core.lookup.Interpolator;
060import org.apache.logging.log4j.core.lookup.MapLookup;
061import org.apache.logging.log4j.core.lookup.StrLookup;
062import org.apache.logging.log4j.core.lookup.StrSubstitutor;
063import org.apache.logging.log4j.core.net.Advertiser;
064import org.apache.logging.log4j.core.selector.ContextSelector;
065import org.apache.logging.log4j.spi.LoggerContextFactory;
066import org.apache.logging.log4j.status.StatusLogger;
067import org.apache.logging.log4j.util.PropertiesUtil;
068
069/**
070 * The Base Configuration. Many configuration implementations will extend this class.
071 */
072public class BaseConfiguration extends AbstractFilterable implements Configuration {
073    /**
074     * Allow subclasses access to the status logger without creating another instance.
075     */
076    protected static final Logger LOGGER = StatusLogger.getLogger();
077
078    /**
079     * The root node of the configuration.
080     */
081    protected Node rootNode;
082
083    /**
084     * Listeners for configuration changes.
085     */
086    protected final List<ConfigurationListener> listeners =
087        new CopyOnWriteArrayList<ConfigurationListener>();
088
089    /**
090     * The ConfigurationMonitor that checks for configuration changes.
091     */
092    protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
093
094    /**
095     * The Advertiser which exposes appender configurations to external systems.
096     */
097    private Advertiser advertiser = new DefaultAdvertiser();
098
099    protected Map<String, String> advertisedConfiguration;
100
101    private Node advertiserNode = null;
102
103    private Object advertisement;
104
105    /**
106     *
107     */
108    protected boolean isShutdownHookEnabled = true;
109
110    private String name;
111
112    private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<String, Appender>();
113
114    private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>();
115
116    private ConcurrentMap<String, String> properties = new ConcurrentHashMap<String, String>();
117
118    private final StrLookup tempLookup = new Interpolator(properties);
119
120    private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
121
122    private LoggerConfig root = new LoggerConfig();
123
124    private final boolean started = false;
125
126    private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>();
127
128    protected PluginManager pluginManager;
129
130    /**
131     * Constructor.
132     */
133    protected BaseConfiguration() {
134        componentMap.put(Configuration.CONTEXT_PROPERTIES, properties);
135        pluginManager = new PluginManager("Core");
136        rootNode = new Node();
137    }
138
139    @Override
140    @SuppressWarnings("unchecked")
141    public Map<String, String> getProperties() {
142        return properties;
143    }
144
145    /**
146     * Initialize the configuration.
147     */
148    @Override
149    public void start() {
150        pluginManager.collectPlugins();
151        PluginManager levelPlugins = new PluginManager("Level");
152        levelPlugins.collectPlugins();
153        Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
154        if (plugins != null) {
155            for (PluginType<?> type : plugins.values()) {
156                try {
157                    // Cause the class to be initialized if it isn't already.
158                    Class.forName(type.getPluginClass().getName(), true, type.getPluginClass().getClassLoader());
159                } catch (Exception ex) {
160                    LOGGER.error("Unable to initialize " + type.getPluginClass().getName() + " due to " +                        ex.getClass().getSimpleName() + ":" + ex.getMessage());
161                }
162            }
163        }
164        setup();
165        setupAdvertisement();
166        doConfigure();
167        for (final LoggerConfig logger : loggers.values()) {
168            logger.startFilter();
169        }
170        for (final Appender appender : appenders.values()) {
171            appender.start();
172        }
173        root.startFilter(); // LOG4J2-336
174        startFilter();
175    }
176
177    /**
178     * Tear down the configuration.
179     */
180    @Override
181    public void stop() {
182        
183        // LOG4J2-392 first stop AsyncLogger Disruptor thread
184        final LoggerContextFactory factory = LogManager.getFactory();
185        if (factory instanceof Log4jContextFactory) {
186            ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
187            if (selector instanceof AsyncLoggerContextSelector) { // all loggers are async
188                // TODO until LOG4J2-493 is fixed we can only stop AsyncLogger once!
189                // but LoggerContext.setConfiguration will call config.stop()
190                // every time the configuration changes...
191                //
192                // Uncomment the line below after LOG4J2-493 is fixed
193                //AsyncLogger.stop();
194            }
195        }
196        // similarly, first stop AsyncLoggerConfig Disruptor thread(s)
197        Set<LoggerConfig> alreadyStopped = new HashSet<LoggerConfig>();
198        for (final LoggerConfig logger : loggers.values()) {
199            if (logger instanceof AsyncLoggerConfig) {
200                logger.clearAppenders();
201                logger.stopFilter();
202                alreadyStopped.add(logger);
203            }
204        }
205        if (root instanceof AsyncLoggerConfig) {
206            root.stopFilter();
207            alreadyStopped.add(root);
208        }
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        for (int i = array.length - 1; i >= 0; --i) {
215            if (array[i] instanceof AsyncAppender) {
216                array[i].stop();
217            }
218        }
219        for (int i = array.length - 1; i >= 0; --i) {
220            if (array[i].isStarted()) { // then stop remaining Appenders
221                array[i].stop();
222            }
223        }
224        for (final LoggerConfig logger : loggers.values()) {
225            if (alreadyStopped.contains(logger)) {
226                continue;
227            }
228            logger.clearAppenders();
229            logger.stopFilter();
230        }
231        if (!alreadyStopped.contains(root)) {
232            root.stopFilter();
233        }
234        stopFilter();
235        if (advertiser != null && advertisement != null) {
236            advertiser.unadvertise(advertisement);
237        }
238    }
239
240    @Override
241    public boolean isShutdownHookEnabled() {
242        return isShutdownHookEnabled;
243    }
244
245    protected void setup() {
246    }
247
248    protected Level getDefaultStatus() {
249        final String statusLevel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL,
250            Level.ERROR.name());
251        try {
252            return Level.toLevel(statusLevel);
253        } catch (final Exception ex) {
254            return Level.ERROR;
255        }
256    }
257
258    protected void createAdvertiser(String advertiserString, ConfigurationFactory.ConfigurationSource configSource,
259                                    byte[] buffer, String contentType) {
260        if (advertiserString != null) {
261            Node node = new Node(null, advertiserString, null);
262            Map<String, String> attributes = node.getAttributes();
263            attributes.put("content", new String(buffer));
264            attributes.put("contentType", contentType);
265            attributes.put("name", "configuration");
266            if (configSource.getLocation() != null) {
267                attributes.put("location", configSource.getLocation());
268            }
269            advertiserNode = node;
270        }
271    }
272
273    private void setupAdvertisement() {
274        if (advertiserNode != null)
275        {
276            String name = advertiserNode.getName();
277            @SuppressWarnings("unchecked")
278            final PluginType<Advertiser> type = (PluginType<Advertiser>) pluginManager.getPluginType(name);
279            if (type != null)
280            {
281                final Class<Advertiser> clazz = type.getPluginClass();
282                try {
283                    advertiser = clazz.newInstance();
284                    advertisement = advertiser.advertise(advertiserNode.getAttributes());
285                } catch (final InstantiationException e) {
286                    System.err.println("InstantiationException attempting to instantiate advertiser: " + name);
287                } catch (final IllegalAccessException e) {
288                    System.err.println("IllegalAccessException attempting to instantiate advertiser: " + name);
289                }
290            }
291        }
292    }
293
294    @SuppressWarnings("unchecked")
295    @Override
296    public <T> T getComponent(final String name) {
297        return (T) componentMap.get(name);
298    }
299
300    @Override
301    public void addComponent(final String name, final Object obj) {
302        componentMap.putIfAbsent(name, obj);
303    }
304
305    @SuppressWarnings("unchecked")
306    protected void doConfigure() {
307        boolean setRoot = false;
308        boolean setLoggers = false;
309        if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
310            Node first = rootNode.getChildren().get(0);
311            createConfiguration(first, null);
312            if (first.getObject() != null) {
313                subst.setVariableResolver((StrLookup) first.getObject());
314            }
315        } else {
316            final Map<String, String> map = (Map<String, String>) componentMap.get(CONTEXT_PROPERTIES);
317            final StrLookup lookup = map == null ? null : new MapLookup(map);
318            subst.setVariableResolver(new Interpolator(lookup));
319        }
320
321        for (final Node child : rootNode.getChildren()) {
322            if (child.getName().equalsIgnoreCase("Properties")) {
323                if (tempLookup == subst.getVariableResolver()) {
324                    LOGGER.error("Properties declaration must be the first element in the configuration");
325                }
326                continue;
327            }
328            createConfiguration(child, null);
329            if (child.getObject() == null) {
330                continue;
331            }
332            if (child.getName().equalsIgnoreCase("Appenders")) {
333                appenders = (ConcurrentMap<String, Appender>) child.getObject();
334            } else if (child.getObject() instanceof Filter) {
335                addFilter((Filter) child.getObject());
336            } else if (child.getName().equalsIgnoreCase("Loggers")) {
337                final Loggers l = (Loggers) child.getObject();
338                loggers = l.getMap();
339                setLoggers = true;
340                if (l.getRoot() != null) {
341                    root = l.getRoot();
342                    setRoot = true;
343                }
344            } else {
345                LOGGER.error("Unknown object \"" + child.getName() + "\" of type " +
346                    child.getObject().getClass().getName() + " is ignored");
347            }
348        }
349
350        if (!setLoggers) {
351            LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
352            setToDefault();
353            return;
354        } else if (!setRoot) {
355            LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
356            setToDefault();
357            // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
358        }
359
360        for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
361            final LoggerConfig l = entry.getValue();
362            for (final AppenderRef ref : l.getAppenderRefs()) {
363                final Appender app = appenders.get(ref.getRef());
364                if (app != null) {
365                    l.addAppender(app, ref.getLevel(), ref.getFilter());
366                } else {
367                    LOGGER.error("Unable to locate appender " + ref.getRef() + " for logger " + l.getName());
368                }
369            }
370
371        }
372
373        setParents();
374    }
375
376    private void setToDefault() {
377        setName(DefaultConfiguration.DEFAULT_NAME);
378        final Layout<? extends Serializable> layout =
379                PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n",
380                        null, null, null, null, null);
381        final Appender appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false",
382            "true");
383        appender.start();
384        addAppender(appender);
385        final LoggerConfig root = getRootLogger();
386        root.addAppender(appender, null, null);
387
388        final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL);
389        final Level level = levelName != null && Level.getLevel(levelName) != null ?
390            Level.getLevel(levelName) : Level.ERROR;
391        root.setLevel(level);
392    }
393
394    /**
395     * Set the name of the configuration.
396     * @param name The name.
397     */
398    public void setName(final String name) {
399        this.name = name;
400    }
401
402    /**
403     * Returns the name of the configuration.
404     * @return the name of the configuration.
405     */
406    @Override
407    public String getName() {
408        return name;
409    }
410
411    /**
412     * Add a listener for changes on the configuration.
413     * @param listener The ConfigurationListener to add.
414     */
415    @Override
416    public void addListener(final ConfigurationListener listener) {
417        listeners.add(listener);
418    }
419
420    /**
421     * Remove a ConfigurationListener.
422     * @param listener The ConfigurationListener to remove.
423     */
424    @Override
425    public void removeListener(final ConfigurationListener listener) {
426        listeners.remove(listener);
427    }
428
429    /**
430     * Returns the Appender with the specified name.
431     * @param name The name of the Appender.
432     * @return the Appender with the specified name or null if the Appender cannot be located.
433     */
434    public Appender getAppender(final String name) {
435        return appenders.get(name);
436    }
437
438    /**
439     * Returns a Map containing all the Appenders and their name.
440     * @return A Map containing each Appender's name and the Appender object.
441     */
442    @Override
443    public Map<String, Appender> getAppenders() {
444        return appenders;
445    }
446
447    /**
448     * Adds an Appender to the configuration.
449     * @param appender The Appender to add.
450     */
451    public void addAppender(final Appender appender) {
452        appenders.put(appender.getName(), appender);
453    }
454
455    @Override
456    public StrSubstitutor getStrSubstitutor() {
457        return subst;
458    }
459
460    @Override
461    public void setConfigurationMonitor(final ConfigurationMonitor monitor) {
462        this.monitor = monitor;
463    }
464
465    @Override
466    public ConfigurationMonitor getConfigurationMonitor() {
467        return monitor;
468    }
469
470    @Override
471    public void setAdvertiser(final Advertiser advertiser) {
472        this.advertiser = advertiser;
473    }
474
475    @Override
476    public Advertiser getAdvertiser() {
477        return advertiser;
478    }
479
480    /**
481     * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the
482     * same name is being updated at the same time.
483     *
484     * Note: This method is not used when configuring via configuration. It is primarily used by
485     * unit tests.
486     * @param logger The Logger the Appender will be associated with.
487     * @param appender The Appender.
488     */
489    @Override
490    public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
491                                               final Appender appender) {
492        final String name = logger.getName();
493        appenders.putIfAbsent(appender.getName(), appender);
494        final LoggerConfig lc = getLoggerConfig(name);
495        if (lc.getName().equals(name)) {
496            lc.addAppender(appender, null, null);
497        } else {
498            final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
499            nlc.addAppender(appender, null, null);
500            nlc.setParent(lc);
501            loggers.putIfAbsent(name, nlc);
502            setParents();
503            logger.getContext().updateLoggers();
504        }
505    }
506    /**
507     * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the
508     * same name is being updated at the same time.
509     *
510     * Note: This method is not used when configuring via configuration. It is primarily used by
511     * unit tests.
512     * @param logger The Logger the Fo;ter will be associated with.
513     * @param filter The Filter.
514     */
515    @Override
516    public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
517        final String name = logger.getName();
518        final LoggerConfig lc = getLoggerConfig(name);
519        if (lc.getName().equals(name)) {
520
521            lc.addFilter(filter);
522        } else {
523            final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
524            nlc.addFilter(filter);
525            nlc.setParent(lc);
526            loggers.putIfAbsent(name, nlc);
527            setParents();
528            logger.getContext().updateLoggers();
529        }
530    }
531    /**
532     * Marks a LoggerConfig as additive. 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 Appender will be associated with.
538     * @param additive True if the LoggerConfig should be additive, false otherwise.
539     */
540    @Override
541    public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger,
542                                               final boolean additive) {
543        final String name = logger.getName();
544        final LoggerConfig lc = getLoggerConfig(name);
545        if (lc.getName().equals(name)) {
546            lc.setAdditive(additive);
547        } else {
548            final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), additive);
549            nlc.setParent(lc);
550            loggers.putIfAbsent(name, nlc);
551            setParents();
552            logger.getContext().updateLoggers();
553        }
554    }
555
556    /**
557     * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes
558     * the Appender from this appender list and then stops the appender. This method is synchronized in
559     * case an Appender with the same name is being added during the removal.
560     * @param name the name of the appender to remove.
561     */
562    public synchronized void removeAppender(final String name) {
563        for (final LoggerConfig logger : loggers.values()) {
564            logger.removeAppender(name);
565        }
566        final Appender app = appenders.remove(name);
567
568        if (app != null) {
569            app.stop();
570        }
571    }
572
573    /**
574     * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the
575     * package name as necessary or return the root LoggerConfig if no other matches were found.
576     * @param name The Logger name.
577     * @return The located LoggerConfig.
578     */
579    @Override
580    public LoggerConfig getLoggerConfig(final String name) {
581        if (loggers.containsKey(name)) {
582            return loggers.get(name);
583        }
584        String substr = name;
585        while ((substr = NameUtil.getSubName(substr)) != null) {
586            if (loggers.containsKey(substr)) {
587                return loggers.get(substr);
588            }
589        }
590        return root;
591    }
592
593    /**
594     * Returns the root Logger.
595     * @return the root Logger.
596     */
597    public LoggerConfig getRootLogger() {
598        return root;
599    }
600
601    /**
602     * Returns a Map of all the LoggerConfigs.
603     * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
604     */
605    @Override
606    public Map<String, LoggerConfig> getLoggers() {
607        return Collections.unmodifiableMap(loggers);
608    }
609
610    /**
611     * Returns the LoggerConfig with the specified name.
612     * @param name The Logger name.
613     * @return The LoggerConfig or null if no match was found.
614     */
615    public LoggerConfig getLogger(final String name) {
616        return loggers.get(name);
617    }
618
619    /**
620     * Adding a logger cannot be done atomically so is not allowed in an active configuration. Adding
621     * or removing a Logger requires creating a new configuration and then switching.
622     *
623     * @param name The name of the Logger.
624     * @param loggerConfig The LoggerConfig.
625     */
626    public void addLogger(final String name, final LoggerConfig loggerConfig) {
627        if (started) {
628            final String msg = "Cannot add logger " + name + " to an active configuration";
629            LOGGER.warn(msg);
630            throw new IllegalStateException(msg);
631        }
632        loggers.put(name, loggerConfig);
633        setParents();
634    }
635
636    /**
637     * Removing a logger cannot be done atomically so is not allowed in an active configuration. Adding
638     * or removing a Logger requires creating a new configuration and then switching.
639     *
640     * @param name The name of the Logger.
641     */
642    public void removeLogger(final String name) {
643        if (started) {
644            final String msg = "Cannot remove logger " + name + " in an active configuration";
645            LOGGER.warn(msg);
646            throw new IllegalStateException(msg);
647        }
648        loggers.remove(name);
649        setParents();
650    }
651
652    @Override
653    public void createConfiguration(final Node node, final LogEvent event) {
654        final PluginType<?> type = node.getType();
655        if (type != null && type.isDeferChildren()) {
656            node.setObject(createPluginObject(type, node, event));
657        } else {
658            for (final Node child : node.getChildren()) {
659                createConfiguration(child, event);
660            }
661
662            if (type == null) {
663                if (node.getParent() != null) {
664                    LOGGER.error("Unable to locate plugin for " + node.getName());
665                }
666            } else {
667                node.setObject(createPluginObject(type, node, event));
668            }
669        }
670    }
671
672   /*
673    * Retrieve a static public 'method to create the desired object. Every parameter
674    * will be annotated to identify the appropriate attribute or element to use to
675    * set the value of the parameter.
676    * Parameters annotated with PluginAttribute will always be set as Strings.
677    * Parameters annotated with PluginElement may be Objects or arrays. Collections
678    * and Maps are currently not supported, although the factory method that is called
679    * can create these from an array.
680    *
681    * Although the happy path works, more work still needs to be done to log incorrect
682    * parameters. These will generally result in unhelpful InvocationTargetExceptions.
683    * @param classClass the class.
684    * @return the instantiate method or null if there is none by that
685    * description.
686    */
687    private <T> Object createPluginObject(final PluginType<T> type, final Node node, final LogEvent event)
688    {
689        final Class<T> clazz = type.getPluginClass();
690
691        if (Map.class.isAssignableFrom(clazz)) {
692            try {
693                @SuppressWarnings("unchecked")
694                final Map<String, Object> map = (Map<String, Object>) clazz.newInstance();
695                for (final Node child : node.getChildren()) {
696                    map.put(child.getName(), child.getObject());
697                }
698                return map;
699            } catch (final Exception ex) {
700                LOGGER.warn("Unable to create Map for " + type.getElementName() + " of class " +
701                    clazz);
702            }
703        }
704
705        if (List.class.isAssignableFrom(clazz)) {
706            try {
707                @SuppressWarnings("unchecked")
708                final List<Object> list = (List<Object>) clazz.newInstance();
709                for (final Node child : node.getChildren()) {
710                    list.add(child.getObject());
711                }
712                return list;
713            } catch (final Exception ex) {
714                LOGGER.warn("Unable to create List for " + type.getElementName() + " of class " +
715                    clazz);
716            }
717        }
718
719        Method factoryMethod = null;
720
721        for (final Method method : clazz.getMethods()) {
722            if (method.isAnnotationPresent(PluginFactory.class)) {
723                factoryMethod = method;
724                break;
725            }
726        }
727        if (factoryMethod == null) {
728            return null;
729        }
730
731        final Annotation[][] parmArray = factoryMethod.getParameterAnnotations();
732        final Class<?>[] parmClasses = factoryMethod.getParameterTypes();
733        if (parmArray.length != parmClasses.length) {
734            LOGGER.error("Number of parameter annotations does not equal the number of paramters");
735        }
736        final Object[] parms = new Object[parmClasses.length];
737
738        int index = 0;
739        final Map<String, String> attrs = node.getAttributes();
740        final List<Node> children = node.getChildren();
741        final StringBuilder sb = new StringBuilder();
742        final List<Node> used = new ArrayList<Node>();
743
744        /*
745         * For each parameter:
746         * If the parameter is an attribute store the value of the attribute in the parameter array.
747         * If the parameter is an element:
748         *   Determine if the required parameter is an array.
749         *     If so, if a child contains the array, use it,
750         *      otherwise create the array from all child nodes of the correct type.
751         *     Store the array into the parameter array.
752         *   If not an array, store the object in the child node into the parameter array.
753         */
754        for (final Annotation[] parmTypes : parmArray) {
755            String[] aliases = null;
756            for (final Annotation a: parmTypes) {
757                if (a instanceof PluginAliases) {
758                    aliases = ((PluginAliases) a).value();
759                }
760            }
761            for (final Annotation a : parmTypes) {
762                if (a instanceof PluginAliases) {
763                    continue;
764                }
765                if (sb.length() == 0) {
766                    sb.append(" with params(");
767                } else {
768                    sb.append(", ");
769                }
770                if (a instanceof PluginNode) {
771                    parms[index] = node;
772                    sb.append("Node=").append(node.getName());
773                } else if (a instanceof PluginConfiguration) {
774                    parms[index] = this;
775                    if (this.name != null) {
776                        sb.append("Configuration(").append(name).append(")");
777                    } else {
778                        sb.append("Configuration");
779                    }
780                } else if (a instanceof PluginValue) {
781                    final String name = ((PluginValue) a).value();
782                    String v = node.getValue();
783                    if (v == null) {
784                        v = getAttrValue("value", null, attrs);
785                    }
786                    final String value = subst.replace(event, v);
787                    sb.append(name).append("=\"").append(value).append("\"");
788                    parms[index] = value;
789                } else if (a instanceof PluginAttribute) {
790                    PluginAttribute attr = (PluginAttribute) a;
791                    final String name = attr.value();
792                    final String value = subst.replace(event, getAttrValue(name, aliases, attrs));
793                    sb.append(name).append("=\"").append(value).append("\"");
794                    parms[index] = value;
795                } else if (a instanceof PluginElement) {
796                    final PluginElement elem = (PluginElement) a;
797                    final String name = elem.value();
798                    if (parmClasses[index].isArray()) {
799                        final Class<?> parmClass = parmClasses[index].getComponentType();
800                        final List<Object> list = new ArrayList<Object>();
801                        sb.append(name).append("={");
802                        boolean first = true;
803                        for (final Node child : children) {
804                            final PluginType<?> childType = child.getType();
805                            if (elem.value().equalsIgnoreCase(childType.getElementName()) ||
806                                parmClass.isAssignableFrom(childType.getPluginClass())) {
807                                used.add(child);
808                                if (!first) {
809                                    sb.append(", ");
810                                }
811                                first = false;
812                                final Object obj = child.getObject();
813                                if (obj == null) {
814                                    LOGGER.error("Null object returned for " + child.getName() + " in " +
815                                        node.getName());
816                                    continue;
817                                }
818                                if (obj.getClass().isArray()) {
819                                    printArray(sb, (Object[]) obj);
820                                    parms[index] = obj;
821                                    break;
822                                }
823                                sb.append(child.toString());
824                                list.add(obj);
825                            }
826                        }
827                        sb.append("}");
828                        if (parms[index] != null) {
829                            break;
830                        }
831                        if (list.size() > 0 && !parmClass.isAssignableFrom(list.get(0).getClass())) {
832                            LOGGER.error("Attempted to assign List containing class " +
833                                list.get(0).getClass().getName() + " to array of type " + parmClass +
834                                " for attribute " + name);
835                            break;
836                        }
837                        final Object[] array = (Object[]) Array.newInstance(parmClass, list.size());
838                        int i = 0;
839                        for (final Object obj : list) {
840                            array[i] = obj;
841                            ++i;
842                        }
843                        parms[index] = array;
844                    } else {
845                        final Class<?> parmClass = parmClasses[index];
846                        boolean present = false;
847                        for (final Node child : children) {
848                            final PluginType<?> childType = child.getType();
849                            if (elem.value().equals(childType.getElementName()) ||
850                                parmClass.isAssignableFrom(childType.getPluginClass())) {
851                                sb.append(child.getName()).append("(").append(child.toString()).append(")");
852                                present = true;
853                                used.add(child);
854                                parms[index] = child.getObject();
855                                break;
856                            }
857                        }
858                        if (!present) {
859                            sb.append("null");
860                        }
861                    }
862                }
863            }
864            ++index;
865        }
866        if (sb.length() > 0) {
867            sb.append(")");
868        }
869
870        if (attrs.size() > 0) {
871            final StringBuilder eb = new StringBuilder();
872            for (final String key : attrs.keySet()) {
873                if (eb.length() == 0) {
874                    eb.append(node.getName());
875                    eb.append(" contains ");
876                    if (attrs.size() == 1) {
877                        eb.append("an invalid element or attribute ");
878                    } else {
879                        eb.append("invalid attributes ");
880                    }
881                } else {
882                    eb.append(", ");
883                }
884                eb.append("\"");
885                eb.append(key);
886                eb.append("\"");
887
888            }
889            LOGGER.error(eb.toString());
890        }
891
892        if (!type.isDeferChildren() && used.size() != children.size()) {
893            for (final Node child : children) {
894                if (used.contains(child)) {
895                    continue;
896                }
897                final String nodeType = node.getType().getElementName();
898                final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + " " + node.getName();
899                LOGGER.error(start + " has no parameter that matches element " + child.getName());
900            }
901        }
902
903        try {
904            final int mod = factoryMethod.getModifiers();
905            if (!Modifier.isStatic(mod)) {
906                LOGGER.error(factoryMethod.getName() + " method is not static on class " +
907                    clazz.getName() + " for element " + node.getName());
908                return null;
909            }
910            LOGGER.debug("Calling {} on class {} for element {}{}", factoryMethod.getName(), clazz.getName(),
911                node.getName(), sb.toString());
912            //if (parms.length > 0) {
913                return factoryMethod.invoke(null, parms);
914            //}
915            //return factoryMethod.invoke(null, node);
916        } catch (final Exception e) {
917            LOGGER.error("Unable to invoke method " + factoryMethod.getName() + " in class " +
918                clazz.getName() + " for element " + node.getName(), e);
919        }
920        return null;
921    }
922
923    private void printArray(final StringBuilder sb, final Object... array) {
924        boolean first = true;
925        for (final Object obj : array) {
926            if (!first) {
927                sb.append(", ");
928            }
929            sb.append(obj.toString());
930            first = false;
931        }
932    }
933
934    private String getAttrValue(final String name, final String[] aliases, final Map<String, String> attrs) {
935        for (final String key : attrs.keySet()) {
936            if (key.equalsIgnoreCase(name)) {
937                final String attr = attrs.get(key);
938                attrs.remove(key);
939                return attr;
940            }
941            if (aliases != null) {
942                for (String alias : aliases) {
943                    if (key.equalsIgnoreCase(alias)) {
944                        final String attr = attrs.get(key);
945                        attrs.remove(key);
946                        return attr;
947                    }
948                }
949            }
950        }
951        return null;
952    }
953
954    private void setParents() {
955         for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
956            final LoggerConfig logger = entry.getValue();
957            String name = entry.getKey();
958            if (!name.equals("")) {
959                final int i = name.lastIndexOf('.');
960                if (i > 0) {
961                    name = name.substring(0, i);
962                    LoggerConfig parent = getLoggerConfig(name);
963                    if (parent == null) {
964                        parent = root;
965                    }
966                    logger.setParent(parent);
967                } else {
968                    logger.setParent(root);
969                }
970            }
971        }
972    }
973}