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                         final PluginType type = getPluginManager().getPluginType(advertiserString);
124                         if (type != null)
125                         {
126                             final Class<Advertiser> clazz = type.getPluginClass();
127                             advertiser = clazz.newInstance();
128                         }
129                     }
130                 }
131             }
132 
133             final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners();
134             boolean found = false;
135             while (statusIter.hasNext()) {
136                 final StatusListener listener = statusIter.next();
137                 if (listener instanceof StatusConsoleListener) {
138                     found = true;
139                     ((StatusConsoleListener) listener).setLevel(status);
140                     if (!verbose) {
141                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
142                     }
143                 }
144             }
145             if (!found && status != Level.OFF) {
146                 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
147                 if (!verbose) {
148                     listener.setFilters(VERBOSE_CLASSES);
149                 }
150                 ((StatusLogger) LOGGER).registerListener(listener);
151                 for (final String msg : messages) {
152                     LOGGER.error(msg);
153                 }
154             }
155             if (getName() == null) {
156                 setName(configSource.getLocation());
157             }
158         } catch (final Exception ex) {
159             LOGGER.error("Error parsing " + configSource.getLocation(), ex);
160             ex.printStackTrace();
161         }
162     }
163 
164      @Override
165     public void setup() {
166         final Iterator<Map.Entry<String, JsonNode>> iter = root.getFields();
167         final List<Node> children = rootNode.getChildren();
168         while (iter.hasNext()) {
169             final Map.Entry<String, JsonNode> entry = iter.next();
170             final JsonNode n = entry.getValue();
171             if (n.isObject()) {
172                 LOGGER.debug("Processing node for object " + entry.getKey());
173                 children.add(constructNode(entry.getKey(), rootNode, n));
174             } else if (n.isArray()) {
175                 LOGGER.error("Arrays are not supported at the root configuration.");
176             }
177         }
178         LOGGER.debug("Completed parsing configuration");
179         if (status.size() > 0) {
180             for (final Status s : status) {
181                 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
182             }
183             return;
184         }
185     }
186 
187     public Configuration reconfigure() {
188         if (configFile != null) {
189             try {
190                 final ConfigurationFactory.ConfigurationSource source =
191                     new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
192                 return new JSONConfiguration(source);
193             } catch (final FileNotFoundException ex) {
194                 LOGGER.error("Cannot locate file " + configFile, ex);
195             }
196         }
197         return null;
198     }
199 
200     private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
201         final PluginType type = getPluginManager().getPluginType(name);
202         final Node node = new Node(parent, name, type);
203         processAttributes(node, jsonNode);
204         final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.getFields();
205         final List<Node> children = node.getChildren();
206         while (iter.hasNext()) {
207             final Map.Entry<String, JsonNode> entry = iter.next();
208             final JsonNode n = entry.getValue();
209             if (n.isArray() || n.isObject()) {
210                 if (type == null) {
211                     status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND));
212                 }
213                 if (n.isArray()) {
214                     LOGGER.debug("Processing node for array " + entry.getKey());
215                     for (int i = 0; i < n.size(); ++i) {
216                         final String pluginType = getType(n.get(i), entry.getKey());
217                         final PluginType entryType = getPluginManager().getPluginType(pluginType);
218                         final Node item = new Node(node, entry.getKey(), entryType);
219                         processAttributes(item, n.get(i));
220                         if (pluginType.equals(entry.getKey())) {
221                             LOGGER.debug("Processing " + entry.getKey() + "[" + i + "]");
222                         } else {
223                             LOGGER.debug("Processing " + pluginType + " " + entry.getKey() + "[" + i + "]");
224                         }
225                         final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).getFields();
226                         final List<Node> itemChildren = item.getChildren();
227                         while (itemIter.hasNext()) {
228                             final Map.Entry<String, JsonNode> itemEntry = itemIter.next();
229                             if (itemEntry.getValue().isObject()) {
230                                 LOGGER.debug("Processing node for object " + itemEntry.getKey());
231                                 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
232                             }
233                         }
234                         children.add(item);
235                     }
236                 } else {
237                     LOGGER.debug("Processing node for object " + entry.getKey());
238                     children.add(constructNode(entry.getKey(), node, n));
239                 }
240             }
241         }
242 
243         String t;
244         if (type == null) {
245             t = "null";
246         } else {
247             t = type.getElementName() + ":" + type.getPluginClass();
248         }
249 
250         final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ?
251             "root" : node.getParent().getName();
252         LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " +  t);
253         return node;
254     }
255 
256     private String getType(final JsonNode node, final String name) {
257         final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
258         while (iter.hasNext()) {
259             final Map.Entry<String, JsonNode> entry = iter.next();
260             if (entry.getKey().equalsIgnoreCase("type")) {
261                 final JsonNode n = entry.getValue();
262                 if (n.isValueNode()) {
263                     return n.asText();
264                 }
265             }
266         }
267         return name;
268     }
269 
270     private void processAttributes(final Node parent, final JsonNode node) {
271         final Map<String, String> attrs = parent.getAttributes();
272         final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
273         while (iter.hasNext()) {
274             final Map.Entry<String, JsonNode> entry = iter.next();
275             if (!entry.getKey().equalsIgnoreCase("type")) {
276                 final JsonNode n = entry.getValue();
277                 if (n.isValueNode()) {
278                     attrs.put(entry.getKey(), n.asText());
279                 }
280             }
281         }
282     }
283 
284     protected byte[] toByteArray(final InputStream is) throws IOException {
285         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
286 
287         int nRead;
288         final byte[] data = new byte[BUF_SIZE];
289 
290         while ((nRead = is.read(data, 0, data.length)) != -1) {
291             buffer.write(data, 0, nRead);
292         }
293 
294         return buffer.toByteArray();
295     }
296 
297     /**
298      * The error that occurred.
299      */
300     private enum ErrorType {
301         CLASS_NOT_FOUND
302     }
303 
304     /**
305      * Status for recording errors.
306      */
307     private class Status {
308         private final JsonNode node;
309         private final String name;
310         private final ErrorType errorType;
311 
312         public Status(final String name, final JsonNode node, final ErrorType errorType) {
313             this.name = name;
314             this.node = node;
315             this.errorType = errorType;
316         }
317     }
318 }