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 java.io.ByteArrayInputStream;
020    import java.io.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.util.ArrayList;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.apache.logging.log4j.core.config.AbstractConfiguration;
029    import org.apache.logging.log4j.core.config.Configuration;
030    import org.apache.logging.log4j.core.config.ConfigurationSource;
031    import org.apache.logging.log4j.core.config.FileConfigurationMonitor;
032    import org.apache.logging.log4j.core.config.Node;
033    import org.apache.logging.log4j.core.config.Reconfigurable;
034    import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
035    import org.apache.logging.log4j.core.config.plugins.util.PluginType;
036    import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
037    import org.apache.logging.log4j.core.config.status.StatusConfiguration;
038    import org.apache.logging.log4j.core.util.Patterns;
039    
040    import com.fasterxml.jackson.core.JsonParser;
041    import com.fasterxml.jackson.databind.JsonNode;
042    import com.fasterxml.jackson.databind.ObjectMapper;
043    
044    /**
045     * Creates a Node hierarchy from a JSON file.
046     */
047    public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable {
048    
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                    if ("status".equalsIgnoreCase(key)) {
078                        statusConfig.withStatus(value);
079                    } else if ("dest".equalsIgnoreCase(key)) {
080                        statusConfig.withDestination(value);
081                    } else if ("shutdownHook".equalsIgnoreCase(key)) {
082                        isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
083                    } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
084                        statusConfig.withVerbosity(value);
085                    } else if ("packages".equalsIgnoreCase(key)) {
086                        final String[] packages = value.split(Patterns.COMMA_SEPARATOR);
087                        for (final String p : packages) {
088                            PluginManager.addPackage(p);
089                        }
090                    } else if ("name".equalsIgnoreCase(key)) {
091                        setName(value);
092                    } else if ("monitorInterval".equalsIgnoreCase(key)) {
093                        final int interval = Integer.parseInt(value);
094                        if (interval > 0 && configFile != null) {
095                            monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
096                        }
097                    } else if ("advertiser".equalsIgnoreCase(key)) {
098                        createAdvertiser(value, configSource, buffer, "application/json");
099                    }
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" : node.getParent().getName() == null ? "root" : node
211                    .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         * The error that occurred.
251         */
252        private enum ErrorType {
253            CLASS_NOT_FOUND
254        }
255    
256        /**
257         * Status for recording errors.
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    }