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.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023
024import org.apache.logging.log4j.Level;
025import org.apache.logging.log4j.core.Filter;
026import org.apache.logging.log4j.core.config.AbstractConfiguration;
027import org.apache.logging.log4j.core.config.Node;
028import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
029import org.apache.logging.log4j.core.config.plugins.util.PluginType;
030import org.apache.logging.log4j.core.filter.CompositeFilter;
031
032/**
033 * The default merge strategy for composite configurations.
034 * <p>
035 * The default merge strategy performs the merge according to the following rules:
036 * <ol>
037 * <li>Aggregates the global configuration attributes with those in later configurations replacing those in previous
038 * configurations with the exception that the highest status level and the lowest monitorInterval greater than 0 will
039 * be used.</li>
040 * <li>Properties from all configurations are aggregated. Duplicate properties replace those in previous
041 * configurations.</li>
042 * <li>Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named
043 * duplicates may be present.</li>
044 * <li>Scripts and ScriptFile references are aggregated. Duplicate definitions replace those in previous
045 * configurations.</li>
046 * <li>Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including
047 * all of the Appender's subcomponents.</li>
048 * <li>Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those
049 * in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in
050 * later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined.
051 * Since Filters are not named duplicates may be present. Filters under Appender references included or discarded
052 * depending on whether their parent Appender reference is kept or discarded.</li>
053 * </ol>
054 */
055public class DefaultMergeStrategy implements MergeStrategy {
056
057    private static final String APPENDERS = "appenders";
058    private static final String PROPERTIES = "properties";
059    private static final String LOGGERS = "loggers";
060    private static final String SCRIPTS = "scripts";
061    private static final String FILTERS = "filters";
062    private static final String STATUS = "status";
063    private static final String NAME = "name";
064    private static final String REF = "ref";
065
066    /**
067     * Merge the root properties.
068     * @param rootNode The composite root node.
069     * @param configuration The configuration to merge.
070     */
071    @Override
072    public void mergeRootProperties(final Node rootNode, final AbstractConfiguration configuration) {
073        for (final Map.Entry<String, String> attribute : configuration.getRootNode().getAttributes().entrySet()) {
074            boolean isFound = false;
075            for (final Map.Entry<String, String> targetAttribute : rootNode.getAttributes().entrySet()) {
076                if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) {
077                    if (attribute.getKey().equalsIgnoreCase(STATUS)) {
078                        final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase());
079                        final Level sourceLevel = Level.getLevel(attribute.getValue().toUpperCase());
080                        if (targetLevel != null && sourceLevel != null) {
081                            if (sourceLevel.isLessSpecificThan(targetLevel)) {
082                                targetAttribute.setValue(attribute.getValue());
083                            }
084                        } else
085                            if (sourceLevel != null) {
086                                targetAttribute.setValue(attribute.getValue());
087                            }
088                    } else if (attribute.getKey().equalsIgnoreCase("monitorInterval")) {
089                        final int sourceInterval = Integer.parseInt(attribute.getValue());
090                        final int targetInterval = Integer.parseInt(targetAttribute.getValue());
091                        if (targetInterval == 0 || sourceInterval < targetInterval) {
092                            targetAttribute.setValue(attribute.getValue());
093                        }
094                    } else if (attribute.getKey().equalsIgnoreCase("packages")) {
095                        String sourcePackages = attribute.getValue();
096                        String targetPackages = targetAttribute.getValue();
097                        if (sourcePackages != null) {
098                            if (targetPackages != null) {
099                                targetAttribute.setValue(targetPackages + "," + sourcePackages);
100                            }
101                            else {
102                                targetAttribute.setValue(sourcePackages);
103                            }
104                        }
105                    }
106                    else {
107                        targetAttribute.setValue(attribute.getValue());
108                    }
109                    isFound = true;
110                }
111            }
112            if (!isFound) {
113                rootNode.getAttributes().put(attribute.getKey(), attribute.getValue());
114            }
115        }
116    }
117
118    /**
119     * Merge the source Configuration into the target Configuration.
120     *
121     * @param target        The target node to merge into.
122     * @param source        The source node.
123     * @param pluginManager The PluginManager.
124     */
125    @Override
126    public void mergConfigurations(final Node target, final Node source, final PluginManager pluginManager) {
127        for (final Node sourceChildNode : source.getChildren()) {
128            final boolean isFilter = isFilterNode(sourceChildNode);
129            boolean isMerged = false;
130            for (final Node targetChildNode : target.getChildren()) {
131                if (isFilter) {
132                    if (isFilterNode(targetChildNode)) {
133                        updateFilterNode(target, targetChildNode, sourceChildNode, pluginManager);
134                        isMerged = true;
135                        break;
136                    }
137                    continue;
138                }
139
140                if (!targetChildNode.getName().equalsIgnoreCase(sourceChildNode.getName())) {
141                    continue;
142                }
143
144                switch (targetChildNode.getName().toLowerCase()) {
145                    case PROPERTIES:
146                    case SCRIPTS:
147                    case APPENDERS: {
148                        for (final Node node : sourceChildNode.getChildren()) {
149                            for (final Node targetNode : targetChildNode.getChildren()) {
150                                if (Objects.equals(targetNode.getAttributes().get(NAME), node.getAttributes().get(NAME))) {
151                                    targetChildNode.getChildren().remove(targetNode);
152                                    break;
153                                }
154                            }
155                            targetChildNode.getChildren().add(node);
156                        }
157                        isMerged = true;
158                        break;
159                    }
160                    case LOGGERS: {
161                        final Map<String, Node> targetLoggers = new HashMap<>();
162                        for (final Node node : targetChildNode.getChildren()) {
163                            targetLoggers.put(node.getName(), node);
164                        }
165                        for (final Node node : sourceChildNode.getChildren()) {
166                            final Node targetNode = getLoggerNode(targetChildNode, node.getAttributes().get(NAME));
167                            final Node loggerNode = new Node(targetChildNode, node.getName(), node.getType());
168                            if (targetNode != null) {
169                                targetNode.getAttributes().putAll(node.getAttributes());
170                                for (final Node sourceLoggerChild : node.getChildren()) {
171                                    if (isFilterNode(sourceLoggerChild)) {
172                                        boolean foundFilter = false;
173                                        for (final Node targetChild : targetNode.getChildren()) {
174                                            if (isFilterNode(targetChild)) {
175                                                updateFilterNode(loggerNode, targetChild, sourceLoggerChild,
176                                                        pluginManager);
177                                                foundFilter = true;
178                                                break;
179                                            }
180                                        }
181                                        if (!foundFilter) {
182                                            final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(),
183                                                    sourceLoggerChild.getType());
184                                            childNode.getAttributes().putAll(sourceLoggerChild.getAttributes());
185                                            childNode.getChildren().addAll(sourceLoggerChild.getChildren());
186                                            targetNode.getChildren().add(childNode);
187                                        }
188                                    } else {
189                                        final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(),
190                                                sourceLoggerChild.getType());
191                                        childNode.getAttributes().putAll(sourceLoggerChild.getAttributes());
192                                        childNode.getChildren().addAll(sourceLoggerChild.getChildren());
193                                        if (childNode.getName().equalsIgnoreCase("AppenderRef")) {
194                                            for (final Node targetChild : targetNode.getChildren()) {
195                                                if (isSameReference(targetChild, childNode)) {
196                                                    targetNode.getChildren().remove(targetChild);
197                                                    break;
198                                                }
199                                            }
200                                        } else {
201                                            for (final Node targetChild : targetNode.getChildren()) {
202                                                if (isSameName(targetChild, childNode)) {
203                                                    targetNode.getChildren().remove(targetChild);
204                                                    break;
205                                                }
206                                            }
207                                        }
208
209                                        targetNode.getChildren().add(childNode);
210                                    }
211                                }
212                            } else {
213                                loggerNode.getAttributes().putAll(node.getAttributes());
214                                loggerNode.getChildren().addAll(node.getChildren());
215                                targetChildNode.getChildren().add(loggerNode);
216                            }
217                        }
218                        isMerged = true;
219                        break;
220                    }
221                    default: {
222                        targetChildNode.getChildren().addAll(sourceChildNode.getChildren());
223                        isMerged = true;
224                        break;
225                    }
226
227                }
228            }
229            if (!isMerged) {
230                if (sourceChildNode.getName().equalsIgnoreCase("Properties")) {
231                    target.getChildren().add(0, sourceChildNode);
232                } else {
233                    target.getChildren().add(sourceChildNode);
234                }
235            }
236        }
237    }
238
239    private Node getLoggerNode(final Node parentNode, final String name) {
240        for (final Node node : parentNode.getChildren()) {
241            final String nodeName = node.getAttributes().get(NAME);
242            if (name == null && nodeName == null) {
243                return node;
244            }
245            if (nodeName != null && nodeName.equals(name)) {
246                return node;
247            }
248        }
249        return null;
250    }
251
252    private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode,
253            final PluginManager pluginManager) {
254        if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) {
255            final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType());
256            node.getChildren().addAll(sourceChildNode.getChildren());
257            node.getAttributes().putAll(sourceChildNode.getAttributes());
258            targetChildNode.getChildren().add(node);
259        } else {
260            final PluginType pluginType = pluginManager.getPluginType(FILTERS);
261            final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType);
262            final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType());
263            node.getAttributes().putAll(sourceChildNode.getAttributes());
264            final List<Node> children = filtersNode.getChildren();
265            children.add(targetChildNode);
266            children.add(node);
267            final List<Node> nodes = target.getChildren();
268            nodes.remove(targetChildNode);
269            nodes.add(filtersNode);
270        }
271    }
272
273    private boolean isFilterNode(final Node node) {
274        return Filter.class.isAssignableFrom(node.getType().getPluginClass());
275    }
276
277    private boolean isSameName(final Node node1, final Node node2) {
278        final String value = node1.getAttributes().get(NAME);
279        return value != null && value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase());
280    }
281
282    private boolean isSameReference(final Node node1, final Node node2) {
283        final String value = node1.getAttributes().get(REF);
284        return value != null && value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase());
285    }
286}