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.json;
    18
    19  import com.fasterxml.jackson.core.JsonParser;
    20  import com.fasterxml.jackson.databind.JsonNode;
    21  import com.fasterxml.jackson.databind.ObjectMapper;
    22  import org.apache.logging.log4j.core.config.AbstractConfiguration;
    23  import org.apache.logging.log4j.core.config.Configuration;
    24  import org.apache.logging.log4j.core.config.ConfigurationSource;
    25  import org.apache.logging.log4j.core.config.FileConfigurationMonitor;
    26  import org.apache.logging.log4j.core.config.Node;
    27  import org.apache.logging.log4j.core.config.Reconfigurable;
    28  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
    29  import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
    30  import org.apache.logging.log4j.core.config.status.StatusConfiguration;
    31  import org.apache.logging.log4j.core.util.Patterns;
    32
    33  import java.io.ByteArrayInputStream;
    34  import java.io.File;
    35  import java.io.IOException;
    36  import java.io.InputStream;
    37  import java.util.ArrayList;
    38  import java.util.Arrays;
    39  import java.util.Iterator;
    40  import java.util.List;
    41  import java.util.Map;
    42
    43  /**
    44   * Creates a Node hierarchy from a JSON file.
    45   */
    46  public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable {
    47
    48      private static final long serialVersionUID = 1L;
    49      private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() };
    50      private final List<Status> status = new ArrayList<Status>();
    51      private JsonNode root;
    52
    53      public JsonConfiguration(final ConfigurationSource configSource) {
    54          super(configSource);
    55          final File configFile = configSource.getFile();
    56          byte[] buffer;
    57          try {
    58              final InputStream configStream = configSource.getInputStream();
    59              try {
    60                  buffer = toByteArray(configStream);
    61              } finally {
    62                  configStream.close();
    63              }
    64              final InputStream is = new ByteArrayInputStream(buffer);
    65              root = getObjectMapper().readTree(is);
    66              if (root.size() == 1) {
    67                  for (final JsonNode node : root) {
    68                      root = node;
    69                  }
    70              }
    71              processAttributes(rootNode, root);
    72              final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
    73                      .withStatus(getDefaultStatus());
    74              for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
    75                  final String key = entry.getKey();
    76                  final String value = getStrSubstitutor().replace(entry.getValue());
    77                  // TODO: this duplicates a lot of the XmlConfiguration constructor
    78                  if ("status".equalsIgnoreCase(key)) {
    79                      statusConfig.withStatus(value);
    80                  } else if ("dest".equalsIgnoreCase(key)) {
    81                      statusConfig.withDestination(value);
    82                  } else if ("shutdownHook".equalsIgnoreCase(key)) {
    83                      isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
    84                  } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
    85                      statusConfig.withVerbosity(value);
    86                  } else if ("packages".equalsIgnoreCase(key)) {
    87                      pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
    88                  } else if ("name".equalsIgnoreCase(key)) {
    89                      setName(value);
    90                  } else if ("monitorInterval".equalsIgnoreCase(key)) {
    91                      final int intervalSeconds = Integer.parseInt(value);
    92                      if (intervalSeconds > 0 && configFile != null) {
    93                          monitor = new FileConfigurationMonitor(this, configFile, listeners, intervalSeconds);
    94                      }
    95                  } else if ("advertiser".equalsIgnoreCase(key)) {
    96                      createAdvertiser(value, configSource, buffer, "application/json");
    97                  }
    98              }
    99              statusConfig.initialize();
    100             if (getName() == null) {
    101                 setName(configSource.getLocation());
    102             }
    103         } catch (final Exception ex) {
    104             LOGGER.error("Error parsing {}", configSource.getLocation(), ex);
    105         }
    106     }
    107
    108     protected ObjectMapper getObjectMapper() {
    109         return new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true);
    110     }
    111
    112     @Override
    113     public void setup() {
    114         final Iterator<Map.Entry<String, JsonNode>> iter = root.fields();
    115         final List<Node> children = rootNode.getChildren();
    116         while (iter.hasNext()) {
    117             final Map.Entry<String, JsonNode> entry = iter.next();
    118             final JsonNode n = entry.getValue();
    119             if (n.isObject()) {
    120                 LOGGER.debug("Processing node for object {}", entry.getKey());
    121                 children.add(constructNode(entry.getKey(), rootNode, n));
    122             } else if (n.isArray()) {
    123                 LOGGER.error("Arrays are not supported at the root configuration.");
    124             }
    125         }
    126         LOGGER.debug("Completed parsing configuration");
    127         if (status.size() > 0) {
    128             for (final Status s : status) {
    129                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
    130             }
    131         }
    132     }
    133
    134     @Override
    135     public Configuration reconfigure() {
    136         try {
    137             final ConfigurationSource source = getConfigurationSource().resetInputStream();
    138             if (source == null) {
    139                 return null;
    140             }
    141             return new JsonConfiguration(source);
    142         } catch (final IOException ex) {
    143             LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
    144         }
    145         return null;
    146     }
    147
    148     private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
    149         final PluginType<?> type = pluginManager.getPluginType(name);
    150         final Node node = new Node(parent, name, type);
    151         processAttributes(node, jsonNode);
    152         final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields();
    153         final List<Node> children = node.getChildren();
    154         while (iter.hasNext()) {
    155             final Map.Entry<String, JsonNode> entry = iter.next();
    156             final JsonNode n = entry.getValue();
    157             if (n.isArray() || n.isObject()) {
    158                 if (type == null) {
    159                     status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND));
    160                 }
    161                 if (n.isArray()) {
    162                     LOGGER.debug("Processing node for array {}", entry.getKey());
    163                     for (int i = 0; i < n.size(); ++i) {
    164                         final String pluginType = getType(n.get(i), entry.getKey());
    165                         final PluginType<?> entryType = pluginManager.getPluginType(pluginType);
    166                         final Node item = new Node(node, entry.getKey(), entryType);
    167                         processAttributes(item, n.get(i));
    168                         if (pluginType.equals(entry.getKey())) {
    169                             LOGGER.debug("Processing {}[{}]", entry.getKey(), i);
    170                         } else {
    171                             LOGGER.debug("Processing {} {}[{}]", pluginType, entry.getKey(), i);
    172                         }
    173                         final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).fields();
    174                         final List<Node> itemChildren = item.getChildren();
    175                         while (itemIter.hasNext()) {
    176                             final Map.Entry<String, JsonNode> itemEntry = itemIter.next();
    177                             if (itemEntry.getValue().isObject()) {
    178                                 LOGGER.debug("Processing node for object {}", itemEntry.getKey());
    179                                 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
    180                             } else if (itemEntry.getValue().isArray()) {
    181                                 final JsonNode array = itemEntry.getValue();
    182                                 final String entryName = itemEntry.getKey();
    183                                 LOGGER.debug("Processing array for object {}", entryName);
    184                                 for (int j = 0; j < array.size(); ++j) {
    185                                     itemChildren.add(constructNode(entryName, item, array.get(j)));
    186                                 }
    187                             }
    188
    189                         }
    190                         children.add(item);
    191                     }
    192                 } else {
    193                     LOGGER.debug("Processing node for object {}", entry.getKey());
    194                     children.add(constructNode(entry.getKey(), node, n));
    195                 }
    196             } else {
    197                 LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType());
    198             }
    199         }
    200
    201         String t;
    202         if (type == null) {
    203             t = "null";
    204         } else {
    205             t = type.getElementName() + ':' + type.getPluginClass();
    206         }
    207
    208         final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ? "root" : node
    209                 .getParent().getName();
    210         LOGGER.debug("Returning {} with parent {} of type {}", node.getName(), p, t);
    211         return node;
    212     }
    213
    214     private String getType(final JsonNode node, final String name) {
    215         final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
    216         while (iter.hasNext()) {
    217             final Map.Entry<String, JsonNode> entry = iter.next();
    218             if (entry.getKey().equalsIgnoreCase("type")) {
    219                 final JsonNode n = entry.getValue();
    220                 if (n.isValueNode()) {
    221                     return n.asText();
    222                 }
    223             }
    224         }
    225         return name;
    226     }
    227
    228     private void processAttributes(final Node parent, final JsonNode node) {
    229         final Map<String, String> attrs = parent.getAttributes();
    230         final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
    231         while (iter.hasNext()) {
    232             final Map.Entry<String, JsonNode> entry = iter.next();
    233             if (!entry.getKey().equalsIgnoreCase("type")) {
    234                 final JsonNode n = entry.getValue();
    235                 if (n.isValueNode()) {
    236                     attrs.put(entry.getKey(), n.asText());
    237                 }
    238             }
    239         }
    240     }
    241
    242     @Override
    243     public String toString() {
    244         return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]";
    245     }
    246
    247     /**
    248      * The error that occurred.
    249      */
    250     private enum ErrorType {
    251         CLASS_NOT_FOUND
    252     }
    253
    254     /**
    255      * Status for recording errors.
    256      */
    257     private static class Status {
    258         private final JsonNode node;
    259         private final String name;
    260         private final ErrorType errorType;
    261
    262         public Status(final String name, final JsonNode node, final ErrorType errorType) {
    263             this.name = name;
    264             this.node = node;
    265             this.errorType = errorType;
    266         }
    267     }
    268 }