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 */ 017package org.apache.logging.log4j.core.config; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.FileNotFoundException; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.PrintStream; 028import java.net.URISyntaxException; 029import java.nio.charset.Charset; 030import java.util.ArrayList; 031import java.util.Iterator; 032import java.util.List; 033import java.util.Map; 034 035import org.apache.logging.log4j.Level; 036import org.apache.logging.log4j.core.config.plugins.PluginManager; 037import org.apache.logging.log4j.core.config.plugins.PluginType; 038import org.apache.logging.log4j.core.config.plugins.ResolverUtil; 039import org.apache.logging.log4j.core.helpers.FileUtils; 040import org.apache.logging.log4j.status.StatusConsoleListener; 041import org.apache.logging.log4j.status.StatusListener; 042import org.apache.logging.log4j.status.StatusLogger; 043 044import com.fasterxml.jackson.core.JsonParser; 045import com.fasterxml.jackson.databind.JsonNode; 046import com.fasterxml.jackson.databind.ObjectMapper; 047 048/** 049 * Creates a Node hierarchy from a JSON file. 050 */ 051public class JSONConfiguration extends BaseConfiguration implements Reconfigurable { 052 053 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 054 055 private static final int BUF_SIZE = 16384; 056 057 private final List<Status> status = new ArrayList<Status>(); 058 059 private JsonNode root; 060 061 private final File configFile; 062 063 public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 064 final List<String> messages = new ArrayList<String>(); 065 066 this.configFile = configSource.getFile(); 067 byte[] buffer; 068 069 try { 070 final InputStream configStream = configSource.getInputStream(); 071 buffer = toByteArray(configStream); 072 configStream.close(); 073 final InputStream is = new ByteArrayInputStream(buffer); 074 final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true); 075 root = mapper.readTree(is); 076 if (root.size() == 1) { 077 final Iterator<JsonNode> i = root.elements(); 078 root = i.next(); 079 } 080 processAttributes(rootNode, root); 081 Level status = getDefaultStatus(); 082 boolean verbose = false; 083 PrintStream stream = System.out; 084 for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) { 085 if ("status".equalsIgnoreCase(entry.getKey())) { 086 status = Level.toLevel(getStrSubstitutor().replace(entry.getValue()), null); 087 if (status == null) { 088 status = Level.ERROR; 089 messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR"); 090 } 091 } else if ("dest".equalsIgnoreCase(entry.getKey())) { 092 final String dest = entry.getValue(); 093 if (dest != null) { 094 if (dest.equalsIgnoreCase("err")) { 095 stream = System.err; 096 } else { 097 try { 098 final File destFile = FileUtils.fileFromURI(FileUtils.getCorrectedFilePathUri(dest)); 099 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}