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