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.Locale; 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(ConfigurationFactory.ConfigurationSource configSource) { 088 this.configFile = configSource.getFile(); 089 byte[] buffer = null; 090 091 try { 092 InputStream configStream = configSource.getInputStream(); 093 buffer = toByteArray(configStream); 094 configStream.close(); 095 InputSource source = new InputSource(new ByteArrayInputStream(buffer)); 096 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 097 Document document = builder.parse(source); 098 rootElement = document.getDocumentElement(); 099 Map<String, String> attrs = processAttributes(rootNode, rootElement); 100 Level status = Level.OFF; 101 boolean verbose = false; 102 PrintStream stream = System.out; 103 104 for (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 String dest = entry.getValue(); 113 if (dest != null) { 114 if (dest.equalsIgnoreCase("err")) { 115 stream = System.err; 116 } else { 117 try { 118 File destFile = FileUtils.fileFromURI(new URI(dest)); 119 stream = new PrintStream(new FileOutputStream(destFile)); 120 } catch (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 String[] packages = entry.getValue().split(","); 129 for (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 int interval = Integer.parseInt(getSubst().replace(entry.getValue())); 140 if (interval > 0 && configFile != null) { 141 monitor = new FileConfigurationMonitor(this, configFile, listeners, interval); 142 } 143 } 144 } 145 Iterator<StatusListener> iter = ((StatusLogger) LOGGER).getListeners(); 146 boolean found = false; 147 while (iter.hasNext()) { 148 StatusListener listener = iter.next(); 149 if (listener instanceof StatusConsoleListener) { 150 found = true; 151 ((StatusConsoleListener) listener).setLevel(status); 152 if (!verbose) { 153 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES); 154 } 155 } 156 } 157 if (!found && status != Level.OFF) { 158 StatusConsoleListener listener = new StatusConsoleListener(status, stream); 159 if (!verbose) { 160 listener.setFilters(VERBOSE_CLASSES); 161 } 162 ((StatusLogger) LOGGER).registerListener(listener); 163 for (String msg : messages) { 164 LOGGER.error(msg); 165 } 166 } 167 168 } catch (SAXException domEx) { 169 LOGGER.error("Error parsing " + configSource.getLocation(), domEx); 170 } catch (IOException ioe) { 171 LOGGER.error("Error parsing " + configSource.getLocation(), ioe); 172 } catch (ParserConfigurationException pex) { 173 LOGGER.error("Error parsing " + configSource.getLocation(), pex); 174 } 175 if (strict && schema != null && buffer != null) { 176 InputStream is = null; 177 try { 178 is = getClass().getClassLoader().getResourceAsStream(schema); 179 } catch (Exception ex) { 180 LOGGER.error("Unable to access schema " + schema); 181 } 182 if (is != null) { 183 Source src = new StreamSource(is, LOG4J_XSD); 184 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 185 Schema schema = null; 186 try { 187 schema = factory.newSchema(src); 188 } catch (SAXException ex) { 189 LOGGER.error("Error parsing Log4j schema", ex); 190 } 191 if (schema != null) { 192 validator = schema.newValidator(); 193 try { 194 validator.validate(new StreamSource(new ByteArrayInputStream(buffer))); 195 } catch (IOException ioe) { 196 LOGGER.error("Error reading configuration for validation", ioe); 197 } catch (SAXException ex) { 198 LOGGER.error("Error validating configuration", ex); 199 } 200 } 201 } 202 } 203 204 if (getName() == null) { 205 setName(configSource.getLocation()); 206 } 207 } 208 209 @Override 210 public void setup() { 211 constructHierarchy(rootNode, rootElement); 212 if (status.size() > 0) { 213 for (Status s : status) { 214 LOGGER.error("Error processing element " + s.name + ": " + s.errorType); 215 } 216 return; 217 } 218 rootElement = null; 219 } 220 221 public Configuration reconfigure() { 222 if (configFile != null) { 223 try { 224 ConfigurationFactory.ConfigurationSource source = 225 new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile); 226 return new XMLConfiguration(source); 227 } catch (FileNotFoundException ex) { 228 LOGGER.error("Cannot locate file " + configFile, ex); 229 } 230 } 231 return null; 232 } 233 234 private void constructHierarchy(Node node, Element element) { 235 processAttributes(node, element); 236 StringBuffer buffer = new StringBuffer(); 237 NodeList list = element.getChildNodes(); 238 List<Node> children = node.getChildren(); 239 for (int i = 0; i < list.getLength(); i++) { 240 org.w3c.dom.Node w3cNode = list.item(i); 241 if (w3cNode instanceof Element) { 242 Element child = (Element) w3cNode; 243 String name = getType(child); 244 PluginType type = getPluginManager().getPluginType(name); 245 Node childNode = new Node(node, name, type); 246 constructHierarchy(childNode, child); 247 if (type == null) { 248 String value = childNode.getValue(); 249 if (!childNode.hasChildren() && value != null) { 250 node.getAttributes().put(name, value); 251 } else { 252 status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND)); 253 } 254 } else { 255 children.add(childNode); 256 } 257 } else if (w3cNode instanceof Text) { 258 Text data = (Text) w3cNode; 259 buffer.append(data.getData()); 260 } 261 } 262 263 String text = buffer.toString().trim(); 264 if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) { 265 node.setValue(text); 266 } 267 } 268 269 private String getType(Element element) { 270 if (strict) { 271 NamedNodeMap attrs = element.getAttributes(); 272 for (int i = 0; i < attrs.getLength(); ++i) { 273 org.w3c.dom.Node w3cNode = attrs.item(i); 274 if (w3cNode instanceof Attr) { 275 Attr attr = (Attr) w3cNode; 276 if (attr.getName().equalsIgnoreCase("type")) { 277 String type = attr.getValue(); 278 attrs.removeNamedItem(attr.getName()); 279 return type; 280 } 281 } 282 } 283 } 284 return element.getTagName(); 285 } 286 287 private byte[] toByteArray(InputStream is) throws IOException { 288 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 289 290 int nRead; 291 byte[] data = new byte[BUF_SIZE]; 292 293 while ((nRead = is.read(data, 0, data.length)) != -1) { 294 buffer.write(data, 0, nRead); 295 } 296 297 return buffer.toByteArray(); 298 } 299 300 private Map<String, String> processAttributes(Node node, Element element) { 301 NamedNodeMap attrs = element.getAttributes(); 302 Map<String, String> attributes = node.getAttributes(); 303 304 for (int i = 0; i < attrs.getLength(); ++i) { 305 org.w3c.dom.Node w3cNode = attrs.item(i); 306 if (w3cNode instanceof Attr) { 307 Attr attr = (Attr) w3cNode; 308 attributes.put(attr.getName(), attr.getValue()); 309 } 310 } 311 return attributes; 312 } 313 314 /** 315 * The error that occurred. 316 */ 317 private enum ErrorType { 318 CLASS_NOT_FOUND 319 } 320 321 /** 322 * Status for recording errors. 323 */ 324 private class Status { 325 private final Element element; 326 private final String name; 327 private final ErrorType errorType; 328 329 public Status(String name, Element element, ErrorType errorType) { 330 this.name = name; 331 this.element = element; 332 this.errorType = errorType; 333 } 334 } 335 336 }