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 java.io.ByteArrayInputStream; 020 import java.io.ByteArrayOutputStream; 021 import java.io.File; 022 import java.io.FileInputStream; 023 import java.io.FileNotFoundException; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.PrintStream; 028 import java.net.URI; 029 import java.net.URISyntaxException; 030 import java.nio.charset.Charset; 031 import java.util.ArrayList; 032 import java.util.HashMap; 033 import java.util.Iterator; 034 import java.util.List; 035 import java.util.Map; 036 037 import org.apache.logging.log4j.Level; 038 import org.apache.logging.log4j.core.config.plugins.PluginManager; 039 import org.apache.logging.log4j.core.config.plugins.PluginType; 040 import org.apache.logging.log4j.core.config.plugins.ResolverUtil; 041 import org.apache.logging.log4j.core.helpers.FileUtils; 042 import org.apache.logging.log4j.core.net.Advertiser; 043 import org.apache.logging.log4j.status.StatusConsoleListener; 044 import org.apache.logging.log4j.status.StatusListener; 045 import org.apache.logging.log4j.status.StatusLogger; 046 047 import com.fasterxml.jackson.core.JsonParser; 048 import com.fasterxml.jackson.databind.JsonNode; 049 import com.fasterxml.jackson.databind.ObjectMapper; 050 051 /** 052 * Creates a Node hierarchy from a JSON file. 053 */ 054 public class JSONConfiguration extends BaseConfiguration implements Reconfigurable { 055 056 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; 057 058 private static final int BUF_SIZE = 16384; 059 060 private final List<Status> status = new ArrayList<Status>(); 061 062 private Map<String, String> advertisedConfiguration; 063 064 private Object advertisement; 065 066 private JsonNode root; 067 068 private final List<String> messages = new ArrayList<String>(); 069 070 private final File configFile; 071 072 public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { 073 this.configFile = configSource.getFile(); 074 byte[] buffer; 075 076 try { 077 final InputStream configStream = configSource.getInputStream(); 078 buffer = toByteArray(configStream); 079 configStream.close(); 080 final InputStream is = new ByteArrayInputStream(buffer); 081 final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true); 082 root = mapper.readTree(is); 083 if (root.size() == 1) { 084 final Iterator<JsonNode> i = root.elements(); 085 root = i.next(); 086 } 087 processAttributes(rootNode, root); 088 Level status = Level.OFF; 089 boolean verbose = false; 090 PrintStream stream = System.out; 091 for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) { 092 if ("status".equalsIgnoreCase(entry.getKey())) { 093 status = Level.toLevel(getSubst().replace(entry.getValue()), null); 094 if (status == null) { 095 status = Level.ERROR; 096 messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR"); 097 } 098 } else if ("dest".equalsIgnoreCase(entry.getKey())) { 099 final String dest = entry.getValue(); 100 if (dest != null) { 101 if (dest.equalsIgnoreCase("err")) { 102 stream = System.err; 103 } else { 104 try { 105 final File destFile = FileUtils.fileFromURI(new URI(dest)); 106 final String enc = Charset.defaultCharset().name(); 107 stream = new PrintStream(new FileOutputStream(destFile), true, enc); 108 } catch (final URISyntaxException use) { 109 System.err.println("Unable to write to " + dest + ". Writing to stdout"); 110 } 111 } 112 } 113 } else if ("verbose".equalsIgnoreCase(entry.getKey())) { 114 verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue())); 115 } else if ("packages".equalsIgnoreCase(entry.getKey())) { 116 final String[] packages = getSubst().replace(entry.getValue()).split(","); 117 for (final String p : packages) { 118 PluginManager.addPackage(p); 119 } 120 } else if ("name".equalsIgnoreCase(entry.getKey())) { 121 setName(getSubst().replace(entry.getValue())); 122 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) { 123 final int interval = Integer.parseInt(getSubst().replace(entry.getValue())); 124 if (interval > 0 && configFile != null) { 125 monitor = new FileConfigurationMonitor(this, configFile, listeners, interval); 126 } 127 } else if ("advertiser".equalsIgnoreCase(entry.getKey())) { 128 final String advertiserString = getSubst().replace(entry.getValue()); 129 if (advertiserString != null) 130 { 131 @SuppressWarnings("unchecked") 132 final PluginType<Advertiser> type = getPluginManager().getPluginType(advertiserString); 133 if (type != null) 134 { 135 final Class<Advertiser> clazz = type.getPluginClass(); 136 advertiser = clazz.newInstance(); 137 advertisedConfiguration = new HashMap<String, String>(); 138 advertisedConfiguration.put("content", new String(buffer)); 139 advertisedConfiguration.put("contentType", "application/json"); 140 advertisedConfiguration.put("name", "configuration"); 141 if (configSource.getLocation() != null) 142 { 143 advertisedConfiguration.put("location", configSource.getLocation()); 144 } 145 } 146 } 147 } 148 } 149 150 final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners(); 151 boolean found = false; 152 while (statusIter.hasNext()) { 153 final StatusListener listener = statusIter.next(); 154 if (listener instanceof StatusConsoleListener) { 155 found = true; 156 ((StatusConsoleListener) listener).setLevel(status); 157 if (!verbose) { 158 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES); 159 } 160 } 161 } 162 if (!found && status != Level.OFF) { 163 final StatusConsoleListener listener = new StatusConsoleListener(status, stream); 164 if (!verbose) { 165 listener.setFilters(VERBOSE_CLASSES); 166 } 167 ((StatusLogger) LOGGER).registerListener(listener); 168 for (final String msg : messages) { 169 LOGGER.error(msg); 170 } 171 } 172 if (getName() == null) { 173 setName(configSource.getLocation()); 174 } 175 } catch (final Exception ex) { 176 LOGGER.error("Error parsing " + configSource.getLocation(), ex); 177 ex.printStackTrace(); 178 } 179 } 180 181 @Override 182 public void stop() { 183 super.stop(); 184 if (advertiser != null && advertisement != null) 185 { 186 advertiser.unadvertise(advertisement); 187 } 188 } 189 190 @Override 191 public void setup() { 192 final Iterator<Map.Entry<String, JsonNode>> iter = root.fields(); 193 final List<Node> children = rootNode.getChildren(); 194 while (iter.hasNext()) { 195 final Map.Entry<String, JsonNode> entry = iter.next(); 196 final JsonNode n = entry.getValue(); 197 if (n.isObject()) { 198 LOGGER.debug("Processing node for object " + entry.getKey()); 199 children.add(constructNode(entry.getKey(), rootNode, n)); 200 } else if (n.isArray()) { 201 LOGGER.error("Arrays are not supported at the root configuration."); 202 } 203 } 204 LOGGER.debug("Completed parsing configuration"); 205 if (status.size() > 0) { 206 for (final Status s : status) { 207 LOGGER.error("Error processing element " + s.name + ": " + s.errorType); 208 } 209 return; 210 } 211 if (advertiser != null && advertisedConfiguration != null) 212 { 213 advertisement = advertiser.advertise(advertisedConfiguration); 214 } 215 } 216 217 @Override 218 public Configuration reconfigure() { 219 if (configFile != null) { 220 try { 221 final ConfigurationFactory.ConfigurationSource source = 222 new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile); 223 return new JSONConfiguration(source); 224 } catch (final FileNotFoundException ex) { 225 LOGGER.error("Cannot locate file " + configFile, ex); 226 } 227 } 228 return null; 229 } 230 231 private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) { 232 final PluginType<?> type = getPluginManager().getPluginType(name); 233 final Node node = new Node(parent, name, type); 234 processAttributes(node, jsonNode); 235 final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields(); 236 final List<Node> children = node.getChildren(); 237 while (iter.hasNext()) { 238 final Map.Entry<String, JsonNode> entry = iter.next(); 239 final JsonNode n = entry.getValue(); 240 if (n.isArray() || n.isObject()) { 241 if (type == null) { 242 status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND)); 243 } 244 if (n.isArray()) { 245 LOGGER.debug("Processing node for array " + entry.getKey()); 246 for (int i = 0; i < n.size(); ++i) { 247 final String pluginType = getType(n.get(i), entry.getKey()); 248 final PluginType<?> entryType = getPluginManager().getPluginType(pluginType); 249 final Node item = new Node(node, entry.getKey(), entryType); 250 processAttributes(item, n.get(i)); 251 if (pluginType.equals(entry.getKey())) { 252 LOGGER.debug("Processing " + entry.getKey() + "[" + i + "]"); 253 } else { 254 LOGGER.debug("Processing " + pluginType + " " + entry.getKey() + "[" + i + "]"); 255 } 256 final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).fields(); 257 final List<Node> itemChildren = item.getChildren(); 258 while (itemIter.hasNext()) { 259 final Map.Entry<String, JsonNode> itemEntry = itemIter.next(); 260 if (itemEntry.getValue().isObject()) { 261 LOGGER.debug("Processing node for object " + itemEntry.getKey()); 262 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue())); 263 } 264 } 265 children.add(item); 266 } 267 } else { 268 LOGGER.debug("Processing node for object " + entry.getKey()); 269 children.add(constructNode(entry.getKey(), node, n)); 270 } 271 } 272 } 273 274 String t; 275 if (type == null) { 276 t = "null"; 277 } else { 278 t = type.getElementName() + ":" + type.getPluginClass(); 279 } 280 281 final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ? 282 "root" : node.getParent().getName(); 283 LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " + t); 284 return node; 285 } 286 287 private String getType(final JsonNode node, final String name) { 288 final Iterator<Map.Entry<String, JsonNode>> iter = node.fields(); 289 while (iter.hasNext()) { 290 final Map.Entry<String, JsonNode> entry = iter.next(); 291 if (entry.getKey().equalsIgnoreCase("type")) { 292 final JsonNode n = entry.getValue(); 293 if (n.isValueNode()) { 294 return n.asText(); 295 } 296 } 297 } 298 return name; 299 } 300 301 private void processAttributes(final Node parent, final JsonNode node) { 302 final Map<String, String> attrs = parent.getAttributes(); 303 final Iterator<Map.Entry<String, JsonNode>> iter = node.fields(); 304 while (iter.hasNext()) { 305 final Map.Entry<String, JsonNode> entry = iter.next(); 306 if (!entry.getKey().equalsIgnoreCase("type")) { 307 final JsonNode n = entry.getValue(); 308 if (n.isValueNode()) { 309 attrs.put(entry.getKey(), n.asText()); 310 } 311 } 312 } 313 } 314 315 protected byte[] toByteArray(final InputStream is) throws IOException { 316 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 317 318 int nRead; 319 final byte[] data = new byte[BUF_SIZE]; 320 321 while ((nRead = is.read(data, 0, data.length)) != -1) { 322 buffer.write(data, 0, nRead); 323 } 324 325 return buffer.toByteArray(); 326 } 327 328 /** 329 * The error that occurred. 330 */ 331 private enum ErrorType { 332 CLASS_NOT_FOUND 333 } 334 335 /** 336 * Status for recording errors. 337 */ 338 private class Status { 339 private final JsonNode node; 340 private final String name; 341 private final ErrorType errorType; 342 343 public Status(final String name, final JsonNode node, final ErrorType errorType) { 344 this.name = name; 345 this.node = node; 346 this.errorType = errorType; 347 } 348 } 349 }