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