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.status.StatusConsoleListener;
24  import org.apache.logging.log4j.status.StatusListener;
25  import org.apache.logging.log4j.status.StatusLogger;
26  import org.w3c.dom.Attr;
27  import org.w3c.dom.Document;
28  import org.w3c.dom.Element;
29  import org.w3c.dom.NamedNodeMap;
30  import org.w3c.dom.NodeList;
31  import org.w3c.dom.Text;
32  import org.xml.sax.InputSource;
33  import org.xml.sax.SAXException;
34  
35  import javax.xml.XMLConstants;
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import javax.xml.parsers.ParserConfigurationException;
39  import javax.xml.transform.Source;
40  import javax.xml.transform.stream.StreamSource;
41  import javax.xml.validation.Schema;
42  import javax.xml.validation.SchemaFactory;
43  import javax.xml.validation.Validator;
44  import java.io.ByteArrayInputStream;
45  import java.io.ByteArrayOutputStream;
46  import java.io.File;
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.util.ArrayList;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Map;
53  
54  /**
55   * Creates a Node hierarchy from an XML file.
56   */
57  public class XMLConfiguration extends BaseConfiguration {
58  
59      private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
60  
61      private static final String LOG4J_XSD = "Log4J-V2.0.xsd";
62  
63      private static final int BUF_SIZE = 16384;
64  
65      private List<Status> status = new ArrayList<Status>();
66  
67      private Element rootElement = null;
68  
69      private boolean strict = false;
70  
71      private String schema = null;
72  
73      private Validator validator;
74  
75      public XMLConfiguration(InputSource source, File configFile) {
76          byte[] buffer = null;
77  
78          try {
79              buffer = toByteArray(source.getByteStream());
80              source = new InputSource(new ByteArrayInputStream(buffer));
81              DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
82              Document document = builder.parse(source);
83              rootElement = document.getDocumentElement();
84              Map<String, String> attrs = processAttributes(rootNode, rootElement);
85              Level status = Level.OFF;
86              boolean verbose = false;
87  
88              for (Map.Entry<String, String> entry : attrs.entrySet()) {
89                  if ("status".equalsIgnoreCase(entry.getKey())) {
90                      status = Level.toLevel(entry.getValue().toUpperCase(), Level.OFF);
91                  } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
92                      verbose = Boolean.parseBoolean(entry.getValue());
93                  } else if ("packages".equalsIgnoreCase(entry.getKey())) {
94                      String[] packages = entry.getValue().split(",");
95                      for (String p : packages) {
96                          PluginManager.addPackage(p);
97                      }
98                  } else if ("name".equalsIgnoreCase(entry.getKey())) {
99                      setName(entry.getValue());
100                 } else if ("strict".equalsIgnoreCase(entry.getKey())) {
101                     strict = Boolean.parseBoolean(entry.getValue());
102                 } else if ("schema".equalsIgnoreCase(entry.getKey())) {
103                     schema = entry.getValue();
104                 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
105                     int interval = Integer.parseInt(entry.getValue());
106                     if (interval > 0 && configFile != null) {
107                         monitor = new FileConfigurationMonitor(configFile, listeners, interval);
108                     }
109                 }
110             }
111             Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners();
112             boolean found = false;
113             while (iter.hasNext()) {
114                 StatusListener listener = iter.next();
115                 if (listener instanceof StatusConsoleListener) {
116                     found = true;
117                     ((StatusConsoleListener) listener).setLevel(status);
118                     if (!verbose) {
119                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
120                     }
121                 }
122             }
123             if (!found && status != Level.OFF) {
124                 StatusConsoleListener listener = new StatusConsoleListener(status);
125                 if (!verbose) {
126                     listener.setFilters(VERBOSE_CLASSES);
127                 }
128                 ((StatusLogger) LOGGER).registerListener(listener);
129             }
130 
131         } catch (SAXException domEx) {
132             LOGGER.error("Error parsing " + source.getSystemId(), domEx);
133         } catch (IOException ioe) {
134             LOGGER.error("Error parsing " + source.getSystemId(), ioe);
135         } catch (ParserConfigurationException pex) {
136             LOGGER.error("Error parsing " + source.getSystemId(), pex);
137         }
138         if (strict && schema != null && buffer != null) {
139             InputStream is = null;
140             try {
141                 is = getClass().getClassLoader().getResourceAsStream(schema);
142             } catch (Exception ex) {
143                 LOGGER.error("Unable to access schema " + schema);
144             }
145             if (is != null) {
146                 Source src = new StreamSource(is, LOG4J_XSD);
147                 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
148                 Schema schema = null;
149                 try {
150                     schema = factory.newSchema(src);
151                 } catch (SAXException ex) {
152                     LOGGER.error("Error parsing Log4j schema", ex);
153                 }
154                 if (schema != null) {
155                     validator = schema.newValidator();
156                     try {
157                         validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
158                     } catch (IOException ioe) {
159                         LOGGER.error("Error reading configuration for validation", ioe);
160                     } catch (SAXException ex) {
161                         LOGGER.error("Error validating configuration", ex);
162                     }
163                 }
164             }
165         }
166 
167         if (getName() == null) {
168             setName(source.getSystemId());
169         }
170     }
171 
172     public void setup() {
173         constructHierarchy(rootNode, rootElement);
174         if (status.size() > 0) {
175             for (Status s : status) {
176                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
177             }
178             return;
179         }
180         rootElement = null;
181     }
182 
183     private void constructHierarchy(Node node, Element element) {
184         processAttributes(node, element);
185         StringBuffer buffer = new StringBuffer();
186         NodeList list = element.getChildNodes();
187         List<Node> children = node.getChildren();
188         for (int i = 0; i < list.getLength(); i++) {
189             org.w3c.dom.Node w3cNode = list.item(i);
190             if (w3cNode instanceof Element) {
191                 Element child = (Element) w3cNode;
192                 String name = getType(child);
193                 PluginType type = getPluginManager().getPluginType(name);
194                 Node childNode = new Node(node, name, type);
195                 constructHierarchy(childNode, child);
196                 if (type == null) {
197                     String value = childNode.getValue();
198                     if (!childNode.hasChildren() && value != null) {
199                         node.getAttributes().put(name, value);
200                     } else {
201                         status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
202                     }
203                 } else {
204                     children.add(childNode);
205                 }
206             } else if (w3cNode instanceof Text) {
207                 Text data = (Text) w3cNode;
208                 buffer.append(data.getData());
209             }
210         }
211 
212         String text = buffer.toString().trim();
213         if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
214             node.setValue(text);
215         }
216     }
217 
218     private String getType(Element element) {
219         if (strict) {
220             NamedNodeMap attrs = element.getAttributes();
221             for (int i = 0; i < attrs.getLength(); ++i) {
222                 org.w3c.dom.Node w3cNode = attrs.item(i);
223                 if (w3cNode instanceof Attr) {
224                     Attr attr = (Attr) w3cNode;
225                     if (attr.getName().equalsIgnoreCase("type")) {
226                         String type = attr.getValue();
227                         attrs.removeNamedItem(attr.getName());
228                         return type;
229                     }
230                 }
231             }
232         }
233         return element.getTagName();
234     }
235 
236     private byte[] toByteArray(InputStream is) throws IOException {
237         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
238 
239         int nRead;
240         byte[] data = new byte[BUF_SIZE];
241 
242         while ((nRead = is.read(data, 0, data.length)) != -1) {
243             buffer.write(data, 0, nRead);
244         }
245 
246         return buffer.toByteArray();
247     }
248 
249     private Map<String, String> processAttributes(Node node, Element element) {
250         NamedNodeMap attrs = element.getAttributes();
251         Map<String, String> attributes = node.getAttributes();
252 
253         for (int i = 0; i < attrs.getLength(); ++i) {
254             org.w3c.dom.Node w3cNode = attrs.item(i);
255             if (w3cNode instanceof Attr) {
256                 Attr attr = (Attr) w3cNode;
257                 attributes.put(attr.getName(), attr.getValue());
258             }
259         }
260         return attributes;
261     }
262 
263     /**
264      * The error that occurred.
265      */
266     private enum ErrorType {
267         CLASS_NOT_FOUND
268     }
269 
270     /**
271      * Status for recording errors.
272      */
273     private class Status {
274         private Element element;
275         private String name;
276         private ErrorType errorType;
277 
278         public Status(String name, Element element, ErrorType errorType) {
279             this.name = name;
280             this.element = element;
281             this.errorType = errorType;
282         }
283     }
284 
285 }