001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.config; 018 019 import java.io.ByteArrayInputStream; 020 import java.io.ByteArrayOutputStream; 021 import java.io.File; 022 import java.io.FileInputStream; 023 import java.io.FileNotFoundException; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.PrintStream; 028 import java.net.URI; 029 import java.net.URISyntaxException; 030 import java.nio.charset.Charset; 031 import java.util.ArrayList; 032 import java.util.HashMap; 033 import java.util.Iterator; 034 import java.util.List; 035 import java.util.Map; 036 import javax.xml.XMLConstants; 037 import javax.xml.parsers.DocumentBuilder; 038 import javax.xml.parsers.DocumentBuilderFactory; 039 import javax.xml.parsers.ParserConfigurationException; 040 import javax.xml.transform.Source; 041 import javax.xml.transform.stream.StreamSource; 042 import javax.xml.validation.Schema; 043 import javax.xml.validation.SchemaFactory; 044 import javax.xml.validation.Validator; 045 046 import org.apache.logging.log4j.Level; 047 import org.apache.logging.log4j.core.config.plugins.PluginManager; 048 import org.apache.logging.log4j.core.config.plugins.PluginType; 049 import org.apache.logging.log4j.core.config.plugins.ResolverUtil; 050 import org.apache.logging.log4j.core.helpers.FileUtils; 051 import org.apache.logging.log4j.core.net.Advertiser; 052 import org.apache.logging.log4j.status.StatusConsoleListener; 053 import org.apache.logging.log4j.status.StatusListener; 054 import org.apache.logging.log4j.status.StatusLogger; 055 import org.w3c.dom.Attr; 056 import org.w3c.dom.Document; 057 import org.w3c.dom.Element; 058 import org.w3c.dom.NamedNodeMap; 059 import org.w3c.dom.NodeList; 060 import org.w3c.dom.Text; 061 import org.xml.sax.InputSource; 062 import org.xml.sax.SAXException; 063 064 /** 065 * Creates a Node hierarchy from an XML file. 066 */ 067 public class XMLConfiguration extends BaseConfiguration implements Reconfigurable { 068 069 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 070 071 private static final String LOG4J_XSD = "Log4j-V2.0.xsd"; 072 073 private static final int BUF_SIZE = 16384; 074 075 private final List<Status> status = new ArrayList<Status>(); 076 077 private Map<String, String> advertisedConfiguration; 078 079 private Object advertisement; 080 081 private Element rootElement; 082 083 private boolean strict; 084 085 private String schema; 086 087 private Validator validator; 088 089 private final List<String> messages = new ArrayList<String>(); 090 091 private final File configFile; 092 093 public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 094 this.configFile = configSource.getFile(); 095 byte[] buffer = null; 096 097 try { 098 final InputStream configStream = configSource.getInputStream(); 099 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 * The error that occurred. 367 */ 368 private enum ErrorType { 369 CLASS_NOT_FOUND 370 } 371 372 /** 373 * Status for recording errors. 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 }