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 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 ConfigurationSource configSource) {
79 super(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 ("verbose".equalsIgnoreCase(key)) {
123 statusConfig.withVerbosity(value);
124 } else if ("packages".equalsIgnoreCase(key)) {
125 pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
126 } else if ("name".equalsIgnoreCase(key)) {
127 setName(value);
128 } else if ("strict".equalsIgnoreCase(key)) {
129 strict = Boolean.parseBoolean(value);
130 } else if ("schema".equalsIgnoreCase(key)) {
131 schemaResource = value;
132 } else if ("monitorInterval".equalsIgnoreCase(key)) {
133 final int intervalSeconds = Integer.parseInt(value);
134 if (intervalSeconds > 0) {
135 getWatchManager().setIntervalSeconds(intervalSeconds);
136 if (configFile != null) {
137 final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
138 getWatchManager().watchFile(configFile, watcher);
139 }
140 }
141 } else if ("advertiser".equalsIgnoreCase(key)) {
142 createAdvertiser(value, configSource, buffer, "text/xml");
143 }
144 }
145 statusConfig.initialize();
146 } catch (final SAXException | IOException | ParserConfigurationException e) {
147 LOGGER.error("Error parsing " + configSource.getLocation(), e);
148 }
149 if (strict && schemaResource != null && buffer != null) {
150 InputStream is = null;
151 try {
152 is = Loader.getResourceAsStream(schemaResource, XmlConfiguration.class.getClassLoader());
153 } catch (final Exception ex) {
154 LOGGER.error("Unable to access schema {}", this.schemaResource, ex);
155 }
156 if (is != null) {
157 final Source src = new StreamSource(is, LOG4J_XSD);
158 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
159 Schema schema = null;
160 try {
161 schema = factory.newSchema(src);
162 } catch (final SAXException ex) {
163 LOGGER.error("Error parsing Log4j schema", ex);
164 }
165 if (schema != null) {
166 final Validator validator = schema.newValidator();
167 try {
168 validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
169 } catch (final IOException ioe) {
170 LOGGER.error("Error reading configuration for validation", ioe);
171 } catch (final SAXException ex) {
172 LOGGER.error("Error validating configuration", ex);
173 }
174 }
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 if (xIncludeAware) {
194 enableXInclude(factory);
195 }
196 return factory.newDocumentBuilder();
197 }
198
199
200
201
202
203
204 private static void enableXInclude(final DocumentBuilderFactory factory) {
205 try {
206
207
208 factory.setXIncludeAware(true);
209 } catch (final UnsupportedOperationException e) {
210 LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e);
211 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError | NoSuchMethodError err) {
212 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support XInclude: {}", factory,
213 err);
214 }
215 try {
216
217
218 factory.setFeature(XINCLUDE_FIXUP_BASE_URIS, true);
219 } catch (final ParserConfigurationException e) {
220 LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory,
221 XINCLUDE_FIXUP_BASE_URIS, e);
222 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) {
223 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory,
224 err);
225 }
226 try {
227 factory.setFeature(XINCLUDE_FIXUP_LANGUAGE, true);
228 } catch (final ParserConfigurationException e) {
229 LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory,
230 XINCLUDE_FIXUP_LANGUAGE, e);
231 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) {
232 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory,
233 err);
234 }
235 }
236
237 @Override
238 public void setup() {
239 if (rootElement == null) {
240 LOGGER.error("No logging configuration");
241 return;
242 }
243 constructHierarchy(rootNode, rootElement);
244 if (status.size() > 0) {
245 for (final Status s : status) {
246 LOGGER.error("Error processing element {} ({}): {}", s.name, s.element, s.errorType);
247 }
248 return;
249 }
250 rootElement = null;
251 }
252
253 @Override
254 public Configuration reconfigure() {
255 try {
256 final ConfigurationSource source = getConfigurationSource().resetInputStream();
257 if (source == null) {
258 return null;
259 }
260 final XmlConfiguration config = new XmlConfiguration(source);
261 return config.rootElement == null ? null : config;
262 } catch (final IOException ex) {
263 LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
264 }
265 return null;
266 }
267
268 private void constructHierarchy(final Node node, final Element element) {
269 processAttributes(node, element);
270 final StringBuilder buffer = new StringBuilder();
271 final NodeList list = element.getChildNodes();
272 final List<Node> children = node.getChildren();
273 for (int i = 0; i < list.getLength(); i++) {
274 final org.w3c.dom.Node w3cNode = list.item(i);
275 if (w3cNode instanceof Element) {
276 final Element child = (Element) w3cNode;
277 final String name = getType(child);
278 final PluginType<?> type = pluginManager.getPluginType(name);
279 final Node childNode = new Node(node, name, type);
280 constructHierarchy(childNode, child);
281 if (type == null) {
282 final String value = childNode.getValue();
283 if (!childNode.hasChildren() && value != null) {
284 node.getAttributes().put(name, value);
285 } else {
286 status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
287 }
288 } else {
289 children.add(childNode);
290 }
291 } else if (w3cNode instanceof Text) {
292 final Text data = (Text) w3cNode;
293 buffer.append(data.getData());
294 }
295 }
296
297 final String text = buffer.toString().trim();
298 if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
299 node.setValue(text);
300 }
301 }
302
303 private String getType(final Element element) {
304 if (strict) {
305 final NamedNodeMap attrs = element.getAttributes();
306 for (int i = 0; i < attrs.getLength(); ++i) {
307 final org.w3c.dom.Node w3cNode = attrs.item(i);
308 if (w3cNode instanceof Attr) {
309 final Attr attr = (Attr) w3cNode;
310 if (attr.getName().equalsIgnoreCase("type")) {
311 final String type = attr.getValue();
312 attrs.removeNamedItem(attr.getName());
313 return type;
314 }
315 }
316 }
317 }
318 return element.getTagName();
319 }
320
321 private Map<String, String> processAttributes(final Node node, final Element element) {
322 final NamedNodeMap attrs = element.getAttributes();
323 final Map<String, String> attributes = node.getAttributes();
324
325 for (int i = 0; i < attrs.getLength(); ++i) {
326 final org.w3c.dom.Node w3cNode = attrs.item(i);
327 if (w3cNode instanceof Attr) {
328 final Attr attr = (Attr) w3cNode;
329 if (attr.getName().equals("xml:base")) {
330 continue;
331 }
332 attributes.put(attr.getName(), attr.getValue());
333 }
334 }
335 return attributes;
336 }
337
338 @Override
339 public String toString() {
340 return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]";
341 }
342
343
344
345
346 private enum ErrorType {
347 CLASS_NOT_FOUND
348 }
349
350
351
352
353 private static class Status {
354 private final Element element;
355 private final String name;
356 private final ErrorType errorType;
357
358 public Status(final String name, final Element element, final ErrorType errorType) {
359 this.name = name;
360 this.element = element;
361 this.errorType = errorType;
362 }
363
364 @Override
365 public String toString() {
366 return "Status [name=" + name + ", element=" + element + ", errorType=" + errorType + "]";
367 }
368
369 }
370
371 }