package org.swellrt.server.box.events; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.swellrt.server.box.events.Event.Type; import org.waveprotocol.wave.util.logging.Log; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; /** * A rule that match events. * * * @author pablojan@gmail.com (Pablo Ojanguren) * */ public class EventRule { private static final Log LOG = Log.get(EventRule.class); public static Collection<EventRule> fromFile(String confFilePath) { LOG.warning("Loading event rules definitions from " + confFilePath); FileReader fr; try { fr = new FileReader(confFilePath); } catch (FileNotFoundException e) { LOG.warning("Event rules definition's file not found: " + confFilePath); return Collections.<EventRule> emptyList(); } return fromReader(fr); } public static Collection<EventRule> fromReader(Reader r) { JsonParser jsonParser = new JsonParser(); JsonElement jsonElement = null; try { jsonElement = jsonParser.parse(r); } catch (JsonParseException e) { LOG.warning("Error parsing event rules definition file.", e); return Collections.<EventRule> emptyList(); } if (!jsonElement.isJsonArray()) { LOG.warning("Event rules definition's file syntax is incorrect, json root array not found."); return Collections.<EventRule> emptyList(); } JsonArray eventArray = jsonElement.getAsJsonArray(); List<EventRule> rules = new ArrayList<EventRule>(); int s = 0; for (int i = 0; i < eventArray.size(); i++) { try { JsonObject eventJson = eventArray.get(i).getAsJsonObject(); EventRule rule = fromJson(eventJson); if (rule != null) { s++; rules.add(rule); } } catch (RuntimeException e) { LOG.warning("Event rule #" + i + " parsing error"); } } LOG.info(s + " event rules loaded successfully"); return rules; } public static EventRule fromJson(JsonObject jso) { EventRule rule = null; try { Map<String, String> conditionsMap = new HashMap<String, String>(); JsonArray conditions = jso.getAsJsonArray("conditions"); if (conditions != null) { for (int i = 0; i < conditions.size(); i++) { Entry<String, JsonElement> element = conditions.get(i).getAsJsonObject().entrySet() .iterator().next(); conditionsMap.put(element.getKey(), element.getValue().getAsString()); } } rule = new EventRule(jso.get("id").getAsString(), jso.get("app").getAsString(), jso.get("dataType").getAsString(), Event.Type.valueOf(jso.get("type").getAsString()), jso.get("path").getAsString(), conditionsMap); Map<String, String> targetsMap = new HashMap<String, String>(); JsonObject targets = jso.getAsJsonObject("targets"); for(Entry<String, JsonElement> entry: targets.entrySet()) { targetsMap.put(entry.getKey(), entry.getValue().toString()); } rule.setTargets(targetsMap); } catch (RuntimeException e) { LOG.warning("Error parsing event rule definition", e); return null; } catch (InvalidEventExpressionException e) { LOG.warning("Error parsing event rule definition", e); return null; } return rule; } private final String id; private final String app; private final String dataType; private Map<String, String> conditions; private final Event.Type type; private final String path; private Map<String, String> targets; /** * Rules can define path expresions to be evaluated against the data model * associated with an event. * * When a rule matches an event, EventRule can get the evaluation of these * expressions calling to * {@link EventRule#evaluateExpression(Event event, String expresion)} * * Expresions must be provided to the parent constructor in order to be * extracted when events are generated. * * Expresions are like this: * * ${root.key.0.value} ${root.key.?.value} $hash{root.key.0.value} * * the simple version '${<path>}', means 'the value on this path'. other * functions can be used with paths, like '$hash{}' * * ? is a wildcard for list indexes, allowing to extract values for the list * item matched by the event. * */ private final Set<String> expressions = new HashSet<String>(); private final Set<String> expressionsPaths = new HashSet<String>(); public EventRule(String id, String app, String dataType, Type type, String path, Map<String, String> conditions) throws InvalidEventExpressionException { super(); this.id = id; this.app = app; this.dataType = dataType; this.conditions = conditions; this.type = type; this.path = path; } public EventRule(String id, String app, String dataType, Type type, String path) throws InvalidEventExpressionException { super(); this.id = id; this.app = app; this.dataType = dataType; this.type = type; this.path = path; } protected void setTargets(Map<String, String> targets) throws InvalidEventExpressionException { this.targets = targets; // Extract expressions from the JSON payload to provide them to the event // system in advance for (String payloadTemplate: targets.values()) { ExpressionParser parser = new ExpressionParser(payloadTemplate, new ExpressionParser.Operation() { @Override public String onExpression(String expression) { try { if (ExpressionParser.isPathExpresion(expression)) { String expressionPath = ExpressionParser.extractExpressionPath(expression); expressions.add(expression); expressionsPaths.add(expressionPath); } } catch (InvalidEventExpressionException e) { LOG.warning("Error parsing JSON payload", e); } return null; } }); parser.parse(); } } protected boolean matchPath(Event event) { return ExpressionParser.comparePaths(path, event.getPath()); } /** * Precondition paths don't admit wildcards. Hence they can't trasverse lists. * * @param event * @return */ protected boolean matchConditions(Event event) { if (conditions == null) return true; if (event.getContextData() == null) return false; boolean doesItMatch = true; for (Entry<String, String> c : conditions.entrySet()) { String dataValue = event.getContextData().get(c.getKey()); doesItMatch = (dataValue != null) && (dataValue.equals(c.getValue())); if (!doesItMatch) break; } return doesItMatch; } public boolean match(Event event) { Preconditions.checkNotNull(event, "Event can't be null"); boolean doesItMatch = true; doesItMatch = (event.getApp().equals(app)) && (event.getDataType().equals(dataType)); doesItMatch = doesItMatch && event.getType().equals(type); if (!doesItMatch) return false; // Avoid unnecessary further logic doesItMatch = doesItMatch && matchPath(event); if (!doesItMatch) return false; // Avoid unnecessary further logic // Match conditions. doesItMatch = doesItMatch && matchConditions(event); return doesItMatch; } public Set<String> getExpressionsPaths() { HashSet<String> expressions = new HashSet<String>(); expressions.addAll(conditions.keySet()); expressions.addAll(expressionsPaths); return expressions; } @Override public boolean equals(Object obj) { if (obj != null && obj instanceof EventRule) { EventRule er = (EventRule) obj; return this.id.equals(er.id) && this.app.equals(er.app) && this.dataType.equals(er.dataType); } return false; } public String getId() { return id; } public String getApp() { return app; } public String getDataType() { return dataType; } public Set<String> getTargets() { return targets.keySet(); } public Type getType() { return type; } public String getPath() { return path; } public String getEventPayload(String target, final Event event) { ExpressionParser ep = new ExpressionParser(targets.get(target), new ExpressionParser.Operation() { @Override public String onExpression(String expression) { try { return ExpressionParser.evaluateExpression(event, expression); } catch (InvalidEventExpressionException e) { LOG.warning("Error evaluating expression for event", e); } return ""; } }); String payload = ep.replaceParse(); // TODO We could parse as Json the calcultared payload to verify correctness return payload; } @Override public String toString() { return id; } }