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.status.StatusConsoleListener; 025 import org.apache.logging.log4j.status.StatusListener; 026 import org.apache.logging.log4j.status.StatusLogger; 027 import org.w3c.dom.Attr; 028 import org.w3c.dom.Document; 029 import org.w3c.dom.Element; 030 import org.w3c.dom.NamedNodeMap; 031 import org.w3c.dom.NodeList; 032 import org.w3c.dom.Text; 033 import org.xml.sax.InputSource; 034 import org.xml.sax.SAXException; 035 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 import java.io.ByteArrayInputStream; 046 import java.io.ByteArrayOutputStream; 047 import java.io.File; 048 import java.io.FileInputStream; 049 import java.io.FileNotFoundException; 050 import java.io.FileOutputStream; 051 import java.io.IOException; 052 import java.io.InputStream; 053 import java.io.PrintStream; 054 import java.net.URI; 055 import java.net.URISyntaxException; 056 import java.util.ArrayList; 057 import java.util.Iterator; 058 import java.util.List; 059 import java.util.Map; 060 061 /** 062 * Creates a Node hierarchy from an XML file. 063 */ 064 public class XMLConfiguration extends BaseConfiguration implements Reconfigurable { 065 066 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 067 068 private static final String LOG4J_XSD = "Log4J-V2.0.xsd"; 069 070 private static final int BUF_SIZE = 16384; 071 072 private final List<Status> status = new ArrayList<Status>(); 073 074 private Element rootElement; 075 076 private boolean strict; 077 078 private String schema; 079 080 private Validator validator; 081 082 private final List<String> messages = new ArrayList<String>(); 083 084 private final File configFile; 085 086 public XMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 087 this.configFile = configSource.getFile(); 088 byte[] buffer = null; 089 090 try { 091 final InputStream configStream = configSource.getInputStream(); 092 buffer = toByteArray(configStream); 093 configStream.close(); 094 final InputSource source = new InputSource(new ByteArrayInputStream(buffer)); 095 final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 096 final Document document = builder.parse(source); 097 rootElement = document.getDocumentElement(); 098 final Map<String, String> attrs = processAttributes(rootNode, rootElement); 099 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 * The error that occurred. 315 */ 316 private enum ErrorType { 317 CLASS_NOT_FOUND 318 } 319 320 /** 321 * Status for recording errors. 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 }