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