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