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.json;
018    
019    import com.fasterxml.jackson.core.JsonParser;
020    import com.fasterxml.jackson.databind.JsonNode;
021    import com.fasterxml.jackson.databind.ObjectMapper;
022    import org.apache.logging.log4j.core.config.AbstractConfiguration;
023    import org.apache.logging.log4j.core.config.Configuration;
024    import org.apache.logging.log4j.core.config.ConfigurationSource;
025    import org.apache.logging.log4j.core.config.FileConfigurationMonitor;
026    import org.apache.logging.log4j.core.config.Node;
027    import org.apache.logging.log4j.core.config.Reconfigurable;
028    import org.apache.logging.log4j.core.config.plugins.util.PluginType;
029    import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
030    import org.apache.logging.log4j.core.config.status.StatusConfiguration;
031    import org.apache.logging.log4j.core.util.Patterns;
032    
033    import java.io.ByteArrayInputStream;
034    import java.io.File;
035    import java.io.IOException;
036    import java.io.InputStream;
037    import java.util.ArrayList;
038    import java.util.Arrays;
039    import java.util.Iterator;
040    import java.util.List;
041    import java.util.Map;
042    
043    /**
044     * Creates a Node hierarchy from a JSON file.
045     */
046    public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable {
047    
048        private static final long serialVersionUID = 1L;
049        private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() };
050        private final List<Status> status = new ArrayList<Status>();
051        private JsonNode root;
052    
053        public JsonConfiguration(final ConfigurationSource configSource) {
054            super(configSource);
055            final File configFile = configSource.getFile();
056            byte[] buffer;
057            try {
058                final InputStream configStream = configSource.getInputStream();
059                try {
060                    buffer = toByteArray(configStream);
061                } finally {
062                    configStream.close();
063                }
064                final InputStream is = new ByteArrayInputStream(buffer);
065                root = getObjectMapper().readTree(is);
066                if (root.size() == 1) {
067                    for (final JsonNode node : root) {
068                        root = node;
069                    }
070                }
071                processAttributes(rootNode, root);
072                final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
073                        .withStatus(getDefaultStatus());
074                for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
075                    final String key = entry.getKey();
076                    final String value = getStrSubstitutor().replace(entry.getValue());
077                    // TODO: this duplicates a lot of the XmlConfiguration constructor
078                    if ("status".equalsIgnoreCase(key)) {
079                        statusConfig.withStatus(value);
080                    } else if ("dest".equalsIgnoreCase(key)) {
081                        statusConfig.withDestination(value);
082                    } else if ("shutdownHook".equalsIgnoreCase(key)) {
083                        isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
084                    } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
085                        statusConfig.withVerbosity(value);
086                    } else if ("packages".equalsIgnoreCase(key)) {
087                        pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
088                    } else if ("name".equalsIgnoreCase(key)) {
089                        setName(value);
090                    } else if ("monitorInterval".equalsIgnoreCase(key)) {
091                        final int intervalSeconds = Integer.parseInt(value);
092                        if (intervalSeconds > 0 && configFile != null) {
093                            monitor = new FileConfigurationMonitor(this, configFile, listeners, intervalSeconds);
094                        }
095                    } else if ("advertiser".equalsIgnoreCase(key)) {
096                        createAdvertiser(value, configSource, buffer, "application/json");
097                    }
098                }
099                statusConfig.initialize();
100                if (getName() == null) {
101                    setName(configSource.getLocation());
102                }
103            } catch (final Exception ex) {
104                LOGGER.error("Error parsing {}", configSource.getLocation(), ex);
105            }
106        }
107    
108        protected ObjectMapper getObjectMapper() {
109            return new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true);
110        }
111    
112        @Override
113        public void setup() {
114            final Iterator<Map.Entry<String, JsonNode>> iter = root.fields();
115            final List<Node> children = rootNode.getChildren();
116            while (iter.hasNext()) {
117                final Map.Entry<String, JsonNode> entry = iter.next();
118                final JsonNode n = entry.getValue();
119                if (n.isObject()) {
120                    LOGGER.debug("Processing node for object {}", entry.getKey());
121                    children.add(constructNode(entry.getKey(), rootNode, n));
122                } else if (n.isArray()) {
123                    LOGGER.error("Arrays are not supported at the root configuration.");
124                }
125            }
126            LOGGER.debug("Completed parsing configuration");
127            if (status.size() > 0) {
128                for (final Status s : status) {
129                    LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
130                }
131            }
132        }
133    
134        @Override
135        public Configuration reconfigure() {
136            try {
137                final ConfigurationSource source = getConfigurationSource().resetInputStream();
138                if (source == null) {
139                    return null;
140                }
141                return new JsonConfiguration(source);
142            } catch (final IOException ex) {
143                LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
144            }
145            return null;
146        }
147    
148        private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
149            final PluginType<?> type = pluginManager.getPluginType(name);
150            final Node node = new Node(parent, name, type);
151            processAttributes(node, jsonNode);
152            final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields();
153            final List<Node> children = node.getChildren();
154            while (iter.hasNext()) {
155                final Map.Entry<String, JsonNode> entry = iter.next();
156                final JsonNode n = entry.getValue();
157                if (n.isArray() || n.isObject()) {
158                    if (type == null) {
159                        status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND));
160                    }
161                    if (n.isArray()) {
162                        LOGGER.debug("Processing node for array {}", entry.getKey());
163                        for (int i = 0; i < n.size(); ++i) {
164                            final String pluginType = getType(n.get(i), entry.getKey());
165                            final PluginType<?> entryType = pluginManager.getPluginType(pluginType);
166                            final Node item = new Node(node, entry.getKey(), entryType);
167                            processAttributes(item, n.get(i));
168                            if (pluginType.equals(entry.getKey())) {
169                                LOGGER.debug("Processing {}[{}]", entry.getKey(), i);
170                            } else {
171                                LOGGER.debug("Processing {} {}[{}]", pluginType, entry.getKey(), i);
172                            }
173                            final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).fields();
174                            final List<Node> itemChildren = item.getChildren();
175                            while (itemIter.hasNext()) {
176                                final Map.Entry<String, JsonNode> itemEntry = itemIter.next();
177                                if (itemEntry.getValue().isObject()) {
178                                    LOGGER.debug("Processing node for object {}", itemEntry.getKey());
179                                    itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
180                                } else if (itemEntry.getValue().isArray()) {
181                                    final JsonNode array = itemEntry.getValue();
182                                    final String entryName = itemEntry.getKey();
183                                    LOGGER.debug("Processing array for object {}", entryName);
184                                    for (int j = 0; j < array.size(); ++j) {
185                                        itemChildren.add(constructNode(entryName, item, array.get(j)));
186                                    }
187                                }
188    
189                            }
190                            children.add(item);
191                        }
192                    } else {
193                        LOGGER.debug("Processing node for object {}", entry.getKey());
194                        children.add(constructNode(entry.getKey(), node, n));
195                    }
196                } else {
197                    LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType());
198                }
199            }
200    
201            String t;
202            if (type == null) {
203                t = "null";
204            } else {
205                t = type.getElementName() + ':' + type.getPluginClass();
206            }
207    
208            final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ? "root" : node
209                    .getParent().getName();
210            LOGGER.debug("Returning {} with parent {} of type {}", node.getName(), p, t);
211            return node;
212        }
213    
214        private String getType(final JsonNode node, final String name) {
215            final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
216            while (iter.hasNext()) {
217                final Map.Entry<String, JsonNode> entry = iter.next();
218                if (entry.getKey().equalsIgnoreCase("type")) {
219                    final JsonNode n = entry.getValue();
220                    if (n.isValueNode()) {
221                        return n.asText();
222                    }
223                }
224            }
225            return name;
226        }
227    
228        private void processAttributes(final Node parent, final JsonNode node) {
229            final Map<String, String> attrs = parent.getAttributes();
230            final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
231            while (iter.hasNext()) {
232                final Map.Entry<String, JsonNode> entry = iter.next();
233                if (!entry.getKey().equalsIgnoreCase("type")) {
234                    final JsonNode n = entry.getValue();
235                    if (n.isValueNode()) {
236                        attrs.put(entry.getKey(), n.asText());
237                    }
238                }
239            }
240        }
241    
242        @Override
243        public String toString() {
244            return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]";
245        }
246    
247        /**
248         * The error that occurred.
249         */
250        private enum ErrorType {
251            CLASS_NOT_FOUND
252        }
253    
254        /**
255         * Status for recording errors.
256         */
257        private static class Status {
258            private final JsonNode node;
259            private final String name;
260            private final ErrorType errorType;
261    
262            public Status(final String name, final JsonNode node, final ErrorType errorType) {
263                this.name = name;
264                this.node = node;
265                this.errorType = errorType;
266            }
267        }
268    }