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 org.apache.logging.log4j.Level; 020 import org.apache.logging.log4j.core.config.plugins.PluginManager; 021 import org.apache.logging.log4j.core.config.plugins.PluginType; 022 import org.apache.logging.log4j.core.config.plugins.ResolverUtil; 023 import org.apache.logging.log4j.core.helpers.FileUtils; 024 import org.apache.logging.log4j.core.net.Advertiser; 025 import org.apache.logging.log4j.status.StatusConsoleListener; 026 import org.apache.logging.log4j.status.StatusListener; 027 import org.apache.logging.log4j.status.StatusLogger; 028 import org.w3c.dom.Attr; 029 import org.w3c.dom.Document; 030 import org.w3c.dom.Element; 031 import org.w3c.dom.NamedNodeMap; 032 import org.w3c.dom.NodeList; 033 import org.w3c.dom.Text; 034 import org.xml.sax.InputSource; 035 import org.xml.sax.SAXException; 036 037 import javax.xml.XMLConstants; 038 import javax.xml.parsers.DocumentBuilder; 039 import javax.xml.parsers.DocumentBuilderFactory; 040 import javax.xml.parsers.ParserConfigurationException; 041 import javax.xml.transform.Source; 042 import javax.xml.transform.stream.StreamSource; 043 import javax.xml.validation.Schema; 044 import javax.xml.validation.SchemaFactory; 045 import javax.xml.validation.Validator; 046 import java.io.ByteArrayInputStream; 047 import java.io.ByteArrayOutputStream; 048 import java.io.File; 049 import java.io.FileInputStream; 050 import java.io.FileNotFoundException; 051 import java.io.FileOutputStream; 052 import java.io.IOException; 053 import java.io.InputStream; 054 import java.io.PrintStream; 055 import java.net.URI; 056 import java.net.URISyntaxException; 057 import java.util.ArrayList; 058 import java.util.Iterator; 059 import java.util.List; 060 import java.util.Map; 061 062 /** 063 * Creates a Node hierarchy from an XML file. 064 */ 065 public class XMLConfiguration extends BaseConfiguration implements Reconfigurable { 066 067 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 068 069 private static final String LOG4J_XSD = "Log4J-V2.0.xsd"; 070 071 private static final int BUF_SIZE = 16384; 072 073 private final List<Status> status = new ArrayList<Status>(); 074 075 private Element rootElement; 076 077 private boolean strict; 078 079 private String schema; 080 081 private Validator validator; 082 083 private final List<String> messages = new ArrayList<String>(); 084 085 private final File configFile; 086 087 public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 088 this.configFile = configSource.getFile(); 089 byte[] buffer = null; 090 091 try { 092 final InputStream configStream = configSource.getInputStream(); 093 buffer = toByteArray(configStream); 094 configStream.close(); 095 final InputSource source = new InputSource(new ByteArrayInputStream(buffer)); 096 final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 097 final Document document = builder.parse(source); 098 rootElement = document.getDocumentElement(); 099 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 final PluginType type = getPluginManager().getPluginType(advertiserString); 148 if (type != null) 149 { 150 final Class<Advertiser> clazz = type.getPluginClass(); 151 try { 152 advertiser = clazz.newInstance(); 153 } catch (InstantiationException e) { 154 System.err.println("InstantiationException attempting to instantiate advertiser: " + advertiserString); 155 } catch (IllegalAccessException e) { 156 System.err.println("IllegalAccessException attempting to instantiate advertiser: " + advertiserString); 157 } 158 } 159 } 160 } 161 } 162 final Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners(); 163 boolean found = false; 164 while (iter.hasNext()) { 165 final StatusListener listener = iter.next(); 166 if (listener instanceof StatusConsoleListener) { 167 found = true; 168 ((StatusConsoleListener) listener).setLevel(status); 169 if (!verbose) { 170 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES); 171 } 172 } 173 } 174 if (!found && status != Level.OFF) { 175 final StatusConsoleListener listener = new StatusConsoleListener(status, stream); 176 if (!verbose) { 177 listener.setFilters(VERBOSE_CLASSES); 178 } 179 ((StatusLogger) LOGGER).registerListener(listener); 180 for (final String msg : messages) { 181 LOGGER.error(msg); 182 } 183 } 184 185 } catch (final SAXException domEx) { 186 LOGGER.error("Error parsing " + configSource.getLocation(), domEx); 187 } catch (final IOException ioe) { 188 LOGGER.error("Error parsing " + configSource.getLocation(), ioe); 189 } catch (final ParserConfigurationException pex) { 190 LOGGER.error("Error parsing " + configSource.getLocation(), pex); 191 } 192 if (strict && schema != null && buffer != null) { 193 InputStream is = null; 194 try { 195 is = getClass().getClassLoader().getResourceAsStream(schema); 196 } catch (final Exception ex) { 197 LOGGER.error("Unable to access schema " + schema); 198 } 199 if (is != null) { 200 final Source src = new StreamSource(is, LOG4J_XSD); 201 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 202 Schema schema = null; 203 try { 204 schema = factory.newSchema(src); 205 } catch (final SAXException ex) { 206 LOGGER.error("Error parsing Log4j schema", ex); 207 } 208 if (schema != null) { 209 validator = schema.newValidator(); 210 try { 211 validator.validate(new StreamSource(new ByteArrayInputStream(buffer))); 212 } catch (final IOException ioe) { 213 LOGGER.error("Error reading configuration for validation", ioe); 214 } catch (final SAXException ex) { 215 LOGGER.error("Error validating configuration", ex); 216 } 217 } 218 } 219 } 220 221 if (getName() == null) { 222 setName(configSource.getLocation()); 223 } 224 } 225 226 @Override 227 public void setup() { 228 constructHierarchy(rootNode, rootElement); 229 if (status.size() > 0) { 230 for (final Status s : status) { 231 LOGGER.error("Error processing element " + s.name + ": " + s.errorType); 232 } 233 return; 234 } 235 rootElement = null; 236 } 237 238 public Configuration reconfigure() { 239 if (configFile != null) { 240 try { 241 final ConfigurationFactory.ConfigurationSource source = 242 new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile); 243 return new XMLConfiguration(source); 244 } catch (final FileNotFoundException ex) { 245 LOGGER.error("Cannot locate file " + configFile, ex); 246 } 247 } 248 return null; 249 } 250 251 private void constructHierarchy(final Node node, final Element element) { 252 processAttributes(node, element); 253 final StringBuffer buffer = new StringBuffer(); 254 final NodeList list = element.getChildNodes(); 255 final List<Node> children = node.getChildren(); 256 for (int i = 0; i < list.getLength(); i++) { 257 final org.w3c.dom.Node w3cNode = list.item(i); 258 if (w3cNode instanceof Element) { 259 final Element child = (Element) w3cNode; 260 final String name = getType(child); 261 final PluginType type = getPluginManager().getPluginType(name); 262 final Node childNode = new Node(node, name, type); 263 constructHierarchy(childNode, child); 264 if (type == null) { 265 final String value = childNode.getValue(); 266 if (!childNode.hasChildren() && value != null) { 267 node.getAttributes().put(name, value); 268 } else { 269 status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND)); 270 } 271 } else { 272 children.add(childNode); 273 } 274 } else if (w3cNode instanceof Text) { 275 final Text data = (Text) w3cNode; 276 buffer.append(data.getData()); 277 } 278 } 279 280 final String text = buffer.toString().trim(); 281 if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) { 282 node.setValue(text); 283 } 284 } 285 286 private String getType(final Element element) { 287 if (strict) { 288 final NamedNodeMap attrs = element.getAttributes(); 289 for (int i = 0; i < attrs.getLength(); ++i) { 290 final org.w3c.dom.Node w3cNode = attrs.item(i); 291 if (w3cNode instanceof Attr) { 292 final Attr attr = (Attr) w3cNode; 293 if (attr.getName().equalsIgnoreCase("type")) { 294 final String type = attr.getValue(); 295 attrs.removeNamedItem(attr.getName()); 296 return type; 297 } 298 } 299 } 300 } 301 return element.getTagName(); 302 } 303 304 private byte[] toByteArray(final InputStream is) throws IOException { 305 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 306 307 int nRead; 308 final byte[] data = new byte[BUF_SIZE]; 309 310 while ((nRead = is.read(data, 0, data.length)) != -1) { 311 buffer.write(data, 0, nRead); 312 } 313 314 return buffer.toByteArray(); 315 } 316 317 private Map<String, String> processAttributes(final Node node, final Element element) { 318 final NamedNodeMap attrs = element.getAttributes(); 319 final Map<String, String> attributes = node.getAttributes(); 320 321 for (int i = 0; i < attrs.getLength(); ++i) { 322 final org.w3c.dom.Node w3cNode = attrs.item(i); 323 if (w3cNode instanceof Attr) { 324 final Attr attr = (Attr) w3cNode; 325 attributes.put(attr.getName(), attr.getValue()); 326 } 327 } 328 return attributes; 329 } 330 331 /** 332 * The error that occurred. 333 */ 334 private enum ErrorType { 335 CLASS_NOT_FOUND 336 } 337 338 /** 339 * Status for recording errors. 340 */ 341 private class Status { 342 private final Element element; 343 private final String name; 344 private final ErrorType errorType; 345 346 public Status(final String name, final Element element, final ErrorType errorType) { 347 this.name = name; 348 this.element = element; 349 this.errorType = errorType; 350 } 351 } 352 353 }