001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.joran.spi; 015 016import java.util.ArrayList; 017import java.util.HashMap; 018import java.util.List; 019import java.util.Map; 020import java.util.function.Supplier; 021 022import ch.qos.logback.core.Context; 023import ch.qos.logback.core.joran.action.Action; 024import ch.qos.logback.core.spi.ContextAwareBase; 025import ch.qos.logback.core.util.OptionHelper; 026 027/** 028 * This class implements the {@link RuleStore} interface. It is the rule store 029 * implementation used by default in Joran. 030 * 031 * @author Ceki Gülcü 032 * 033 */ 034public class SimpleRuleStore extends ContextAwareBase implements RuleStore { 035 036 static String KLEENE_STAR = "*"; 037 038 // key: Pattern instance, value: ArrayList containing actions 039 HashMap<ElementSelector, Supplier<Action>> rules = new HashMap<>(); 040 041 List<String> transparentPathParts = new ArrayList<>(2); 042 Map<String, String> pathPartsMapForRenaming = new HashMap<>(2); 043 044 public SimpleRuleStore(Context context) { 045 setContext(context); 046 } 047 048 049 public void addTransparentPathPart(String pathPart) { 050 if (pathPart == null) 051 throw new IllegalArgumentException("pathPart cannot be null"); 052 053 pathPart = pathPart.trim(); 054 055 if (pathPart.isEmpty()) 056 throw new IllegalArgumentException("pathPart cannot be empty or to consist of only spaces"); 057 058 if (pathPart.contains("/")) 059 throw new IllegalArgumentException("pathPart cannot contain '/', i.e. the forward slash character"); 060 061 transparentPathParts.add(pathPart); 062 063 } 064 065 /** 066 * Rename path parts. 067 * 068 * @param originalName the name before renaming 069 * @param modifiedName the after renaming 070 * @since 1.5.5 071 */ 072 @Override 073 public void addPathPathMapping(String originalName, String modifiedName) { 074 pathPartsMapForRenaming.put(originalName, modifiedName); 075 } 076 077 /** 078 * Add a new rule, i.e. a pattern, action pair to the rule store. 079 * <p> 080 * Note that the added action's LoggerRepository will be set in the process. 081 */ 082 public void addRule(ElementSelector elementSelector, Supplier<Action> actionSupplier) { 083 084 Supplier<Action> existing = rules.get(elementSelector); 085 086 if (existing == null) { 087 rules.put(elementSelector, actionSupplier); 088 } else { 089 throw new IllegalStateException(elementSelector.toString() + " already has an associated action supplier"); 090 } 091 } 092 093 public void addRule(ElementSelector elementSelector, String actionClassName) { 094 Action action = null; 095 096 try { 097 action = (Action) OptionHelper.instantiateByClassName(actionClassName, Action.class, context); 098 } catch (Exception e) { 099 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}