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.Locale;
60  import java.util.Map;
61  
62  /**
63   * Creates a Node hierarchy from an XML file.
64   */
65  public class XMLConfiguration extends BaseConfiguration implements Reconfigurable {
66  
67      private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
68  
69      private static final String LOG4J_XSD = "Log4J-V2.0.xsd";
70  
71      private static final int BUF_SIZE = 16384;
72  
73      private final List<Status> status = new ArrayList<Status>();
74  
75      private Element rootElement;
76  
77      private boolean strict;
78  
79      private String schema;
80  
81      private Validator validator;
82  
83      private final List<String> messages = new ArrayList<String>();
84  
85      private final File configFile;
86  
87      public XMLConfiguration(ConfigurationFactory.ConfigurationSource configSource) {
88          this.configFile = configSource.getFile();
89          byte[] buffer = null;
90  
91          try {
92              InputStream configStream = configSource.getInputStream();
93              buffer = toByteArray(configStream);
94              configStream.close();
95              InputSource source = new InputSource(new ByteArrayInputStream(buffer));
96              DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
97              Document document = builder.parse(source);
98              rootElement = document.getDocumentElement();
99              Map<String, String> attrs = processAttributes(rootNode, rootElement);
100             Level status = Level.OFF;
101             boolean verbose = false;
102             PrintStream stream = System.out;
103 
104             for (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                     String dest = entry.getValue();
113                     if (dest != null) {
114                         if (dest.equalsIgnoreCase("err")) {
115                             stream = System.err;
116                         } else {
117                             try {
118                                 File destFile = FileUtils.fileFromURI(new URI(dest));
119                                 stream = new PrintStream(new FileOutputStream(destFile));
120                             } catch (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                     String[] packages = entry.getValue().split(",");
129                     for (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                     int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
140                     if (interval > 0 && configFile != null) {
141                         monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
142                     }
143                 }
144             }
145             Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners();
146             boolean found = false;
147             while (iter.hasNext()) {
148                 StatusListener listener = iter.next();
149                 if (listener instanceof StatusConsoleListener) {
150                     found = true;
151                     ((StatusConsoleListener) listener).setLevel(status);
152                     if (!verbose) {
153                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
154                     }
155                 }
156             }
157             if (!found && status != Level.OFF) {
158                 StatusConsoleListener listener = new StatusConsoleListener(status, stream);
159                 if (!verbose) {
160                     listener.setFilters(VERBOSE_CLASSES);
161                 }
162                 ((StatusLogger) LOGGER).registerListener(listener);
163                 for (String msg : messages) {
164                     LOGGER.error(msg);
165                 }
166             }
167 
168         } catch (SAXException domEx) {
169             LOGGER.error("Error parsing " + configSource.getLocation(), domEx);
170         } catch (IOException ioe) {
171             LOGGER.error("Error parsing " + configSource.getLocation(), ioe);
172         } catch (ParserConfigurationException pex) {
173             LOGGER.error("Error parsing " + configSource.getLocation(), pex);
174         }
175         if (strict && schema != null && buffer != null) {
176             InputStream is = null;
177             try {
178                 is = getClass().getClassLoader().getResourceAsStream(schema);
179             } catch (Exception ex) {
180                 LOGGER.error("Unable to access schema " + schema);
181             }
182             if (is != null) {
183                 Source src = new StreamSource(is, LOG4J_XSD);
184                 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
185                 Schema schema = null;
186                 try {
187                     schema = factory.newSchema(src);
188                 } catch (SAXException ex) {
189                     LOGGER.error("Error parsing Log4j schema", ex);
190                 }
191                 if (schema != null) {
192                     validator = schema.newValidator();
193                     try {
194                         validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
195                     } catch (IOException ioe) {
196                         LOGGER.error("Error reading configuration for validation", ioe);
197                     } catch (SAXException ex) {
198                         LOGGER.error("Error validating configuration", ex);
199                     }
200                 }
201             }
202         }
203 
204         if (getName() == null) {
205             setName(configSource.getLocation());
206         }
207     }
208 
209     @Override
210     public void setup() {
211         constructHierarchy(rootNode, rootElement);
212         if (status.size() > 0) {
213             for (Status s : status) {
214                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
215             }
216             return;
217         }
218         rootElement = null;
219     }
220 
221     public Configuration reconfigure() {
222         if (configFile != null) {
223             try {
224                 ConfigurationFactory.ConfigurationSource source =
225                     new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
226                 return new XMLConfiguration(source);
227             } catch (FileNotFoundException ex) {
228                 LOGGER.error("Cannot locate file " + configFile, ex);
229             }
230         }
231         return null;
232     }
233 
234     private void constructHierarchy(Node node, Element element) {
235         processAttributes(node, element);
236         StringBuffer buffer = new StringBuffer();
237         NodeList list = element.getChildNodes();
238         List<Node> children = node.getChildren();
239         for (int i = 0; i < list.getLength(); i++) {
240             org.w3c.dom.Node w3cNode = list.item(i);
241             if (w3cNode instanceof Element) {
242                 Element child = (Element) w3cNode;
243                 String name = getType(child);
244                 PluginType type = getPluginManager().getPluginType(name);
245                 Node childNode = new Node(node, name, type);
246                 constructHierarchy(childNode, child);
247                 if (type == null) {
248                     String value = childNode.getValue();
249                     if (!childNode.hasChildren() && value != null) {
250                         node.getAttributes().put(name, value);
251                     } else {
252                         status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
253                     }
254                 } else {
255                     children.add(childNode);
256                 }
257             } else if (w3cNode instanceof Text) {
258                 Text data = (Text) w3cNode;
259                 buffer.append(data.getData());
260             }
261         }
262 
263         String text = buffer.toString().trim();
264         if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
265             node.setValue(text);
266         }
267     }
268 
269     private String getType(Element element) {
270         if (strict) {
271             NamedNodeMap attrs = element.getAttributes();
272             for (int i = 0; i < attrs.getLength(); ++i) {
273                 org.w3c.dom.Node w3cNode = attrs.item(i);
274                 if (w3cNode instanceof Attr) {
275                     Attr attr = (Attr) w3cNode;
276                     if (attr.getName().equalsIgnoreCase("type")) {
277                         String type = attr.getValue();
278                         attrs.removeNamedItem(attr.getName());
279                         return type;
280                     }
281                 }
282             }
283         }
284         return element.getTagName();
285     }
286 
287     private byte[] toByteArray(InputStream is) throws IOException {
288         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
289 
290         int nRead;
291         byte[] data = new byte[BUF_SIZE];
292 
293         while ((nRead = is.read(data, 0, data.length)) != -1) {
294             buffer.write(data, 0, nRead);
295         }
296 
297         return buffer.toByteArray();
298     }
299 
300     private Map<String, String> processAttributes(Node node, Element element) {
301         NamedNodeMap attrs = element.getAttributes();
302         Map<String, String> attributes = node.getAttributes();
303 
304         for (int i = 0; i < attrs.getLength(); ++i) {
305             org.w3c.dom.Node w3cNode = attrs.item(i);
306             if (w3cNode instanceof Attr) {
307                 Attr attr = (Attr) w3cNode;
308                 attributes.put(attr.getName(), attr.getValue());
309             }
310         }
311         return attributes;
312     }
313 
314     /**
315      * The error that occurred.
316      */
317     private enum ErrorType {
318         CLASS_NOT_FOUND
319     }
320 
321     /**
322      * Status for recording errors.
323      */
324     private class Status {
325         private final Element element;
326         private final String name;
327         private final ErrorType errorType;
328 
329         public Status(String name, Element element, ErrorType errorType) {
330             this.name = name;
331             this.element = element;
332             this.errorType = errorType;
333         }
334     }
335 
336 }