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(final Node rootNode, final AbstractConfiguration configuration) { 072 for (final Map.Entry<String, String> attribute : configuration.getRootNode().getAttributes().entrySet()) { 073 boolean isFound = false; 074 for (final Map.Entry<String, String> targetAttribute : rootNode.getAttributes().entrySet()) { 075 if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) { 076 if (attribute.getKey().equalsIgnoreCase(STATUS)) { 077 final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase()); 078 final 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 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 { 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(final Node target, final Node source, final PluginManager pluginManager) { 116 for (final Node sourceChildNode : source.getChildren()) { 117 final boolean isFilter = isFilterNode(sourceChildNode); 118 boolean isMerged = false; 119 for (final 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 (final Node node : sourceChildNode.getChildren()) { 139 for (final 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 final Map<String, Node> targetLoggers = new HashMap<>(); 152 for (final Node node : targetChildNode.getChildren()) { 153 targetLoggers.put(node.getName(), node); 154 } 155 for (final Node node : sourceChildNode.getChildren()) { 156 final Node targetNode = getLoggerNode(targetChildNode, node.getAttributes().get(NAME)); 157 final Node loggerNode = new Node(targetChildNode, node.getName(), node.getType()); 158 if (targetNode != null) { 159 for (final Node sourceLoggerChild : node.getChildren()) { 160 if (isFilterNode(sourceLoggerChild)) { 161 boolean foundFilter = false; 162 for (final 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 final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), 172 sourceLoggerChild.getType()); 173 targetNode.getChildren().add(childNode); 174 } 175 } else { 176 final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), 177 sourceLoggerChild.getType()); 178 childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); 179 if (childNode.getName().equalsIgnoreCase("AppenderRef")) { 180 for (final Node targetChild : targetNode.getChildren()) { 181 if (isSameReference(targetChild, childNode)) { 182 targetNode.getChildren().remove(targetChild); 183 break; 184 } 185 } 186 } else { 187 for (final 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(final Node parentNode, final String name) { 226 for (final Node node : parentNode.getChildren()) { 227 final 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(final Node target, final Node targetChildNode, final Node sourceChildNode, 239 final PluginManager pluginManager) { 240 if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) { 241 final 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 final PluginType pluginType = pluginManager.getPluginType(FILTERS); 247 final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType); 248 final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType()); 249 node.getAttributes().putAll(sourceChildNode.getAttributes()); 250 final List<Node> children = filtersNode.getChildren(); 251 children.add(targetChildNode); 252 children.add(node); 253 final List<Node> nodes = target.getChildren(); 254 nodes.remove(targetChildNode); 255 nodes.add(filtersNode); 256 } 257 } 258 259 private boolean isFilterNode(final Node node) { 260 return Filter.class.isAssignableFrom(node.getType().getPluginClass()); 261 } 262 263 private boolean isSameName(final Node node1, final Node node2) { 264 return node1.getAttributes().get(NAME).toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase()); 265 } 266 267 private boolean isSameReference(final Node node1, final Node node2) { 268 return node1.getAttributes().get(REF).toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase()); 269 } 270}