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.json; 018 019import com.fasterxml.jackson.core.JsonParser; 020import com.fasterxml.jackson.databind.JsonNode; 021import com.fasterxml.jackson.databind.ObjectMapper; 022import org.apache.logging.log4j.core.config.AbstractConfiguration; 023import org.apache.logging.log4j.core.config.Configuration; 024import org.apache.logging.log4j.core.config.ConfigurationSource; 025import org.apache.logging.log4j.core.config.FileConfigurationMonitor; 026import org.apache.logging.log4j.core.config.Node; 027import org.apache.logging.log4j.core.config.Reconfigurable; 028import org.apache.logging.log4j.core.config.plugins.util.PluginType; 029import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; 030import org.apache.logging.log4j.core.config.status.StatusConfiguration; 031import org.apache.logging.log4j.core.util.Patterns; 032 033import java.io.ByteArrayInputStream; 034import java.io.File; 035import java.io.IOException; 036import java.io.InputStream; 037import java.util.ArrayList; 038import java.util.Arrays; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Map; 042 043/** 044 * Creates a Node hierarchy from a JSON file. 045 */ 046public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable { 047 048 private static final long serialVersionUID = 1L; 049 private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() }; 050 private final List<Status> status = new ArrayList<Status>(); 051 private JsonNode root; 052 053 public JsonConfiguration(final ConfigurationSource configSource) { 054 super(configSource); 055 final File configFile = configSource.getFile(); 056 byte[] buffer; 057 try { 058 final InputStream configStream = configSource.getInputStream(); 059 try { 060 buffer = toByteArray(configStream); 061 } finally { 062 configStream.close(); 063 } 064 final InputStream is = new ByteArrayInputStream(buffer); 065 root = getObjectMapper().readTree(is); 066 if (root.size() == 1) { 067 for (final JsonNode node : root) { 068 root = node; 069 } 070 } 071 processAttributes(rootNode, root); 072 final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) 073 .withStatus(getDefaultStatus()); 074 for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) { 075 final String key = entry.getKey(); 076 final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); 077 // TODO: this duplicates a lot of the XmlConfiguration constructor 078 if ("status".equalsIgnoreCase(key)) { 079 statusConfig.withStatus(value); 080 } else if ("dest".equalsIgnoreCase(key)) { 081 statusConfig.withDestination(value); 082 } else if ("shutdownHook".equalsIgnoreCase(key)) { 083 isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); 084 } else if ("verbose".equalsIgnoreCase(entry.getKey())) { 085 statusConfig.withVerbosity(value); 086 } else if ("packages".equalsIgnoreCase(key)) { 087 pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); 088 } else if ("name".equalsIgnoreCase(key)) { 089 setName(value); 090 } else if ("monitorInterval".equalsIgnoreCase(key)) { 091 final int intervalSeconds = Integer.parseInt(value); 092 if (intervalSeconds > 0 && configFile != null) { 093 monitor = new FileConfigurationMonitor(this, configFile, listeners, intervalSeconds); 094 } 095 } else if ("advertiser".equalsIgnoreCase(key)) { 096 createAdvertiser(value, configSource, buffer, "application/json"); 097 } 098 } 099 statusConfig.initialize(); 100 if (getName() == null) { 101 setName(configSource.getLocation()); 102 } 103 } catch (final Exception ex) { 104 LOGGER.error("Error parsing {}", configSource.getLocation(), ex); 105 } 106 } 107 108 protected ObjectMapper getObjectMapper() { 109 return new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true); 110 } 111 112 @Override 113 public void setup() { 114 final Iterator<Map.Entry<String, JsonNode>> iter = root.fields(); 115 final List<Node> children = rootNode.getChildren(); 116 while (iter.hasNext()) { 117 final Map.Entry<String, JsonNode> entry = iter.next(); 118 final JsonNode n = entry.getValue(); 119 if (n.isObject()) { 120 LOGGER.debug("Processing node for object {}", entry.getKey()); 121 children.add(constructNode(entry.getKey(), rootNode, n)); 122 } else if (n.isArray()) { 123 LOGGER.error("Arrays are not supported at the root configuration."); 124 } 125 } 126 LOGGER.debug("Completed parsing configuration"); 127 if (status.size() > 0) { 128 for (final Status s : status) { 129 LOGGER.error("Error processing element " + s.name + ": " + s.errorType); 130 } 131 } 132 } 133 134 @Override 135 public Configuration reconfigure() { 136 try { 137 final ConfigurationSource source = getConfigurationSource().resetInputStream(); 138 if (source == null) { 139 return null; 140 } 141 return new JsonConfiguration(source); 142 } catch (final IOException ex) { 143 LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex); 144 } 145 return null; 146 } 147 148 private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) { 149 final PluginType<?> type = pluginManager.getPluginType(name); 150 final Node node = new Node(parent, name, type); 151 processAttributes(node, jsonNode); 152 final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields(); 153 final List<Node> children = node.getChildren(); 154 while (iter.hasNext()) { 155 final Map.Entry<String, JsonNode> entry = iter.next(); 156 final JsonNode n = entry.getValue(); 157 if (n.isArray() || n.isObject()) { 158 if (type == null) { 159 status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND)); 160 } 161 if (n.isArray()) { 162 LOGGER.debug("Processing node for array {}", entry.getKey()); 163 for (int i = 0; i < n.size(); ++i) { 164 final String pluginType = getType(n.get(i), entry.getKey()); 165 final PluginType<?> entryType = pluginManager.getPluginType(pluginType); 166 final Node item = new Node(node, entry.getKey(), entryType); 167 processAttributes(item, n.get(i)); 168 if (pluginType.equals(entry.getKey())) { 169 LOGGER.debug("Processing {}[{}]", entry.getKey(), i); 170 } else { 171 LOGGER.debug("Processing {} {}[{}]", pluginType, entry.getKey(), i); 172 } 173 final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).fields(); 174 final List<Node> itemChildren = item.getChildren(); 175 while (itemIter.hasNext()) { 176 final Map.Entry<String, JsonNode> itemEntry = itemIter.next(); 177 if (itemEntry.getValue().isObject()) { 178 LOGGER.debug("Processing node for object {}", itemEntry.getKey()); 179 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue())); 180 } else if (itemEntry.getValue().isArray()) { 181 final JsonNode array = itemEntry.getValue(); 182 final String entryName = itemEntry.getKey(); 183 LOGGER.debug("Processing array for object {}", entryName); 184 for (int j = 0; j < array.size(); ++j) { 185 itemChildren.add(constructNode(entryName, item, array.get(j))); 186 } 187 } 188 189 } 190 children.add(item); 191 } 192 } else { 193 LOGGER.debug("Processing node for object {}", entry.getKey()); 194 children.add(constructNode(entry.getKey(), node, n)); 195 } 196 } else { 197 LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType()); 198 } 199 } 200 201 String t; 202 if (type == null) { 203 t = "null"; 204 } else { 205 t = type.getElementName() + ':' + type.getPluginClass(); 206 } 207 208 final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ? "root" : node 209 .getParent().getName(); 210 LOGGER.debug("Returning {} with parent {} of type {}", node.getName(), p, t); 211 return node; 212 } 213 214 private String getType(final JsonNode node, final String name) { 215 final Iterator<Map.Entry<String, JsonNode>> iter = node.fields(); 216 while (iter.hasNext()) { 217 final Map.Entry<String, JsonNode> entry = iter.next(); 218 if (entry.getKey().equalsIgnoreCase("type")) { 219 final JsonNode n = entry.getValue(); 220 if (n.isValueNode()) { 221 return n.asText(); 222 } 223 } 224 } 225 return name; 226 } 227 228 private void processAttributes(final Node parent, final JsonNode node) { 229 final Map<String, String> attrs = parent.getAttributes(); 230 final Iterator<Map.Entry<String, JsonNode>> iter = node.fields(); 231 while (iter.hasNext()) { 232 final Map.Entry<String, JsonNode> entry = iter.next(); 233 if (!entry.getKey().equalsIgnoreCase("type")) { 234 final JsonNode n = entry.getValue(); 235 if (n.isValueNode()) { 236 attrs.put(entry.getKey(), n.asText()); 237 } 238 } 239 } 240 } 241 242 @Override 243 public String toString() { 244 return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]"; 245 } 246 247 /** 248 * The error that occurred. 249 */ 250 private enum ErrorType { 251 CLASS_NOT_FOUND 252 } 253 254 /** 255 * Status for recording errors. 256 */ 257 private static class Status { 258 private final JsonNode node; 259 private final String name; 260 private final ErrorType errorType; 261 262 public Status(final String name, final JsonNode node, final ErrorType errorType) { 263 this.name = name; 264 this.node = node; 265 this.errorType = errorType; 266 } 267 } 268}