View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config.composite;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.logging.log4j.Level;
24  import org.apache.logging.log4j.core.Filter;
25  import org.apache.logging.log4j.core.config.AbstractConfiguration;
26  import org.apache.logging.log4j.core.config.Node;
27  import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
28  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
29  import org.apache.logging.log4j.core.filter.CompositeFilter;
30  
31  /**
32   * The default merge strategy for composite configurations.
33   * <p>
34   * The default merge strategy performs the merge according to the following rules:
35   * <ol>
36   * <li>Aggregates the global configuration attributes with those in later configurations replacing those in previous
37   * configurations with the exception that the highest status level and the lowest monitorInterval greater than 0 will
38   * be used.</li>
39   * <li>Properties from all configurations are aggregated. Duplicate properties replace those in previous
40   * configurations.</li>
41   * <li>Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named
42   * duplicates may be present.</li>
43   * <li>Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous
44   * configurations.</li>
45   * <li>Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including
46   * all of the Appender's subcomponents.</li>
47   * <li>Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those
48   * in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in
49   * later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined.
50   * Since Filters are not named duplicates may be present. Filters under Appender references included or discarded
51   * depending on whether their parent Appender reference is kept or discarded.</li>
52   * </ol>
53   */
54  public class DefaultMergeStrategy implements MergeStrategy {
55  
56      private static final String APPENDERS = "appenders";
57      private static final String PROPERTIES = "properties";
58      private static final String LOGGERS = "loggers";
59      private static final String SCRIPTS = "scripts";
60      private static final String FILTERS = "filters";
61      private static final String STATUS = "status";
62      private static final String NAME = "name";
63      private static final String REF = "ref";
64  
65      /**
66       * Merge the root properties.
67       * @param rootNode The composite root node.
68       * @param configuration The configuration to merge.
69       */
70      @Override
71      public void mergeRootProperties(final Node rootNode, final AbstractConfiguration configuration) {
72          for (final Map.Entry<String, String> attribute : configuration.getRootNode().getAttributes().entrySet()) {
73              boolean isFound = false;
74              for (final Map.Entry<String, String> targetAttribute : rootNode.getAttributes().entrySet()) {
75                  if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) {
76                      if (attribute.getKey().equalsIgnoreCase(STATUS)) {
77                          final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase());
78                          final Level sourceLevel = Level.getLevel(attribute.getValue().toUpperCase());
79                          if (targetLevel != null && sourceLevel != null) {
80                              if (sourceLevel.isLessSpecificThan(targetLevel)) {
81                                  targetAttribute.setValue(attribute.getValue());
82                              }
83                          } else
84                              if (sourceLevel != null) {
85                                  targetAttribute.setValue(attribute.getValue());
86                              }
87                      } else {
88                          if (attribute.getKey().equalsIgnoreCase("monitorInterval")) {
89                              final int sourceInterval = Integer.parseInt(attribute.getValue());
90                              final int targetInterval = Integer.parseInt(targetAttribute.getValue());
91                              if (targetInterval == 0 || sourceInterval < targetInterval) {
92                                  targetAttribute.setValue(attribute.getValue());
93                              }
94                          } else {
95                              targetAttribute.setValue(attribute.getValue());
96                          }
97                      }
98                      isFound = true;
99                  }
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 }