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}