001package org.javasimon.callback; 002 003import org.javasimon.Counter; 004import org.javasimon.Simon; 005import org.javasimon.SimonException; 006import org.javasimon.SimonPattern; 007import org.javasimon.Split; 008import org.javasimon.Stopwatch; 009import org.javasimon.utils.Replacer; 010 011import java.math.BigDecimal; 012 013import javax.script.Bindings; 014import javax.script.Compilable; 015import javax.script.CompiledScript; 016import javax.script.ScriptEngine; 017import javax.script.ScriptEngineManager; 018import javax.script.ScriptException; 019 020/** 021 * Represents filtering rule that checks whether sub-callbacks will get the event. 022 * Rule can be one of the following types: 023 * <ul> 024 * <li>{@link Type#MUST} - rule MUST be true and following rules are checked 025 * <li>{@link Type#SUFFICE} - if this rule is true the filter passes the event to children 026 * otherwise next rules are checked 027 * <li>{@link Type#MUST_NOT} - if this rule is true the filter ignores the event, otherwise 028 * next rules are checked 029 * </ul> 030 * As the order is important not all MUST rules must pass if there is any satisfied SUFFICE rule before. 031 * 032 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 033 * @since 3.1 (previously was {@code FilterCallback.Rule}) 034 */ 035public class FilterRule { 036 037 /** Enumeration of rule types that determines the evaluation of multiple rules in a chain. */ 038 public enum Type { 039 /** Rule must pass and next rule is consulted. */ 040 MUST, 041 042 /** 043 * If the rule passes the whole filter passes and no other rule is consulted. If the rule 044 * doesn't pass next rule is consulted. 045 */ 046 SUFFICE, 047 048 /** 049 * Rule must not pass. If the rule passes the whole filter doesn't pass. If it fails next 050 * rule is checked. 051 */ 052 MUST_NOT 053 } 054 055 /** Name of the rule variable for last split time in ns (split). */ 056 public static final String VAR_SPLIT = "split"; 057 058 /** Name of the rule variable for number of concurrently active splits of a particular Simon (active). */ 059 public static final String VAR_ACTIVE = "active"; 060 061 /** Name of the rule variable for maximal number of concurrently active splits (maxactive). */ 062 public static final String VAR_MAX_ACTIVE = "maxactive"; 063 064 /** Name of the rule variable for current value of the counter (counter). */ 065 public static final String VAR_COUNTER = "counter"; 066 067 /** Name of the rule variable for maximal value of the Simon - stopwatch in ns, counter without unit (max). */ 068 public static final String VAR_MAX = "max"; 069 070 /** Name of the rule variable for minimal value of the Simon - stopwatch in ns, counter without unit (min). */ 071 public static final String VAR_MIN = "min"; 072 073 /** Name of the rule variable for total split time (total). */ 074 public static final String VAR_TOTAL = "total"; 075 076 /** Name of the rule variable for increment or decrement value (value). */ 077 public static final String VAR_VALUE = "value"; 078 079 private static final ScriptEngine ECMA_SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName("ecmascript"); 080 081 private static final Replacer[] CONDITION_REPLACERS = new Replacer[] { 082 new Replacer(" lt ", " < "), 083 new Replacer(" le ", " <= "), 084 new Replacer(" eq ", " == "), 085 new Replacer(" ne ", " != "), 086 new Replacer(" gt ", " > "), 087 new Replacer(" ge ", " >= "), 088 new Replacer(" and ", " && "), 089 new Replacer(" or ", " || "), 090 new Replacer(" not ", " ! "), 091 new Replacer("(\\d)s", "$1000000000"), 092 new Replacer("(\\d)ms", "$1000000"), 093 new Replacer("(\\d)us", "$1000"), 094 }; 095 096 private Type type; 097 private String condition; 098 private CompiledScript expression; 099 private SimonPattern pattern; 100 101 /** 102 * Creates the rule with a specified type, condition and pattern. Rule can have a condition and/or a pattern. 103 * Pattern is not relevant for manager-level callback operations ({@link Callback#onManagerWarning(String, Exception)}, {@link Callback#onManagerMessage(String)}). 104 * Both condition and pattern are optional and can be null. 105 * 106 * @param type rule type determining the role of the rule in the chain of the filter 107 * @param condition additional conditional expression that must be true 108 * @param pattern Simon pattern that must match 109 */ 110 public FilterRule(Type type, String condition, SimonPattern pattern) { 111 this.type = type; 112 this.condition = condition; 113 if (condition != null) { 114 condition = condition.toLowerCase(); 115 for (Replacer conditionReplacer : CONDITION_REPLACERS) { 116 condition = conditionReplacer.process(condition); 117 } 118 try { 119 expression = ((Compilable) ECMA_SCRIPT_ENGINE).compile(condition); 120 Bindings bindings = ECMA_SCRIPT_ENGINE.createBindings(); 121 bindings.put(VAR_ACTIVE, 0); 122 bindings.put(VAR_COUNTER, 0); 123 bindings.put(VAR_MAX, 0); 124 bindings.put(VAR_MAX_ACTIVE, 0); 125 bindings.put(VAR_MIN, 0); 126 bindings.put(VAR_SPLIT, 0); 127 bindings.put(VAR_TOTAL, 0); 128 bindings.put(VAR_VALUE, 0); 129 if (!(expression.eval(bindings) instanceof Boolean)) { 130 throw new SimonException("Expression '" + condition + "' does not return boolean."); 131 } 132 } catch (ScriptException e) { 133 throw new SimonException(e); 134 } 135 } 136 this.pattern = pattern; 137 } 138 139 /** 140 * Returns the type of this rule. 141 * 142 * @return type of this rule 143 */ 144 public Type getType() { 145 return type; 146 } 147 148 /** 149 * Returns the additional condition of this rule. Values from the affected Simon can be checked and compared. 150 * 151 * @return additional condition of this rule 152 */ 153 public String getCondition() { 154 return condition; 155 } 156 157 /** 158 * Returns the Simon pattern of this rule. 159 * 160 * @return Simon pattern of this rule 161 */ 162 public SimonPattern getPattern() { 163 return pattern; 164 } 165 166 /** 167 * Checks the Simon and optional parameters against the condition specified for a rule. 168 * 169 * @param simon related Simon 170 * @param params optional parameters, e.g. value that is added to a Counter 171 * @return true if no condition is specified or the condition is satisfied, otherwise false 172 * @throws javax.script.ScriptException possible exception raised by the expression evaluation 173 */ 174 public synchronized boolean checkCondition(Simon simon, Object... params) throws ScriptException { 175 if (condition == null) { 176 return true; 177 } 178 if (simon instanceof Stopwatch) { 179 return checkStopwtach((Stopwatch) simon, params); 180 } 181 if (simon instanceof Counter) { 182 return checkCounter((Counter) simon, params); 183 } 184 return true; 185 } 186 187 private boolean checkCounter(Counter counter, Object... params) throws ScriptException { 188 Bindings bindings = ECMA_SCRIPT_ENGINE.createBindings(); 189 processParams(bindings, params); 190 bindings.put(VAR_COUNTER, counter.getCounter()); 191 bindings.put(VAR_MAX, counter.getMax()); 192 bindings.put(VAR_MIN, counter.getMin()); 193 return eval(bindings); 194 } 195 196 private boolean checkStopwtach(Stopwatch stopwatch, Object... params) throws ScriptException { 197 Bindings bindings = ECMA_SCRIPT_ENGINE.createBindings(); 198 processParams(bindings, params); 199 bindings.put(VAR_ACTIVE, stopwatch.getActive()); 200 bindings.put(VAR_COUNTER, stopwatch.getCounter()); 201 bindings.put(VAR_MAX, stopwatch.getMax()); 202 bindings.put(VAR_MIN, stopwatch.getMin()); 203 bindings.put(VAR_MAX_ACTIVE, stopwatch.getMaxActive()); 204 bindings.put(VAR_TOTAL, stopwatch.getTotal()); 205 return eval(bindings); 206 } 207 208 private void processParams(Bindings bindings, Object... params) { 209 for (Object param : params) { 210 if (param instanceof Split) { 211 bindings.put(VAR_SPLIT, BigDecimal.valueOf(((Split) param).runningFor())); 212 } else if (param instanceof Long) { 213 bindings.put(VAR_VALUE, BigDecimal.valueOf((Long) param)); 214 } 215 } 216 } 217 218 private boolean eval(Bindings bindings) throws ScriptException { 219 return (Boolean) expression.eval(bindings); 220 } 221}