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}