1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.config.xml;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Map;
27 import javax.xml.XMLConstants;
28 import javax.xml.parsers.DocumentBuilder;
29 import javax.xml.parsers.DocumentBuilderFactory;
30 import javax.xml.parsers.ParserConfigurationException;
31 import javax.xml.transform.Source;
32 import javax.xml.transform.stream.StreamSource;
33 import javax.xml.validation.Schema;
34 import javax.xml.validation.SchemaFactory;
35 import javax.xml.validation.Validator;
36
37 import org.apache.logging.log4j.core.LoggerContext;
38 import org.apache.logging.log4j.core.config.AbstractConfiguration;
39 import org.apache.logging.log4j.core.config.Configuration;
40 import org.apache.logging.log4j.core.config.ConfigurationSource;
41 import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher;
42 import org.apache.logging.log4j.core.config.Node;
43 import org.apache.logging.log4j.core.config.Reconfigurable;
44 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
45 import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
46 import org.apache.logging.log4j.core.config.status.StatusConfiguration;
47 import org.apache.logging.log4j.core.util.Closer;
48 import org.apache.logging.log4j.core.util.FileWatcher;
49 import org.apache.logging.log4j.core.util.Loader;
50 import org.apache.logging.log4j.core.util.Patterns;
51 import org.apache.logging.log4j.core.util.Throwables;
52 import org.w3c.dom.Attr;
53 import org.w3c.dom.Document;
54 import org.w3c.dom.Element;
55 import org.w3c.dom.NamedNodeMap;
56 import org.w3c.dom.NodeList;
57 import org.w3c.dom.Text;
58 import org.xml.sax.InputSource;
59 import org.xml.sax.SAXException;
60
61
62
63
64 public class XmlConfiguration extends AbstractConfiguration implements Reconfigurable {
65
66 private static final String XINCLUDE_FIXUP_LANGUAGE =
67 "http://apache.org/xml/features/xinclude/fixup-language";
68 private static final String XINCLUDE_FIXUP_BASE_URIS =
69 "http://apache.org/xml/features/xinclude/fixup-base-uris";
70 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
71 private static final String LOG4J_XSD = "Log4j-config.xsd";
72
73 private final List<Status> status = new ArrayList<>();
74 private Element rootElement;
75 private boolean strict;
76 private String schemaResource;
77
78 public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource configSource) {
79 super(loggerContext, configSource);
80 final File configFile = configSource.getFile();
81 byte[] buffer = null;
82
83 try {
84 final InputStream configStream = configSource.getInputStream();
85 try {
86 buffer = toByteArray(configStream);
87 } finally {
88 Closer.closeSilently(configStream);
89 }
90 final InputSource source = new InputSource(new ByteArrayInputStream(buffer));
91 source.setSystemId(configSource.getLocation());
92 final DocumentBuilder documentBuilder = newDocumentBuilder(true);
93 Document document;
94 try {
95 document = documentBuilder.parse(source);
96 } catch (final Exception e) {
97
98 final Throwable throwable = Throwables.getRootCause(e);
99 if (throwable instanceof UnsupportedOperationException) {
100 LOGGER.warn(
101 "The DocumentBuilder {} does not support an operation: {}."
102 + "Trying again without XInclude...",
103 documentBuilder, e);
104 document = newDocumentBuilder(false).parse(source);
105 } else {
106 throw e;
107 }
108 }
109 rootElement = document.getDocumentElement();
110 final Map<String, String> attrs = processAttributes(rootNode, rootElement);
111 final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
112 .withStatus(getDefaultStatus());
113 for (final Map.Entry<String, String> entry : attrs.entrySet()) {
114 final String key = entry.getKey();
115 final String value = getStrSubstitutor().replace(entry.getValue());
116 if ("status".equalsIgnoreCase(key)) {
117 statusConfig.withStatus(value);
118 } else if ("dest".equalsIgnoreCase(key)) {
119 statusConfig.withDestination(value);
120 } else if ("shutdownHook".equalsIgnoreCase(key)) {
121 isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
122 } else if ("shutdownTimeout".equalsIgnoreCase(key)) {
123 shutdownTimeoutMillis = Long.parseLong(value);
124 } else if ("verbose".equalsIgnoreCase(key)) {
125 statusConfig.withVerbosity(value);
126 } else if ("packages".equalsIgnoreCase(key)) {
127 pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
128 } else if ("name".equalsIgnoreCase(key)) {
129 setName(value);
130 } else if ("strict".equalsIgnoreCase(key)) {
131 strict = Boolean.parseBoolean(value);
132 } else if ("schema".equalsIgnoreCase(key)) {
133 schemaResource = value;
134 } else if ("monitorInterval".equalsIgnoreCase(key)) {
135 final int intervalSeconds = Integer.parseInt(value);
136 if (intervalSeconds > 0) {
137 getWatchManager().setIntervalSeconds(intervalSeconds);
138 if (configFile != null) {
139 final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
140 getWatchManager().watchFile(configFile, watcher);
141 }
142 }
143 } else if ("advertiser".equalsIgnoreCase(key)) {
144 createAdvertiser(value, configSource, buffer, "text/xml");
145 }
146 }
147 statusConfig.initialize();
148 } catch (final SAXException | IOException | ParserConfigurationException e) {
149 LOGGER.error("Error parsing " + configSource.getLocation(), e);
150 }
151 if (strict && schemaResource != null && buffer != null) {
152 try (InputStream is = Loader.getResourceAsStream(schemaResource, XmlConfiguration.class.getClassLoader())) {
153 if (is != null) {
154 final Source src = new StreamSource(is, LOG4J_XSD);
155 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
156 Schema schema = null;
157 try {
158 schema = factory.newSchema(src);
159 } catch (final SAXException ex) {
160 LOGGER.error("Error parsing Log4j schema", ex);
161 }
162 if (schema != null) {
163 final Validator validator = schema.newValidator();
164 try {
165 validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
166 } catch (final IOException ioe) {
167 LOGGER.error("Error reading configuration for validation", ioe);
168 } catch (final SAXException ex) {
169 LOGGER.error("Error validating configuration", ex);
170 }
171 }
172 }
173 } catch (final Exception ex) {
174 LOGGER.error("Unable to access schema {}", this.schemaResource, ex);
175 }
176 }
177
178 if (getName() == null) {
179 setName(configSource.getLocation());
180 }
181 }
182
183
184
185
186
187
188
189
190 static DocumentBuilder newDocumentBuilder(final boolean xIncludeAware) throws ParserConfigurationException {
191 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
192 factory.setNamespaceAware(true);
193
194 disableDtdProcessing(factory);
195
196 if (xIncludeAware) {
197 enableXInclude(factory);
198 }
199 return factory.newDocumentBuilder();
200 }
201
202 private static void disableDtdProcessing(final DocumentBuilderFactory factory) throws ParserConfigurationException {
203 factory.setValidating(false);
204 factory.setExpandEntityReferences(false);
205 setFeature(factory, "http://xml.org/sax/features/external-general-entities", false);
206 setFeature(factory, "http://xml.org/sax/features/external-parameter-entities", false);
207 setFeature(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
208 }
209
210 private static void setFeature(final DocumentBuilderFactory factory, final String featureName, final boolean value)
211 throws ParserConfigurationException {
212 try {
213 factory.setFeature(featureName, value);
214 } catch (ParserConfigurationException e) {
215 throw e;
216 } catch (Exception | LinkageError e) {
217 getStatusLogger().error("Caught {} setting feature {} to {} on DocumentBuilderFactory {}: {}",
218 e.getClass().getCanonicalName(), featureName, value, factory, e, e);
219 }
220 }
221
222
223
224
225
226
227 private static void enableXInclude(final DocumentBuilderFactory factory) {
228 try {
229
230
231 factory.setXIncludeAware(true);
232 } catch (final UnsupportedOperationException e) {
233 LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
234 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError | NoSuchMethodError err) {
235 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support XInclude: {}", factory,
236 err);
237 }
238 try {
239
240
241 factory.setFeature(XINCLUDE_FIXUP_BASE_URIS, true);
242 } catch (final ParserConfigurationException e) {
243 LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory,
244 XINCLUDE_FIXUP_BASE_URIS, e);
245 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) {
246 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory,
247 err);
248 }
249 try {
250 factory.setFeature(XINCLUDE_FIXUP_LANGUAGE, true);
251 } catch (final ParserConfigurationException e) {
252 LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory,
253 XINCLUDE_FIXUP_LANGUAGE, e);
254 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) {
255 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory,
256 err);
257 }
258 }
259
260 @Override
261 public void setup() {
262 if (rootElement == null) {
263 LOGGER.error("No logging configuration");
264 return;
265 }
266 constructHierarchy(rootNode, rootElement);
267 if (status.size() > 0) {
268 for (final Status s : status) {
269 LOGGER.error("Error processing element {} ({}): {}", s.name, s.element, s.errorType);
270 }
271 return;
272 }
273 rootElement = null;
274 }
275
276 @Override
277 public Configuration reconfigure() {
278 try {
279 final ConfigurationSource source = getConfigurationSource().resetInputStream();
280 if (source == null) {
281 return null;
282 }
283 final XmlConfiguration config = new XmlConfiguration(getLoggerContext(), source);
284 return config.rootElement == null ? null : config;
285 } catch (final IOException ex) {
286 LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
287 }
288 return null;
289 }
290
291 private void constructHierarchy(final Node node, final Element element) {
292 processAttributes(node, element);
293 final StringBuilder buffer = new StringBuilder();
294 final NodeList list = element.getChildNodes();
295 final List<Node> children = node.getChildren();
296 for (int i = 0; i < list.getLength(); i++) {
297 final org.w3c.dom.Node w3cNode = list.item(i);
298 if (w3cNode instanceof Element) {
299 final Element child = (Element) w3cNode;
300 final String name = getType(child);
301 final PluginType<?> type = pluginManager.getPluginType(name);
302 final Node childNode = new Node(node, name, type);
303 constructHierarchy(childNode, child);
304 if (type == null) {
305 final String value = childNode.getValue();
306 if (!childNode.hasChildren() && value != null) {
307 node.getAttributes().put(name, value);
308 } else {
309 status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
310 }
311 } else {
312 children.add(childNode);
313 }
314 } else if (w3cNode instanceof Text) {
315 final Text data = (Text) w3cNode;
316 buffer.append(data.getData());
317 }
318 }
319
320 final String text = buffer.toString().trim();
321 if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
322 node.setValue(text);
323 }
324 }
325
326 private String getType(final Element element) {
327 if (strict) {
328 final NamedNodeMap attrs = element.getAttributes();
329 for (int i = 0; i < attrs.getLength(); ++i) {
330 final org.w3c.dom.Node w3cNode = attrs.item(i);
331 if (w3cNode instanceof Attr) {
332 final Attr attr = (Attr) w3cNode;
333 if (attr.getName().equalsIgnoreCase("type")) {
334 final String type = attr.getValue();
335 attrs.removeNamedItem(attr.getName());
336 return type;
337 }
338 }
339 }
340 }
341 return element.getTagName();
342 }
343
344 private Map<String, String> processAttributes(final Node node, final Element element) {
345 final NamedNodeMap attrs = element.getAttributes();
346 final Map<String, String> attributes = node.getAttributes();
347
348 for (int i = 0; i < attrs.getLength(); ++i) {
349 final org.w3c.dom.Node w3cNode = attrs.item(i);
350 if (w3cNode instanceof Attr) {
351 final Attr attr = (Attr) w3cNode;
352 if (attr.getName().equals("xml:base")) {
353 continue;
354 }
355 attributes.put(attr.getName(), attr.getValue());
356 }
357 }
358 return attributes;
359 }
360
361 @Override
362 public String toString() {
363 return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]";
364 }
365
366
367
368
369 private enum ErrorType {
370 CLASS_NOT_FOUND
371 }
372
373
374
375
376 private static class Status {
377 private final Element element;
378 private final String name;
379 private final ErrorType errorType;
380
381 public Status(final String name, final Element element, final ErrorType errorType) {
382 this.name = name;
383 this.element = element;
384 this.errorType = errorType;
385 }
386
387 @Override
388 public String toString() {
389 return "Status [name=" + name + ", element=" + element + ", errorType=" + errorType + "]";
390 }
391
392 }
393
394 }