1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.config;
18
19 import org.apache.logging.log4j.Level;
20 import org.apache.logging.log4j.core.config.plugins.PluginManager;
21 import org.apache.logging.log4j.core.config.plugins.PluginType;
22 import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
23 import org.apache.logging.log4j.core.helpers.FileUtils;
24 import org.apache.logging.log4j.status.StatusConsoleListener;
25 import org.apache.logging.log4j.status.StatusListener;
26 import org.apache.logging.log4j.status.StatusLogger;
27 import org.w3c.dom.Attr;
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Element;
30 import org.w3c.dom.NamedNodeMap;
31 import org.w3c.dom.NodeList;
32 import org.w3c.dom.Text;
33 import org.xml.sax.InputSource;
34 import org.xml.sax.SAXException;
35
36 import javax.xml.XMLConstants;
37 import javax.xml.parsers.DocumentBuilder;
38 import javax.xml.parsers.DocumentBuilderFactory;
39 import javax.xml.parsers.ParserConfigurationException;
40 import javax.xml.transform.Source;
41 import javax.xml.transform.stream.StreamSource;
42 import javax.xml.validation.Schema;
43 import javax.xml.validation.SchemaFactory;
44 import javax.xml.validation.Validator;
45 import java.io.ByteArrayInputStream;
46 import java.io.ByteArrayOutputStream;
47 import java.io.File;
48 import java.io.FileInputStream;
49 import java.io.FileNotFoundException;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.PrintStream;
54 import java.net.URI;
55 import java.net.URISyntaxException;
56 import java.util.ArrayList;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.Map;
60
61
62
63
64 public class XMLConfiguration extends BaseConfiguration implements Reconfigurable {
65
66 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
67
68 private static final String LOG4J_XSD = "Log4J-V2.0.xsd";
69
70 private static final int BUF_SIZE = 16384;
71
72 private final List<Status> status = new ArrayList<Status>();
73
74 private Element rootElement;
75
76 private boolean strict;
77
78 private String schema;
79
80 private Validator validator;
81
82 private final List<String> messages = new ArrayList<String>();
83
84 private final File configFile;
85
86 public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
87 this.configFile = configSource.getFile();
88 byte[] buffer = null;
89
90 try {
91 final InputStream configStream = configSource.getInputStream();
92 buffer = toByteArray(configStream);
93 configStream.close();
94 final InputSource source = new InputSource(new ByteArrayInputStream(buffer));
95 final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
96 final Document document = builder.parse(source);
97 rootElement = document.getDocumentElement();
98 final Map<String, String> attrs = processAttributes(rootNode, rootElement);
99 Level status = Level.OFF;
100 boolean verbose = false;
101 PrintStream stream = System.out;
102
103 for (final Map.Entry<String, String> entry : attrs.entrySet()) {
104 if ("status".equalsIgnoreCase(entry.getKey())) {
105 status = Level.toLevel(getSubst().replace(entry.getValue()), null);
106 if (status == null) {
107 status = Level.ERROR;
108 messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
109 }
110 } else if ("dest".equalsIgnoreCase(entry.getKey())) {
111 final String dest = entry.getValue();
112 if (dest != null) {
113 if (dest.equalsIgnoreCase("err")) {
114 stream = System.err;
115 } else {
116 try {
117 final File destFile = FileUtils.fileFromURI(new URI(dest));
118 stream = new PrintStream(new FileOutputStream(destFile));
119 } catch (final URISyntaxException use) {
120 System.err.println("Unable to write to " + dest + ". Writing to stdout");
121 }
122 }
123 }
124 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
125 verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
126 } else if ("packages".equalsIgnoreCase(getSubst().replace(entry.getKey()))) {
127 final String[] packages = entry.getValue().split(",");
128 for (final String p : packages) {
129 PluginManager.addPackage(p);
130 }
131 } else if ("name".equalsIgnoreCase(entry.getKey())) {
132 setName(getSubst().replace(entry.getValue()));
133 } else if ("strict".equalsIgnoreCase(entry.getKey())) {
134 strict = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
135 } else if ("schema".equalsIgnoreCase(entry.getKey())) {
136 schema = getSubst().replace(entry.getValue());
137 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
138 final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
139 if (interval > 0 && configFile != null) {
140 monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
141 }
142 }
143 }
144 final Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners();
145 boolean found = false;
146 while (iter.hasNext()) {
147 final StatusListener listener = iter.next();
148 if (listener instanceof StatusConsoleListener) {
149 found = true;
150 ((StatusConsoleListener) listener).setLevel(status);
151 if (!verbose) {
152 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
153 }
154 }
155 }
156 if (!found && status != Level.OFF) {
157 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
158 if (!verbose) {
159 listener.setFilters(VERBOSE_CLASSES);
160 }
161 ((StatusLogger) LOGGER).registerListener(listener);
162 for (final String msg : messages) {
163 LOGGER.error(msg);
164 }
165 }
166
167 } catch (final SAXException domEx) {
168 LOGGER.error("Error parsing " + configSource.getLocation(), domEx);
169 } catch (final IOException ioe) {
170 LOGGER.error("Error parsing " + configSource.getLocation(), ioe);
171 } catch (final ParserConfigurationException pex) {
172 LOGGER.error("Error parsing " + configSource.getLocation(), pex);
173 }
174 if (strict && schema != null && buffer != null) {
175 InputStream is = null;
176 try {
177 is = getClass().getClassLoader().getResourceAsStream(schema);
178 } catch (final Exception ex) {
179 LOGGER.error("Unable to access schema " + schema);
180 }
181 if (is != null) {
182 final Source src = new StreamSource(is, LOG4J_XSD);
183 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
184 Schema schema = null;
185 try {
186 schema = factory.newSchema(src);
187 } catch (final SAXException ex) {
188 LOGGER.error("Error parsing Log4j schema", ex);
189 }
190 if (schema != null) {
191 validator = schema.newValidator();
192 try {
193 validator.validate(new StreamSource(new ByteArrayInputStream(buffer)));
194 } catch (final IOException ioe) {
195 LOGGER.error("Error reading configuration for validation", ioe);
196 } catch (final SAXException ex) {
197 LOGGER.error("Error validating configuration", ex);
198 }
199 }
200 }
201 }
202
203 if (getName() == null) {
204 setName(configSource.getLocation());
205 }
206 }
207
208 @Override
209 public void setup() {
210 constructHierarchy(rootNode, rootElement);
211 if (status.size() > 0) {
212 for (final Status s : status) {
213 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
214 }
215 return;
216 }
217 rootElement = null;
218 }
219
220 public Configuration reconfigure() {
221 if (configFile != null) {
222 try {
223 final ConfigurationFactory.ConfigurationSource source =
224 new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
225 return new XMLConfiguration(source);
226 } catch (final FileNotFoundException ex) {
227 LOGGER.error("Cannot locate file " + configFile, ex);
228 }
229 }
230 return null;
231 }
232
233 private void constructHierarchy(final Node node, final Element element) {
234 processAttributes(node, element);
235 final StringBuffer buffer = new StringBuffer();
236 final NodeList list = element.getChildNodes();
237 final List<Node> children = node.getChildren();
238 for (int i = 0; i < list.getLength(); i++) {
239 final org.w3c.dom.Node w3cNode = list.item(i);
240 if (w3cNode instanceof Element) {
241 final Element child = (Element) w3cNode;
242 final String name = getType(child);
243 final PluginType type = getPluginManager().getPluginType(name);
244 final Node childNode = new Node(node, name, type);
245 constructHierarchy(childNode, child);
246 if (type == null) {
247 final String value = childNode.getValue();
248 if (!childNode.hasChildren() && value != null) {
249 node.getAttributes().put(name, value);
250 } else {
251 status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
252 }
253 } else {
254 children.add(childNode);
255 }
256 } else if (w3cNode instanceof Text) {
257 final Text data = (Text) w3cNode;
258 buffer.append(data.getData());
259 }
260 }
261
262 final String text = buffer.toString().trim();
263 if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
264 node.setValue(text);
265 }
266 }
267
268 private String getType(final Element element) {
269 if (strict) {
270 final NamedNodeMap attrs = element.getAttributes();
271 for (int i = 0; i < attrs.getLength(); ++i) {
272 final org.w3c.dom.Node w3cNode = attrs.item(i);
273 if (w3cNode instanceof Attr) {
274 final Attr attr = (Attr) w3cNode;
275 if (attr.getName().equalsIgnoreCase("type")) {
276 final String type = attr.getValue();
277 attrs.removeNamedItem(attr.getName());
278 return type;
279 }
280 }
281 }
282 }
283 return element.getTagName();
284 }
285
286 private byte[] toByteArray(final InputStream is) throws IOException {
287 final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
288
289 int nRead;
290 final byte[] data = new byte[BUF_SIZE];
291
292 while ((nRead = is.read(data, 0, data.length)) != -1) {
293 buffer.write(data, 0, nRead);
294 }
295
296 return buffer.toByteArray();
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 attributes.put(attr.getName(), attr.getValue());
308 }
309 }
310 return attributes;
311 }
312
313
314
315
316 private enum ErrorType {
317 CLASS_NOT_FOUND
318 }
319
320
321
322
323 private class Status {
324 private final Element element;
325 private final String name;
326 private final ErrorType errorType;
327
328 public Status(final String name, final Element element, final ErrorType errorType) {
329 this.name = name;
330 this.element = element;
331 this.errorType = errorType;
332 }
333 }
334
335 }