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.function.Supplier; 020 021import ch.qos.logback.core.Context; 022import ch.qos.logback.core.joran.action.Action; 023import ch.qos.logback.core.spi.ContextAwareBase; 024import ch.qos.logback.core.util.OptionHelper; 025 026/** 027 * This class implements the {@link RuleStore} interface. It is the rule store 028 * implementation used by default in Joran. 029 * 030 * @author Ceki Gülcü 031 * 032 */ 033public class SimpleRuleStore extends ContextAwareBase implements RuleStore { 034 035 static String KLEENE_STAR = "*"; 036 037 // key: Pattern instance, value: ArrayList containing actions 038 HashMap<ElementSelector, Supplier<Action>> rules = new HashMap<>(); 039 040 List<String> transparentPathParts = new ArrayList<>(2); 041 042 public SimpleRuleStore(Context context) { 043 setContext(context); 044 } 045 046 public void addTransparentPathPart(String pathPart) { 047 if (pathPart == null) 048 throw new IllegalArgumentException("pathPart cannot be null"); 049 050 pathPart = pathPart.trim(); 051 052 if (pathPart.isEmpty()) 053 throw new IllegalArgumentException("pathPart cannot be empty or to consist of only spaces"); 054 055 if (pathPart.contains("/")) 056 throw new IllegalArgumentException("pathPart cannot contain '/', i.e. the forward slash character"); 057 058 transparentPathParts.add(pathPart); 059 060 } 061 062 /** 063 * Add a new rule, i.e. a pattern, action pair to the rule store. 064 * <p> 065 * Note that the added action's LoggerRepository will be set in the process. 066 */ 067 public void addRule(ElementSelector elementSelector, Supplier<Action> actionSupplier) { 068 069 Supplier<Action> existing = rules.get(elementSelector); 070 071 if (existing == null) { 072 rules.put(elementSelector, actionSupplier); 073 } else { 074 throw new IllegalStateException(elementSelector.toString() + " already has an associated action supplier"); 075 } 076 } 077 078 public void addRule(ElementSelector elementSelector, String actionClassName) { 079 Action action = null; 080 081 try { 082 action = (Action) OptionHelper.instantiateByClassName(actionClassName, Action.class, context); 083 } catch (Exception e) { 084 addError("Could not instantiate class [" + actionClassName + "]", e); 085 } 086 if (action != null) { 087 // addRule(elementSelector, action); 088 } 089 } 090 091 // exact match has the highest priority 092 // if no exact match, check for suffix (tail) match, i.e matches 093 // of type */x/y. Suffix match for */x/y has higher priority than match for 094 // */x 095 // if no suffix match, check for prefix match, i.e. matches for x/* 096 // match for x/y/* has higher priority than matches for x/* 097 098 public Supplier<Action> matchActions(ElementPath elementPath) { 099 100 Supplier<Action> actionSupplier = internalMatchAction(elementPath); 101 if(actionSupplier != null) { 102 return actionSupplier; 103 } else { 104 ElementPath cleanedElementPath = removeTransparentPathParts(elementPath); 105 return internalMatchAction(cleanedElementPath); 106 } 107 } 108 109 private Supplier<Action> internalMatchAction(ElementPath elementPath) { 110 Supplier<Action> actionSupplier; 111 112 if ((actionSupplier = fullPathMatch(elementPath)) != null) { 113 return actionSupplier; 114 } else if ((actionSupplier = suffixMatch(elementPath)) != null) { 115 return actionSupplier; 116 } else if ((actionSupplier = prefixMatch(elementPath)) != null) { 117 return actionSupplier; 118 } else if ((actionSupplier = middleMatch(elementPath)) != null) { 119 return actionSupplier; 120 } else { 121 return null; 122 } 123 } 124 125 ElementPath removeTransparentPathParts(ElementPath originalElementPath) { 126 127 List<String> preservedElementList = new ArrayList<>(originalElementPath.partList.size()); 128 129 for (String part : originalElementPath.partList) { 130 boolean shouldKeep = transparentPathParts.stream().noneMatch(p -> p.equalsIgnoreCase(part)); 131 if (shouldKeep) 132 preservedElementList.add(part); 133 } 134 135 return new ElementPath(preservedElementList); 136 137 } 138 139 Supplier<Action> fullPathMatch(ElementPath elementPath) { 140 for (ElementSelector selector : rules.keySet()) { 141 if (selector.fullPathMatch(elementPath)) 142 return rules.get(selector); 143 } 144 return null; 145 } 146 147 // Suffix matches are matches of type */x/y 148 Supplier<Action> suffixMatch(ElementPath elementPath) { 149 int max = 0; 150 ElementSelector longestMatchingElementSelector = null; 151 152 for (ElementSelector selector : rules.keySet()) { 153 if (isSuffixPattern(selector)) { 154 int r = selector.getTailMatchLength(elementPath); 155 if (r > max) { 156 max = r; 157 longestMatchingElementSelector = selector; 158 } 159 } 160 } 161 162 if (longestMatchingElementSelector != null) { 163 return rules.get(longestMatchingElementSelector); 164 } else { 165 return null; 166 } 167 } 168 169 private boolean isSuffixPattern(ElementSelector p) { 170 return (p.size() > 1) && p.get(0).equals(KLEENE_STAR); 171 } 172 173 Supplier<Action> prefixMatch(ElementPath elementPath) { 174 int max = 0; 175 ElementSelector longestMatchingElementSelector = null; 176 177 for (ElementSelector selector : rules.keySet()) { 178 String last = selector.peekLast(); 179 if (isKleeneStar(last)) { 180 int r = selector.getPrefixMatchLength(elementPath); 181 // to qualify the match length must equal p's size omitting the '*' 182 if ((r == selector.size() - 1) && (r > max)) { 183 max = r; 184 longestMatchingElementSelector = selector; 185 } 186 } 187 } 188 189 if (longestMatchingElementSelector != null) { 190 return rules.get(longestMatchingElementSelector); 191 } else { 192 return null; 193 } 194 } 195 196 private boolean isKleeneStar(String last) { 197 return KLEENE_STAR.equals(last); 198 } 199 200 Supplier<Action> middleMatch(ElementPath path) { 201 202 int max = 0; 203 ElementSelector longestMatchingElementSelector = null; 204 205 for (ElementSelector selector : rules.keySet()) { 206 String last = selector.peekLast(); 207 String first = null; 208 if (selector.size() > 1) { 209 first = selector.get(0); 210 } 211 if (isKleeneStar(last) && isKleeneStar(first)) { 212 List<String> copyOfPartList = selector.getCopyOfPartList(); 213 if (copyOfPartList.size() > 2) { 214 copyOfPartList.remove(0); 215 copyOfPartList.remove(copyOfPartList.size() - 1); 216 } 217 218 int r = 0; 219 ElementSelector clone = new ElementSelector(copyOfPartList); 220 if (clone.isContainedIn(path)) { 221 r = clone.size(); 222 } 223 if (r > max) { 224 max = r; 225 longestMatchingElementSelector = selector; 226 } 227 } 228 } 229 230 if (longestMatchingElementSelector != null) { 231 return rules.get(longestMatchingElementSelector); 232 } else { 233 return null; 234 } 235 } 236 237 public String toString() { 238 final String TAB = " "; 239 240 StringBuilder retValue = new StringBuilder(); 241 242 retValue.append("SimpleRuleStore ( ").append("rules = ").append(this.rules).append(TAB).append(" )"); 243 244 return retValue.toString(); 245 } 246 247}