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