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