| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| package org.apache.logging.log4j.core.config.composite; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.apache.logging.log4j.Level; |
| import org.apache.logging.log4j.core.Filter; |
| import org.apache.logging.log4j.core.config.AbstractConfiguration; |
| import org.apache.logging.log4j.core.filter.CompositeFilter; |
| import org.apache.logging.log4j.plugins.Node; |
| import org.apache.logging.log4j.plugins.util.PluginManager; |
| import org.apache.logging.log4j.plugins.util.PluginType; |
| |
| /** |
| * The default merge strategy for composite configurations. |
| * <p> |
| * The default merge strategy performs the merge according to the following rules: |
| * <ol> |
| * <li>Aggregates the global configuration attributes with those in later configurations replacing those in previous |
| * configurations with the exception that the highest status level and the lowest monitorInterval greater than 0 will |
| * be used.</li> |
| * <li>Properties from all configurations are aggregated. Duplicate properties replace those in previous |
| * configurations.</li> |
| * <li>Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named |
| * duplicates may be present.</li> |
| * <li>Scripts and ScriptFile references are aggregated. Duplicate definitions replace those in previous |
| * configurations.</li> |
| * <li>Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including |
| * all of the Appender's subcomponents.</li> |
| * <li>Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those |
| * in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in |
| * later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined. |
| * Since Filters are not named duplicates may be present. Filters under Appender references included or discarded |
| * depending on whether their parent Appender reference is kept or discarded.</li> |
| * </ol> |
| */ |
| public class DefaultMergeStrategy implements MergeStrategy { |
| |
| private static final String APPENDERS = "appenders"; |
| private static final String PROPERTIES = "properties"; |
| private static final String LOGGERS = "loggers"; |
| private static final String SCRIPTS = "scripts"; |
| private static final String FILTERS = "filters"; |
| private static final String STATUS = "status"; |
| private static final String NAME = "name"; |
| private static final String REF = "ref"; |
| |
| /** |
| * Merge the root properties. |
| * @param rootNode The composite root node. |
| * @param configuration The configuration to merge. |
| */ |
| @Override |
| public void mergeRootProperties(final Node rootNode, final AbstractConfiguration configuration) { |
| for (final Map.Entry<String, String> attribute : configuration.getRootNode().getAttributes().entrySet()) { |
| boolean isFound = false; |
| for (final Map.Entry<String, String> targetAttribute : rootNode.getAttributes().entrySet()) { |
| if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) { |
| if (attribute.getKey().equalsIgnoreCase(STATUS)) { |
| final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase()); |
| final Level sourceLevel = Level.getLevel(attribute.getValue().toUpperCase()); |
| if (targetLevel != null && sourceLevel != null) { |
| if (sourceLevel.isLessSpecificThan(targetLevel)) { |
| targetAttribute.setValue(attribute.getValue()); |
| } |
| } else |
| if (sourceLevel != null) { |
| targetAttribute.setValue(attribute.getValue()); |
| } |
| } else { |
| if (attribute.getKey().equalsIgnoreCase("monitorInterval")) { |
| final int sourceInterval = Integer.parseInt(attribute.getValue()); |
| final int targetInterval = Integer.parseInt(targetAttribute.getValue()); |
| if (targetInterval == 0 || sourceInterval < targetInterval) { |
| targetAttribute.setValue(attribute.getValue()); |
| } |
| } else { |
| targetAttribute.setValue(attribute.getValue()); |
| } |
| } |
| isFound = true; |
| } |
| } |
| if (!isFound) { |
| rootNode.getAttributes().put(attribute.getKey(), attribute.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * Merge the source Configuration into the target Configuration. |
| * |
| * @param target The target node to merge into. |
| * @param source The source node. |
| * @param pluginManager The PluginManager. |
| */ |
| @Override |
| public void mergeConfigurations(final Node target, final Node source, final PluginManager pluginManager) { |
| for (final Node sourceChildNode : source.getChildren()) { |
| final boolean isFilter = isFilterNode(sourceChildNode); |
| boolean isMerged = false; |
| for (final Node targetChildNode : target.getChildren()) { |
| if (isFilter) { |
| if (isFilterNode(targetChildNode)) { |
| updateFilterNode(target, targetChildNode, sourceChildNode, pluginManager); |
| isMerged = true; |
| break; |
| } |
| continue; |
| } |
| |
| if (!targetChildNode.getName().equalsIgnoreCase(sourceChildNode.getName())) { |
| continue; |
| } |
| |
| switch (targetChildNode.getName().toLowerCase()) { |
| case PROPERTIES: |
| case SCRIPTS: |
| case APPENDERS: { |
| for (final Node node : sourceChildNode.getChildren()) { |
| for (final Node targetNode : targetChildNode.getChildren()) { |
| if (Objects.equals(targetNode.getAttributes().get(NAME), node.getAttributes().get(NAME))) { |
| targetChildNode.getChildren().remove(targetNode); |
| break; |
| } |
| } |
| targetChildNode.getChildren().add(node); |
| } |
| isMerged = true; |
| break; |
| } |
| case LOGGERS: { |
| final Map<String, Node> targetLoggers = new HashMap<>(); |
| for (final Node node : targetChildNode.getChildren()) { |
| targetLoggers.put(node.getName(), node); |
| } |
| for (final Node node : sourceChildNode.getChildren()) { |
| final Node targetNode = getLoggerNode(targetChildNode, node.getAttributes().get(NAME)); |
| final Node loggerNode = new Node(targetChildNode, node.getName(), node.getType()); |
| if (targetNode != null) { |
| targetNode.getAttributes().putAll(node.getAttributes()); |
| for (final Node sourceLoggerChild : node.getChildren()) { |
| if (isFilterNode(sourceLoggerChild)) { |
| boolean foundFilter = false; |
| for (final Node targetChild : targetNode.getChildren()) { |
| if (isFilterNode(targetChild)) { |
| updateFilterNode(loggerNode, targetChild, sourceLoggerChild, |
| pluginManager); |
| foundFilter = true; |
| break; |
| } |
| } |
| if (!foundFilter) { |
| final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), |
| sourceLoggerChild.getType()); |
| childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); |
| childNode.getChildren().addAll(sourceLoggerChild.getChildren()); |
| targetNode.getChildren().add(childNode); |
| } |
| } else { |
| final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), |
| sourceLoggerChild.getType()); |
| childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); |
| childNode.getChildren().addAll(sourceLoggerChild.getChildren()); |
| if (childNode.getName().equalsIgnoreCase("AppenderRef")) { |
| for (final Node targetChild : targetNode.getChildren()) { |
| if (isSameReference(targetChild, childNode)) { |
| targetNode.getChildren().remove(targetChild); |
| break; |
| } |
| } |
| } else { |
| for (final Node targetChild : targetNode.getChildren()) { |
| if (isSameName(targetChild, childNode)) { |
| targetNode.getChildren().remove(targetChild); |
| break; |
| } |
| } |
| } |
| |
| targetNode.getChildren().add(childNode); |
| } |
| } |
| } else { |
| loggerNode.getAttributes().putAll(node.getAttributes()); |
| loggerNode.getChildren().addAll(node.getChildren()); |
| targetChildNode.getChildren().add(loggerNode); |
| } |
| } |
| isMerged = true; |
| break; |
| } |
| default: { |
| targetChildNode.getChildren().addAll(sourceChildNode.getChildren()); |
| isMerged = true; |
| break; |
| } |
| |
| } |
| } |
| if (!isMerged) { |
| if (sourceChildNode.getName().equalsIgnoreCase("Properties")) { |
| target.getChildren().add(0, sourceChildNode); |
| } else { |
| target.getChildren().add(sourceChildNode); |
| } |
| } |
| } |
| } |
| |
| private Node getLoggerNode(final Node parentNode, final String name) { |
| for (final Node node : parentNode.getChildren()) { |
| final String nodeName = node.getAttributes().get(NAME); |
| if (name == null && nodeName == null) { |
| return node; |
| } |
| if (nodeName != null && nodeName.equals(name)) { |
| return node; |
| } |
| } |
| return null; |
| } |
| |
| private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode, |
| final PluginManager pluginManager) { |
| if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) { |
| final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType()); |
| node.getChildren().addAll(sourceChildNode.getChildren()); |
| node.getAttributes().putAll(sourceChildNode.getAttributes()); |
| targetChildNode.getChildren().add(node); |
| } else { |
| final PluginType pluginType = pluginManager.getPluginType(FILTERS); |
| final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType); |
| final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType()); |
| node.getAttributes().putAll(sourceChildNode.getAttributes()); |
| final List<Node> children = filtersNode.getChildren(); |
| children.add(targetChildNode); |
| children.add(node); |
| final List<Node> nodes = target.getChildren(); |
| nodes.remove(targetChildNode); |
| nodes.add(filtersNode); |
| } |
| } |
| |
| private boolean isFilterNode(final Node node) { |
| return Filter.class.isAssignableFrom(node.getType().getPluginClass()); |
| } |
| |
| private boolean isSameName(final Node node1, final Node node2) { |
| final String value = node1.getAttributes().get(NAME); |
| return value != null && value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase()); |
| } |
| |
| private boolean isSameReference(final Node node1, final Node node2) { |
| final String value = node1.getAttributes().get(REF); |
| return value != null && value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase()); |
| } |
| } |