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;
18  
19  import org.apache.logging.log4j.Level;
20  import org.apache.logging.log4j.core.config.plugins.PluginManager;
21  import org.apache.logging.log4j.core.config.plugins.PluginType;
22  import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
23  import org.apache.logging.log4j.core.helpers.FileUtils;
24  import org.apache.logging.log4j.status.StatusConsoleListener;
25  import org.apache.logging.log4j.status.StatusListener;
26  import org.apache.logging.log4j.status.StatusLogger;
27  import org.codehaus.jackson.JsonNode;
28  import org.codehaus.jackson.JsonParser;
29  import org.codehaus.jackson.map.ObjectMapper;
30  
31  import java.io.ByteArrayInputStream;
32  import java.io.ByteArrayOutputStream;
33  import java.io.File;
34  import java.io.FileInputStream;
35  import java.io.FileNotFoundException;
36  import java.io.FileOutputStream;
37  import java.io.IOException;
38  import java.io.InputStream;
39  import java.io.PrintStream;
40  import java.net.URI;
41  import java.net.URISyntaxException;
42  import java.util.ArrayList;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  
47  /**
48   * Creates a Node hierarchy from a JSON file.
49   */
50  public class JSONConfiguration extends BaseConfiguration implements Reconfigurable {
51  
52      private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
53  
54      private static final int BUF_SIZE = 16384;
55  
56      private final List<Status> status = new ArrayList<Status>();
57  
58      private JsonNode root;
59  
60      private final List<String> messages = new ArrayList<String>();
61  
62      private final File configFile;
63  
64      public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
65          this.configFile = configSource.getFile();
66          byte[] buffer;
67  
68          try {
69              final InputStream configStream = configSource.getInputStream();
70              buffer = toByteArray(configStream);
71              configStream.close();
72              final InputStream is = new ByteArrayInputStream(buffer);
73              final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true);
74              root = mapper.readTree(is);
75              if (root.size() == 1) {
76                  final Iterator<JsonNode> i = root.getElements();
77                  root = i.next();
78              }
79              processAttributes(rootNode, root);
80              Level status = Level.OFF;
81              boolean verbose = false;
82              PrintStream stream = System.out;
83              for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
84                  if ("status".equalsIgnoreCase(entry.getKey())) {
85                      status = Level.toLevel(getSubst().replace(entry.getValue()), null);
86                      if (status == null) {
87                          status = Level.ERROR;
88                          messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
89                      }
90                  } else if ("dest".equalsIgnoreCase(entry.getKey())) {
91                      final String dest = entry.getValue();
92                      if (dest != null) {
93                          if (dest.equalsIgnoreCase("err")) {
94                              stream = System.err;
95                          } else {
96                              try {
97                                  final File destFile = FileUtils.fileFromURI(new URI(dest));
98                                  stream = new PrintStream(new FileOutputStream(destFile));
99                              } catch (final URISyntaxException use) {
100                                 System.err.println("Unable to write to " + dest + ". Writing to stdout");
101                             }
102                         }
103                     }
104                 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
105                     verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
106                 } else if ("packages".equalsIgnoreCase(entry.getKey())) {
107                     final String[] packages = getSubst().replace(entry.getValue()).split(",");
108                     for (final String p : packages) {
109                         PluginManager.addPackage(p);
110                     }
111                 } else if ("name".equalsIgnoreCase(entry.getKey())) {
112                     setName(getSubst().replace(entry.getValue()));
113                 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
114                     final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
115                     if (interval > 0 && configFile != null) {
116                         monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
117                     }
118                 }
119             }
120 
121             final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners();
122             boolean found = false;
123             while (statusIter.hasNext()) {
124                 final StatusListener listener = statusIter.next();
125                 if (listener instanceof StatusConsoleListener) {
126                     found = true;
127                     ((StatusConsoleListener) listener).setLevel(status);
128                     if (!verbose) {
129                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
130                     }
131                 }
132             }
133             if (!found && status != Level.OFF) {
134                 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
135                 if (!verbose) {
136                     listener.setFilters(VERBOSE_CLASSES);
137                 }
138                 ((StatusLogger) LOGGER).registerListener(listener);
139                 for (final String msg : messages) {
140                     LOGGER.error(msg);
141                 }
142             }
143             if (getName() == null) {
144                 setName(configSource.getLocation());
145             }
146         } catch (final Exception ex) {
147             LOGGER.error("Error parsing " + configSource.getLocation(), ex);
148             ex.printStackTrace();
149         }
150     }
151 
152      @Override
153     public void setup() {
154         final Iterator<Map.Entry<String, JsonNode>> iter = root.getFields();
155         final List<Node> children = rootNode.getChildren();
156         while (iter.hasNext()) {
157             final Map.Entry<String, JsonNode> entry = iter.next();
158             final JsonNode n = entry.getValue();
159             if (n.isObject()) {
160                 LOGGER.debug("Processing node for object " + entry.getKey());
161                 children.add(constructNode(entry.getKey(), rootNode, n));
162             } else if (n.isArray()) {
163                 LOGGER.error("Arrays are not supported at the root configuration.");
164             }
165         }
166         LOGGER.debug("Completed parsing configuration");
167         if (status.size() > 0) {
168             for (final Status s : status) {
169                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
170             }
171             return;
172         }
173     }
174 
175     public Configuration reconfigure() {
176         if (configFile != null) {
177             try {
178                 final ConfigurationFactory.ConfigurationSource source =
179                     new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
180                 return new JSONConfiguration(source);
181             } catch (final FileNotFoundException ex) {
182                 LOGGER.error("Cannot locate file " + configFile, ex);
183             }
184         }
185         return null;
186     }
187 
188     private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
189         final PluginType type = getPluginManager().getPluginType(name);
190         final Node node = new Node(parent, name, type);
191         processAttributes(node, jsonNode);
192         final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.getFields();
193         final List<Node> children = node.getChildren();
194         while (iter.hasNext()) {
195             final Map.Entry<String, JsonNode> entry = iter.next();
196             final JsonNode n = entry.getValue();
197             if (n.isArray() || n.isObject()) {
198                 if (type == null) {
199                     status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND));
200                 }
201                 if (n.isArray()) {
202                     LOGGER.debug("Processing node for array " + entry.getKey());
203                     for (int i = 0; i < n.size(); ++i) {
204                         final String pluginType = getType(n.get(i), entry.getKey());
205                         final PluginType entryType = getPluginManager().getPluginType(pluginType);
206                         final Node item = new Node(node, entry.getKey(), entryType);
207                         processAttributes(item, n.get(i));
208                         if (pluginType.equals(entry.getKey())) {
209                             LOGGER.debug("Processing " + entry.getKey() + "[" + i + "]");
210                         } else {
211                             LOGGER.debug("Processing " + pluginType + " " + entry.getKey() + "[" + i + "]");
212                         }
213                         final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).getFields();
214                         final List<Node> itemChildren = item.getChildren();
215                         while (itemIter.hasNext()) {
216                             final Map.Entry<String, JsonNode> itemEntry = itemIter.next();
217                             if (itemEntry.getValue().isObject()) {
218                                 LOGGER.debug("Processing node for object " + itemEntry.getKey());
219                                 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
220                             }
221                         }
222                         children.add(item);
223                     }
224                 } else {
225                     LOGGER.debug("Processing node for object " + entry.getKey());
226                     children.add(constructNode(entry.getKey(), node, n));
227                 }
228             }
229         }
230 
231         String t;
232         if (type == null) {
233             t = "null";
234         } else {
235             t = type.getElementName() + ":" + type.getPluginClass();
236         }
237 
238         final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ?
239             "root" : node.getParent().getName();
240         LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " +  t);
241         return node;
242     }
243 
244     private String getType(final JsonNode node, final String name) {
245         final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
246         while (iter.hasNext()) {
247             final Map.Entry<String, JsonNode> entry = iter.next();
248             if (entry.getKey().equalsIgnoreCase("type")) {
249                 final JsonNode n = entry.getValue();
250                 if (n.isValueNode()) {
251                     return n.asText();
252                 }
253             }
254         }
255         return name;
256     }
257 
258     private void processAttributes(final Node parent, final JsonNode node) {
259         final Map<String, String> attrs = parent.getAttributes();
260         final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
261         while (iter.hasNext()) {
262             final Map.Entry<String, JsonNode> entry = iter.next();
263             if (!entry.getKey().equalsIgnoreCase("type")) {
264                 final JsonNode n = entry.getValue();
265                 if (n.isValueNode()) {
266                     attrs.put(entry.getKey(), n.asText());
267                 }
268             }
269         }
270     }
271 
272     protected byte[] toByteArray(final InputStream is) throws IOException {
273         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
274 
275         int nRead;
276         final byte[] data = new byte[BUF_SIZE];
277 
278         while ((nRead = is.read(data, 0, data.length)) != -1) {
279             buffer.write(data, 0, nRead);
280         }
281 
282         return buffer.toByteArray();
283     }
284 
285     /**
286      * The error that occurred.
287      */
288     private enum ErrorType {
289         CLASS_NOT_FOUND
290     }
291 
292     /**
293      * Status for recording errors.
294      */
295     private class Status {
296         private final JsonNode node;
297         private final String name;
298         private final ErrorType errorType;
299 
300         public Status(final String name, final JsonNode node, final ErrorType errorType) {
301             this.name = name;
302             this.node = node;
303             this.errorType = errorType;
304         }
305     }
306 }