001package org.javasimon.callback;
002
003import org.javasimon.Counter;
004import org.javasimon.CounterSample;
005import org.javasimon.Manager;
006import org.javasimon.Simon;
007import org.javasimon.SimonPattern;
008import org.javasimon.Split;
009import org.javasimon.Stopwatch;
010import org.javasimon.StopwatchSample;
011
012import java.util.EnumMap;
013import java.util.List;
014import java.util.Map;
015import java.util.concurrent.CopyOnWriteArrayList;
016
017import javax.script.ScriptException;
018
019/**
020 * This callback combines Composite and Filter behavior. Filter can be configured
021 * via {@link #addRule(FilterRule.Type, String, String, Callback.Event...)}
022 * method and if the rule is satisfied the event is propagated to all
023 * children callbacks added via {@link #addCallback(Callback)}. XML facility for configuration
024 * is provided via {@link org.javasimon.ManagerConfiguration#readConfig(java.io.Reader)}.
025 * <p/>
026 * Filter without any rules does not propagate events (default DENY behavior).
027 * Any number of global rules (for {@link Callback.Event#ALL}) and per event rules can be added.
028 * Event rules have higher priority and if the filter passes on event rules, global rules are not consulted.
029 * Rules are checked in the order they were added to the filter.
030 *
031 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
032 * @see FilterRule
033 */
034public final class CompositeFilterCallback implements FilterCallback, CompositeCallback {
035
036        private CompositeCallbackImpl callback = new CompositeCallbackImpl();
037
038        private Map<Event, List<FilterRule>> rules;
039
040        /** Constructs composite filter callback. */
041        public CompositeFilterCallback() {
042                rules = new EnumMap<>(Event.class);
043                for (Event event : Event.values()) {
044                        rules.put(event, new CopyOnWriteArrayList<FilterRule>());
045                }
046        }
047
048        @Override
049        public List<Callback> callbacks() {
050                return callback.callbacks();
051        }
052
053        @Override
054        public void addCallback(Callback callback) {
055                this.callback.addCallback(callback);
056        }
057
058        @Override
059        public void removeCallback(Callback callback) {
060                this.callback.removeCallback(callback);
061        }
062
063        @Override
064        public void removeAllCallbacks() {
065                this.callback.removeAllCallbacks();
066        }
067
068        @Override
069        public void initialize(Manager manager) {
070                callback.initialize(manager);
071        }
072
073        @Override
074        public void cleanup() {
075                callback.cleanup();
076        }
077
078        @Override
079        public void onStopwatchAdd(Stopwatch stopwatch, Split split, StopwatchSample sample) {
080                if (rulesApplyTo(stopwatch, Event.STOPWATCH_ADD, split.runningFor())) {
081                        callback.onStopwatchAdd(stopwatch, split, sample);
082                }
083        }
084
085        @Override
086        public void onStopwatchStart(Split split) {
087                Stopwatch stopwatch = split.getStopwatch();
088                if (stopwatch != null && rulesApplyTo(stopwatch, Event.STOPWATCH_START, split)) {
089                        callback.onStopwatchStart(split);
090                }
091        }
092
093        @Override
094        public void onStopwatchStop(Split split, StopwatchSample sample) {
095                Stopwatch stopwatch = split.getStopwatch();
096                if (stopwatch != null && rulesApplyTo(stopwatch, Event.STOPWATCH_STOP, split)) {
097                        callback.onStopwatchStop(split, sample);
098                }
099        }
100
101        @Override
102        public void onCounterDecrease(Counter counter, long dec, CounterSample sample) {
103                if (rulesApplyTo(counter, Event.COUNTER_DECREASE, dec)) {
104                        callback.onCounterDecrease(counter, dec, sample);
105                }
106        }
107
108        @Override
109        public void onCounterIncrease(Counter counter, long inc, CounterSample sample) {
110                if (rulesApplyTo(counter, Event.COUNTER_INCREASE, inc)) {
111                        callback.onCounterIncrease(counter, inc, sample);
112                }
113        }
114
115        @Override
116        public void onCounterSet(Counter counter, long val, CounterSample sample) {
117                if (rulesApplyTo(counter, Event.COUNTER_SET, val)) {
118                        callback.onCounterSet(counter, val, sample);
119                }
120        }
121
122        @Override
123        public void onSimonCreated(Simon simon) {
124                if (rulesApplyTo(simon, Event.CREATED)) {
125                        callback.onSimonCreated(simon);
126                }
127        }
128
129        @Override
130        public void onSimonDestroyed(Simon simon) {
131                if (rulesApplyTo(simon, Event.DESTROYED)) {
132                        callback.onSimonDestroyed(simon);
133                }
134        }
135
136        @Override
137        public void onManagerClear() {
138                if (rulesApplyTo(null, Event.MANAGER_CLEAR)) {
139                        callback.onManagerClear();
140                }
141        }
142
143        @Override
144        public void onManagerMessage(String message) {
145                if (rulesApplyTo(null, Event.MESSAGE, message)) {
146                        callback.onManagerMessage(message);
147                }
148        }
149
150        @Override
151        public void onManagerWarning(String warning, Exception cause) {
152                if (rulesApplyTo(null, Event.WARNING, cause)) {
153                        callback.onManagerWarning(warning, cause);
154                }
155        }
156
157        @Override
158        public void addRule(FilterRule.Type type, String condition, String pattern, Event... events) {
159                SimonPattern simonPattern = SimonPattern.create(pattern);
160                FilterRule rule = new FilterRule(type, condition, simonPattern);
161                for (Event event : events) {
162                        if (event != null) {
163                                rules.get(event).add(rule);
164                        }
165                }
166                if (events.length == 0) {
167                        rules.get(Event.ALL).add(rule);
168                }
169        }
170
171        private boolean rulesApplyTo(Simon simon, Event checkedEvent, Object... params) {
172                // only if event rules are empty, check rules for ALL as a fallback
173                if (rules.get(checkedEvent).size() == 0) {
174                        return checkRules(simon, Event.ALL, params);
175                }
176                return checkRules(simon, checkedEvent, params);
177        }
178
179        private boolean checkRules(Simon simon, Event event, Object... params) {
180                List<FilterRule> rulesForEvent = rules.get(event);
181                if (rulesForEvent.size() == 0) { // empty rule list => DENY
182                        return false;
183                }
184                boolean allMustSatisfied = false;
185                for (FilterRule rule : rulesForEvent) {
186                        boolean result = false;
187                        try {
188                                result = patternAndConditionCheck(simon, rule, params);
189                        } catch (ScriptException e) {
190                                onManagerWarning("Script exception while evaluating rule expression", e);
191                        }
192
193                        if (!result && rule.getType().equals(FilterRule.Type.MUST)) { // fast fail on MUST condition
194                                return false;
195                        } else if (result && rule.getType().equals(FilterRule.Type.MUST)) { // MUST condition met, let's go on
196                                allMustSatisfied = true;
197                        } else if (result && rule.getType().equals(FilterRule.Type.MUST_NOT)) { // fast fail on MUST NOT condition
198                                return false;
199                        } else if (!result && rule.getType().equals(FilterRule.Type.MUST_NOT)) { // MUST NOT condition met, go on
200                                allMustSatisfied = true;
201                        } else if (result && rule.getType().equals(FilterRule.Type.SUFFICE)) { // fast success on SUFFICE condition
202                                return true;
203                        }
204                }
205                return allMustSatisfied;
206        }
207
208        private boolean patternAndConditionCheck(Simon simon, FilterRule rule, Object... params) throws ScriptException {
209                //noinspection SimplifiableIfStatement
210                if (simon != null && rule.getPattern() != null && !rule.getPattern().accept(simon)) {
211                        return false;
212                }
213                return rule.checkCondition(simon, params);
214        }
215}