1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
316
317 private enum ErrorType {
318 CLASS_NOT_FOUND
319 }
320
321
322
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 }