1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.config;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.ByteArrayOutputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.PrintStream;
28 import java.net.URI;
29 import java.net.URISyntaxException;
30 import java.nio.charset.Charset;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36
37 import org.apache.logging.log4j.Level;
38 import org.apache.logging.log4j.core.config.plugins.PluginManager;
39 import org.apache.logging.log4j.core.config.plugins.PluginType;
40 import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
41 import org.apache.logging.log4j.core.helpers.FileUtils;
42 import org.apache.logging.log4j.core.net.Advertiser;
43 import org.apache.logging.log4j.status.StatusConsoleListener;
44 import org.apache.logging.log4j.status.StatusListener;
45 import org.apache.logging.log4j.status.StatusLogger;
46
47 import com.fasterxml.jackson.core.JsonParser;
48 import com.fasterxml.jackson.databind.JsonNode;
49 import com.fasterxml.jackson.databind.ObjectMapper;
50
51
52
53
54 public class JSONConfiguration extends BaseConfiguration implements Reconfigurable {
55
56 private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
57
58 private static final int BUF_SIZE = 16384;
59
60 private final List<Status> status = new ArrayList<Status>();
61
62 private Map<String, String> advertisedConfiguration;
63
64 private Object advertisement;
65
66 private JsonNode root;
67
68 private final List<String> messages = new ArrayList<String>();
69
70 private final File configFile;
71
72 public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
73 this.configFile = configSource.getFile();
74 byte[] buffer;
75
76 try {
77 final InputStream configStream = configSource.getInputStream();
78 buffer = toByteArray(configStream);
79 configStream.close();
80 final InputStream is = new ByteArrayInputStream(buffer);
81 final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true);
82 root = mapper.readTree(is);
83 if (root.size() == 1) {
84 final Iterator<JsonNode> i = root.elements();
85 root = i.next();
86 }
87 processAttributes(rootNode, root);
88 Level status = Level.OFF;
89 boolean verbose = false;
90 PrintStream stream = System.out;
91 for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
92 if ("status".equalsIgnoreCase(entry.getKey())) {
93 status = Level.toLevel(getSubst().replace(entry.getValue()), null);
94 if (status == null) {
95 status = Level.ERROR;
96 messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
97 }
98 } else if ("dest".equalsIgnoreCase(entry.getKey())) {
99 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
330
331 private enum ErrorType {
332 CLASS_NOT_FOUND
333 }
334
335
336
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 }