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.core.net.Advertiser;
25  import org.apache.logging.log4j.status.StatusConsoleListener;
26  import org.apache.logging.log4j.status.StatusListener;
27  import org.apache.logging.log4j.status.StatusLogger;
28  import org.w3c.dom.Attr;
29  import org.w3c.dom.Document;
30  import org.w3c.dom.Element;
31  import org.w3c.dom.NamedNodeMap;
32  import org.w3c.dom.NodeList;
33  import org.w3c.dom.Text;
34  import org.xml.sax.InputSource;
35  import org.xml.sax.SAXException;
36  
37  import javax.xml.XMLConstants;
38  import javax.xml.parsers.DocumentBuilder;
39  import javax.xml.parsers.DocumentBuilderFactory;
40  import javax.xml.parsers.ParserConfigurationException;
41  import javax.xml.transform.Source;
42  import javax.xml.transform.stream.StreamSource;
43  import javax.xml.validation.Schema;
44  import javax.xml.validation.SchemaFactory;
45  import javax.xml.validation.Validator;
46  import java.io.ByteArrayInputStream;
47  import java.io.ByteArrayOutputStream;
48  import java.io.File;
49  import java.io.FileInputStream;
50  import java.io.FileNotFoundException;
51  import java.io.FileOutputStream;
52  import java.io.IOException;
53  import java.io.InputStream;
54  import java.io.PrintStream;
55  import java.net.URI;
56  import java.net.URISyntaxException;
57  import java.util.ArrayList;
58  import java.util.Iterator;
59  import java.util.List;
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(final ConfigurationFactory.ConfigurationSource configSource) {
88          this.configFile = configSource.getFile();
89          byte[] buffer = null;
90  
91          try {
92              final InputStream configStream = configSource.getInputStream();
93              buffer = toByteArray(configStream);
94              configStream.close();
95              final InputSource source = new InputSource(new ByteArrayInputStream(buffer));
96              final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
97              final Document document = builder.parse(source);
98              rootElement = document.getDocumentElement();
99              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 }