View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config;
18  
19  import java.io.Serializable;
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ConcurrentMap;
32  import java.util.concurrent.CopyOnWriteArrayList;
33  
34  import org.apache.logging.log4j.Level;
35  import org.apache.logging.log4j.LogManager;
36  import org.apache.logging.log4j.Logger;
37  import org.apache.logging.log4j.core.Appender;
38  import org.apache.logging.log4j.core.Filter;
39  import org.apache.logging.log4j.core.Layout;
40  import org.apache.logging.log4j.core.LogEvent;
41  import org.apache.logging.log4j.core.appender.AsyncAppender;
42  import org.apache.logging.log4j.core.appender.ConsoleAppender;
43  import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
44  import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
45  import org.apache.logging.log4j.core.config.plugins.PluginAliases;
46  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
47  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
48  import org.apache.logging.log4j.core.config.plugins.PluginElement;
49  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
50  import org.apache.logging.log4j.core.config.plugins.PluginManager;
51  import org.apache.logging.log4j.core.config.plugins.PluginNode;
52  import org.apache.logging.log4j.core.config.plugins.PluginType;
53  import org.apache.logging.log4j.core.config.plugins.PluginValue;
54  import org.apache.logging.log4j.core.filter.AbstractFilterable;
55  import org.apache.logging.log4j.core.helpers.Constants;
56  import org.apache.logging.log4j.core.helpers.NameUtil;
57  import org.apache.logging.log4j.core.impl.Log4jContextFactory;
58  import org.apache.logging.log4j.core.layout.PatternLayout;
59  import org.apache.logging.log4j.core.lookup.Interpolator;
60  import org.apache.logging.log4j.core.lookup.MapLookup;
61  import org.apache.logging.log4j.core.lookup.StrLookup;
62  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
63  import org.apache.logging.log4j.core.net.Advertiser;
64  import org.apache.logging.log4j.core.selector.ContextSelector;
65  import org.apache.logging.log4j.spi.LoggerContextFactory;
66  import org.apache.logging.log4j.status.StatusLogger;
67  import org.apache.logging.log4j.util.PropertiesUtil;
68  
69  /**
70   * The Base Configuration. Many configuration implementations will extend this class.
71   */
72  public class BaseConfiguration extends AbstractFilterable implements Configuration {
73      /**
74       * Allow subclasses access to the status logger without creating another instance.
75       */
76      protected static final Logger LOGGER = StatusLogger.getLogger();
77  
78      /**
79       * The root node of the configuration.
80       */
81      protected Node rootNode;
82  
83      /**
84       * Listeners for configuration changes.
85       */
86      protected final List<ConfigurationListener> listeners =
87          new CopyOnWriteArrayList<ConfigurationListener>();
88  
89      /**
90       * The ConfigurationMonitor that checks for configuration changes.
91       */
92      protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
93  
94      /**
95       * The Advertiser which exposes appender configurations to external systems.
96       */
97      private Advertiser advertiser = new DefaultAdvertiser();
98  
99      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 }