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}