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 org.apache.logging.log4j.Level;
020 import org.apache.logging.log4j.core.config.plugins.PluginManager;
021 import org.apache.logging.log4j.core.config.plugins.PluginType;
022 import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
023 import org.apache.logging.log4j.core.helpers.FileUtils;
024 import org.apache.logging.log4j.core.net.Advertiser;
025 import org.apache.logging.log4j.status.StatusConsoleListener;
026 import org.apache.logging.log4j.status.StatusListener;
027 import org.apache.logging.log4j.status.StatusLogger;
028 import org.codehaus.jackson.JsonNode;
029 import org.codehaus.jackson.JsonParser;
030 import org.codehaus.jackson.map.ObjectMapper;
031
032 import java.io.ByteArrayInputStream;
033 import java.io.ByteArrayOutputStream;
034 import java.io.File;
035 import java.io.FileInputStream;
036 import java.io.FileNotFoundException;
037 import java.io.FileOutputStream;
038 import java.io.IOException;
039 import java.io.InputStream;
040 import java.io.PrintStream;
041 import java.net.URI;
042 import java.net.URISyntaxException;
043 import java.util.ArrayList;
044 import java.util.Iterator;
045 import java.util.List;
046 import java.util.Map;
047
048 /**
049 * Creates a Node hierarchy from a JSON file.
050 */
051 public 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 List<String> messages = new ArrayList<String>();
062
063 private final File configFile;
064
065 public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
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.getElements();
078 root = i.next();
079 }
080 processAttributes(rootNode, root);
081 Level status = Level.OFF;
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(getSubst().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(new URI(dest));
099 stream = new PrintStream(new FileOutputStream(destFile));
100 } catch (final URISyntaxException use) {
101 System.err.println("Unable to write to " + dest + ". Writing to stdout");
102 }
103 }
104 }
105 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
106 verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
107 } else if ("packages".equalsIgnoreCase(entry.getKey())) {
108 final String[] packages = getSubst().replace(entry.getValue()).split(",");
109 for (final String p : packages) {
110 PluginManager.addPackage(p);
111 }
112 } else if ("name".equalsIgnoreCase(entry.getKey())) {
113 setName(getSubst().replace(entry.getValue()));
114 } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
115 final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
116 if (interval > 0 && configFile != null) {
117 monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
118 }
119 } else if ("advertiser".equalsIgnoreCase(entry.getKey())) {
120 final String advertiserString = getSubst().replace(entry.getValue());
121 if (advertiserString != null)
122 {
123 @SuppressWarnings("unchecked")
124 final PluginType<Advertiser> type = getPluginManager().getPluginType(advertiserString);
125 if (type != null)
126 {
127 final Class<Advertiser> clazz = type.getPluginClass();
128 advertiser = clazz.newInstance();
129 }
130 }
131 }
132 }
133
134 final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners();
135 boolean found = false;
136 while (statusIter.hasNext()) {
137 final StatusListener listener = statusIter.next();
138 if (listener instanceof StatusConsoleListener) {
139 found = true;
140 ((StatusConsoleListener) listener).setLevel(status);
141 if (!verbose) {
142 ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
143 }
144 }
145 }
146 if (!found && status != Level.OFF) {
147 final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
148 if (!verbose) {
149 listener.setFilters(VERBOSE_CLASSES);
150 }
151 ((StatusLogger) LOGGER).registerListener(listener);
152 for (final String msg : messages) {
153 LOGGER.error(msg);
154 }
155 }
156 if (getName() == null) {
157 setName(configSource.getLocation());
158 }
159 } catch (final Exception ex) {
160 LOGGER.error("Error parsing " + configSource.getLocation(), ex);
161 ex.printStackTrace();
162 }
163 }
164
165 @Override
166 public void setup() {
167 final Iterator<Map.Entry<String, JsonNode>> iter = root.getFields();
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 return;
185 }
186 }
187
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 = getPluginManager().getPluginType(name);
203 final Node node = new Node(parent, name, type);
204 processAttributes(node, jsonNode);
205 final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.getFields();
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 = getPluginManager().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).getFields();
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 }
234 }
235 children.add(item);
236 }
237 } else {
238 LOGGER.debug("Processing node for object " + entry.getKey());
239 children.add(constructNode(entry.getKey(), node, n));
240 }
241 }
242 }
243
244 String t;
245 if (type == null) {
246 t = "null";
247 } else {
248 t = type.getElementName() + ":" + type.getPluginClass();
249 }
250
251 final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ?
252 "root" : node.getParent().getName();
253 LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " + t);
254 return node;
255 }
256
257 private String getType(final JsonNode node, final String name) {
258 final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
259 while (iter.hasNext()) {
260 final Map.Entry<String, JsonNode> entry = iter.next();
261 if (entry.getKey().equalsIgnoreCase("type")) {
262 final JsonNode n = entry.getValue();
263 if (n.isValueNode()) {
264 return n.asText();
265 }
266 }
267 }
268 return name;
269 }
270
271 private void processAttributes(final Node parent, final JsonNode node) {
272 final Map<String, String> attrs = parent.getAttributes();
273 final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
274 while (iter.hasNext()) {
275 final Map.Entry<String, JsonNode> entry = iter.next();
276 if (!entry.getKey().equalsIgnoreCase("type")) {
277 final JsonNode n = entry.getValue();
278 if (n.isValueNode()) {
279 attrs.put(entry.getKey(), n.asText());
280 }
281 }
282 }
283 }
284
285 protected byte[] toByteArray(final InputStream is) throws IOException {
286 final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
287
288 int nRead;
289 final byte[] data = new byte[BUF_SIZE];
290
291 while ((nRead = is.read(data, 0, data.length)) != -1) {
292 buffer.write(data, 0, nRead);
293 }
294
295 return buffer.toByteArray();
296 }
297
298 /**
299 * The error that occurred.
300 */
301 private enum ErrorType {
302 CLASS_NOT_FOUND
303 }
304
305 /**
306 * Status for recording errors.
307 */
308 private class Status {
309 private final JsonNode node;
310 private final String name;
311 private final ErrorType errorType;
312
313 public Status(final String name, final JsonNode node, final ErrorType errorType) {
314 this.name = name;
315 this.node = node;
316 this.errorType = errorType;
317 }
318 }
319 }