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.composite;
018
019import java.io.File;
020import java.lang.reflect.InvocationTargetException;
021import java.net.URI;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.logging.log4j.Level;
028import org.apache.logging.log4j.core.config.AbstractConfiguration;
029import org.apache.logging.log4j.core.config.Configuration;
030import org.apache.logging.log4j.core.config.ConfigurationFactory;
031import org.apache.logging.log4j.core.config.ConfigurationSource;
032import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher;
033import org.apache.logging.log4j.core.config.Node;
034import org.apache.logging.log4j.core.config.Reconfigurable;
035import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
036import org.apache.logging.log4j.core.config.status.StatusConfiguration;
037import org.apache.logging.log4j.core.util.FileWatcher;
038import org.apache.logging.log4j.core.util.Patterns;
039import org.apache.logging.log4j.core.util.WatchManager;
040import org.apache.logging.log4j.util.LoaderUtil;
041import org.apache.logging.log4j.util.PropertiesUtil;
042
043/**
044 * A Composite Configuration.
045 */
046public class CompositeConfiguration extends AbstractConfiguration implements Reconfigurable {
047
048    /**
049     * Allow the ConfigurationFactory class to be specified as a system property.
050     */
051    public static final String MERGE_STRATEGY_PROPERTY = "log4j.mergeStrategy";
052
053    private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()};
054
055    private final List<? extends AbstractConfiguration> configurations;
056
057    private MergeStrategy mergeStrategy;
058
059    /**
060     * Construct the ComponsiteConfiguration.
061     *
062     * @param configurations The List of Configurations to merge.
063     */
064    public CompositeConfiguration(final List<? extends AbstractConfiguration> configurations) {
065        super(configurations.get(0).getLoggerContext(), ConfigurationSource.NULL_SOURCE);
066        rootNode = configurations.get(0).getRootNode();
067        this.configurations = configurations;
068        final String mergeStrategyClassName = PropertiesUtil.getProperties().getStringProperty(MERGE_STRATEGY_PROPERTY,
069                DefaultMergeStrategy.class.getName());
070        try {
071            mergeStrategy = LoaderUtil.newInstanceOf(mergeStrategyClassName);
072        } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException |
073                InstantiationException ex) {
074            mergeStrategy = new DefaultMergeStrategy();
075        }
076        for (final AbstractConfiguration config : configurations) {
077            mergeStrategy.mergeRootProperties(rootNode, config);
078        }
079        final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
080                .withStatus(getDefaultStatus());
081        for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
082            final String key = entry.getKey();
083            final String value = getStrSubstitutor().replace(entry.getValue());
084            if ("status".equalsIgnoreCase(key)) {
085                statusConfig.withStatus(value.toUpperCase());
086            } else if ("dest".equalsIgnoreCase(key)) {
087                statusConfig.withDestination(value);
088            } else if ("shutdownHook".equalsIgnoreCase(key)) {
089                isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
090            } else if ("shutdownTimeout".equalsIgnoreCase(key)) {
091                shutdownTimeoutMillis = Long.parseLong(value);
092            } else if ("verbose".equalsIgnoreCase(key)) {
093                statusConfig.withVerbosity(value);
094            } else if ("packages".equalsIgnoreCase(key)) {
095                pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
096            } else if ("name".equalsIgnoreCase(key)) {
097                setName(value);
098            }
099        }
100        statusConfig.initialize();
101    }
102
103    @Override
104    public void setup() {
105        final AbstractConfiguration targetConfiguration = configurations.get(0);
106        staffChildConfiguration(targetConfiguration);
107        final WatchManager watchManager = getWatchManager();
108        final WatchManager targetWatchManager = targetConfiguration.getWatchManager();
109        final FileWatcher fileWatcher = new ConfiguratonFileWatcher(this, listeners);
110        if (targetWatchManager.getIntervalSeconds() > 0) {
111            watchManager.setIntervalSeconds(targetWatchManager.getIntervalSeconds());
112            final Map<File, FileWatcher> watchers = targetWatchManager.getWatchers();
113            for (final Map.Entry<File, FileWatcher> entry : watchers.entrySet()) {
114                if (entry.getValue() instanceof ConfiguratonFileWatcher) {
115                    watchManager.watchFile(entry.getKey(), fileWatcher);
116                }
117            }
118        }
119        for (final AbstractConfiguration sourceConfiguration : configurations.subList(1, configurations.size())) {
120            staffChildConfiguration(sourceConfiguration);
121            final Node sourceRoot = sourceConfiguration.getRootNode();
122            mergeStrategy.mergConfigurations(rootNode, sourceRoot, getPluginManager());
123            if (LOGGER.isEnabled(Level.ALL)) {
124                final StringBuilder sb = new StringBuilder();
125                printNodes("", rootNode, sb);
126                System.out.println(sb.toString());
127            }
128            final int monitorInterval = sourceConfiguration.getWatchManager().getIntervalSeconds();
129            if (monitorInterval > 0) {
130                final int currentInterval = watchManager.getIntervalSeconds();
131                if (currentInterval <= 0 || monitorInterval < currentInterval) {
132                    watchManager.setIntervalSeconds(monitorInterval);
133                }
134                final WatchManager sourceWatchManager = sourceConfiguration.getWatchManager();
135                final Map<File, FileWatcher> watchers = sourceWatchManager.getWatchers();
136                for (final Map.Entry<File, FileWatcher> entry : watchers.entrySet()) {
137                    if (entry.getValue() instanceof ConfiguratonFileWatcher) {
138                        watchManager.watchFile(entry.getKey(), fileWatcher);
139                    }
140                }
141            }
142        }
143    }
144
145    @Override
146    public Configuration reconfigure() {
147        LOGGER.debug("Reconfiguring composite configuration");
148        final List<AbstractConfiguration> configs = new ArrayList<>();
149        final ConfigurationFactory factory = ConfigurationFactory.getInstance();
150        for (final AbstractConfiguration config : configurations) {
151            final ConfigurationSource source = config.getConfigurationSource();
152            final URI sourceURI = source.getURI();
153            Configuration currentConfig = config;
154            if (sourceURI == null) {
155                LOGGER.warn("Unable to determine URI for configuration {}, changes to it will be ignored",
156                        config.getName());
157            } else {
158                currentConfig = factory.getConfiguration(getLoggerContext(), config.getName(), sourceURI);
159                if (currentConfig == null) {
160                    LOGGER.warn("Unable to reload configuration {}, changes to it will be ignored", config.getName());
161                }
162            }
163            configs.add((AbstractConfiguration) currentConfig);
164
165        }
166
167        return new CompositeConfiguration(configs);
168    }
169
170    private void staffChildConfiguration(final AbstractConfiguration childConfiguration) {
171        childConfiguration.setPluginManager(pluginManager);
172        childConfiguration.setScriptManager(scriptManager);
173        childConfiguration.setup();
174    }
175
176    private void printNodes(final String indent, final Node node, final StringBuilder sb) {
177        sb.append(indent).append(node.getName()).append(" type: ").append(node.getType()).append("\n");
178        sb.append(indent).append(node.getAttributes().toString()).append("\n");
179        for (final Node child : node.getChildren()) {
180            printNodes(indent + "  ", child, sb);
181        }
182    }
183
184    @Override
185    public String toString() {
186        return getClass().getName() + "@" + Integer.toHexString(hashCode()) + " [configurations=" + configurations
187                + ", mergeStrategy=" + mergeStrategy + ", rootNode=" + rootNode + ", listeners=" + listeners
188                + ", pluginPackages=" + pluginPackages + ", pluginManager=" + pluginManager + ", isShutdownHookEnabled="
189                + isShutdownHookEnabled + ", shutdownTimeoutMillis=" + shutdownTimeoutMillis + ", scriptManager="
190                + scriptManager + "]";
191    }
192}