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