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;
018    
019    import org.apache.logging.log4j.Level;
020    import org.apache.logging.log4j.core.config.plugins.PluginManager;
021    import org.apache.logging.log4j.core.config.plugins.PluginType;
022    import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
023    import org.apache.logging.log4j.core.helpers.FileUtils;
024    import org.apache.logging.log4j.core.net.Advertiser;
025    import org.apache.logging.log4j.status.StatusConsoleListener;
026    import org.apache.logging.log4j.status.StatusListener;
027    import org.apache.logging.log4j.status.StatusLogger;
028    import org.codehaus.jackson.JsonNode;
029    import org.codehaus.jackson.JsonParser;
030    import org.codehaus.jackson.map.ObjectMapper;
031    
032    import java.io.ByteArrayInputStream;
033    import java.io.ByteArrayOutputStream;
034    import java.io.File;
035    import java.io.FileInputStream;
036    import java.io.FileNotFoundException;
037    import java.io.FileOutputStream;
038    import java.io.IOException;
039    import java.io.InputStream;
040    import java.io.PrintStream;
041    import java.net.URI;
042    import java.net.URISyntaxException;
043    import java.util.ArrayList;
044    import java.util.Iterator;
045    import java.util.List;
046    import java.util.Map;
047    
048    /**
049     * Creates a Node hierarchy from a JSON file.
050     */
051    public class JSONConfiguration extends BaseConfiguration implements Reconfigurable {
052    
053        private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
054    
055        private static final int BUF_SIZE = 16384;
056    
057        private final List<Status> status = new ArrayList<Status>();
058    
059        private JsonNode root;
060    
061        private final List<String> messages = new ArrayList<String>();
062    
063        private final File configFile;
064    
065        public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
066            this.configFile = configSource.getFile();
067            byte[] buffer;
068    
069            try {
070                final InputStream configStream = configSource.getInputStream();
071                buffer = toByteArray(configStream);
072                configStream.close();
073                final InputStream is = new ByteArrayInputStream(buffer);
074                final ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true);
075                root = mapper.readTree(is);
076                if (root.size() == 1) {
077                    final Iterator<JsonNode> i = root.getElements();
078                    root = i.next();
079                }
080                processAttributes(rootNode, root);
081                Level status = Level.OFF;
082                boolean verbose = false;
083                PrintStream stream = System.out;
084                for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
085                    if ("status".equalsIgnoreCase(entry.getKey())) {
086                        status = Level.toLevel(getSubst().replace(entry.getValue()), null);
087                        if (status == null) {
088                            status = Level.ERROR;
089                            messages.add("Invalid status specified: " + entry.getValue() + ". Defaulting to ERROR");
090                        }
091                    } else if ("dest".equalsIgnoreCase(entry.getKey())) {
092                        final String dest = entry.getValue();
093                        if (dest != null) {
094                            if (dest.equalsIgnoreCase("err")) {
095                                stream = System.err;
096                            } else {
097                                try {
098                                    final File destFile = FileUtils.fileFromURI(new URI(dest));
099                                    stream = new PrintStream(new FileOutputStream(destFile));
100                                } catch (final URISyntaxException use) {
101                                    System.err.println("Unable to write to " + dest + ". Writing to stdout");
102                                }
103                            }
104                        }
105                    } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
106                        verbose = Boolean.parseBoolean(getSubst().replace(entry.getValue()));
107                    } else if ("packages".equalsIgnoreCase(entry.getKey())) {
108                        final String[] packages = getSubst().replace(entry.getValue()).split(",");
109                        for (final String p : packages) {
110                            PluginManager.addPackage(p);
111                        }
112                    } else if ("name".equalsIgnoreCase(entry.getKey())) {
113                        setName(getSubst().replace(entry.getValue()));
114                    } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
115                        final int interval = Integer.parseInt(getSubst().replace(entry.getValue()));
116                        if (interval > 0 && configFile != null) {
117                            monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
118                        }
119                    } else if ("advertiser".equalsIgnoreCase(entry.getKey())) {
120                        final String advertiserString = getSubst().replace(entry.getValue());
121                        if (advertiserString != null)
122                        {
123                            @SuppressWarnings("unchecked")
124                            final PluginType<Advertiser> type = getPluginManager().getPluginType(advertiserString);
125                            if (type != null)
126                            {
127                                final Class<Advertiser> clazz = type.getPluginClass();
128                                advertiser = clazz.newInstance();
129                            }
130                        }
131                    }
132                }
133    
134                final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners();
135                boolean found = false;
136                while (statusIter.hasNext()) {
137                    final StatusListener listener = statusIter.next();
138                    if (listener instanceof StatusConsoleListener) {
139                        found = true;
140                        ((StatusConsoleListener) listener).setLevel(status);
141                        if (!verbose) {
142                            ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
143                        }
144                    }
145                }
146                if (!found && status != Level.OFF) {
147                    final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
148                    if (!verbose) {
149                        listener.setFilters(VERBOSE_CLASSES);
150                    }
151                    ((StatusLogger) LOGGER).registerListener(listener);
152                    for (final String msg : messages) {
153                        LOGGER.error(msg);
154                    }
155                }
156                if (getName() == null) {
157                    setName(configSource.getLocation());
158                }
159            } catch (final Exception ex) {
160                LOGGER.error("Error parsing " + configSource.getLocation(), ex);
161                ex.printStackTrace();
162            }
163        }
164    
165         @Override
166        public void setup() {
167            final Iterator<Map.Entry<String, JsonNode>> iter = root.getFields();
168            final List<Node> children = rootNode.getChildren();
169            while (iter.hasNext()) {
170                final Map.Entry<String, JsonNode> entry = iter.next();
171                final JsonNode n = entry.getValue();
172                if (n.isObject()) {
173                    LOGGER.debug("Processing node for object " + entry.getKey());
174                    children.add(constructNode(entry.getKey(), rootNode, n));
175                } else if (n.isArray()) {
176                    LOGGER.error("Arrays are not supported at the root configuration.");
177                }
178            }
179            LOGGER.debug("Completed parsing configuration");
180            if (status.size() > 0) {
181                for (final Status s : status) {
182                    LOGGER.error("Error processing element " + s.name + ": " + s.errorType);
183                }
184                return;
185            }
186        }
187    
188        public Configuration reconfigure() {
189            if (configFile != null) {
190                try {
191                    final ConfigurationFactory.ConfigurationSource source =
192                        new ConfigurationFactory.ConfigurationSource(new FileInputStream(configFile), configFile);
193                    return new JSONConfiguration(source);
194                } catch (final FileNotFoundException ex) {
195                    LOGGER.error("Cannot locate file " + configFile, ex);
196                }
197            }
198            return null;
199        }
200    
201        private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) {
202            final PluginType type = getPluginManager().getPluginType(name);
203            final Node node = new Node(parent, name, type);
204            processAttributes(node, jsonNode);
205            final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.getFields();
206            final List<Node> children = node.getChildren();
207            while (iter.hasNext()) {
208                final Map.Entry<String, JsonNode> entry = iter.next();
209                final JsonNode n = entry.getValue();
210                if (n.isArray() || n.isObject()) {
211                    if (type == null) {
212                        status.add(new Status(name, n, ErrorType.CLASS_NOT_FOUND));
213                    }
214                    if (n.isArray()) {
215                        LOGGER.debug("Processing node for array " + entry.getKey());
216                        for (int i = 0; i < n.size(); ++i) {
217                            final String pluginType = getType(n.get(i), entry.getKey());
218                            final PluginType entryType = getPluginManager().getPluginType(pluginType);
219                            final Node item = new Node(node, entry.getKey(), entryType);
220                            processAttributes(item, n.get(i));
221                            if (pluginType.equals(entry.getKey())) {
222                                LOGGER.debug("Processing " + entry.getKey() + "[" + i + "]");
223                            } else {
224                                LOGGER.debug("Processing " + pluginType + " " + entry.getKey() + "[" + i + "]");
225                            }
226                            final Iterator<Map.Entry<String, JsonNode>> itemIter = n.get(i).getFields();
227                            final List<Node> itemChildren = item.getChildren();
228                            while (itemIter.hasNext()) {
229                                final Map.Entry<String, JsonNode> itemEntry = itemIter.next();
230                                if (itemEntry.getValue().isObject()) {
231                                    LOGGER.debug("Processing node for object " + itemEntry.getKey());
232                                    itemChildren.add(constructNode(itemEntry.getKey(), item, itemEntry.getValue()));
233                                }
234                            }
235                            children.add(item);
236                        }
237                    } else {
238                        LOGGER.debug("Processing node for object " + entry.getKey());
239                        children.add(constructNode(entry.getKey(), node, n));
240                    }
241                }
242            }
243    
244            String t;
245            if (type == null) {
246                t = "null";
247            } else {
248                t = type.getElementName() + ":" + type.getPluginClass();
249            }
250    
251            final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ?
252                "root" : node.getParent().getName();
253            LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " +  t);
254            return node;
255        }
256    
257        private String getType(final JsonNode node, final String name) {
258            final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
259            while (iter.hasNext()) {
260                final Map.Entry<String, JsonNode> entry = iter.next();
261                if (entry.getKey().equalsIgnoreCase("type")) {
262                    final JsonNode n = entry.getValue();
263                    if (n.isValueNode()) {
264                        return n.asText();
265                    }
266                }
267            }
268            return name;
269        }
270    
271        private void processAttributes(final Node parent, final JsonNode node) {
272            final Map<String, String> attrs = parent.getAttributes();
273            final Iterator<Map.Entry<String, JsonNode>> iter = node.getFields();
274            while (iter.hasNext()) {
275                final Map.Entry<String, JsonNode> entry = iter.next();
276                if (!entry.getKey().equalsIgnoreCase("type")) {
277                    final JsonNode n = entry.getValue();
278                    if (n.isValueNode()) {
279                        attrs.put(entry.getKey(), n.asText());
280                    }
281                }
282            }
283        }
284    
285        protected byte[] toByteArray(final InputStream is) throws IOException {
286            final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
287    
288            int nRead;
289            final byte[] data = new byte[BUF_SIZE];
290    
291            while ((nRead = is.read(data, 0, data.length)) != -1) {
292                buffer.write(data, 0, nRead);
293            }
294    
295            return buffer.toByteArray();
296        }
297    
298        /**
299         * The error that occurred.
300         */
301        private enum ErrorType {
302            CLASS_NOT_FOUND
303        }
304    
305        /**
306         * Status for recording errors.
307         */
308        private class Status {
309            private final JsonNode node;
310            private final String name;
311            private final ErrorType errorType;
312    
313            public Status(final String name, final JsonNode node, final ErrorType errorType) {
314                this.name = name;
315                this.node = node;
316                this.errorType = errorType;
317            }
318        }
319    }