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}