View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config;
18  
19  import org.apache.logging.log4j.Level;
20  import org.apache.logging.log4j.core.config.plugins.PluginManager;
21  import org.apache.logging.log4j.core.config.plugins.PluginType;
22  import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
23  import org.apache.logging.log4j.core.helpers.FileUtils;
24  import org.apache.logging.log4j.status.StatusConsoleListener;
25  import org.apache.logging.log4j.status.StatusListener;
26  import org.apache.logging.log4j.status.StatusLogger;
27  import org.w3c.dom.Attr;
28  import org.w3c.dom.Document;
29  import org.w3c.dom.Element;
30  import org.w3c.dom.NamedNodeMap;
31  import org.w3c.dom.NodeList;
32  import org.w3c.dom.Text;
33  import org.xml.sax.InputSource;
34  import org.xml.sax.SAXException;
35  
36  import javax.xml.XMLConstants;
37  import javax.xml.parsers.DocumentBuilder;
38  import javax.xml.parsers.DocumentBuilderFactory;
39  import javax.xml.parsers.ParserConfigurationException;
40  import javax.xml.transform.Source;
41  import javax.xml.transform.stream.StreamSource;
42  import javax.xml.validation.Schema;
43  import javax.xml.validation.SchemaFactory;
44  import javax.xml.validation.Validator;
45  import java.io.ByteArrayInputStream;
46  import java.io.ByteArrayOutputStream;
47  import java.io.File;
48  import java.io.FileInputStream;
49  import java.io.FileNotFoundException;
50  import java.io.FileOutputStream;
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.io.PrintStream;
54  import java.net.URI;
55  import java.net.URISyntaxException;
56  import java.util.ArrayList;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Map;
60  
61  /**
62   * Creates a Node hierarchy from an XML file.
63   */
64  public class XMLConfiguration extends BaseConfiguration implements Reconfigurable {
65  
66      private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
67  
68      private static final String LOG4J_XSD = "Log4J-V2.0.xsd";
69  
70      private static final int BUF_SIZE = 16384;
71  
72      private final List<Status> status = new ArrayList<Status>();
73  
74      private Element rootElement;
75  
76      private boolean strict;
77  
78      private String schema;
79  
80      private Validator validator;
81  
82      private final List<String> messages = new ArrayList<String>();
83  
84      private final File configFile;
85  
86      public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
87          this.configFile = configSource.getFile();
88          byte[] buffer = null;
89  
90          try {
91              final InputStream configStream = configSource.getInputStream();
92              buffer = toByteArray(configStream);
93              configStream.close();
94              final InputSource source = new InputSource(new ByteArrayInputStream(buffer));
95              final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
96              final Document document = builder.parse(source);
97              rootElement = document.getDocumentElement();
98              final Map<String, String> attrs = processAttributes(rootNode, rootElement);
99              Level status = Level.OFF;
100             boolean verbose = false;
101             PrintStream stream = System.out;
102 
103             for (final Map.Entry<String, String> entry : attrs.entrySet()) {
104                 if ("status".equalsIgnoreCase(entry.getKey())) {
105                     status = Level.toLevel(getSubst().replace(entry.getValue()), null);
106                     if (status == null) {
107                         status = Level.ERROR;
108                         messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
109                     }
110                 } else if ("dest".equalsIgnoreCase(entry.getKey())) {
111                     final String dest = entry.getValue();
112                     if (dest != null) {
113                         if (dest.equalsIgnoreCase("err")) {
114                             stream = System.err;
115                         } else {
116                             try {
117                                 final File destFile = FileUtils.fileFromURI(new URI(dest));
118                                 stream = new PrintStream(new FileOutputStream(destFile));
119                             } catch (final URISyntaxException use) {
120                                 System.err.println("Unable to write to " + dest + ". Writing to stdout");
121                             }
122                         }
123                     }
124                 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
125                     verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
126                 } else if ("packages".equalsIgnoreCase(getSubst().replace(entry.getKey()))) {
127                     final String[] packages = entry.getValue().split(",");
128                     for (final String p : packages) {
129                         PluginManager.addPackage(p);
130                     }
131                 } else if ("name".equalsIgnoreCase(entry.getKey())) {
132                     setName(getSubst().replace(entry.getValue()));
133                 } else if ("strict".equalsIgnoreCase(entry.getKey())) {
134                     strict = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
135                 } else if ("schema".equalsIgnoreCase(entry.getKey())) {
136                     schema = getSubst().replace(entry.getValue());
137                 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
138                     final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
139                     if (interval > 0 && configFile != null) {
140                         monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
141                     }
142                 }
143             }
144             final Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners();
145             boolean found = false;
146             while (iter.hasNext()) {
147                 final StatusListener listener = iter.next();
148                 if (listener instanceof StatusConsoleListener) {
149                     found = true;
150                     ((StatusConsoleListener) listener).setLevel(status);
151                     if (!verbose) {
152                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
153                     }
154                 }
155             }
156             if (!found && status != Level.OFF) {
157                 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
158                 if (!verbose) {
159                     listener.setFilters(VERBOSE_CLASSES);
160                 }
161                 ((StatusLogger) LOGGER).registerListener(listener);
162                 for (final String msg : messages) {
163                     LOGGER.error(msg);
164                 }
165             }
166 
167         } catch (final SAXException domEx) {
168             LOGGER.error("Error parsing " + configSource.getLocation(), domEx);
169         } catch (final IOException ioe) {
170             LOGGER.error("Error parsing " + configSource.getLocation(), ioe);
171         } catch (final ParserConfigurationException pex) {
172             LOGGER.error("Error parsing " + configSource.getLocation(), pex);
173         }
174         if (strict && schema != null && buffer != null) {
175             InputStream is = null;
176             try {
177                 is = getClass().getClassLoader().getResourceAsStream(schema);
178             } catch (final Exception ex) {
179                 LOGGER.error("Unable to access schema " + schema);
180             }
181             if (is != null) {
182                 final Source src = new StreamSource(is, LOG4J_XSD);
183                 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
184                 Schema schema = null;
185                 try {
186                     schema = factory.newSchema(src);
187                 } catch (final SAXException ex) {
188                     LOGGER.error("Error parsing Log4j schema", ex);
189                 }
190                 if (schema != null) {
191                     validator = schema.newValidator();
192                     try {
193                         validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
194                     } catch (final IOException ioe) {
195                         LOGGER.error("Error reading configuration for validation", ioe);
196                     } catch (final SAXException ex) {
197                         LOGGER.error("Error validating configuration", ex);
198                     }
199                 }
200             }
201         }
202 
203         if (getName() == null) {
204             setName(configSource.getLocation());
205         }
206     }
207 
208     @Override
209     public void setup() {
210         constructHierarchy(rootNode, rootElement);
211         if (status.size() > 0) {
212             for (final Status s : status) {
213                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
214             }
215             return;
216         }
217         rootElement = null;
218     }
219 
220     public Configuration reconfigure() {
221         if (configFile != null) {
222             try {
223                 final ConfigurationFactory.ConfigurationSource source =
224                     new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
225                 return new XMLConfiguration(source);
226             } catch (final FileNotFoundException ex) {
227                 LOGGER.error("Cannot locate file " + configFile, ex);
228             }
229         }
230         return null;
231     }
232 
233     private void constructHierarchy(final Node node, final Element element) {
234         processAttributes(node, element);
235         final StringBuffer buffer = new StringBuffer();
236         final NodeList list = element.getChildNodes();
237         final List<Node> children = node.getChildren();
238         for (int i = 0; i < list.getLength(); i++) {
239             final org.w3c.dom.Node w3cNode = list.item(i);
240             if (w3cNode instanceof Element) {
241                 final Element child = (Element) w3cNode;
242                 final String name = getType(child);
243                 final PluginType type = getPluginManager().getPluginType(name);
244                 final Node childNode = new Node(node, name, type);
245                 constructHierarchy(childNode, child);
246                 if (type == null) {
247                     final String value = childNode.getValue();
248                     if (!childNode.hasChildren() && value != null) {
249                         node.getAttributes().put(name, value);
250                     } else {
251                         status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
252                     }
253                 } else {
254                     children.add(childNode);
255                 }
256             } else if (w3cNode instanceof Text) {
257                 final Text data = (Text) w3cNode;
258                 buffer.append(data.getData());
259             }
260         }
261 
262         final String text = buffer.toString().trim();
263         if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
264             node.setValue(text);
265         }
266     }
267 
268     private String getType(final Element element) {
269         if (strict) {
270             final NamedNodeMap attrs = element.getAttributes();
271             for (int i = 0; i < attrs.getLength(); ++i) {
272                 final org.w3c.dom.Node w3cNode = attrs.item(i);
273                 if (w3cNode instanceof Attr) {
274                     final Attr attr = (Attr) w3cNode;
275                     if (attr.getName().equalsIgnoreCase("type")) {
276                         final String type = attr.getValue();
277                         attrs.removeNamedItem(attr.getName());
278                         return type;
279                     }
280                 }
281             }
282         }
283         return element.getTagName();
284     }
285 
286     private byte[] toByteArray(final InputStream is) throws IOException {
287         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
288 
289         int nRead;
290         final byte[] data = new byte[BUF_SIZE];
291 
292         while ((nRead = is.read(data, 0, data.length)) != -1) {
293             buffer.write(data, 0, nRead);
294         }
295 
296         return buffer.toByteArray();
297     }
298 
299     private Map<String, String> processAttributes(final Node node, final Element element) {
300         final NamedNodeMap attrs = element.getAttributes();
301         final Map<String, String> attributes = node.getAttributes();
302 
303         for (int i = 0; i < attrs.getLength(); ++i) {
304             final org.w3c.dom.Node w3cNode = attrs.item(i);
305             if (w3cNode instanceof Attr) {
306                 final Attr attr = (Attr) w3cNode;
307                 attributes.put(attr.getName(), attr.getValue());
308             }
309         }
310         return attributes;
311     }
312 
313     /**
314      * The error that occurred.
315      */
316     private enum ErrorType {
317         CLASS_NOT_FOUND
318     }
319 
320     /**
321      * Status for recording errors.
322      */
323     private class Status {
324         private final Element element;
325         private final String name;
326         private final ErrorType errorType;
327 
328         public Status(final String name, final Element element, final ErrorType errorType) {
329             this.name = name;
330             this.element = element;
331             this.errorType = errorType;
332         }
333     }
334 
335 }