1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.config.json;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28
29 import com.fasterxml.jackson.core.JsonParser;
30 import com.fasterxml.jackson.databind.JsonNode;
31 import com.fasterxml.jackson.databind.ObjectMapper;
32 import org.apache.logging.log4j.core.config.AbstractConfiguration;
33 import org.apache.logging.log4j.core.config.Configuration;
34 import org.apache.logging.log4j.core.config.ConfigurationSource;
35 import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher;
36 import org.apache.logging.log4j.core.config.LoggerConfig;
37 import org.apache.logging.log4j.core.config.Node;
38 import org.apache.logging.log4j.core.config.Reconfigurable;
39 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
40 import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
41 import org.apache.logging.log4j.core.config.status.StatusConfiguration;
42 import org.apache.logging.log4j.core.util.FileWatcher;
43 import org.apache.logging.log4j.core.util.Patterns;
44
45
46
47
48 public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable {
49
50 private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() };
51 private final List<Status> status = new ArrayList<>();
52 private JsonNode root;
53
54 public JsonConfiguration(final ConfigurationSource configSource) {
55 super(configSource);
56 final File configFile = configSource.getFile();
57 byte[] buffer;
58 try {
59 try (final InputStream configStream = configSource.getInputStream()) {
60 buffer = toByteArray(configStream);
61 }
62 final InputStream is = new ByteArrayInputStream(buffer);
63 root = getObjectMapper().readTree(is);
64 if (root.size() == 1) {
65 for (final JsonNode node : root) {
66 root = node;
67 }
68 }
69 processAttributes(rootNode, root);
70 final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
71 .withStatus(getDefaultStatus());
72 for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
73 final String key = entry.getKey();
74 final String value = getStrSubstitutor().replace(entry.getValue());
75
76 if ("status".equalsIgnoreCase(key)) {
77 statusConfig.withStatus(value);
78 } else if ("dest".equalsIgnoreCase(key)) {
79 statusConfig.withDestination(value);
80 } else if ("shutdownHook".equalsIgnoreCase(key)) {
81 isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
82 } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
83 statusConfig.withVerbosity(value);
84 } else if ("packages".equalsIgnoreCase(key)) {
85 pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
86 } else if ("name".equalsIgnoreCase(key)) {
87 setName(value);
88 } else if ("monitorInterval".equalsIgnoreCase(key)) {
89 final int intervalSeconds = Integer.parseInt(value);
90 if (intervalSeconds > 0) {
91 getWatchManager().setIntervalSeconds(intervalSeconds);
92 if (configFile != null) {
93 final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
94 getWatchManager().watchFile(configFile, watcher);
95 }
96 }
97 } else if ("advertiser".equalsIgnoreCase(key)) {
98 createAdvertiser(value, configSource, buffer, "application/json");
99 }
100 }
101 statusConfig.initialize();
102 if (getName() == null) {
103 setName(configSource.getLocation());
104 }
105 } catch (final Exception ex) {
106 LOGGER.error("Error parsing " + configSource.getLocation(), ex);
107 }
108 }
109
110 protected ObjectMapper getObjectMapper() {
111 return new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true);
112 }
113
114 @Override
115 public void setup() {
116 final Iterator<Map.Entry<String, JsonNode>> iter = root.fields();
117 final List<Node> children = rootNode.getChildren();
118 while (iter.hasNext()) {
119 final Map.Entry<String, JsonNode> entry = iter.next();
120 final JsonNode n = entry.getValue();
121 if (n.isObject()) {
122 LOGGER.debug("Processing node for object {}", entry.getKey());
123 children.add(constructNode(entry.getKey(), rootNode, n));
124 } else if (n.isArray()) {
125 LOGGER.error("Arrays are not supported at the root configuration.");
126 }
127 }
128 LOGGER.debug("Completed parsing configuration");
129 if (status.size() > 0) {
130 for (final Status s : status) {
131 LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
132 }
133 }
134 }
135
136 @Override
137 public Configuration reconfigure() {
138 try {
139 final ConfigurationSource source = getConfigurationSource().resetInputStream();
140 if (source == null) {
141 return null;
142 }
143 return new JsonConfiguration(source);
144 } catch (final IOException ex) {
145 LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
146 }
147 return null;
148 }
149
150 private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
151 final PluginType<?> type = pluginManager.getPluginType(name);
152 final Node node = new Node(parent, name, type);
153 processAttributes(node, jsonNode);
154 final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields();
155 final List<Node> children = node.getChildren();
156 while (iter.hasNext()) {
157 final Map.Entry<String, JsonNode> entry = iter.next();
158 final JsonNode n = entry.getValue();
159 if (n.isArray() || n.isObject()) {
160 if (type == null) {
161 status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND));
162 }
163 if (n.isArray()) {
164 LOGGER.debug("Processing node for array {}", entry.getKey());
165 for (int i = 0; i < n.size(); ++i) {
166 final String pluginType = getType(n.get(i), entry.getKey());
167 final PluginType<?> entryType = pluginManager.getPluginType(pluginType);
168 final Node item = new Node(node, entry.getKey(), entryType);
169 processAttributes(item, n.get(i));
170 if (pluginType.equals(entry.getKey())) {
171 LOGGER.debug("Processing {}[{}]", entry.getKey(), i);
172 } else {
173 LOGGER.debug("Processing {} {}[{}]", pluginType, entry.getKey(), i);
174 }
175 final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).fields();
176 final List<Node> itemChildren = item.getChildren();
177 while (itemIter.hasNext()) {
178 final Map.Entry<String, JsonNode> itemEntry = itemIter.next();
179 if (itemEntry.getValue().isObject()) {
180 LOGGER.debug("Processing node for object {}", itemEntry.getKey());
181 itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
182 } else if (itemEntry.getValue().isArray()) {
183 final JsonNode array = itemEntry.getValue();
184 final String entryName = itemEntry.getKey();
185 LOGGER.debug("Processing array for object {}", entryName);
186 for (int j = 0; j < array.size(); ++j) {
187 itemChildren.add(constructNode(entryName, item, array.get(j)));
188 }
189 }
190
191 }
192 children.add(item);
193 }
194 } else {
195 LOGGER.debug("Processing node for object {}", entry.getKey());
196 children.add(constructNode(entry.getKey(), node, n));
197 }
198 } else {
199 LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType());
200 }
201 }
202
203 String t;
204 if (type == null) {
205 t = "null";
206 } else {
207 t = type.getElementName() + ':' + type.getPluginClass();
208 }
209
210 final String p = node.getParent() == null ? "null"
211 : node.getParent().getName() == null ? LoggerConfig.ROOT : node.getParent().getName();
212 LOGGER.debug("Returning {} with parent {} of type {}", node.getName(), p, t);
213 return node;
214 }
215
216 private String getType(final JsonNode node, final String name) {
217 final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
218 while (iter.hasNext()) {
219 final Map.Entry<String, JsonNode> entry = iter.next();
220 if (entry.getKey().equalsIgnoreCase("type")) {
221 final JsonNode n = entry.getValue();
222 if (n.isValueNode()) {
223 return n.asText();
224 }
225 }
226 }
227 return name;
228 }
229
230 private void processAttributes(final Node parent, final JsonNode node) {
231 final Map<String, String> attrs = parent.getAttributes();
232 final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
233 while (iter.hasNext()) {
234 final Map.Entry<String, JsonNode> entry = iter.next();
235 if (!entry.getKey().equalsIgnoreCase("type")) {
236 final JsonNode n = entry.getValue();
237 if (n.isValueNode()) {
238 attrs.put(entry.getKey(), n.asText());
239 }
240 }
241 }
242 }
243
244 @Override
245 public String toString() {
246 return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]";
247 }
248
249
250
251
252 private enum ErrorType {
253 CLASS_NOT_FOUND
254 }
255
256
257
258
259 private static class Status {
260 private final JsonNode node;
261 private final String name;
262 private final ErrorType errorType;
263
264 public Status(final String name, final JsonNode node, final ErrorType errorType) {
265 this.name = name;
266 this.node = node;
267 this.errorType = errorType;
268 }
269
270 @Override
271 public String toString() {
272 return "Status [name=" + name + ", errorType=" + errorType + ", node=" + node + "]";
273 }
274 }
275 }