ConstraintMatcher<T> matcher = createMatcher(user, constraints); matchers.add(matcher);
@Override public Task<Boolean> matchesAsync(T object, ParseSQLiteDatabase db) { if (!ignoreACLs && !hasReadAccess(user, object)) { return Task.forResult(false); } return constraintMatcher.matchesAsync(object, db); } };
/** * Matches $all constraints. */ private static boolean matchesAllConstraint(Object constraint, Object value) { if (value == null || value == JSONObject.NULL) { return false; } if (!(value instanceof Collection)) { throw new IllegalArgumentException("Value type not supported for $all queries."); } if (constraint instanceof Collection) { if (isAnyValueRegexStartsWith((Collection<?>) constraint)) { constraint = cleanRegexStartsWith((Collection<?>) constraint); if (constraint == null) { throw new IllegalArgumentException("All values in $all queries must be of starting with regex or non regex."); } } for (Object requiredItem : (Collection<?>) constraint) { if (!matchesEqualConstraint(requiredItem, value)) { return false; } } return true; } throw new IllegalArgumentException("Constraint type not supported for $all queries."); }
private <T extends ParseObject> ConstraintMatcher<T> createMatcher(ParseUser user, final String operator, final Object constraint, final String key, final KeyConstraints allKeyConstraints) { switch (operator) { case "$inQuery": return createInQueryMatcher(user, constraint, key); case "$notInQuery": return createNotInQueryMatcher(user, constraint, key); case "$select": return createSelectMatcher(user, constraint, key); case "$dontSelect": return createDontSelectMatcher(user, constraint, key); default: /* * All of the other operators we know about are stateless, so return a simple matcher. */ return new ConstraintMatcher<T>(user) { @Override public Task<Boolean> matchesAsync(T object, ParseSQLiteDatabase db) { try { Object value = getValue(object, key); return Task.forResult(matchesStatelessConstraint(operator, constraint, value, allKeyConstraints)); } catch (ParseException e) { return Task.forError(e); } } }; } }
/** * Returns true if the decider returns true for the given value and the given constraint. This * method handles Mongo's logic where an item can match either an item itself, or any item within * the item, if the item is an array. */ private static boolean compare(Object constraint, Object value, Decider decider) { if (value instanceof List) { return compareList(constraint, (List<?>) value, decider); } else if (value instanceof JSONArray) { return compareArray(constraint, (JSONArray) value, decider); } else { return decider.decide(constraint, value); } }
@Test public void testMatcherWithNoReadAccess() throws ParseException { OfflineQueryLogic logic = new OfflineQueryLogic(null); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .build(); ParseACL acl = new ParseACL(); acl.setPublicReadAccess(false); ParseObject object = new ParseObject("TestObject"); object.setACL(acl); ParseUser user = mock(ParseUser.class); when(user.getObjectId()).thenReturn("test"); assertFalse(matches(logic, query, object, user)); }
return compareTo(constraint, value) == 0; if (isStartsWithRegex(constraint)) { decider = new Decider() { @Override return compare(constraint, value, decider);
createOrMatcher(user, (ArrayList<QueryConstraints>) queryConstraintValue); matchers.add(matcher); final Object keyConstraintValue = keyConstraints.get(operator); ConstraintMatcher<T> matcher = createMatcher(user, operator, keyConstraintValue, key, keyConstraints); matchers.add(matcher);
/** * Matches $lte constraints. */ private static boolean matchesLessThanOrEqualToConstraint(Object constraint, Object value) { return compare(constraint, value, new Decider() { @Override public boolean decide(Object constraint, Object value) { if (value == null || value == JSONObject.NULL) { return false; } return compareTo(constraint, value) >= 0; } }); }
/** * Creates a matcher that handles $notInQuery constraints. */ private <T extends ParseObject> ConstraintMatcher<T> createNotInQueryMatcher(ParseUser user, Object constraint, final String key) { final ConstraintMatcher<T> inQueryMatcher = createInQueryMatcher(user, constraint, key); return new ConstraintMatcher<T>(user) { @Override public Task<Boolean> matchesAsync(T object, ParseSQLiteDatabase db) { return inQueryMatcher.matchesAsync(object, db).onSuccess(new Continuation<Boolean, Boolean>() { @Override public Boolean then(Task<Boolean> task) { return !task.getResult(); } }); } }; }
/** * Cleans all regex constraints. If any of the constraints is not a regex, then null is returned. * All values in a $all constraint must be a starting with another string regex. */ private static Collection<?> cleanRegexStartsWith(Collection<?> constraints) { ArrayList<KeyConstraints> cleanedValues = new ArrayList<>(); for (Object constraint : constraints) { if (!(constraint instanceof KeyConstraints)) { return null; } KeyConstraints cleanedRegex = cleanRegexStartsWith((KeyConstraints) constraint); if (cleanedRegex == null) { return null; } cleanedValues.add(cleanedRegex); } return cleanedValues; }
@Test public void testCompareList() throws Exception { ParseObject object = new ParseObject("SomeObject"); List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); object.put("list", list); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("list", 2) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("list", 4) .build(); assertFalse(matches(logic, query, object)); }
/** * Matches $gt constraints. */ private static boolean matchesGreaterThanConstraint(Object constraint, Object value) { return compare(constraint, value, new Decider() { @Override public boolean decide(Object constraint, Object value) { if (value == null || value == JSONObject.NULL) { return false; } return compareTo(constraint, value) < 0; } }); }
@Test public void testCompareJSONArray() throws Exception { ParseObject object = new ParseObject("SomeObject"); JSONArray array = new JSONArray(); array.put(1); array.put(2); array.put(3); object.put("array", array); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("array", 2) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("array", 4) .build(); assertFalse(matches(logic, query, object)); }
@Test public void testHasReadAccessWithSameObject() { ParseUser user = mock(ParseUser.class); assertTrue(OfflineQueryLogic.hasReadAccess(user, user)); verify(user, never()).getACL(); }
/** * Returns a ConstraintMatcher that return true iff the object matches the given query's * constraints. This takes in a SQLiteDatabase connection because SQLite is finicky about nesting * connections, so we want to reuse them whenever possible. * * @param state The query. * @param user The user we are testing ACL access for. * @param <T> Subclass of ParseObject. * @return A new instance of ConstraintMatcher. */ /* package */ <T extends ParseObject> ConstraintMatcher<T> createMatcher( ParseQuery.State<T> state, final ParseUser user) { final boolean ignoreACLs = state.ignoreACLs(); final ConstraintMatcher<T> constraintMatcher = createMatcher(user, state.constraints()); return new ConstraintMatcher<T>(user) { @Override public Task<Boolean> matchesAsync(T object, ParseSQLiteDatabase db) { if (!ignoreACLs && !hasReadAccess(user, object)) { return Task.forResult(false); } return constraintMatcher.matchesAsync(object, db); } }; }
/** * Matches $gte constraints. */ private static boolean matchesGreaterThanOrEqualToConstraint(Object constraint, Object value) { return compare(constraint, value, new Decider() { @Override public boolean decide(Object constraint, Object value) { if (value == null || value == JSONObject.NULL) { return false; } return compareTo(constraint, value) <= 0; } }); }
@Test public void testMatchesGeoWithin() throws ParseException { List<ParseGeoPoint> smallBox = new ArrayList<>(); smallBox.add(new ParseGeoPoint(0, 0)); smallBox.add(new ParseGeoPoint(0, 1)); smallBox.add(new ParseGeoPoint(1, 1)); smallBox.add(new ParseGeoPoint(1, 0)); List<ParseGeoPoint> largeBox = new ArrayList<>(); largeBox.add(new ParseGeoPoint(0, 0)); largeBox.add(new ParseGeoPoint(0, 10)); largeBox.add(new ParseGeoPoint(10, 10)); largeBox.add(new ParseGeoPoint(10, 0)); ParseGeoPoint point = new ParseGeoPoint(5, 5); //ParsePolygon polygon = new ParsePolygon(points); ParseObject object = new ParseObject("TestObject"); object.put("point", point); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .whereGeoWithin("point", largeBox) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .whereGeoWithin("point", smallBox) .build(); assertFalse(matches(logic, query, object)); // Non-existant key object = new ParseObject("TestObject"); assertFalse(matches(logic, query, object)); }
@Test public void testHasReadAccessWithReadAccess() { ParseUser user = mock(ParseUser.class); when(user.getObjectId()).thenReturn("test"); ParseACL acl = mock(ParseACL.class); when(acl.getReadAccess(user)).thenReturn(true); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertTrue(OfflineQueryLogic.hasReadAccess(user, object)); }
private static <T extends ParseObject> boolean matches( OfflineQueryLogic logic, ParseQuery.State<T> query, T object, ParseUser user) throws ParseException { Task<Boolean> task = logic.createMatcher(query, user).matchesAsync(object, null); return ParseTaskUtils.wait(task); }