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