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