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