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