001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.config;
018    
019    import org.apache.logging.log4j.Level;
020    import org.apache.logging.log4j.Logger;
021    import org.apache.logging.log4j.core.Appender;
022    import org.apache.logging.log4j.core.Filter;
023    import org.apache.logging.log4j.core.Layout;
024    import org.apache.logging.log4j.core.LogEvent;
025    import org.apache.logging.log4j.core.appender.ConsoleAppender;
026    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
027    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
028    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
029    import org.apache.logging.log4j.core.config.plugins.PluginManager;
030    import org.apache.logging.log4j.core.config.plugins.PluginElement;
031    import org.apache.logging.log4j.core.config.plugins.PluginNode;
032    import org.apache.logging.log4j.core.config.plugins.PluginType;
033    import org.apache.logging.log4j.core.config.plugins.PluginValue;
034    import org.apache.logging.log4j.core.filter.AbstractFilterable;
035    import org.apache.logging.log4j.core.helpers.NameUtil;
036    import org.apache.logging.log4j.core.layout.PatternLayout;
037    import org.apache.logging.log4j.core.lookup.Interpolator;
038    import org.apache.logging.log4j.core.lookup.MapLookup;
039    import org.apache.logging.log4j.core.lookup.StrLookup;
040    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
041    import org.apache.logging.log4j.core.net.Advertiser;
042    import org.apache.logging.log4j.status.StatusLogger;
043    import org.apache.logging.log4j.util.PropertiesUtil;
044    
045    import java.lang.annotation.Annotation;
046    import java.lang.reflect.Array;
047    import java.lang.reflect.Method;
048    import java.lang.reflect.Modifier;
049    import java.util.ArrayList;
050    import java.util.Collections;
051    import java.util.List;
052    import java.util.Map;
053    import java.util.concurrent.ConcurrentHashMap;
054    import java.util.concurrent.ConcurrentMap;
055    import java.util.concurrent.CopyOnWriteArrayList;
056    
057    /**
058     * The Base Configuration. Many configuration implementations will extend this class.
059     */
060    public class BaseConfiguration extends AbstractFilterable implements Configuration {
061        /**
062         * Allow subclasses access to the status logger without creating another instance.
063         */
064        protected static final Logger LOGGER = StatusLogger.getLogger();
065    
066        /**
067         * The root node of the configuration.
068         */
069        protected Node rootNode;
070    
071        /**
072         * Listeners for configuration changes.
073         */
074        protected final List<ConfigurationListener> listeners =
075            new CopyOnWriteArrayList<ConfigurationListener>();
076    
077        /**
078         * The ConfigurationMonitor that checks for configuration changes.
079         */
080        protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
081    
082        /**
083         * The Advertiser which exposes appender configurations to external systems.
084         */
085        protected Advertiser advertiser = new DefaultAdvertiser();
086    
087        private String name;
088    
089        private ConcurrentMap<String, Appender<?>> appenders = new ConcurrentHashMap<String, Appender<?>>();
090    
091        private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>();
092    
093        private final StrLookup tempLookup = new Interpolator();
094    
095        private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
096    
097        private LoggerConfig root = new LoggerConfig();
098    
099        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    }