package automately.core.data.predicates; import com.hazelcast.query.Predicate; import io.jsync.json.JsonArray; import io.jsync.json.JsonElement; import io.jsync.json.JsonObject; import java.io.Serializable; import java.math.BigDecimal; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A simple Hazelcast Predicate that allows you to filter data using * queries in the form of a JsonObject. */ public class JsonQueryPredicate implements Serializable, Predicate { final private JsonElement query; public JsonQueryPredicate(JsonElement query) { if(query == null){ throw new IllegalArgumentException("Your query cannot be null"); } this.query = query; } @Override public boolean apply(Map.Entry entry) { Object obj = entry.getValue(); if(obj instanceof JsonObject && query.isObject()){ // Since the entry is a single JsonObject we will match a single return processJsonObjectQuery((JsonObject) obj, query.asObject()); } else if(obj instanceof JsonObject && query.isArray()){ // We can loop through the query array to check each query against the value for(Object q : query.asArray()){ // Since the query is an actual JsonObject we can treat it as a Json Query if(q instanceof JsonObject){ JsonObject jsonQuery = (JsonObject) q; if(processJsonObjectQuery((JsonObject) obj, jsonQuery)){ return true; } } } } else if(obj instanceof JsonArray){ // Loop through all the values in this entry // If any value matches we will return this entry for(Object inObj : (JsonArray) obj){ // If the value is a json object we will process it with the query array if(inObj instanceof JsonObject){ JsonObject jsonVal = (JsonObject) inObj; if(query.isObject()){ if(processJsonObjectQuery(jsonVal, query.asObject())){ return true; } } else { // We can loop through the query array to check each query against the value for(Object q : query.asArray()){ // Since the query is an actual JsonObject we can treat it as a Json Query if(q instanceof JsonObject){ JsonObject jsonQuery = (JsonObject) q; if(processJsonObjectQuery(jsonVal, jsonQuery)){ return true; } } } } } } } // If no JsonObjects were found we will return false by default since we are looking for Json Data return false; } private boolean processJsonObjectQuery(JsonObject value, JsonObject query){ // An empty query is the same as a wildcard but without a value if(query.size() == 0){ return true; } // We return false for sure because that doesn't even make sense if(query.size() > value.size()){ return false; } JsonObject valueObj = value; boolean success = false; for(String field : query.getFieldNames()){ Object queryValue = query.getValue(field); if(field.contains(".")){ String[] subFields = field.split("\\."); JsonObject newValue = null; String newField = null; for(String sub : subFields){ if(newValue == null){ if(value.containsField(sub) && value.getValue(sub) instanceof JsonObject){ newValue = value.getObject(sub); continue; } break; } newField = sub; if(newValue.containsField(sub) && newValue.getValue(sub) instanceof JsonObject){ newValue = newValue.getObject(sub); } } if(newValue != null && newField != null && field.endsWith("." + newField)){ valueObj = newValue; field = newField; } } // If it doesn't contain the field we cannot match it. if(!valueObj.containsField(field)){ return false; } // This is the object we are checking Object value2 = valueObj.getValue(field); if(value2 instanceof JsonArray){ JsonArray array = (JsonArray) value2; for(Object val : array.toArray()){ if(checkValue(queryValue, val)){ success = true; } } // if it's an array let's loop through it } else { if(checkValue(queryValue, value2)){ success = true; } else { return false; } } } // An empty query is the same as a wildcard but without a value return success; } private static boolean checkValue(Object queryVal, Object value2){ if(queryVal.equals(value2)){ return true; } else if(queryVal instanceof String && queryVal.toString().equals("*")){ return true; } else if(queryVal instanceof String && queryVal.toString().matches("^v>\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split(">")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) > 0){ return true; } } else if(queryVal instanceof String && queryVal.toString().matches("^v<\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split("<")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) < 0){ return true; } } else if(queryVal instanceof String && queryVal.toString().matches("^v>=\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split(">=")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) >= 0){ return true; } } else if(queryVal instanceof String && queryVal.toString().matches("^v<=\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split("<=")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) <= 0){ return true; } } else if(queryVal instanceof String && queryVal.toString().matches("^v=\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split("=")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) == 0){ return true; } } else if(queryVal instanceof String && queryVal.toString().matches("^v==\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split("==")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) == 0){ return true; } } else if(queryVal instanceof String && queryVal.toString().matches("^v!=\\d+$") && value2 instanceof Number){ Number numb1 = Long.parseLong(queryVal.toString().split("!=")[1]); Number numb2 = (Number) value2; if(compareTo(numb2, numb1) != 0){ return true; } } else if(queryVal instanceof Boolean && value2 instanceof Boolean){ if(queryVal == value2){ return true; } } else if(queryVal instanceof String && value2 instanceof String){ try { Pattern p = Pattern.compile((String) queryVal); Matcher m = p.matcher((String) value2); if(m.matches()) { return true; } else if(m.find()){ return true; } } catch (Exception ignored){ // we ignore this since the pattern will not match } } else if(queryVal instanceof JsonArray){ JsonArray queryArray = (JsonArray) queryVal; // We check the value against an array of query stuff for(Object qv : queryArray){ if(checkValue(qv, value2)){ return true; } } } else if(queryVal instanceof JsonObject){ // JSON objects are more like mongo queries JsonObject queryObj = (JsonObject) queryVal; // By default an empty query value boolean success = false; for(String newF : queryObj.getFieldNames()){ Object obj = queryObj.getValue(newF); if(obj != null){ // We already know they key exists or we would not be doing this if(newF.equals("$exists") && obj instanceof Boolean){ success = (boolean) obj; } else if(newF.equals("$eq")){ success = value2.equals(obj); } else if(newF.equals("$gt") && obj instanceof Number && value2 instanceof Number){ success = checkValue("v>" + ((Number) obj).longValue(), value2); } else if(newF.equals("$gte") && obj instanceof Number && value2 instanceof Number){ success = checkValue("v>=" + ((Number) obj).longValue(), value2); } else if(newF.equals("$lt") && obj instanceof Number && value2 instanceof Number){ success = checkValue("v<" + ((Number) obj).longValue(), value2); } else if(newF.equals("$lte") && obj instanceof Number && value2 instanceof Number){ success = checkValue("v<=" + ((Number) obj).longValue(), value2); } else if(newF.equals("$ne") && obj instanceof Number && value2 instanceof Number){ success = checkValue("v!=" + ((Number) obj).longValue(), value2); } else if(newF.equals("$in") && obj instanceof JsonArray){ JsonArray values = (JsonArray) obj; for(Object value : values){ if(value2.equals(value)){ success = true; break; } else { try { Pattern p = Pattern.compile((String) value); Matcher m = p.matcher((String) value2); if(m.matches()) { success = true; break; } else if(m.find()){ success = true; break; } } catch (Exception ignored){ // we ignore this since the pattern will not match } } } } else if(newF.equals("$nin") && obj instanceof JsonArray){ // None of the values can exist success = true; // Let's say it's successful JsonArray values = (JsonArray) obj; for(Object value : values){ if(value2.equals(value)){ success = false; break; } else { try { Pattern p = Pattern.compile((String) value); Matcher m = p.matcher((String) value2); if(m.matches()) { success = false; break; } else if(m.find()){ success = false; break; } } catch (Exception ignored){ // we ignore this since the pattern will not match } } } } } } return success; } // If it doesn't match that means the query didn't line up return false; } private static int compareTo(Number n1, Number n2) { // ignoring null handling BigDecimal b1 = new BigDecimal(n1.doubleValue()); BigDecimal b2 = new BigDecimal(n2.doubleValue()); return b1.compareTo(b2); } }