001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.config; 018 019 import org.apache.logging.log4j.Level; 020 import org.apache.logging.log4j.core.config.plugins.PluginManager; 021 import org.apache.logging.log4j.core.config.plugins.PluginType; 022 import org.apache.logging.log4j.core.config.plugins.ResolverUtil; 023 import org.apache.logging.log4j.core.helpers.FileUtils; 024 import org.apache.logging.log4j.status.StatusConsoleListener; 025 import org.apache.logging.log4j.status.StatusListener; 026 import org.apache.logging.log4j.status.StatusLogger; 027 import org.codehaus.jackson.JsonNode; 028 import org.codehaus.jackson.JsonParser; 029 import org.codehaus.jackson.map.ObjectMapper; 030 031 import java.io.ByteArrayInputStream; 032 import java.io.ByteArrayOutputStream; 033 import java.io.File; 034 import java.io.FileInputStream; 035 import java.io.FileNotFoundException; 036 import java.io.FileOutputStream; 037 import java.io.IOException; 038 import java.io.InputStream; 039 import java.io.PrintStream; 040 import java.net.URI; 041 import java.net.URISyntaxException; 042 import java.util.ArrayList; 043 import java.util.Iterator; 044 import java.util.List; 045 import java.util.Map; 046 047 /** 048 * Creates a Node hierarchy from a JSON file. 049 */ 050 public class JSONConfiguration extends BaseConfiguration implements Reconfigurable { 051 052 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 053 054 private static final int BUF_SIZE = 16384; 055 056 private final List<Status> status = new ArrayList<Status>(); 057 058 private JsonNode root; 059 060 private final List<String> messages = new ArrayList<String>(); 061 062 private final File configFile; 063 064 public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 065 this.configFile = configSource.getFile(); 066 byte[] buffer; 067 068 try { 069 final InputStream configStream = configSource.getInputStream(); 070 buffer = toByteArray(configStream); 071 configStream.close(); 072 final InputStream is = new ByteArrayInputStream(buffer); 073 final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true); 074 root = mapper.readTree(is); 075 if (root.size() == 1) { 076 final Iterator<JsonNode> i = root.getElements(); 077 root = i.next(); 078 } 079 processAttributes(rootNode, root); 080 Level status = Level.OFF; 081 boolean verbose = false; 082 PrintStream stream = System.out; 083 for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) { 084 if ("status".equalsIgnoreCase(entry.getKey())) { 085 status = Level.toLevel(getSubst().replace(entry.getValue()), null); 086 if (status == null) { 087 status = Level.ERROR; 088 messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR"); 089 } 090 } else if ("dest".equalsIgnoreCase(entry.getKey())) { 091 final String dest = entry.getValue(); 092 if (dest != null) { 093 if (dest.equalsIgnoreCase("err")) { 094 stream = System.err; 095 } else { 096 try { 097 final File destFile = FileUtils.fileFromURI(new URI(dest)); 098 stream = new PrintStream(new FileOutputStream(destFile)); 099 } 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 }