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