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