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 java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.PrintStream;
28  import java.net.URI;
29  import java.net.URISyntaxException;
30  import java.nio.charset.Charset;
31  import java.util.ArrayList;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
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  
46  import org.apache.logging.log4j.Level;
47  import org.apache.logging.log4j.core.config.plugins.PluginManager;
48  import org.apache.logging.log4j.core.config.plugins.PluginType;
49  import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
50  import org.apache.logging.log4j.core.helpers.FileUtils;
51  import org.apache.logging.log4j.core.net.Advertiser;
52  import org.apache.logging.log4j.status.StatusConsoleListener;
53  import org.apache.logging.log4j.status.StatusListener;
54  import org.apache.logging.log4j.status.StatusLogger;
55  import org.w3c.dom.Attr;
56  import org.w3c.dom.Document;
57  import org.w3c.dom.Element;
58  import org.w3c.dom.NamedNodeMap;
59  import org.w3c.dom.NodeList;
60  import org.w3c.dom.Text;
61  import org.xml.sax.InputSource;
62  import org.xml.sax.SAXException;
63  
64  /**
65   * Creates a Node hierarchy from an XML file.
66   */
67  public class XMLConfiguration extends BaseConfiguration implements Reconfigurable {
68  
69      private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
70  
71      private static final String LOG4J_XSD = "Log4j-V2.0.xsd";
72  
73      private static final int BUF_SIZE = 16384;
74  
75      private final List<Status> status = new ArrayList<Status>();
76  
77      private Map<String, String> advertisedConfiguration;
78  
79      private Object advertisement;
80  
81      private Element rootElement;
82  
83      private boolean strict;
84  
85      private String schema;
86  
87      private Validator validator;
88  
89      private final List<String> messages = new ArrayList<String>();
90  
91      private final File configFile;
92  
93      public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
94          this.configFile = configSource.getFile();
95          byte[] buffer = null;
96  
97          try {
98              final InputStream configStream = configSource.getInputStream();
99              buffer = toByteArray(configStream);
100             configStream.close();
101             final InputSource source = new InputSource(new ByteArrayInputStream(buffer));
102             final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
103             final Document document = builder.parse(source);
104             rootElement = document.getDocumentElement();
105             final Map<String, String> attrs = processAttributes(rootNode, rootElement);
106             Level status = Level.OFF;
107             boolean verbose = false;
108             PrintStream stream = System.out;
109 
110             for (final Map.Entry<String, String> entry : attrs.entrySet()) {
111                 if ("status".equalsIgnoreCase(entry.getKey())) {
112                     status = Level.toLevel(getSubst().replace(entry.getValue()), null);
113                     if (status == null) {
114                         status = Level.ERROR;
115                         messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
116                     }
117                 } else if ("dest".equalsIgnoreCase(entry.getKey())) {
118                     final String dest = entry.getValue();
119                     if (dest != null) {
120                         if (dest.equalsIgnoreCase("err")) {
121                             stream = System.err;
122                         } else {
123                             try {
124                                 final File destFile = FileUtils.fileFromURI(new URI(dest));
125                                 final String enc = Charset.defaultCharset().name();
126                                 stream = new PrintStream(new FileOutputStream(destFile), true, enc);
127                             } catch (final URISyntaxException use) {
128                                 System.err.println("Unable to write to " + dest + ". Writing to stdout");
129                             }
130                         }
131                     }
132                 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
133                     verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
134                 } else if ("packages".equalsIgnoreCase(getSubst().replace(entry.getKey()))) {
135                     final String[] packages = entry.getValue().split(",");
136                     for (final String p : packages) {
137                         PluginManager.addPackage(p);
138                     }
139                 } else if ("name".equalsIgnoreCase(entry.getKey())) {
140                     setName(getSubst().replace(entry.getValue()));
141                 } else if ("strict".equalsIgnoreCase(entry.getKey())) {
142                     strict = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
143                 } else if ("schema".equalsIgnoreCase(entry.getKey())) {
144                     schema = getSubst().replace(entry.getValue());
145                 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
146                     final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
147                     if (interval > 0 && configFile != null) {
148                         monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
149                     }
150                 } else if ("advertiser".equalsIgnoreCase(entry.getKey())) {
151                     final String advertiserString = getSubst().replace(entry.getValue());
152                     if (advertiserString != null)
153                     {
154                         @SuppressWarnings("unchecked")
155                         final PluginType<Advertiser> type = getPluginManager().getPluginType(advertiserString);
156                         if (type != null)
157                         {
158                             final Class<Advertiser> clazz = type.getPluginClass();
159                             try {
160                                 advertiser = clazz.newInstance();
161                                 advertisedConfiguration = new HashMap<String, String>();
162                                 advertisedConfiguration.put("content", new String(buffer));
163                                 advertisedConfiguration.put("contentType", "text/xml");
164                                 advertisedConfiguration.put("name", "configuration");
165                                 if (configSource.getLocation() != null)
166                                 {
167                                     advertisedConfiguration.put("location", configSource.getLocation());
168                                 }
169                             } catch (InstantiationException e) {
170                                 System.err.println("InstantiationException attempting to instantiate advertiser: " + advertiserString);
171                             } catch (IllegalAccessException e) {
172                                 System.err.println("IllegalAccessException attempting to instantiate advertiser: " + advertiserString);
173                             }
174                         }
175                     }
176                 }
177             }
178             final Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners();
179             boolean found = false;
180             while (iter.hasNext()) {
181                 final StatusListener listener = iter.next();
182                 if (listener instanceof StatusConsoleListener) {
183                     found = true;
184                     ((StatusConsoleListener) listener).setLevel(status);
185                     if (!verbose) {
186                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
187                     }
188                 }
189             }
190             if (!found && status != Level.OFF) {
191                 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
192                 if (!verbose) {
193                     listener.setFilters(VERBOSE_CLASSES);
194                 }
195                 ((StatusLogger) LOGGER).registerListener(listener);
196                 for (final String msg : messages) {
197                     LOGGER.error(msg);
198                 }
199             }
200 
201         } catch (final SAXException domEx) {
202             LOGGER.error("Error parsing " + configSource.getLocation(), domEx);
203         } catch (final IOException ioe) {
204             LOGGER.error("Error parsing " + configSource.getLocation(), ioe);
205         } catch (final ParserConfigurationException pex) {
206             LOGGER.error("Error parsing " + configSource.getLocation(), pex);
207         }
208         if (strict && schema != null && buffer != null) {
209             InputStream is = null;
210             try {
211                 is = getClass().getClassLoader().getResourceAsStream(schema);
212             } catch (final Exception ex) {
213                 LOGGER.error("Unable to access schema " + schema);
214             }
215             if (is != null) {
216                 final Source src = new StreamSource(is, LOG4J_XSD);
217                 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
218                 Schema schema = null;
219                 try {
220                     schema = factory.newSchema(src);
221                 } catch (final SAXException ex) {
222                     LOGGER.error("Error parsing Log4j schema", ex);
223                 }
224                 if (schema != null) {
225                     validator = schema.newValidator();
226                     try {
227                         validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
228                     } catch (final IOException ioe) {
229                         LOGGER.error("Error reading configuration for validation", ioe);
230                     } catch (final SAXException ex) {
231                         LOGGER.error("Error validating configuration", ex);
232                     }
233                 }
234             }
235         }
236 
237         if (getName() == null) {
238             setName(configSource.getLocation());
239         }
240     }
241 
242     @Override
243     public void stop() {
244         super.stop();
245         if (advertiser != null && advertisement != null)
246         {
247             advertiser.unadvertise(advertisement);
248         }
249     }
250 
251     @Override
252     public void setup() {
253         if (rootElement == null) {
254             LOGGER.error("No logging configuration");
255             return;
256         }
257         constructHierarchy(rootNode, rootElement);
258         if (status.size() > 0) {
259             for (final Status s : status) {
260                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
261             }
262             return;
263         }
264         rootElement = null;
265         if (advertiser != null && advertisedConfiguration != null)
266         {
267             advertisement = advertiser.advertise(advertisedConfiguration);
268         }
269     }
270 
271     @Override
272     public Configuration reconfigure() {
273         if (configFile != null) {
274             try {
275                 final ConfigurationFactory.ConfigurationSource source =
276                     new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
277                 return new XMLConfiguration(source);
278             } catch (final FileNotFoundException ex) {
279                 LOGGER.error("Cannot locate file " + configFile, ex);
280             }
281         }
282         return null;
283     }
284 
285     private void constructHierarchy(final Node node, final Element element) {
286         processAttributes(node, element);
287         final StringBuffer buffer = new StringBuffer();
288         final NodeList list = element.getChildNodes();
289         final List<Node> children = node.getChildren();
290         for (int i = 0; i < list.getLength(); i++) {
291             final org.w3c.dom.Node w3cNode = list.item(i);
292             if (w3cNode instanceof Element) {
293                 final Element child = (Element) w3cNode;
294                 final String name = getType(child);
295                 final PluginType<?> type = getPluginManager().getPluginType(name);
296                 final Node childNode = new Node(node, name, type);
297                 constructHierarchy(childNode, child);
298                 if (type == null) {
299                     final String value = childNode.getValue();
300                     if (!childNode.hasChildren() && value != null) {
301                         node.getAttributes().put(name, value);
302                     } else {
303                         status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
304                     }
305                 } else {
306                     children.add(childNode);
307                 }
308             } else if (w3cNode instanceof Text) {
309                 final Text data = (Text) w3cNode;
310                 buffer.append(data.getData());
311             }
312         }
313 
314         final String text = buffer.toString().trim();
315         if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
316             node.setValue(text);
317         }
318     }
319 
320     private String getType(final Element element) {
321         if (strict) {
322             final NamedNodeMap attrs = element.getAttributes();
323             for (int i = 0; i < attrs.getLength(); ++i) {
324                 final org.w3c.dom.Node w3cNode = attrs.item(i);
325                 if (w3cNode instanceof Attr) {
326                     final Attr attr = (Attr) w3cNode;
327                     if (attr.getName().equalsIgnoreCase("type")) {
328                         final String type = attr.getValue();
329                         attrs.removeNamedItem(attr.getName());
330                         return type;
331                     }
332                 }
333             }
334         }
335         return element.getTagName();
336     }
337 
338     private byte[] toByteArray(final InputStream is) throws IOException {
339         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
340 
341         int nRead;
342         final byte[] data = new byte[BUF_SIZE];
343 
344         while ((nRead = is.read(data, 0, data.length)) != -1) {
345             buffer.write(data, 0, nRead);
346         }
347 
348         return buffer.toByteArray();
349     }
350 
351     private Map<String, String> processAttributes(final Node node, final Element element) {
352         final NamedNodeMap attrs = element.getAttributes();
353         final Map<String, String> attributes = node.getAttributes();
354 
355         for (int i = 0; i < attrs.getLength(); ++i) {
356             final org.w3c.dom.Node w3cNode = attrs.item(i);
357             if (w3cNode instanceof Attr) {
358                 final Attr attr = (Attr) w3cNode;
359                 attributes.put(attr.getName(), attr.getValue());
360             }
361         }
362         return attributes;
363     }
364 
365     /**
366      * The error that occurred.
367      */
368     private enum ErrorType {
369         CLASS_NOT_FOUND
370     }
371 
372     /**
373      * Status for recording errors.
374      */
375     private class Status {
376         private final Element element;
377         private final String name;
378         private final ErrorType errorType;
379 
380         public Status(final String name, final Element element, final ErrorType errorType) {
381             this.name = name;
382             this.element = element;
383             this.errorType = errorType;
384         }
385     }
386 
387 }