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