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