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