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