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