1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.joran.spi;
15  
16  import java.util.ArrayList;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.function.Supplier;
21  
22  import ch.qos.logback.core.Context;
23  import ch.qos.logback.core.joran.action.Action;
24  import ch.qos.logback.core.spi.ContextAwareBase;
25  import ch.qos.logback.core.util.OptionHelper;
26  
27  /**
28   * This class implements the {@link RuleStore} interface. It is the rule store
29   * implementation used by default in Joran.
30   * 
31   * @author Ceki Gülcü
32   * 
33   */
34  public class SimpleRuleStore extends ContextAwareBase implements RuleStore {
35  
36      static String KLEENE_STAR = "*";
37  
38      // key: Pattern instance, value: ArrayList containing actions
39      HashMap<ElementSelector, Supplier<Action>> rules = new HashMap<>();
40  
41      List<String> transparentPathParts = new ArrayList<>(2);
42      Map<String, String> pathPartsMapForRenaming = new HashMap<>(2);
43  
44      public SimpleRuleStore(Context context) {
45          setContext(context);
46      }
47  
48  
49      public void addTransparentPathPart(String pathPart) {
50          if (pathPart == null)
51              throw new IllegalArgumentException("pathPart cannot be null");
52  
53          pathPart = pathPart.trim();
54  
55          if (pathPart.isEmpty())
56              throw new IllegalArgumentException("pathPart cannot be empty or to consist of only spaces");
57  
58          if (pathPart.contains("/"))
59              throw new IllegalArgumentException("pathPart cannot contain '/', i.e. the forward slash character");
60  
61          transparentPathParts.add(pathPart);
62  
63      }
64  
65      /**
66       * Rename path parts.
67       *
68       * @param originalName the name before renaming
69       * @param modifiedName the after renaming
70       * @since 1.5.5
71       */
72      @Override
73      public void addPathPathMapping(String originalName, String modifiedName) {
74          pathPartsMapForRenaming.put(originalName, modifiedName);
75      }
76  
77      /**
78       * Add a new rule, i.e. a pattern, action pair to the rule store.
79       * <p>
80       * Note that the added action's LoggerRepository will be set in the process.
81       */
82      public void addRule(ElementSelector elementSelector, Supplier<Action> actionSupplier) {
83  
84          Supplier<Action> existing = rules.get(elementSelector);
85  
86          if (existing == null) {
87              rules.put(elementSelector, actionSupplier);
88          } else {
89              throw new IllegalStateException(elementSelector.toString() + " already has an associated action supplier");
90          }
91      }
92  
93      public void addRule(ElementSelector elementSelector, String actionClassName) {
94          Action action = null;
95  
96          try {
97              action = (Action) OptionHelper.instantiateByClassName(actionClassName, Action.class, context);
98          } catch (Exception e) {
99              addError("Could not instantiate class [" + actionClassName + "]", e);
100         }
101         if (action != null) {
102      //       addRule(elementSelector, action);
103         }
104     }
105 
106     // exact match has the highest priority
107     // if no exact match, check for suffix (tail) match, i.e matches
108     // of type */x/y. Suffix match for */x/y has higher priority than match for
109     // */x
110     // if no suffix match, check for prefix match, i.e. matches for x/*
111     // match for x/y/* has higher priority than matches for x/*
112 
113     public Supplier<Action> matchActions(ElementPath elementPath) {
114         
115         Supplier<Action> actionSupplier = internalMatchAction(elementPath);
116         if(actionSupplier != null) {
117             return actionSupplier;
118         }
119 
120         return matchActionsWithoutTransparentPartsAndRenamedParts(elementPath);
121     }
122 
123     private Supplier<Action> matchActionsWithoutTransparentPartsAndRenamedParts(ElementPath elementPath) {
124         ElementPath cleanedElementPath = removeTransparentPathParts(elementPath);
125         ElementPath renamePathParts = renamePathParts(cleanedElementPath);
126 
127         return internalMatchAction(renamePathParts);
128     }
129 
130 //    private Supplier<Action> matchActionsWithoutTransparentParts(ElementPath elementPath) {
131 //        ElementPath cleanedElementPath = removeTransparentPathParts(elementPath);
132 //        return internalMatchAction(cleanedElementPath);
133 //    }
134 //
135 //    private Supplier<Action> matchActionsWithRenamedParts(ElementPath elementPath) {
136 //        ElementPath renamedElementPath = renamePathParts(elementPath);
137 //        return internalMatchAction(renamedElementPath);
138 //    }
139 
140     private Supplier<Action> internalMatchAction(ElementPath elementPath) {
141         Supplier<Action> actionSupplier;
142 
143         if ((actionSupplier = fullPathMatch(elementPath)) != null) {
144             return actionSupplier;
145         } else if ((actionSupplier = suffixMatch(elementPath)) != null) {
146             return actionSupplier;
147         } else if ((actionSupplier = prefixMatch(elementPath)) != null) {
148             return actionSupplier;
149         } else if ((actionSupplier = middleMatch(elementPath)) != null) {
150             return actionSupplier;
151         } else {
152             return null;
153         }
154     }
155 
156     ElementPath removeTransparentPathParts(ElementPath originalElementPath) {
157 
158         List<String> preservedElementList = new ArrayList<>(originalElementPath.partList.size());
159 
160         for (String part : originalElementPath.partList) {
161             boolean shouldKeep = transparentPathParts.stream().noneMatch(p -> p.equalsIgnoreCase(part));
162             if (shouldKeep)
163                 preservedElementList.add(part);
164         }
165 
166         return new ElementPath(preservedElementList);
167 
168     }
169 
170 
171     ElementPath renamePathParts(ElementPath originalElementPath) {
172 
173         List<String> result = new ArrayList<>(originalElementPath.partList.size());
174 
175         for (String part : originalElementPath.partList) {
176             String modifiedName = pathPartsMapForRenaming.getOrDefault(part, part);
177             result.add(modifiedName);
178         }
179 
180         return new ElementPath(result);
181     }
182 
183 
184     Supplier<Action> fullPathMatch(ElementPath elementPath) {
185         for (ElementSelector selector : rules.keySet()) {
186             if (selector.fullPathMatch(elementPath))
187                 return rules.get(selector);
188         }
189         return null;
190     }
191 
192     // Suffix matches are matches of type */x/y
193     Supplier<Action> suffixMatch(ElementPath elementPath) {
194         int max = 0;
195         ElementSelector longestMatchingElementSelector = null;
196 
197         for (ElementSelector selector : rules.keySet()) {
198             if (isSuffixPattern(selector)) {
199                 int r = selector.getTailMatchLength(elementPath);
200                 if (r > max) {
201                     max = r;
202                     longestMatchingElementSelector = selector;
203                 }
204             }
205         }
206 
207         if (longestMatchingElementSelector != null) {
208             return rules.get(longestMatchingElementSelector);
209         } else {
210             return null;
211         }
212     }
213 
214     private boolean isSuffixPattern(ElementSelector p) {
215         return (p.size() > 1) && p.get(0).equals(KLEENE_STAR);
216     }
217 
218     Supplier<Action> prefixMatch(ElementPath elementPath) {
219         int max = 0;
220         ElementSelector longestMatchingElementSelector = null;
221 
222         for (ElementSelector selector : rules.keySet()) {
223             String last = selector.peekLast();
224             if (isKleeneStar(last)) {
225                 int r = selector.getPrefixMatchLength(elementPath);
226                 // to qualify the match length must equal p's size omitting the '*'
227                 if ((r == selector.size() - 1) && (r > max)) {
228                     max = r;
229                     longestMatchingElementSelector = selector;
230                 }
231             }
232         }
233 
234         if (longestMatchingElementSelector != null) {
235             return rules.get(longestMatchingElementSelector);
236         } else {
237             return null;
238         }
239     }
240 
241     private boolean isKleeneStar(String last) {
242         return KLEENE_STAR.equals(last);
243     }
244 
245     Supplier<Action> middleMatch(ElementPath path) {
246 
247         int max = 0;
248         ElementSelector longestMatchingElementSelector = null;
249 
250         for (ElementSelector selector : rules.keySet()) {
251             String last = selector.peekLast();
252             String first = null;
253             if (selector.size() > 1) {
254                 first = selector.get(0);
255             }
256             if (isKleeneStar(last) && isKleeneStar(first)) {
257                 List<String> copyOfPartList = selector.getCopyOfPartList();
258                 if (copyOfPartList.size() > 2) {
259                     copyOfPartList.remove(0);
260                     copyOfPartList.remove(copyOfPartList.size() - 1);
261                 }
262 
263                 int r = 0;
264                 ElementSelector clone = new ElementSelector(copyOfPartList);
265                 if (clone.isContainedIn(path)) {
266                     r = clone.size();
267                 }
268                 if (r > max) {
269                     max = r;
270                     longestMatchingElementSelector = selector;
271                 }
272             }
273         }
274 
275         if (longestMatchingElementSelector != null) {
276             return rules.get(longestMatchingElementSelector);
277         } else {
278             return null;
279         }
280     }
281 
282     public String toString() {
283         final String TAB = "  ";
284 
285         StringBuilder retValue = new StringBuilder();
286 
287         retValue.append("SimpleRuleStore ( ").append("rules = ").append(this.rules).append(TAB).append(" )");
288 
289         return retValue.toString();
290     }
291 
292 }