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 */ 017package org.apache.logging.log4j.core.config.xml; 018 019import org.apache.logging.log4j.core.config.AbstractConfiguration; 020import org.apache.logging.log4j.core.config.Configuration; 021import org.apache.logging.log4j.core.config.ConfigurationSource; 022import org.apache.logging.log4j.core.config.FileConfigurationMonitor; 023import org.apache.logging.log4j.core.config.Node; 024import org.apache.logging.log4j.core.config.Reconfigurable; 025import org.apache.logging.log4j.core.config.plugins.util.PluginType; 026import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; 027import org.apache.logging.log4j.core.config.status.StatusConfiguration; 028import org.apache.logging.log4j.core.util.Closer; 029import org.apache.logging.log4j.core.util.Loader; 030import org.apache.logging.log4j.core.util.Patterns; 031import org.w3c.dom.Attr; 032import org.w3c.dom.Document; 033import org.w3c.dom.Element; 034import org.w3c.dom.NamedNodeMap; 035import org.w3c.dom.NodeList; 036import org.w3c.dom.Text; 037import org.xml.sax.InputSource; 038import org.xml.sax.SAXException; 039 040import javax.xml.XMLConstants; 041import javax.xml.parsers.DocumentBuilder; 042import javax.xml.parsers.DocumentBuilderFactory; 043import javax.xml.parsers.ParserConfigurationException; 044import javax.xml.transform.Source; 045import javax.xml.transform.stream.StreamSource; 046import javax.xml.validation.Schema; 047import javax.xml.validation.SchemaFactory; 048import javax.xml.validation.Validator; 049import java.io.ByteArrayInputStream; 050import java.io.File; 051import java.io.IOException; 052import java.io.InputStream; 053import java.util.ArrayList; 054import java.util.Arrays; 055import java.util.List; 056import java.util.Map; 057 058/** 059 * Creates a Node hierarchy from an XML file. 060 */ 061public class XmlConfiguration extends AbstractConfiguration implements Reconfigurable { 062 063 private static final long serialVersionUID = 1L; 064 065 private static final String XINCLUDE_FIXUP_LANGUAGE = "http://apache.org/xml/features/xinclude/fixup-language"; 066 private static final String XINCLUDE_FIXUP_BASE_URIS = "http://apache.org/xml/features/xinclude/fixup-base-uris"; 067 private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() }; 068 private static final String LOG4J_XSD = "Log4j-config.xsd"; 069 070 private final List<Status> status = new ArrayList<Status>(); 071 private Element rootElement; 072 private boolean strict; 073 private String schemaResource; 074 075 /** 076 * Creates a new DocumentBuilder suitable for parsing a configuration file. 077 * 078 * @return a new DocumentBuilder 079 * @throws ParserConfigurationException 080 */ 081 static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { 082 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 083 factory.setNamespaceAware(true); 084 enableXInclude(factory); 085 return factory.newDocumentBuilder(); 086 } 087 088 /** 089 * Enables XInclude for the given DocumentBuilderFactory 090 * 091 * @param factory a DocumentBuilderFactory 092 */ 093 private static void enableXInclude(final DocumentBuilderFactory factory) { 094 try { 095 // Alternative: We set if a system property on the command line is set, for example: 096 // -DLog4j.XInclude=true 097 factory.setXIncludeAware(true); 098 } catch (final UnsupportedOperationException e) { 099 LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e); 100 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) { 101 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support XInclude: {}", factory, err); 102 } catch (final NoSuchMethodError err) { 103 // LOG4J2-919 104 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support XInclude: {}", factory, err); 105 } 106 try { 107 // Alternative: We could specify all features and values with system properties like: 108 // -DLog4j.DocumentBuilderFactory.Feature="http://apache.org/xml/features/xinclude/fixup-base-uris true" 109 factory.setFeature(XINCLUDE_FIXUP_BASE_URIS, true); 110 } catch (final ParserConfigurationException e) { 111 LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory, 112 XINCLUDE_FIXUP_BASE_URIS, e); 113 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) { 114 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory, err); 115 } 116 try { 117 factory.setFeature(XINCLUDE_FIXUP_LANGUAGE, true); 118 } catch (final ParserConfigurationException e) { 119 LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory, 120 XINCLUDE_FIXUP_LANGUAGE, e); 121 } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) { 122 LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory, err); 123 } 124 } 125 126 public XmlConfiguration(final ConfigurationSource configSource) { 127 super(configSource); 128 final File configFile = configSource.getFile(); 129 byte[] buffer = null; 130 131 try { 132 final InputStream configStream = configSource.getInputStream(); 133 try { 134 buffer = toByteArray(configStream); 135 } finally { 136 Closer.closeSilently(configStream); 137 } 138 final InputSource source = new InputSource(new ByteArrayInputStream(buffer)); 139 source.setSystemId(configSource.getLocation()); 140 final Document document = newDocumentBuilder().parse(source); 141 rootElement = document.getDocumentElement(); 142 final Map<String, String> attrs = processAttributes(rootNode, rootElement); 143 final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) 144 .withStatus(getDefaultStatus()); 145 for (final Map.Entry<String, String> entry : attrs.entrySet()) { 146 final String key = entry.getKey(); 147 final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); 148 if ("status".equalsIgnoreCase(key)) { 149 statusConfig.withStatus(value); 150 } else if ("dest".equalsIgnoreCase(key)) { 151 statusConfig.withDestination(value); 152 } else if ("shutdownHook".equalsIgnoreCase(key)) { 153 isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); 154 } else if ("verbose".equalsIgnoreCase(key)) { 155 statusConfig.withVerbosity(value); 156 } else if ("packages".equalsIgnoreCase(key)) { 157 pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); 158 } else if ("name".equalsIgnoreCase(key)) { 159 setName(value); 160 } else if ("strict".equalsIgnoreCase(key)) { 161 strict = Boolean.parseBoolean(value); 162 } else if ("schema".equalsIgnoreCase(key)) { 163 schemaResource = value; 164 } else if ("monitorInterval".equalsIgnoreCase(key)) { 165 final int intervalSeconds = Integer.parseInt(value); 166 if (intervalSeconds > 0 && configFile != null) { 167 monitor = new FileConfigurationMonitor(this, configFile, listeners, intervalSeconds); 168 } 169 } else if ("advertiser".equalsIgnoreCase(key)) { 170 createAdvertiser(value, configSource, buffer, "text/xml"); 171 } 172 } 173 statusConfig.initialize(); 174 } catch (final SAXException domEx) { 175 LOGGER.error("Error parsing {}", configSource.getLocation(), domEx); 176 } catch (final IOException ioe) { 177 LOGGER.error("Error parsing {}", configSource.getLocation(), ioe); 178 } catch (final ParserConfigurationException pex) { 179 LOGGER.error("Error parsing {}", configSource.getLocation(), pex); 180 } 181 if (strict && schemaResource != null && buffer != null) { 182 InputStream is = null; 183 try { 184 is = Loader.getResourceAsStream(schemaResource, XmlConfiguration.class.getClassLoader()); 185 } catch (final Exception ex) { 186 LOGGER.error("Unable to access schema {}", this.schemaResource, ex); 187 } 188 if (is != null) { 189 final Source src = new StreamSource(is, LOG4J_XSD); 190 final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 191 Schema schema = null; 192 try { 193 schema = factory.newSchema(src); 194 } catch (final SAXException ex) { 195 LOGGER.error("Error parsing Log4j schema", ex); 196 } 197 if (schema != null) { 198 final Validator validator = schema.newValidator(); 199 try { 200 validator.validate(new StreamSource(new ByteArrayInputStream(buffer))); 201 } catch (final IOException ioe) { 202 LOGGER.error("Error reading configuration for validation", ioe); 203 } catch (final SAXException ex) { 204 LOGGER.error("Error validating configuration", ex); 205 } 206 } 207 } 208 } 209 210 if (getName() == null) { 211 setName(configSource.getLocation()); 212 } 213 } 214 215 @Override 216 public void setup() { 217 if (rootElement == null) { 218 LOGGER.error("No logging configuration"); 219 return; 220 } 221 constructHierarchy(rootNode, rootElement); 222 if (status.size() > 0) { 223 for (final Status s : status) { 224 LOGGER.error("Error processing element {}: {}", s.name, s.errorType); 225 } 226 return; 227 } 228 rootElement = null; 229 } 230 231 @Override 232 public Configuration reconfigure() { 233 try { 234 final ConfigurationSource source = getConfigurationSource().resetInputStream(); 235 if (source == null) { 236 return null; 237 } 238 final XmlConfiguration config = new XmlConfiguration(source); 239 return config.rootElement == null ? null : config; 240 } catch (final IOException ex) { 241 LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex); 242 } 243 return null; 244 } 245 246 private void constructHierarchy(final Node node, final Element element) { 247 processAttributes(node, element); 248 final StringBuilder buffer = new StringBuilder(); 249 final NodeList list = element.getChildNodes(); 250 final List<Node> children = node.getChildren(); 251 for (int i = 0; i < list.getLength(); i++) { 252 final org.w3c.dom.Node w3cNode = list.item(i); 253 if (w3cNode instanceof Element) { 254 final Element child = (Element) w3cNode; 255 final String name = getType(child); 256 final PluginType<?> type = pluginManager.getPluginType(name); 257 final Node childNode = new Node(node, name, type); 258 constructHierarchy(childNode, child); 259 if (type == null) { 260 final String value = childNode.getValue(); 261 if (!childNode.hasChildren() && value != null) { 262 node.getAttributes().put(name, value); 263 } else { 264 status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND)); 265 } 266 } else { 267 children.add(childNode); 268 } 269 } else if (w3cNode instanceof Text) { 270 final Text data = (Text) w3cNode; 271 buffer.append(data.getData()); 272 } 273 } 274 275 final String text = buffer.toString().trim(); 276 if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) { 277 node.setValue(text); 278 } 279 } 280 281 private String getType(final Element element) { 282 if (strict) { 283 final NamedNodeMap attrs = element.getAttributes(); 284 for (int i = 0; i < attrs.getLength(); ++i) { 285 final org.w3c.dom.Node w3cNode = attrs.item(i); 286 if (w3cNode instanceof Attr) { 287 final Attr attr = (Attr) w3cNode; 288 if (attr.getName().equalsIgnoreCase("type")) { 289 final String type = attr.getValue(); 290 attrs.removeNamedItem(attr.getName()); 291 return type; 292 } 293 } 294 } 295 } 296 return element.getTagName(); 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 if (attr.getName().equals("xml:base")) { 308 continue; 309 } 310 attributes.put(attr.getName(), attr.getValue()); 311 } 312 } 313 return attributes; 314 } 315 316 @Override 317 public String toString() { 318 return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]"; 319 } 320 321 /** 322 * The error that occurred. 323 */ 324 private enum ErrorType { 325 CLASS_NOT_FOUND 326 } 327 328 /** 329 * Status for recording errors. 330 */ 331 private static class Status { 332 private final Element element; 333 private final String name; 334 private final ErrorType errorType; 335 336 public Status(final String name, final Element element, final ErrorType errorType) { 337 this.name = name; 338 this.element = element; 339 this.errorType = errorType; 340 } 341 } 342 343}