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                                             childNode.getAttributes().putAll(sourceLoggerChild.getAttributes());
175                                             childNode.getChildren().addAll(sourceLoggerChild.getChildren());
176                                             targetNode.getChildren().add(childNode);
177                                         }
178                                     } else {
179                                         final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(),
180                                                 sourceLoggerChild.getType());
181                                         childNode.getAttributes().putAll(sourceLoggerChild.getAttributes());
182                                         childNode.getChildren().addAll(sourceLoggerChild.getChildren());
183                                         if (childNode.getName().equalsIgnoreCase("AppenderRef")) {
184                                             for (final Node targetChild : targetNode.getChildren()) {
185                                                 if (isSameReference(targetChild, childNode)) {
186                                                     targetNode.getChildren().remove(targetChild);
187                                                     break;
188                                                 }
189                                             }
190                                         } else {
191                                             for (final Node targetChild : targetNode.getChildren()) {
192                                                 if (isSameName(targetChild, childNode)) {
193                                                     targetNode.getChildren().remove(targetChild);
194                                                     break;
195                                                 }
196                                             }
197                                         }
198 
199                                         targetNode.getChildren().add(childNode);
200                                     }
201                                 }
202                             } else {
203                                 loggerNode.getAttributes().putAll(node.getAttributes());
204                                 loggerNode.getChildren().addAll(node.getChildren());
205                                 targetChildNode.getChildren().add(loggerNode);
206                             }
207                         }
208                         isMerged = true;
209                         break;
210                     }
211                     default: {
212                         targetChildNode.getChildren().addAll(sourceChildNode.getChildren());
213                         isMerged = true;
214                         break;
215                     }
216 
217                 }
218             }
219             if (!isMerged) {
220                 if (sourceChildNode.getName().equalsIgnoreCase("Properties")) {
221                     target.getChildren().add(0, sourceChildNode);
222                 } else {
223                     target.getChildren().add(sourceChildNode);
224                 }
225             }
226         }
227     }
228 
229     private Node getLoggerNode(final Node parentNode, final String name) {
230         for (final Node node : parentNode.getChildren()) {
231             final String nodeName = node.getAttributes().get(NAME);
232             if (name == null && nodeName == null) {
233                 return node;
234             }
235             if (nodeName != null && nodeName.equals(name)) {
236                 return node;
237             }
238         }
239         return null;
240     }
241 
242     private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode,
243             final PluginManager pluginManager) {
244         if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) {
245             final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType());
246             node.getChildren().addAll(sourceChildNode.getChildren());
247             node.getAttributes().putAll(sourceChildNode.getAttributes());
248             targetChildNode.getChildren().add(node);
249         } else {
250             final PluginType pluginType = pluginManager.getPluginType(FILTERS);
251             final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType);
252             final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType());
253             node.getAttributes().putAll(sourceChildNode.getAttributes());
254             final List<Node> children = filtersNode.getChildren();
255             children.add(targetChildNode);
256             children.add(node);
257             final List<Node> nodes = target.getChildren();
258             nodes.remove(targetChildNode);
259             nodes.add(filtersNode);
260         }
261     }
262 
263     private boolean isFilterNode(final Node node) {
264         return Filter.class.isAssignableFrom(node.getType().getPluginClass());
265     }
266 
267     private boolean isSameName(final Node node1, final Node node2) {
268         final String value = node1.getAttributes().get(NAME);
269         return value != null && value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase());
270     }
271 
272     private boolean isSameReference(final Node node1, final Node node2) {
273         final String value = node1.getAttributes().get(REF);
274         return value != null && value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase());
275     }
276 }