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.core.config.plugins.PluginManager;
021    import org.apache.logging.log4j.core.config.plugins.PluginType;
022    import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
023    import org.apache.logging.log4j.core.helpers.FileUtils;
024    import org.apache.logging.log4j.core.net.Advertiser;
025    import org.apache.logging.log4j.status.StatusConsoleListener;
026    import org.apache.logging.log4j.status.StatusListener;
027    import org.apache.logging.log4j.status.StatusLogger;
028    import org.w3c.dom.Attr;
029    import org.w3c.dom.Document;
030    import org.w3c.dom.Element;
031    import org.w3c.dom.NamedNodeMap;
032    import org.w3c.dom.NodeList;
033    import org.w3c.dom.Text;
034    import org.xml.sax.InputSource;
035    import org.xml.sax.SAXException;
036    
037    import javax.xml.XMLConstants;
038    import javax.xml.parsers.DocumentBuilder;
039    import javax.xml.parsers.DocumentBuilderFactory;
040    import javax.xml.parsers.ParserConfigurationException;
041    import javax.xml.transform.Source;
042    import javax.xml.transform.stream.StreamSource;
043    import javax.xml.validation.Schema;
044    import javax.xml.validation.SchemaFactory;
045    import javax.xml.validation.Validator;
046    import java.io.ByteArrayInputStream;
047    import java.io.ByteArrayOutputStream;
048    import java.io.File;
049    import java.io.FileInputStream;
050    import java.io.FileNotFoundException;
051    import java.io.FileOutputStream;
052    import java.io.IOException;
053    import java.io.InputStream;
054    import java.io.PrintStream;
055    import java.net.URI;
056    import java.net.URISyntaxException;
057    import java.util.ArrayList;
058    import java.util.Iterator;
059    import java.util.List;
060    import java.util.Map;
061    
062    /**
063     * Creates a Node hierarchy from an XML file.
064     */
065    public class XMLConfiguration extends BaseConfiguration implements Reconfigurable {
066    
067        private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
068    
069        private static final String LOG4J_XSD = "Log4J-V2.0.xsd";
070    
071        private static final int BUF_SIZE = 16384;
072    
073        private final List<Status> status = new ArrayList<Status>();
074    
075        private Element rootElement;
076    
077        private boolean strict;
078    
079        private String schema;
080    
081        private Validator validator;
082    
083        private final List<String> messages = new ArrayList<String>();
084    
085        private final File configFile;
086    
087        public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
088            this.configFile = configSource.getFile();
089            byte[] buffer = null;
090    
091            try {
092                final InputStream configStream = configSource.getInputStream();
093                buffer = toByteArray(configStream);
094                configStream.close();
095                final InputSource source = new InputSource(new ByteArrayInputStream(buffer));
096                final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
097                final Document document = builder.parse(source);
098                rootElement = document.getDocumentElement();
099                final Map<String, String> attrs = processAttributes(rootNode, rootElement);
100                Level status = Level.OFF;
101                boolean verbose = false;
102                PrintStream stream = System.out;
103    
104                for (final Map.Entry<String, String> entry : attrs.entrySet()) {
105                    if ("status".equalsIgnoreCase(entry.getKey())) {
106                        status = Level.toLevel(getSubst().replace(entry.getValue()), null);
107                        if (status == null) {
108                            status = Level.ERROR;
109                            messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
110                        }
111                    } else if ("dest".equalsIgnoreCase(entry.getKey())) {
112                        final String dest = entry.getValue();
113                        if (dest != null) {
114                            if (dest.equalsIgnoreCase("err")) {
115                                stream = System.err;
116                            } else {
117                                try {
118                                    final File destFile = FileUtils.fileFromURI(new URI(dest));
119                                    stream = new PrintStream(new FileOutputStream(destFile));
120                                } catch (final URISyntaxException use) {
121                                    System.err.println("Unable to write to " + dest + ". Writing to stdout");
122                                }
123                            }
124                        }
125                    } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
126                        verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
127                    } else if ("packages".equalsIgnoreCase(getSubst().replace(entry.getKey()))) {
128                        final String[] packages = entry.getValue().split(",");
129                        for (final String p : packages) {
130                            PluginManager.addPackage(p);
131                        }
132                    } else if ("name".equalsIgnoreCase(entry.getKey())) {
133                        setName(getSubst().replace(entry.getValue()));
134                    } else if ("strict".equalsIgnoreCase(entry.getKey())) {
135                        strict = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
136                    } else if ("schema".equalsIgnoreCase(entry.getKey())) {
137                        schema = getSubst().replace(entry.getValue());
138                    } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
139                        final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
140                        if (interval > 0 && configFile != null) {
141                            monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
142                        }
143                    } else if ("advertiser".equalsIgnoreCase(entry.getKey())) {
144                        final String advertiserString = getSubst().replace(entry.getValue());
145                        if (advertiserString != null)
146                        {
147                            @SuppressWarnings("unchecked")
148                            final PluginType<Advertiser> type = getPluginManager().getPluginType(advertiserString);
149                            if (type != null)
150                            {
151                                final Class<Advertiser> clazz = type.getPluginClass();
152                                try {
153                                    advertiser = clazz.newInstance();
154                                } catch (InstantiationException e) {
155                                    System.err.println("InstantiationException attempting to instantiate advertiser: " + advertiserString);
156                                } catch (IllegalAccessException e) {
157                                    System.err.println("IllegalAccessException attempting to instantiate advertiser: " + advertiserString);
158                                }
159                            }
160                        }
161                    }
162                }
163                final Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners();
164                boolean found = false;
165                while (iter.hasNext()) {
166                    final StatusListener listener = iter.next();
167                    if (listener instanceof StatusConsoleListener) {
168                        found = true;
169                        ((StatusConsoleListener) listener).setLevel(status);
170                        if (!verbose) {
171                            ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
172                        }
173                    }
174                }
175                if (!found && status != Level.OFF) {
176                    final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
177                    if (!verbose) {
178                        listener.setFilters(VERBOSE_CLASSES);
179                    }
180                    ((StatusLogger) LOGGER).registerListener(listener);
181                    for (final String msg : messages) {
182                        LOGGER.error(msg);
183                    }
184                }
185    
186            } catch (final SAXException domEx) {
187                LOGGER.error("Error parsing " + configSource.getLocation(), domEx);
188            } catch (final IOException ioe) {
189                LOGGER.error("Error parsing " + configSource.getLocation(), ioe);
190            } catch (final ParserConfigurationException pex) {
191                LOGGER.error("Error parsing " + configSource.getLocation(), pex);
192            }
193            if (strict && schema != null && buffer != null) {
194                InputStream is = null;
195                try {
196                    is = getClass().getClassLoader().getResourceAsStream(schema);
197                } catch (final Exception ex) {
198                    LOGGER.error("Unable to access schema " + schema);
199                }
200                if (is != null) {
201                    final Source src = new StreamSource(is, LOG4J_XSD);
202                    final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
203                    Schema schema = null;
204                    try {
205                        schema = factory.newSchema(src);
206                    } catch (final SAXException ex) {
207                        LOGGER.error("Error parsing Log4j schema", ex);
208                    }
209                    if (schema != null) {
210                        validator = schema.newValidator();
211                        try {
212                            validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
213                        } catch (final IOException ioe) {
214                            LOGGER.error("Error reading configuration for validation", ioe);
215                        } catch (final SAXException ex) {
216                            LOGGER.error("Error validating configuration", ex);
217                        }
218                    }
219                }
220            }
221    
222            if (getName() == null) {
223                setName(configSource.getLocation());
224            }
225        }
226    
227        @Override
228        public void setup() {
229            if (rootElement == null) {
230                LOGGER.error("No logging configuration");
231                return;
232            }
233            constructHierarchy(rootNode, rootElement);
234            if (status.size() > 0) {
235                for (final Status s : status) {
236                    LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
237                }
238                return;
239            }
240            rootElement = null;
241        }
242    
243        public Configuration reconfigure() {
244            if (configFile != null) {
245                try {
246                    final ConfigurationFactory.ConfigurationSource source =
247                        new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
248                    return new XMLConfiguration(source);
249                } catch (final FileNotFoundException ex) {
250                    LOGGER.error("Cannot locate file " + configFile, ex);
251                }
252            }
253            return null;
254        }
255    
256        private void constructHierarchy(final Node node, final Element element) {
257            processAttributes(node, element);
258            final StringBuffer buffer = new StringBuffer();
259            final NodeList list = element.getChildNodes();
260            final List<Node> children = node.getChildren();
261            for (int i = 0; i < list.getLength(); i++) {
262                final org.w3c.dom.Node w3cNode = list.item(i);
263                if (w3cNode instanceof Element) {
264                    final Element child = (Element) w3cNode;
265                    final String name = getType(child);
266                    final PluginType type = getPluginManager().getPluginType(name);
267                    final Node childNode = new Node(node, name, type);
268                    constructHierarchy(childNode, child);
269                    if (type == null) {
270                        final String value = childNode.getValue();
271                        if (!childNode.hasChildren() && value != null) {
272                            node.getAttributes().put(name, value);
273                        } else {
274                            status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
275                        }
276                    } else {
277                        children.add(childNode);
278                    }
279                } else if (w3cNode instanceof Text) {
280                    final Text data = (Text) w3cNode;
281                    buffer.append(data.getData());
282                }
283            }
284    
285            final String text = buffer.toString().trim();
286            if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
287                node.setValue(text);
288            }
289        }
290    
291        private String getType(final Element element) {
292            if (strict) {
293                final NamedNodeMap attrs = element.getAttributes();
294                for (int i = 0; i < attrs.getLength(); ++i) {
295                    final org.w3c.dom.Node w3cNode = attrs.item(i);
296                    if (w3cNode instanceof Attr) {
297                        final Attr attr = (Attr) w3cNode;
298                        if (attr.getName().equalsIgnoreCase("type")) {
299                            final String type = attr.getValue();
300                            attrs.removeNamedItem(attr.getName());
301                            return type;
302                        }
303                    }
304                }
305            }
306            return element.getTagName();
307        }
308    
309        private byte[] toByteArray(final InputStream is) throws IOException {
310            final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
311    
312            int nRead;
313            final byte[] data = new byte[BUF_SIZE];
314    
315            while ((nRead = is.read(data, 0, data.length)) != -1) {
316                buffer.write(data, 0, nRead);
317            }
318    
319            return buffer.toByteArray();
320        }
321    
322        private Map<String, String> processAttributes(final Node node, final Element element) {
323            final NamedNodeMap attrs = element.getAttributes();
324            final Map<String, String> attributes = node.getAttributes();
325    
326            for (int i = 0; i < attrs.getLength(); ++i) {
327                final org.w3c.dom.Node w3cNode = attrs.item(i);
328                if (w3cNode instanceof Attr) {
329                    final Attr attr = (Attr) w3cNode;
330                    attributes.put(attr.getName(), attr.getValue());
331                }
332            }
333            return attributes;
334        }
335    
336        /**
337         * The error that occurred.
338         */
339        private enum ErrorType {
340            CLASS_NOT_FOUND
341        }
342    
343        /**
344         * Status for recording errors.
345         */
346        private class Status {
347            private final Element element;
348            private final String name;
349            private final ErrorType errorType;
350    
351            public Status(final String name, final Element element, final ErrorType errorType) {
352                this.name = name;
353                this.element = element;
354                this.errorType = errorType;
355            }
356        }
357    
358    }