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 java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.PrintStream;
28  import java.net.URISyntaxException;
29  import java.nio.charset.Charset;
30  import java.util.ArrayList;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.apache.logging.log4j.Level;
36  import org.apache.logging.log4j.core.config.plugins.PluginManager;
37  import org.apache.logging.log4j.core.config.plugins.PluginType;
38  import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
39  import org.apache.logging.log4j.core.helpers.FileUtils;
40  import org.apache.logging.log4j.status.StatusConsoleListener;
41  import org.apache.logging.log4j.status.StatusListener;
42  import org.apache.logging.log4j.status.StatusLogger;
43  
44  import com.fasterxml.jackson.core.JsonParser;
45  import com.fasterxml.jackson.databind.JsonNode;
46  import com.fasterxml.jackson.databind.ObjectMapper;
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 File configFile;
62  
63      public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
64          final List<String> messages = new ArrayList<String>();
65  
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.elements();
78                  root = i.next();
79              }
80              processAttributes(rootNode, root);
81              Level status = getDefaultStatus();
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(getStrSubstitutor().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(FileUtils.getCorrectedFilePathUri(dest));
99                                  final String enc = Charset.defaultCharset().name();
100                                 stream = new PrintStream(new FileOutputStream(destFile), true, enc);
101                             } catch (final URISyntaxException use) {
102                                 System.err.println("Unable to write to " + dest + ". Writing to stdout");
103                             }
104                         }
105                     }
106                 } else if ("shutdownHook".equalsIgnoreCase(entry.getKey())) {
107                     String hook = getStrSubstitutor().replace(entry.getValue());
108                     isShutdownHookEnabled = !hook.equalsIgnoreCase("disable");
109                 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
110                     verbose = Boolean.parseBoolean(getStrSubstitutor().replace(entry.getValue()));
111                 } else if ("packages".equalsIgnoreCase(entry.getKey())) {
112                     final String[] packages = getStrSubstitutor().replace(entry.getValue()).split(",");
113                     for (final String p : packages) {
114                         PluginManager.addPackage(p);
115                     }
116                 } else if ("name".equalsIgnoreCase(entry.getKey())) {
117                     setName(getStrSubstitutor().replace(entry.getValue()));
118                 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
119                     final int interval = Integer.parseInt(getStrSubstitutor().replace(entry.getValue()));
120                     if (interval > 0 && configFile != null) {
121                         monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
122                     }
123                 } else if ("advertiser".equalsIgnoreCase(entry.getKey())) {
124                     createAdvertiser(getStrSubstitutor().replace(entry.getValue()), configSource, buffer,
125                         "application/json");
126                 }
127             }
128 
129             final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners();
130             boolean found = false;
131             while (statusIter.hasNext()) {
132                 final StatusListener listener = statusIter.next();
133                 if (listener instanceof StatusConsoleListener) {
134                     found = true;
135                     ((StatusConsoleListener) listener).setLevel(status);
136                     if (!verbose) {
137                         ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
138                     }
139                 }
140             }
141             if (!found && status != Level.OFF) {
142                 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
143                 if (!verbose) {
144                     listener.setFilters(VERBOSE_CLASSES);
145                 }
146                 ((StatusLogger) LOGGER).registerListener(listener);
147                 for (final String msg : messages) {
148                     LOGGER.error(msg);
149                 }
150             }
151             if (getName() == null) {
152                 setName(configSource.getLocation());
153             }
154         } catch (final Exception ex) {
155             LOGGER.error("Error parsing " + configSource.getLocation(), ex);
156             ex.printStackTrace();
157         }
158     }
159 
160     @Override
161     public void stop() {
162         super.stop();
163     }
164 
165     @Override
166     public void setup() {
167         final Iterator<Map.Entry<String, JsonNode>> iter = root.fields();
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         }
185     }
186 
187     @Override
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 = pluginManager.getPluginType(name);
203         final Node node = new Node(parent, name, type);
204         processAttributes(node, jsonNode);
205         final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields();
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 = pluginManager.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).fields();
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                             } else if (itemEntry.getValue().isArray()) {
234                                 JsonNode array = itemEntry.getValue();
235                                 String entryName = itemEntry.getKey();
236                                 LOGGER.debug("Processing array for object " + entryName);
237                                 for (int j = 0; j < array.size(); ++j) {
238                                     itemChildren.add(constructNode(entryName, item, array.get(j)));
239                                 }
240                             }
241 
242                         }
243                         children.add(item);
244                     }
245                 } else {
246                     LOGGER.debug("Processing node for object " + entry.getKey());
247                     children.add(constructNode(entry.getKey(), node, n));
248                 }
249             } else {
250                 LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType());
251             }
252         }
253 
254         String t;
255         if (type == null) {
256             t = "null";
257         } else {
258             t = type.getElementName() + ":" + type.getPluginClass();
259         }
260 
261         final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ?
262             "root" : node.getParent().getName();
263         LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " +  t);
264         return node;
265     }
266 
267     private String getType(final JsonNode node, final String name) {
268         final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
269         while (iter.hasNext()) {
270             final Map.Entry<String, JsonNode> entry = iter.next();
271             if (entry.getKey().equalsIgnoreCase("type")) {
272                 final JsonNode n = entry.getValue();
273                 if (n.isValueNode()) {
274                     return n.asText();
275                 }
276             }
277         }
278         return name;
279     }
280 
281     private void processAttributes(final Node parent, final JsonNode node) {
282         final Map<String, String> attrs = parent.getAttributes();
283         final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
284         while (iter.hasNext()) {
285             final Map.Entry<String, JsonNode> entry = iter.next();
286             if (!entry.getKey().equalsIgnoreCase("type")) {
287                 final JsonNode n = entry.getValue();
288                 if (n.isValueNode()) {
289                     attrs.put(entry.getKey(), n.asText());
290                 }
291             }
292         }
293     }
294 
295     protected byte[] toByteArray(final InputStream is) throws IOException {
296         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
297 
298         int nRead;
299         final byte[] data = new byte[BUF_SIZE];
300 
301         while ((nRead = is.read(data, 0, data.length)) != -1) {
302             buffer.write(data, 0, nRead);
303         }
304 
305         return buffer.toByteArray();
306     }
307 
308     /**
309      * The error that occurred.
310      */
311     private enum ErrorType {
312         CLASS_NOT_FOUND
313     }
314 
315     /**
316      * Status for recording errors.
317      */
318     private class Status {
319         private final JsonNode node;
320         private final String name;
321         private final ErrorType errorType;
322 
323         public Status(final String name, final JsonNode node, final ErrorType errorType) {
324             this.name = name;
325             this.node = node;
326             this.errorType = errorType;
327         }
328     }
329 }