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