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