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 actionSupplier = matchActionsWithoutTransparentParts(elementPath); 121 if(actionSupplier != null) { 122 return actionSupplier; 123 } 124 125 return matchActionsWithRenamedParts(elementPath); 126 127 } 128 129 private Supplier<Action> matchActionsWithoutTransparentParts(ElementPath elementPath) { 130 ElementPath cleanedElementPath = removeTransparentPathParts(elementPath); 131 return internalMatchAction(cleanedElementPath); 132 } 133 134 private Supplier<Action> matchActionsWithRenamedParts(ElementPath elementPath) { 135 ElementPath renamedElementPath = renamePathParts(elementPath); 136 return internalMatchAction(renamedElementPath); 137 } 138 139 private Supplier<Action> internalMatchAction(ElementPath elementPath) { 140 Supplier<Action> actionSupplier; 141 142 if ((actionSupplier = fullPathMatch(elementPath)) != null) { 143 return actionSupplier; 144 } else if ((actionSupplier = suffixMatch(elementPath)) != null) { 145 return actionSupplier; 146 } else if ((actionSupplier = prefixMatch(elementPath)) != null) { 147 return actionSupplier; 148 } else if ((actionSupplier = middleMatch(elementPath)) != null) { 149 return actionSupplier; 150 } else { 151 return null; 152 } 153 } 154 155 ElementPath removeTransparentPathParts(ElementPath originalElementPath) { 156 157 List<String> preservedElementList = new ArrayList<>(originalElementPath.partList.size()); 158 159 for (String part : originalElementPath.partList) { 160 boolean shouldKeep = transparentPathParts.stream().noneMatch(p -> p.equalsIgnoreCase(part)); 161 if (shouldKeep) 162 preservedElementList.add(part); 163 } 164 165 return new ElementPath(preservedElementList); 166 167 } 168 169 170 ElementPath renamePathParts(ElementPath originalElementPath) { 171 172 List<String> result = new ArrayList<>(originalElementPath.partList.size()); 173 174 for (String part : originalElementPath.partList) { 175 String modifiedName = pathPartsMapForRenaming.getOrDefault(part, part); 176 result.add(modifiedName); 177 } 178 179 return new ElementPath(result); 180 } 181 182 183 Supplier<Action> fullPathMatch(ElementPath elementPath) { 184 for (ElementSelector selector : rules.keySet()) { 185 if (selector.fullPathMatch(elementPath)) 186 return rules.get(selector); 187 } 188 return null; 189 } 190 191 // Suffix matches are matches of type */x/y 192 Supplier<Action> suffixMatch(ElementPath elementPath) { 193 int max = 0; 194 ElementSelector longestMatchingElementSelector = null; 195 196 for (ElementSelector selector : rules.keySet()) { 197 if (isSuffixPattern(selector)) { 198 int r = selector.getTailMatchLength(elementPath); 199 if (r > max) { 200 max = r; 201 longestMatchingElementSelector = selector; 202 } 203 } 204 } 205 206 if (longestMatchingElementSelector != null) { 207 return rules.get(longestMatchingElementSelector); 208 } else { 209 return null; 210 } 211 } 212 213 private boolean isSuffixPattern(ElementSelector p) { 214 return (p.size() > 1) && p.get(0).equals(KLEENE_STAR); 215 } 216 217 Supplier<Action> prefixMatch(ElementPath elementPath) { 218 int max = 0; 219 ElementSelector longestMatchingElementSelector = null; 220 221 for (ElementSelector selector : rules.keySet()) { 222 String last = selector.peekLast(); 223 if (isKleeneStar(last)) { 224 int r = selector.getPrefixMatchLength(elementPath); 225 // to qualify the match length must equal p's size omitting the '*' 226 if ((r == selector.size() - 1) && (r > max)) { 227 max = r; 228 longestMatchingElementSelector = selector; 229 } 230 } 231 } 232 233 if (longestMatchingElementSelector != null) { 234 return rules.get(longestMatchingElementSelector); 235 } else { 236 return null; 237 } 238 } 239 240 private boolean isKleeneStar(String last) { 241 return KLEENE_STAR.equals(last); 242 } 243 244 Supplier<Action> middleMatch(ElementPath path) { 245 246 int max = 0; 247 ElementSelector longestMatchingElementSelector = null; 248 249 for (ElementSelector selector : rules.keySet()) { 250 String last = selector.peekLast(); 251 String first = null; 252 if (selector.size() > 1) { 253 first = selector.get(0); 254 } 255 if (isKleeneStar(last) && isKleeneStar(first)) { 256 List<String> copyOfPartList = selector.getCopyOfPartList(); 257 if (copyOfPartList.size() > 2) { 258 copyOfPartList.remove(0); 259 copyOfPartList.remove(copyOfPartList.size() - 1); 260 } 261 262 int r = 0; 263 ElementSelector clone = new ElementSelector(copyOfPartList); 264 if (clone.isContainedIn(path)) { 265 r = clone.size(); 266 } 267 if (r > max) { 268 max = r; 269 longestMatchingElementSelector = selector; 270 } 271 } 272 } 273 274 if (longestMatchingElementSelector != null) { 275 return rules.get(longestMatchingElementSelector); 276 } else { 277 return null; 278 } 279 } 280 281 public String toString() { 282 final String TAB = " "; 283 284 StringBuilder retValue = new StringBuilder(); 285 286 retValue.append("SimpleRuleStore ( ").append("rules = ").append(this.rules).append(TAB).append(" )"); 287 288 return retValue.toString(); 289 } 290 291}