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 */
017package org.apache.logging.log4j.core.config;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileNotFoundException;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.PrintStream;
028import java.net.URISyntaxException;
029import java.nio.charset.Charset;
030import java.util.ArrayList;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034
035import org.apache.logging.log4j.Level;
036import org.apache.logging.log4j.core.config.plugins.PluginManager;
037import org.apache.logging.log4j.core.config.plugins.PluginType;
038import org.apache.logging.log4j.core.config.plugins.ResolverUtil;
039import org.apache.logging.log4j.core.helpers.FileUtils;
040import org.apache.logging.log4j.status.StatusConsoleListener;
041import org.apache.logging.log4j.status.StatusListener;
042import org.apache.logging.log4j.status.StatusLogger;
043
044import com.fasterxml.jackson.core.JsonParser;
045import com.fasterxml.jackson.databind.JsonNode;
046import com.fasterxml.jackson.databind.ObjectMapper;
047
048/**
049 * Creates a Node hierarchy from a JSON file.
050 */
051public 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 File configFile;
062
063    public JSONConfiguration(final ConfigurationFactory.ConfigurationSource configSource) {
064        final List<String> messages = new ArrayList<String>();
065
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.elements();
078                root = i.next();
079            }
080            processAttributes(rootNode, root);
081            Level status = getDefaultStatus();
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(getStrSubstitutor().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(FileUtils.getCorrectedFilePathUri(dest));
099                                final String enc = Charset.defaultCharset().name();
100                                stream = new PrintStream(new FileOutputStream(destFile), true, enc);
101                            } catch (final URISyntaxException use) {
102                                System.err.println("Unable to write to " + dest + ". Writing to stdout");
103                            }
104                        }
105                    }
106                } else if ("shutdownHook".equalsIgnoreCase(entry.getKey())) {
107                    String hook = getStrSubstitutor().replace(entry.getValue());
108                    isShutdownHookEnabled = !hook.equalsIgnoreCase("disable");
109                } else if ("verbose".equalsIgnoreCase(entry.getKey())) {
110                    verbose = Boolean.parseBoolean(getStrSubstitutor().replace(entry.getValue()));
111                } else if ("packages".equalsIgnoreCase(entry.getKey())) {
112                    final String[] packages = getStrSubstitutor().replace(entry.getValue()).split(",");
113                    for (final String p : packages) {
114                        PluginManager.addPackage(p);
115                    }
116                } else if ("name".equalsIgnoreCase(entry.getKey())) {
117                    setName(getStrSubstitutor().replace(entry.getValue()));
118                } else if ("monitorInterval".equalsIgnoreCase(entry.getKey())) {
119                    final int interval = Integer.parseInt(getStrSubstitutor().replace(entry.getValue()));
120                    if (interval > 0 && configFile != null) {
121                        monitor = new FileConfigurationMonitor(this, configFile, listeners, interval);
122                    }
123                } else if ("advertiser".equalsIgnoreCase(entry.getKey())) {
124                    createAdvertiser(getStrSubstitutor().replace(entry.getValue()), configSource, buffer,
125                        "application/json");
126                }
127            }
128
129            final Iterator<StatusListener> statusIter = ((StatusLogger) LOGGER).getListeners();
130            boolean found = false;
131            while (statusIter.hasNext()) {
132                final StatusListener listener = statusIter.next();
133                if (listener instanceof StatusConsoleListener) {
134                    found = true;
135                    ((StatusConsoleListener) listener).setLevel(status);
136                    if (!verbose) {
137                        ((StatusConsoleListener) listener).setFilters(VERBOSE_CLASSES);
138                    }
139                }
140            }
141            if (!found && status != Level.OFF) {
142                final StatusConsoleListener listener = new StatusConsoleListener(status, stream);
143                if (!verbose) {
144                    listener.setFilters(VERBOSE_CLASSES);
145                }
146                ((StatusLogger) LOGGER).registerListener(listener);
147                for (final String msg : messages) {
148                    LOGGER.error(msg);
149                }
150            }
151            if (getName() == null) {
152                setName(configSource.getLocation());
153            }
154        } catch (final Exception ex) {
155            LOGGER.error("Error parsing " + configSource.getLocation(), ex);
156            ex.printStackTrace();
157        }
158    }
159
160    @Override
161    public void stop() {
162        super.stop();
163    }
164
165    @Override
166    public void setup() {
167        final Iterator<Map.Entry<String, JsonNode>> iter = root.fields();
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        }
185    }
186
187    @Override
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 = pluginManager.getPluginType(name);
203        final Node node = new Node(parent, name, type);
204        processAttributes(node, jsonNode);
205        final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields();
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 = pluginManager.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).fields();
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                            } else if (itemEntry.getValue().isArray()) {
234                                JsonNode array = itemEntry.getValue();
235                                String entryName = itemEntry.getKey();
236                                LOGGER.debug("Processing array for object " + entryName);
237                                for (int j = 0; j < array.size(); ++j) {
238                                    itemChildren.add(constructNode(entryName, item, array.get(j)));
239                                }
240                            }
241
242                        }
243                        children.add(item);
244                    }
245                } else {
246                    LOGGER.debug("Processing node for object " + entry.getKey());
247                    children.add(constructNode(entry.getKey(), node, n));
248                }
249            } else {
250                LOGGER.debug("Node {} is of type {}", entry.getKey(), n.getNodeType());
251            }
252        }
253
254        String t;
255        if (type == null) {
256            t = "null";
257        } else {
258            t = type.getElementName() + ":" + type.getPluginClass();
259        }
260
261        final String p = node.getParent() == null ? "null" : node.getParent().getName() == null ?
262            "root" : node.getParent().getName();
263        LOGGER.debug("Returning " + node.getName() + " with parent " + p + " of type " +  t);
264        return node;
265    }
266
267    private String getType(final JsonNode node, final String name) {
268        final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
269        while (iter.hasNext()) {
270            final Map.Entry<String, JsonNode> entry = iter.next();
271            if (entry.getKey().equalsIgnoreCase("type")) {
272                final JsonNode n = entry.getValue();
273                if (n.isValueNode()) {
274                    return n.asText();
275                }
276            }
277        }
278        return name;
279    }
280
281    private void processAttributes(final Node parent, final JsonNode node) {
282        final Map<String, String> attrs = parent.getAttributes();
283        final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
284        while (iter.hasNext()) {
285            final Map.Entry<String, JsonNode> entry = iter.next();
286            if (!entry.getKey().equalsIgnoreCase("type")) {
287                final JsonNode n = entry.getValue();
288                if (n.isValueNode()) {
289                    attrs.put(entry.getKey(), n.asText());
290                }
291            }
292        }
293    }
294
295    protected byte[] toByteArray(final InputStream is) throws IOException {
296        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
297
298        int nRead;
299        final byte[] data = new byte[BUF_SIZE];
300
301        while ((nRead = is.read(data, 0, data.length)) != -1) {
302            buffer.write(data, 0, nRead);
303        }
304
305        return buffer.toByteArray();
306    }
307
308    /**
309     * The error that occurred.
310     */
311    private enum ErrorType {
312        CLASS_NOT_FOUND
313    }
314
315    /**
316     * Status for recording errors.
317     */
318    private class Status {
319        private final JsonNode node;
320        private final String name;
321        private final ErrorType errorType;
322
323        public Status(final String name, final JsonNode node, final ErrorType errorType) {
324            this.name = name;
325            this.node = node;
326            this.errorType = errorType;
327        }
328    }
329}