Java tutorial
/** * * Copyright (c) 2015 NG Modular Oy. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package io.spikex.filter.internal; import com.google.common.base.Preconditions; import io.spikex.core.helper.Events; import io.spikex.core.helper.Variables; import io.spikex.core.util.Numbers; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.vertx.java.core.json.JsonArray; import org.vertx.java.core.json.JsonObject; /** * * @author cli */ public final class Rule { private final String m_id; private final String m_field; private final String m_tag; private final String m_schedule; private final String m_action; // throttle or modifier private final String m_expression; // Parsii expression private final Object m_fmt; // format of event value private final int m_constraint; private final Object m_value; private final List<? extends Object> m_values; private static final String CONFIG_FIELD_MATCH_FIELD = "match-field"; private static final String CONFIG_FIELD_MATCH_TAG = "match-tag"; private static final String CONFIG_FIELD_SCHEDULE = "schedule"; private static final String CONFIG_FIELD_THROTTLE = "throttle"; private static final String CONFIG_FIELD_MODIFIER = "modifier"; private static final String CONFIG_FIELD_VARIABLES = "variables"; private static final String CONFIG_FIELD_EXPRESSION = "expression"; private static final String CONFIG_FIELD_VALUE_EQUALS = "value-equals"; private static final String CONFIG_FIELD_VALUE_CONTAINS = "value-contains"; private static final String CONFIG_FIELD_VALUE_IN = "value-in"; // Array private static final String CONFIG_FIELD_VALUE_NOT_IN = "value-not-in"; // Array private static final String CONFIG_FIELD_VALUE_LT = "value-lt"; // Less than private static final String CONFIG_FIELD_VALUE_LTE = "value-lte"; // Less than or equal to private static final String CONFIG_FIELD_VALUE_GT = "value-gt"; // Greater than private static final String CONFIG_FIELD_VALUE_GTE = "value-gte"; // Greater than or equal to private static final String CONFIG_FIELD_DATE_LT = "date-lt"; // Less than private static final String CONFIG_FIELD_DATE_LTE = "date-lte"; // Less than or equal to private static final String CONFIG_FIELD_DATE_GT = "date-gt"; // Greater than private static final String CONFIG_FIELD_DATE_GTE = "date-gte"; // Greater than or equal to private static final String CONFIG_FIELD_DATE_FMT = "date-fmt"; // Date format private static final String DEF_DATE_FMT = "yyyy-MM-dd'T'HH:mm:ssZZ"; // Built-ins private static final String BUILTIN_NOW = "#now"; private static final Pattern REGEXP_NOW = Pattern.compile("#now[(]?" + "([A-Z][0-9\\\\w\\\\-\\\\+_/]+)?,?" // Timezone + "([\\\\+\\\\-]?[0-9]+h)?,?" // Hours + "([\\\\+\\\\-]?[0-9]+m)?,?" // Minutes + "([\\\\+\\\\-]?[0-9]+s)?" // Seconds + "[)]"); public static final int CONSTRAINT_EQUALS = 100; public static final int CONSTRAINT_CONTAINS = 101; // String only public static final int CONSTRAINT_IN = 102; public static final int CONSTRAINT_NOT_IN = 103; public static final int CONSTRAINT_LT = 104; public static final int CONSTRAINT_LTE = 105; public static final int CONSTRAINT_GT = 106; public static final int CONSTRAINT_GTE = 107; private static final Logger m_logger = LoggerFactory.getLogger(Rule.class); private Rule(final String id, final String field, final String tag, final String schedule, final String action, final String expression, final Object fmt, final int constraint, final Object value, final List<? extends Object> values) { // Sanity checks Preconditions.checkArgument(id != null && id.length() > 0, "id is null or empty"); Preconditions.checkArgument(action != null && action.length() > 0, "action is null or empty"); Preconditions.checkArgument(schedule != null && schedule.length() > 0, "schedule is null or empty"); m_id = id; m_field = field; m_tag = tag; m_schedule = schedule; m_action = action; m_expression = expression; m_fmt = fmt; m_constraint = constraint; m_value = value; m_values = values; } @Override public int hashCode() { int hash = 5; hash = 59 * hash + Objects.hashCode(m_id); hash = 59 * hash + Objects.hashCode(m_field); hash = 59 * hash + Objects.hashCode(m_tag); hash = 59 * hash + Objects.hashCode(m_value); hash = 59 * hash + Objects.hashCode(m_values); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Rule other = (Rule) obj; if (!Objects.equals(m_id, other.m_id)) { return false; } if (!Objects.equals(m_field, other.m_field)) { return false; } if (!Objects.equals(m_tag, other.m_tag)) { return false; } if (!Objects.equals(m_value, other.m_value)) { return false; } if (!Objects.equals(m_values, other.m_values)) { return false; } return true; } public String getId() { return m_id; } public String getField() { return m_field; } public String getTag() { return m_tag; } public String getSchedule() { return m_schedule; } public String getAction() { return m_action; } public Object getFormat() { return m_fmt; } public String getExpression() { return m_expression; } public boolean match(final JsonObject event) { boolean match = true; boolean matchTag = isMatchTag(); boolean matchField = isMatchField(); // // Matching tag and field (and value)? // if (matchTag && matchField) { match = matchesTag(event) && matchesFieldAndValue(event); } // Matching tag only? else if (matchTag) { match = matchesTag(event); } // Matching field and value only? else if (matchField) { match = matchesFieldAndValue(event); } return match; } @Override public String toString() { String sname = getClass().getSimpleName(); StringBuilder sb = new StringBuilder(sname); sb.append("["); sb.append(hashCode()); sb.append("] id: "); sb.append(getId()); sb.append(" field: "); sb.append(getField()); sb.append(" tag: "); sb.append(getTag()); sb.append(" schedule: "); sb.append(getSchedule()); sb.append(" action: "); sb.append(getAction()); sb.append(" format: "); sb.append(getFormat()); sb.append(" constraint: "); sb.append(m_constraint); sb.append(" value: "); sb.append(m_value); sb.append(" values: "); sb.append(m_values); return sb.toString(); } private boolean matchesTag(final JsonObject event) { boolean match = false; JsonArray tags = event.getArray(Events.EVENT_FIELD_TAGS); if (tags != null && tags.contains(getTag())) { match = true; } return match; } private boolean matchesFieldAndValue(final JsonObject event) { boolean match = false; String field = getField(); if (field.length() > 0) { Object value = null; int pos = field.indexOf('/'); // Sorry, only one level of nesting... if (pos >= 0) { String field1 = field.substring(0, pos); String field2 = field.substring(pos + 1); if (event.containsField(field1)) { JsonObject map = event.getObject(field1); if (map != null && map.containsField(field2)) { value = map.getValue(field2); } } } else { if (event.containsField(field)) { value = event.getValue(field); } } if (value != null) { match = matchValue(value); } } return match; } private boolean matchValue(final Object eventValue) { boolean match = true; // // Handle array and single value // if (eventValue instanceof JsonArray) { JsonArray array = (JsonArray) eventValue; for (int i = 0; i < array.size(); i++) { Object singleValue = array.get(i); if (!matchEventValue(singleValue)) { match = false; break; } } } else { // Single value match = matchEventValue(eventValue); } return match; } private boolean isMatchField() { return (m_field.length() > 0); } private boolean isMatchTag() { return (m_tag.length() > 0); } private boolean matchEventValue(final Object eventValue) { boolean match = false; Object value = m_value; // Just matching field name - no constraint or value given if (m_constraint == -1 && value == null) { return true; } switch (m_constraint) { // String, date numerical case CONSTRAINT_EQUALS: { if (value != null) { match = (compareToValue(value, eventValue) == 0); } else { match = true; List<? extends Object> values = m_values; for (Object val : values) { if (compareToValue(val, eventValue) != 0) { match = false; break; } } } break; } // String (array) case CONSTRAINT_CONTAINS: { match = true; List<? extends Object> values = m_values; for (Object val : values) { if (!containsString((String) val, eventValue)) { match = false; break; } } break; } // String or numerical case CONSTRAINT_NOT_IN: match = true; case CONSTRAINT_IN: { List<? extends Object> values = m_values; for (Object val : values) { String strValue; if (val instanceof String) { strValue = (String) val; } else { strValue = String.valueOf(val); } if (compareToString(strValue, eventValue) == 0) { match = !match; break; } } break; } // Numerical, date or string case CONSTRAINT_LT: { match = (compareToValue(value, eventValue) > 0); break; } // Numerical, date or string case CONSTRAINT_LTE: { match = (compareToValue(value, eventValue) >= 0); break; } // Numerical, date or string case CONSTRAINT_GT: { int diff = compareToValue(value, eventValue); match = (diff < 0 && diff != Integer.MIN_VALUE); break; } // Numerical, date or string case CONSTRAINT_GTE: { int diff = compareToValue(value, eventValue); match = (diff <= 0 && diff != Integer.MIN_VALUE); break; } } return match; } private boolean containsString(final String str, final Object obj) { boolean contains = false; if (obj != null) { String value; if (obj instanceof String) { value = (String) obj; } else { value = String.valueOf(obj); } contains = value.contains(str); } return contains; } private int compareToValue(final Object value, final Object obj) { if (value instanceof DateTime) { // DateTime return compareToDateTime((DateTime) value, obj); } else if (value instanceof BigDecimal) { // BigDecimal return compareToNumber((BigDecimal) value, obj); } else { return compareToString(String.valueOf(value), obj); } } private int compareToString(final String str, final Object obj) { if (obj instanceof String) { return str.compareTo((String) obj); } else { return str.compareTo(String.valueOf(obj)); } } private int compareToNumber(final BigDecimal value, final Object obj) { int diff = Integer.MIN_VALUE; if (obj instanceof Number) { diff = compareToBigDecimal(value, obj); } else { String strValue = String.valueOf(obj); if (Numbers.isDecimal(strValue) || Numbers.isInteger(strValue)) { diff = compareToBigDecimal(value, obj); } } return diff; } private int compareToBigDecimal(final BigDecimal n1, final Object obj) { String n2; if (obj instanceof String) { n2 = (String) obj; } else { n2 = String.valueOf(obj); } return n1.compareTo(new BigDecimal(n2)); } private int compareToDateTime(final DateTime d1, final Object obj) { DateTimeFormatter fmt = (DateTimeFormatter) m_fmt; String dateStr = String.valueOf(obj); DateTime d2 = fmt.parseDateTime(dateStr); // https://github.com/JodaOrg/joda-time/issues/73 return d1.toLocalDateTime().compareTo(d2.toLocalDateTime()); } public static Rule create(final String id, final JsonObject config) { int constraint = -1; Object value = null; List values = new ArrayList(); Object fmt = ""; if (config.containsField(CONFIG_FIELD_VALUE_EQUALS)) { constraint = CONSTRAINT_EQUALS; value = createBigDecimal(config, CONFIG_FIELD_VALUE_EQUALS); if (value == null) { values.add(config.getString(CONFIG_FIELD_VALUE_EQUALS)); } } else if (config.containsField(CONFIG_FIELD_VALUE_CONTAINS)) { constraint = CONSTRAINT_CONTAINS; Object strings = config.getValue(CONFIG_FIELD_VALUE_CONTAINS); if (!(strings instanceof JsonArray)) { strings = new JsonArray().add(strings); } int len = ((JsonArray) strings).size(); for (int i = 0; i < len; i++) { String str = String.valueOf(((JsonArray) strings).get(i)); Preconditions.checkArgument(str != null && str.length() > 0, CONFIG_FIELD_VALUE_CONTAINS + " must not be null or empty"); values.add(str); } } else if (config.containsField(CONFIG_FIELD_VALUE_IN)) { constraint = CONSTRAINT_IN; JsonArray array = config.getArray(CONFIG_FIELD_VALUE_IN); values = array.toList(); } else if (config.containsField(CONFIG_FIELD_VALUE_NOT_IN)) { constraint = CONSTRAINT_NOT_IN; JsonArray array = config.getArray(CONFIG_FIELD_VALUE_NOT_IN); values = array.toList(); } else if (config.containsField(CONFIG_FIELD_VALUE_LT)) { constraint = CONSTRAINT_LT; value = createBigDecimal(config, CONFIG_FIELD_VALUE_LT); Preconditions.checkNotNull(value, "Non-numeric value defined for: " + CONFIG_FIELD_VALUE_LT); } else if (config.containsField(CONFIG_FIELD_VALUE_LTE)) { constraint = CONSTRAINT_LTE; value = createBigDecimal(config, CONFIG_FIELD_VALUE_LTE); Preconditions.checkNotNull(value, "Non-numeric value defined for: " + CONFIG_FIELD_VALUE_LTE); } else if (config.containsField(CONFIG_FIELD_VALUE_GT)) { constraint = CONSTRAINT_GT; value = createBigDecimal(config, CONFIG_FIELD_VALUE_GT); Preconditions.checkNotNull(value, "Non-numeric value defined for: " + CONFIG_FIELD_VALUE_GT); } else if (config.containsField(CONFIG_FIELD_VALUE_GTE)) { constraint = CONSTRAINT_GTE; value = createBigDecimal(config, CONFIG_FIELD_VALUE_GTE); Preconditions.checkNotNull(value, "Non-numeric value defined for: " + CONFIG_FIELD_VALUE_GTE); } else if (config.containsField(CONFIG_FIELD_DATE_LT)) { constraint = CONSTRAINT_LT; value = createDateTime(config, CONFIG_FIELD_DATE_LT); fmt = DateTimeFormat.forPattern(config.getString(CONFIG_FIELD_DATE_FMT, DEF_DATE_FMT)); } else if (config.containsField(CONFIG_FIELD_DATE_LTE)) { constraint = CONSTRAINT_LTE; value = createDateTime(config, CONFIG_FIELD_DATE_LTE); fmt = DateTimeFormat.forPattern(config.getString(CONFIG_FIELD_DATE_FMT, DEF_DATE_FMT)); } else if (config.containsField(CONFIG_FIELD_DATE_GT)) { constraint = CONSTRAINT_GT; value = createDateTime(config, CONFIG_FIELD_DATE_GT); fmt = DateTimeFormat.forPattern(config.getString(CONFIG_FIELD_DATE_FMT, DEF_DATE_FMT)); } else if (config.containsField(CONFIG_FIELD_DATE_GTE)) { constraint = CONSTRAINT_GTE; value = createDateTime(config, CONFIG_FIELD_DATE_GTE); fmt = DateTimeFormat.forPattern(config.getString(CONFIG_FIELD_DATE_FMT, DEF_DATE_FMT)); } String action = ""; if (config.containsField(CONFIG_FIELD_THROTTLE)) { action = config.getString(CONFIG_FIELD_THROTTLE); } else if (config.containsField(CONFIG_FIELD_MODIFIER)) { action = config.getString(CONFIG_FIELD_MODIFIER); } String expression = config.getString(CONFIG_FIELD_EXPRESSION); return new Rule(id, config.getString(CONFIG_FIELD_MATCH_FIELD, ""), config.getString(CONFIG_FIELD_MATCH_TAG, ""), config.getString(CONFIG_FIELD_SCHEDULE, "* * * *"), action, expression, fmt, constraint, value, values); } private static BigDecimal createBigDecimal(final JsonObject config, final String field) { BigDecimal bd = null; Object numValue = config.getValue(field); if (numValue instanceof Number) { bd = new BigDecimal(String.valueOf(numValue)); } else { String strValue = config.getString(field); if (Numbers.isDecimal(strValue) || Numbers.isInteger(strValue)) { bd = new BigDecimal(strValue); } } return bd; } private static DateTime createDateTime(final JsonObject config, final String field) { DateTime dt; String dateStr = config.getString(field); m_logger.trace("date string: {}", dateStr); if (dateStr != null && dateStr.startsWith(BUILTIN_NOW)) { dt = Variables.createDateTimeNow(dateStr); } else { DateTimeFormatter fmt = ISODateTimeFormat.dateTime(); dt = fmt.parseDateTime(dateStr); } return dt; } }