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  import java.util.Objects;
23  
24  import org.apache.logging.log4j.Level;
25  import org.apache.logging.log4j.core.Filter;
26  import org.apache.logging.log4j.core.config.AbstractConfiguration;
27  import org.apache.logging.log4j.core.config.Node;
28  import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
29  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
30  import org.apache.logging.log4j.core.filter.CompositeFilter;
31  
32  /**
33   * The default merge strategy for composite configurations.
34   * <p>
35   * The default merge strategy performs the merge according to the following rules:
36   * <ol>
37   * <li>Aggregates the global configuration attributes with those in later configurations replacing those in previous
38   * configurations with the exception that the highest status level and the lowest monitorInterval greater than 0 will
39   * be used.</li>
40   * <li>Properties from all configurations are aggregated. Duplicate properties replace those in previous
41   * configurations.</li>
42   * <li>Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named
43   * duplicates may be present.</li>
44   * <li>Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous
45   * configurations.</li>
46   * <li>Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including
47   * all of the Appender's subcomponents.</li>
48   * <li>Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those
49   * in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in
50   * later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined.
51   * Since Filters are not named duplicates may be present. Filters under Appender references included or discarded
52   * depending on whether their parent Appender reference is kept or discarded.</li>
53   * </ol>
54   */
55  public class DefaultMergeStrategy implements MergeStrategy {
56  
57      private static final String APPENDERS = "appenders";
58      private static final String PROPERTIES = "properties";
59      private static final String LOGGERS = "loggers";
60      private static final String SCRIPTS = "scripts";
61      private static final String FILTERS = "filters";
62      private static final String STATUS = "status";
63      private static final String NAME = "name";
64      private static final String REF = "ref";
65  
66      /**
67       * Merge the root properties.
68       * @param rootNode The composite root node.
69       * @param configuration The configuration to merge.
70       */
71      @Override
72      public void mergeRootProperties(final Node rootNode, final AbstractConfiguration configuration) {
73          for (final Map.Entry<String, String> attribute : configuration.getRootNode().getAttributes().entrySet()) {
74              boolean isFound = false;
75              for (final Map.Entry<String, String> targetAttribute : rootNode.getAttributes().entrySet()) {
76                  if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) {
77                      if (attribute.getKey().equalsIgnoreCase(STATUS)) {
78                          final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase());
79                          final Level sourceLevel = Level.getLevel(attribute.getValue().toUpperCase());
80                          if (targetLevel != null && sourceLevel != null) {
81                              if (sourceLevel.isLessSpecificThan(targetLevel)) {
82                                  targetAttribute.setValue(attribute.getValue());
83                              }
84                          } else
85                              if (sourceLevel != null) {
86                                  targetAttribute.setValue(attribute.getValue());
87                              }
88                      } else {
89                          if (attribute.getKey().equalsIgnoreCase("monitorInterval")) {
90                              final int sourceInterval = Integer.parseInt(attribute.getValue());
91                              final int targetInterval = Integer.parseInt(targetAttribute.getValue());
92                              if (targetInterval == 0 || sourceInterval < targetInterval) {
93                                  targetAttribute.setValue(attribute.getValue());
94                              }
95                          } else {
96                              targetAttribute.setValue(attribute.getValue());
97                          }
98                      }
99                      isFound = true;
100                 }
101             }
102             if (!isFound) {
103                 rootNode.getAttributes().put(attribute.getKey(), attribute.getValue());
104             }
105         }
106     }
107 
108     /**
109      * Merge the source Configuration into the target Configuration.
110      *
111      * @param target        The target node to merge into.
112      * @param source        The source node.
113      * @param pluginManager The PluginManager.
114      */
115     @Override
116     public void mergConfigurations(final Node target, final Node source, final PluginManager pluginManager) {
117         for (final Node sourceChildNode : source.getChildren()) {
118             final boolean isFilter = isFilterNode(sourceChildNode);
119             boolean isMerged = false;
120             for (final Node targetChildNode : target.getChildren()) {
121                 if (isFilter) {
122                     if (isFilterNode(targetChildNode)) {
123                         updateFilterNode(target, targetChildNode, sourceChildNode, pluginManager);
124                         isMerged = true;
125                         break;
126                     }
127                     continue;
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 (Objects.equals(targetNode.getAttributes().get(NAME), 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                                 targetNode.getAttributes().putAll(node.getAttributes());
160                                 for (final Node sourceLoggerChild : node.getChildren()) {
161                                     if (isFilterNode(sourceLoggerChild)) {
162                                         boolean foundFilter = false;
163                                         for (final Node targetChild : targetNode.getChildren()) {
164                                             if (isFilterNode(targetChild)) {
165                                                 updateFilterNode(loggerNode, targetChild, sourceLoggerChild,
166                                                         pluginManager);
167                                                 foundFilter = true;
168                                                 break;
169                                             }
170                                         }
171                                         if (!foundFilter) {
172                                             final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(),
173                                                     sourceLoggerChild.getType());
174                                             targetNode.getChildren().add(childNode);
175                                         }
176                                     } else {
177                                         final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(),
178                                                 sourceLoggerChild.getType());
179                                         childNode.getAttributes().putAll(sourceLoggerChild.getAttributes());
180                                         childNode.getChildren().addAll(sourceLoggerChild.getChildren());
181                                         if (childNode.getName().equalsIgnoreCase("AppenderRef")) {
182                                             for (final Node targetChild : targetNode.getChildren()) {
183                                                 if (isSameReference(targetChild, childNode)) {
184                                                     targetNode.getChildren().remove(targetChild);
185                                                     break;
186                                                 }
187                                             }
188                                         } else {
189                                             for (final Node targetChild : targetNode.getChildren()) {
190                                                 if (isSameName(targetChild, childNode)) {
191                                                     targetNode.getChildren().remove(targetChild);
192                                                     break;
193                                                 }
194                                             }
195                                         }
196 
197                                         targetNode.getChildren().add(childNode);
198                                     }
199                                 }
200                             } else {
201                                 loggerNode.getAttributes().putAll(node.getAttributes());
202                                 loggerNode.getChildren().addAll(node.getChildren());
203                                 targetChildNode.getChildren().add(loggerNode);
204                             }
205                         }
206                         isMerged = true;
207                         break;
208                     }
209                     default: {
210                         targetChildNode.getChildren().addAll(sourceChildNode.getChildren());
211                         isMerged = true;
212                         break;
213                     }
214 
215                 }
216             }
217             if (!isMerged) {
218                 if (sourceChildNode.getName().equalsIgnoreCase("Properties")) {
219                     target.getChildren().add(0, sourceChildNode);
220                 } else {
221                     target.getChildren().add(sourceChildNode);
222                 }
223             }
224         }
225     }
226 
227     private Node getLoggerNode(final Node parentNode, final String name) {
228         for (final Node node : parentNode.getChildren()) {
229             final String nodeName = node.getAttributes().get(NAME);
230             if (name == null && nodeName == null) {
231                 return node;
232             }
233             if (nodeName != null && nodeName.equals(name)) {
234                 return node;
235             }
236         }
237         return null;
238     }
239 
240     private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode,
241             final PluginManager pluginManager) {
242         if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) {
243             final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType());
244             node.getChildren().addAll(sourceChildNode.getChildren());
245             node.getAttributes().putAll(sourceChildNode.getAttributes());
246             targetChildNode.getChildren().add(node);
247         } else {
248             final PluginType pluginType = pluginManager.getPluginType(FILTERS);
249             final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType);
250             final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType());
251             node.getAttributes().putAll(sourceChildNode.getAttributes());
252             final List<Node> children = filtersNode.getChildren();
253             children.add(targetChildNode);
254             children.add(node);
255             final List<Node> nodes = target.getChildren();
256             nodes.remove(targetChildNode);
257             nodes.add(filtersNode);
258         }
259     }
260 
261     private boolean isFilterNode(final Node node) {
262         return Filter.class.isAssignableFrom(node.getType().getPluginClass());
263     }
264 
265     private boolean isSameName(final Node node1, final Node node2) {
266         final String value = node1.getAttributes().get(NAME);
267         return value != null && value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase());
268     }
269 
270     private boolean isSameReference(final Node node1, final Node node2) {
271         final String value = node1.getAttributes().get(REF);
272         return value != null && value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase());
273     }
274 }