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