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 org.apache.logging.log4j.Level;
20  import org.apache.logging.log4j.Logger;
21  import org.apache.logging.log4j.core.Appender;
22  import org.apache.logging.log4j.core.Filter;
23  import org.apache.logging.log4j.core.Layout;
24  import org.apache.logging.log4j.core.LogEvent;
25  import org.apache.logging.log4j.core.appender.ConsoleAppender;
26  import org.apache.logging.log4j.core.config.plugins.PluginAttr;
27  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
28  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
29  import org.apache.logging.log4j.core.config.plugins.PluginManager;
30  import org.apache.logging.log4j.core.config.plugins.PluginElement;
31  import org.apache.logging.log4j.core.config.plugins.PluginNode;
32  import org.apache.logging.log4j.core.config.plugins.PluginType;
33  import org.apache.logging.log4j.core.config.plugins.PluginValue;
34  import org.apache.logging.log4j.core.filter.AbstractFilterable;
35  import org.apache.logging.log4j.core.helpers.NameUtil;
36  import org.apache.logging.log4j.core.layout.PatternLayout;
37  import org.apache.logging.log4j.core.lookup.Interpolator;
38  import org.apache.logging.log4j.core.lookup.MapLookup;
39  import org.apache.logging.log4j.core.lookup.StrLookup;
40  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
41  import org.apache.logging.log4j.core.net.Advertiser;
42  import org.apache.logging.log4j.status.StatusLogger;
43  import org.apache.logging.log4j.util.PropertiesUtil;
44  
45  import java.io.Serializable;
46  import java.lang.annotation.Annotation;
47  import java.lang.reflect.Array;
48  import java.lang.reflect.Method;
49  import java.lang.reflect.Modifier;
50  import java.util.ArrayList;
51  import java.util.Collections;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.concurrent.ConcurrentHashMap;
55  import java.util.concurrent.ConcurrentMap;
56  import java.util.concurrent.CopyOnWriteArrayList;
57  
58  /**
59   * The Base Configuration. Many configuration implementations will extend this class.
60   */
61  public class BaseConfiguration extends AbstractFilterable implements Configuration {
62      /**
63       * Allow subclasses access to the status logger without creating another instance.
64       */
65      protected static final Logger LOGGER = StatusLogger.getLogger();
66  
67      /**
68       * The root node of the configuration.
69       */
70      protected Node rootNode;
71  
72      /**
73       * Listeners for configuration changes.
74       */
75      protected final List<ConfigurationListener> listeners =
76          new CopyOnWriteArrayList<ConfigurationListener>();
77  
78      /**
79       * The ConfigurationMonitor that checks for configuration changes.
80       */
81      protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
82  
83      /**
84       * The Advertiser which exposes appender configurations to external systems.
85       */
86      protected Advertiser advertiser = new DefaultAdvertiser();
87  
88      private String name;
89  
90      private ConcurrentMap<String, Appender<?>> appenders = new ConcurrentHashMap<String, Appender<?>>();
91  
92      private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>();
93  
94      private final StrLookup tempLookup = new Interpolator();
95  
96      private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
97  
98      private LoggerConfig root = new LoggerConfig();
99  
100     private final boolean started = false;
101 
102     private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>();
103 
104     /**
105      * Constructor.
106      */
107     protected BaseConfiguration() {
108         rootNode = new Node();
109     }
110 
111     @Override
112     @SuppressWarnings("unchecked")
113     public Map<String, String> getProperties() {
114         return (Map<String, String>) componentMap.get(CONTEXT_PROPERTIES);
115     }
116 
117     /**
118      * Initialize the configuration.
119      */
120     @Override
121     public void start() {
122         setup();
123         doConfigure();
124         for (final LoggerConfig logger : loggers.values()) {
125             logger.startFilter();
126         }
127         for (final Appender appender : appenders.values()) {
128             appender.start();
129         }
130 
131         startFilter();
132     }
133 
134     /**
135      * Tear down the configuration.
136      */
137     @Override
138     public void stop() {
139         // Stop the appenders in reverse order in case they still have activity.
140         final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]);
141         for (int i = array.length - 1; i >= 0; --i) {
142             array[i].stop();
143         }
144         for (final LoggerConfig logger : loggers.values()) {
145             logger.clearAppenders();
146             logger.stopFilter();
147         }
148         root.stopFilter();
149         stopFilter();
150     }
151 
152     protected void setup() {
153     }
154 
155     @Override
156     public Object getComponent(final String name) {
157         return componentMap.get(name);
158     }
159 
160     @Override
161     public void addComponent(final String name, final Object obj) {
162         componentMap.putIfAbsent(name, obj);
163     }
164 
165     @SuppressWarnings("unchecked")
166     protected void doConfigure() {
167         boolean setRoot = false;
168         boolean setLoggers = false;
169         for (final Node child : rootNode.getChildren()) {
170             createConfiguration(child, null);
171             if (child.getObject() == null) {
172                 continue;
173             }
174             if (child.getName().equalsIgnoreCase("properties")) {
175                 if (tempLookup == subst.getVariableResolver()) {
176                     subst.setVariableResolver((StrLookup) child.getObject());
177                 } else {
178                     LOGGER.error("Properties declaration must be the first element in the configuration");
179                 }
180                 continue;
181             } else if (tempLookup == subst.getVariableResolver()) {
182                 final Map<String, String> map = (Map<String, String>) componentMap.get(CONTEXT_PROPERTIES);
183                 final StrLookup lookup = map == null ? null : new MapLookup(map);
184                 subst.setVariableResolver(new Interpolator(lookup));
185             }
186             if (child.getName().equalsIgnoreCase("appenders")) {
187                 appenders = (ConcurrentMap<String, Appender<?>>) child.getObject();
188             } else if (child.getObject() instanceof Filter) {
189                 addFilter((Filter) child.getObject());
190             } else if (child.getName().equalsIgnoreCase("loggers")) {
191                 final Loggers l = (Loggers) child.getObject();
192                 loggers = l.getMap();
193                 setLoggers = true;
194                 if (l.getRoot() != null) {
195                     root = l.getRoot();
196                     setRoot = true;
197                 }
198             } else {
199                 LOGGER.error("Unknown object \"" + child.getName() + "\" of type " +
200                     child.getObject().getClass().getName() + " is ignored");
201             }
202         }
203 
204         if (!setLoggers) {
205             LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
206             setToDefault();
207             return;
208         } else if (!setRoot) {
209             LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
210             setToDefault();
211             // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
212         }
213 
214         for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
215             final LoggerConfig l = entry.getValue();
216             for (final AppenderRef ref : l.getAppenderRefs()) {
217                 final Appender app = appenders.get(ref.getRef());
218                 if (app != null) {
219                     l.addAppender(app, ref.getLevel(), ref.getFilter());
220                 } else {
221                     LOGGER.error("Unable to locate appender " + ref.getRef() + " for logger " + l.getName());
222                 }
223             }
224 
225         }
226 
227         setParents();
228     }
229 
230     private void setToDefault() {
231         setName(DefaultConfiguration.DEFAULT_NAME);
232         final Layout<? extends Serializable> layout =
233                 PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n",
234                         null, null, null, null);
235         final Appender<?> appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false",
236             "true");
237         appender.start();
238         addAppender(appender);
239         final LoggerConfig root = getRootLogger();
240         root.addAppender(appender, null, null);
241 
242         final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL);
243         final Level level = levelName != null && Level.valueOf(levelName) != null ?
244             Level.valueOf(levelName) : Level.ERROR;
245         root.setLevel(level);
246     }
247 
248     protected PluginManager getPluginManager() {
249         //don't cache a pluginmanager instance - packages may be updated, requiring
250         // re-discovery of plugins
251         PluginManager mgr = new PluginManager("Core");
252         mgr.collectPlugins();
253         return mgr;
254     }
255 
256     /**
257      * Set the name of the configuration.
258      * @param name The name.
259      */
260     public void setName(final String name) {
261         this.name = name;
262     }
263 
264     /**
265      * Returns the name of the configuration.
266      * @return the name of the configuration.
267      */
268     @Override
269     public String getName() {
270         return name;
271     }
272 
273     /**
274      * Add a listener for changes on the configuration.
275      * @param listener The ConfigurationListener to add.
276      */
277     @Override
278     public void addListener(final ConfigurationListener listener) {
279         listeners.add(listener);
280     }
281 
282     /**
283      * Remove a ConfigurationListener.
284      * @param listener The ConfigurationListener to remove.
285      */
286     @Override
287     public void removeListener(final ConfigurationListener listener) {
288         listeners.remove(listener);
289     }
290 
291     /**
292      * Returns the Appender with the specified name.
293      * @param name The name of the Appender.
294      * @return the Appender with the specified name or null if the Appender cannot be located.
295      */
296     public Appender<?> getAppender(final String name) {
297         return appenders.get(name);
298     }
299 
300     /**
301      * Returns a Map containing all the Appenders and their name.
302      * @return A Map containing each Appender's name and the Appender object.
303      */
304     @Override
305     public Map<String, Appender<?>> getAppenders() {
306         return appenders;
307     }
308 
309     /**
310      * Adds an Appender to the configuration.
311      * @param appender The Appender to add.
312      */
313     public void addAppender(final Appender appender) {
314         appenders.put(appender.getName(), appender);
315     }
316 
317     @Override
318     public StrSubstitutor getSubst() {
319         return subst;
320     }
321 
322     @Override
323     public void setConfigurationMonitor(ConfigurationMonitor monitor) {
324         this.monitor = monitor;
325     }
326 
327     @Override
328     public ConfigurationMonitor getConfigurationMonitor() {
329         return monitor;
330     }
331 
332     @Override
333     public void setAdvertiser(Advertiser advertiser) {
334         this.advertiser = advertiser;
335     }
336 
337     @Override
338     public Advertiser getAdvertiser() {
339         return advertiser;
340     }
341 
342     /**
343      * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the
344      * same name is being updated at the same time.
345      *
346      * Note: This method is not used when configuring via configuration. It is primarily used by
347      * unit tests.
348      * @param logger The Logger the Appender will be associated with.
349      * @param appender The Appender.
350      */
351     @Override
352     public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
353                                                final Appender<?> appender) {
354         final String name = logger.getName();
355         appenders.putIfAbsent(appender.getName(), appender);
356         final LoggerConfig lc = getLoggerConfig(name);
357         if (lc.getName().equals(name)) {
358             lc.addAppender(appender, null, null);
359         } else {
360             final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
361             nlc.addAppender(appender, null, null);
362             nlc.setParent(lc);
363             loggers.putIfAbsent(name, nlc);
364             setParents();
365             logger.getContext().updateLoggers();
366         }
367     }
368     /**
369      * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the
370      * same name is being updated at the same time.
371      *
372      * Note: This method is not used when configuring via configuration. It is primarily used by
373      * unit tests.
374      * @param logger The Logger the Fo;ter will be associated with.
375      * @param filter The Filter.
376      */
377     @Override
378     public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
379         final String name = logger.getName();
380         final LoggerConfig lc = getLoggerConfig(name);
381         if (lc.getName().equals(name)) {
382 
383             lc.addFilter(filter);
384         } else {
385             final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
386             nlc.addFilter(filter);
387             nlc.setParent(lc);
388             loggers.putIfAbsent(name, nlc);
389             setParents();
390             logger.getContext().updateLoggers();
391         }
392     }
393     /**
394      * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the
395      * same name is being updated at the same time.
396      *
397      * Note: This method is not used when configuring via configuration. It is primarily used by
398      * unit tests.
399      * @param logger The Logger the Appender will be associated with.
400      * @param additive True if the LoggerConfig should be additive, false otherwise.
401      */
402     @Override
403     public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger,
404                                                final boolean additive) {
405         final String name = logger.getName();
406         final LoggerConfig lc = getLoggerConfig(name);
407         if (lc.getName().equals(name)) {
408             lc.setAdditive(additive);
409         } else {
410             final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), additive);
411             nlc.setParent(lc);
412             loggers.putIfAbsent(name, nlc);
413             setParents();
414             logger.getContext().updateLoggers();
415         }
416     }
417 
418     /**
419      * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes
420      * the Appender from this appender list and then stops the appender. This method is synchronized in
421      * case an Appender with the same name is being added during the removal.
422      * @param name the name of the appender to remove.
423      */
424     public synchronized void removeAppender(final String name) {
425         for (final LoggerConfig logger : loggers.values()) {
426             logger.removeAppender(name);
427         }
428         final Appender app = appenders.remove(name);
429 
430         if (app != null) {
431             app.stop();
432         }
433     }
434 
435     /**
436      * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the
437      * package name as necessary or return the root LoggerConfig if no other matches were found.
438      * @param name The Logger name.
439      * @return The located LoggerConfig.
440      */
441     @Override
442     public LoggerConfig getLoggerConfig(final String name) {
443         if (loggers.containsKey(name)) {
444             return loggers.get(name);
445         }
446         String substr = name;
447         while ((substr = NameUtil.getSubName(substr)) != null) {
448             if (loggers.containsKey(substr)) {
449                 return loggers.get(substr);
450             }
451         }
452         return root;
453     }
454 
455     /**
456      * Returns the root Logger.
457      * @return the root Logger.
458      */
459     public LoggerConfig getRootLogger() {
460         return root;
461     }
462 
463     /**
464      * Returns a Map of all the LoggerConfigs.
465      * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
466      */
467     @Override
468     public Map<String, LoggerConfig> getLoggers() {
469         return Collections.unmodifiableMap(loggers);
470     }
471 
472     /**
473      * Returns the LoggerConfig with the specified name.
474      * @param name The Logger name.
475      * @return The LoggerConfig or null if no match was found.
476      */
477     public LoggerConfig getLogger(final String name) {
478         return loggers.get(name);
479     }
480 
481     /**
482      * Adding a logger cannot be done atomically so is not allowed in an active configuration. Adding
483      * or removing a Logger requires creating a new configuration and then switching.
484      *
485      * @param name The name of the Logger.
486      * @param loggerConfig The LoggerConfig.
487      */
488     public void addLogger(final String name, final LoggerConfig loggerConfig) {
489         if (started) {
490             final String msg = "Cannot add logger " + name + " to an active configuration";
491             LOGGER.warn(msg);
492             throw new IllegalStateException(msg);
493         }
494         loggers.put(name, loggerConfig);
495         setParents();
496     }
497 
498     /**
499      * Removing a logger cannot be done atomically so is not allowed in an active configuration. Adding
500      * or removing a Logger requires creating a new configuration and then switching.
501      *
502      * @param name The name of the Logger.
503      */
504     public void removeLogger(final String name) {
505         if (started) {
506             final String msg = "Cannot remove logger " + name + " in an active configuration";
507             LOGGER.warn(msg);
508             throw new IllegalStateException(msg);
509         }
510         loggers.remove(name);
511         setParents();
512     }
513 
514     @Override
515     public void createConfiguration(final Node node, final LogEvent event) {
516         final PluginType type = node.getType();
517         if (type != null && type.isDeferChildren()) {
518             node.setObject(createPluginObject(type, node, event));
519         } else {
520             for (final Node child : node.getChildren()) {
521                 createConfiguration(child, event);
522             }
523 
524             if (type == null) {
525                 if (node.getParent() != null) {
526                     LOGGER.error("Unable to locate plugin for " + node.getName());
527                 }
528             } else {
529                 node.setObject(createPluginObject(type, node, event));
530             }
531         }
532     }
533 
534    /*
535     * Retrieve a static public 'method to create the desired object. Every parameter
536     * will be annotated to identify the appropriate attribute or element to use to
537     * set the value of the parameter.
538     * Parameters annotated with PluginAttr will always be set as Strings.
539     * Parameters annotated with PluginElement may be Objects or arrays. Collections
540     * and Maps are currently not supported, although the factory method that is called
541     * can create these from an array.
542     *
543     * Although the happy path works, more work still needs to be done to log incorrect
544     * parameters. These will generally result in unhelpful InvocationTargetExceptions.
545     * @param classClass the class.
546     * @return the instantiate method or null if there is none by that
547     * description.
548     */
549     private Object createPluginObject(final PluginType type, final Node node, final LogEvent event)
550     {
551         final Class clazz = type.getPluginClass();
552 
553         if (Map.class.isAssignableFrom(clazz)) {
554             try {
555                 @SuppressWarnings("unchecked")
556                 final Map<String, Object> map = (Map<String, Object>) clazz.newInstance();
557                 for (final Node child : node.getChildren()) {
558                     map.put(child.getName(), child.getObject());
559                 }
560                 return map;
561             } catch (final Exception ex) {
562                 LOGGER.warn("Unable to create Map for " + type.getElementName() + " of class " +
563                     clazz);
564             }
565         }
566 
567         if (List.class.isAssignableFrom(clazz)) {
568             try {
569                 @SuppressWarnings("unchecked")
570                 final List<Object> list = (List<Object>) clazz.newInstance();
571                 for (final Node child : node.getChildren()) {
572                     list.add(child.getObject());
573                 }
574                 return list;
575             } catch (final Exception ex) {
576                 LOGGER.warn("Unable to create List for " + type.getElementName() + " of class " +
577                     clazz);
578             }
579         }
580 
581         Method factoryMethod = null;
582 
583         for (final Method method : clazz.getMethods()) {
584             if (method.isAnnotationPresent(PluginFactory.class)) {
585                 factoryMethod = method;
586                 break;
587             }
588         }
589         if (factoryMethod == null) {
590             return null;
591         }
592 
593         final Annotation[][] parmArray = factoryMethod.getParameterAnnotations();
594         final Class[] parmClasses = factoryMethod.getParameterTypes();
595         if (parmArray.length != parmClasses.length) {
596             LOGGER.error("Number of parameter annotations does not equal the number of paramters");
597         }
598         final Object[] parms = new Object[parmClasses.length];
599 
600         int index = 0;
601         final Map<String, String> attrs = node.getAttributes();
602         final List<Node> children = node.getChildren();
603         final StringBuilder sb = new StringBuilder();
604         final List<Node> used = new ArrayList<Node>();
605 
606         /*
607          * For each parameter:
608          * If the parameter is an attribute store the value of the attribute in the parameter array.
609          * If the parameter is an element:
610          *   Determine if the required parameter is an array.
611          *     If so, if a child contains the array, use it,
612          *      otherwise create the array from all child nodes of the correct type.
613          *     Store the array into the parameter array.
614          *   If not an array, store the object in the child node into the parameter array.
615          */
616         for (final Annotation[] parmTypes : parmArray) {
617             for (final Annotation a : parmTypes) {
618                 if (sb.length() == 0) {
619                     sb.append(" with params(");
620                 } else {
621                     sb.append(", ");
622                 }
623                 if (a instanceof PluginNode) {
624                     parms[index] = node;
625                     sb.append("Node=").append(node.getName());
626                 } else if (a instanceof PluginConfiguration) {
627                     parms[index] = this;
628                     if (this.name != null) {
629                         sb.append("Configuration(").append(name).append(")");
630                     } else {
631                         sb.append("Configuration");
632                     }
633                 } else if (a instanceof PluginValue) {
634                     final String name = ((PluginValue) a).value();
635                     String v = node.getValue();
636                     if (v == null) {
637                         v = getAttrValue("value", attrs);
638                     }
639                     final String value = subst.replace(event, v);
640                     sb.append(name).append("=\"").append(value).append("\"");
641                     parms[index] = value;
642                 } else if (a instanceof PluginAttr) {
643                     final String name = ((PluginAttr) a).value();
644                     final String value = subst.replace(event, getAttrValue(name, attrs));
645                     sb.append(name).append("=\"").append(value).append("\"");
646                     parms[index] = value;
647                 } else if (a instanceof PluginElement) {
648                     final PluginElement elem = (PluginElement) a;
649                     final String name = elem.value();
650                     if (parmClasses[index].isArray()) {
651                         final Class<?> parmClass = parmClasses[index].getComponentType();
652                         final List<Object> list = new ArrayList<Object>();
653                         sb.append(name).append("={");
654                         boolean first = true;
655                         for (final Node child : children) {
656                             final PluginType childType = child.getType();
657                             if (elem.value().equalsIgnoreCase(childType.getElementName()) ||
658                                 parmClass.isAssignableFrom(childType.getPluginClass())) {
659                                 used.add(child);
660                                 if (!first) {
661                                     sb.append(", ");
662                                 }
663                                 first = false;
664                                 final Object obj = child.getObject();
665                                 if (obj == null) {
666                                     LOGGER.error("Null object returned for " + child.getName() + " in " +
667                                         node.getName());
668                                     continue;
669                                 }
670                                 if (obj.getClass().isArray()) {
671                                     printArray(sb, (Object[]) obj);
672                                     parms[index] = obj;
673                                     break;
674                                 }
675                                 sb.append(child.toString());
676                                 list.add(obj);
677                             }
678                         }
679                         sb.append("}");
680                         if (parms[index] != null) {
681                             break;
682                         }
683                         if (list.size() > 0 && !parmClass.isAssignableFrom(list.get(0).getClass())) {
684                             LOGGER.error("Attempted to assign List containing class " +
685                                 list.get(0).getClass().getName() + " to array of type " + parmClass +
686                                 " for attribute " + name);
687                             break;
688                         }
689                         final Object[] array = (Object[]) Array.newInstance(parmClass, list.size());
690                         int i = 0;
691                         for (final Object obj : list) {
692                             array[i] = obj;
693                             ++i;
694                         }
695                         parms[index] = array;
696                     } else {
697                         final Class<?> parmClass = parmClasses[index];
698                         boolean present = false;
699                         for (final Node child : children) {
700                             final PluginType childType = child.getType();
701                             if (elem.value().equals(childType.getElementName()) ||
702                                 parmClass.isAssignableFrom(childType.getPluginClass())) {
703                                 sb.append(child.getName()).append("(").append(child.toString()).append(")");
704                                 present = true;
705                                 used.add(child);
706                                 parms[index] = child.getObject();
707                                 break;
708                             }
709                         }
710                         if (!present) {
711                             sb.append("null");
712                         }
713                     }
714                 }
715             }
716             ++index;
717         }
718         if (sb.length() > 0) {
719             sb.append(")");
720         }
721 
722         if (attrs.size() > 0) {
723             final StringBuilder eb = new StringBuilder();
724             for (final String key : attrs.keySet()) {
725                 if (eb.length() == 0) {
726                     eb.append(node.getName());
727                     eb.append(" contains ");
728                     if (attrs.size() == 1) {
729                         eb.append("an invalid element or attribute ");
730                     } else {
731                         eb.append("invalid attributes ");
732                     }
733                 } else {
734                     eb.append(", ");
735                 }
736                 eb.append("\"");
737                 eb.append(key);
738                 eb.append("\"");
739 
740             }
741             LOGGER.error(eb.toString());
742         }
743 
744         if (!type.isDeferChildren() && used.size() != children.size()) {
745             for (final Node child : children) {
746                 if (used.contains(child)) {
747                     continue;
748                 }
749                 final String nodeType = node.getType().getElementName();
750                 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + " " + node.getName();
751                 LOGGER.error(start + " has no parameter that matches element " + child.getName());
752             }
753         }
754 
755         try {
756             final int mod = factoryMethod.getModifiers();
757             if (!Modifier.isStatic(mod)) {
758                 LOGGER.error(factoryMethod.getName() + " method is not static on class " +
759                     clazz.getName() + " for element " + node.getName());
760                 return null;
761             }
762             LOGGER.debug("Calling {} on class {} for element {}{}", factoryMethod.getName(), clazz.getName(),
763                 node.getName(), sb.toString());
764             //if (parms.length > 0) {
765                 return factoryMethod.invoke(null, parms);
766             //}
767             //return factoryMethod.invoke(null, node);
768         } catch (final Exception e) {
769             LOGGER.error("Unable to invoke method " + factoryMethod.getName() + " in class " +
770                 clazz.getName() + " for element " + node.getName(), e);
771         }
772         return null;
773     }
774 
775     private void printArray(final StringBuilder sb, final Object... array) {
776         boolean first = true;
777         for (final Object obj : array) {
778             if (!first) {
779                 sb.append(", ");
780             }
781             sb.append(obj.toString());
782             first = false;
783         }
784     }
785 
786     private String getAttrValue(final String name, final Map<String, String> attrs) {
787         for (final String key : attrs.keySet()) {
788             if (key.equalsIgnoreCase(name)) {
789                 final String attr = attrs.get(key);
790                 attrs.remove(key);
791                 return attr;
792             }
793         }
794         return null;
795     }
796 
797     private void setParents() {
798          for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
799             final LoggerConfig logger = entry.getValue();
800             String name = entry.getKey();
801             if (!name.equals("")) {
802                 final int i = name.lastIndexOf('.');
803                 if (i > 0) {
804                     name = name.substring(0, i);
805                     LoggerConfig parent = getLoggerConfig(name);
806                     if (parent == null) {
807                         parent = root;
808                     }
809                     logger.setParent(parent);
810                 } else {
811                     logger.setParent(root);
812                 }
813             }
814         }
815     }
816 }