View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.config.xml;
    18
    19  import org.apache.logging.log4j.core.config.AbstractConfiguration;
    20  import org.apache.logging.log4j.core.config.Configuration;
    21  import org.apache.logging.log4j.core.config.ConfigurationSource;
    22  import org.apache.logging.log4j.core.config.FileConfigurationMonitor;
    23  import org.apache.logging.log4j.core.config.Node;
    24  import org.apache.logging.log4j.core.config.Reconfigurable;
    25  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
    26  import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
    27  import org.apache.logging.log4j.core.config.status.StatusConfiguration;
    28  import org.apache.logging.log4j.core.util.Closer;
    29  import org.apache.logging.log4j.core.util.Loader;
    30  import org.apache.logging.log4j.core.util.Patterns;
    31  import org.w3c.dom.Attr;
    32  import org.w3c.dom.Document;
    33  import org.w3c.dom.Element;
    34  import org.w3c.dom.NamedNodeMap;
    35  import org.w3c.dom.NodeList;
    36  import org.w3c.dom.Text;
    37  import org.xml.sax.InputSource;
    38  import org.xml.sax.SAXException;
    39
    40  import javax.xml.XMLConstants;
    41  import javax.xml.parsers.DocumentBuilder;
    42  import javax.xml.parsers.DocumentBuilderFactory;
    43  import javax.xml.parsers.ParserConfigurationException;
    44  import javax.xml.transform.Source;
    45  import javax.xml.transform.stream.StreamSource;
    46  import javax.xml.validation.Schema;
    47  import javax.xml.validation.SchemaFactory;
    48  import javax.xml.validation.Validator;
    49  import java.io.ByteArrayInputStream;
    50  import java.io.File;
    51  import java.io.IOException;
    52  import java.io.InputStream;
    53  import java.util.ArrayList;
    54  import java.util.Arrays;
    55  import java.util.List;
    56  import java.util.Map;
    57
    58  /**
    59   * Creates a Node hierarchy from an XML file.
    60   */
    61  public class XmlConfiguration extends AbstractConfiguration implements Reconfigurable {
    62
    63      private static final long serialVersionUID = 1L;
    64
    65      private static final String XINCLUDE_FIXUP_LANGUAGE = "http://apache.org/xml/features/xinclude/fixup-language";
    66      private static final String XINCLUDE_FIXUP_BASE_URIS = "http://apache.org/xml/features/xinclude/fixup-base-uris";
    67      private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() };
    68      private static final String LOG4J_XSD = "Log4j-config.xsd";
    69
    70      private final List<Status> status = new ArrayList<Status>();
    71      private Element rootElement;
    72      private boolean strict;
    73      private String schemaResource;
    74
    75      /**
    76       * Creates a new DocumentBuilder suitable for parsing a configuration file.
    77       *
    78       * @return a new DocumentBuilder
    79       * @throws ParserConfigurationException
    80       */
    81      static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
    82          final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    83          factory.setNamespaceAware(true);
    84          enableXInclude(factory);
    85          return factory.newDocumentBuilder();
    86      }
    87
    88      /**
    89       * Enables XInclude for the given DocumentBuilderFactory
    90       *
    91       * @param factory a DocumentBuilderFactory
    92       */
    93      private static void enableXInclude(final DocumentBuilderFactory factory) {
    94          try {
    95              // Alternative: We set if a system property on the command line is set, for example:
    96              // -DLog4j.XInclude=true
    97              factory.setXIncludeAware(true);
    98          } catch (final UnsupportedOperationException e) {
    99              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 = getStrSubstitutor().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 }