Java tutorial
/******************************************************************************* * Copyright 2015, The IKANOW Open Source Project. * * 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 com.ikanow.aleph2.data_model.utils; import static org.junit.Assert.*; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.EnumSet; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collector.Characteristics; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; import org.mongojack.internal.MongoJackModule; import org.mongojack.internal.object.BsonObjectGenerator; import scala.Tuple2; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Maps; import com.ikanow.aleph2.data_model.utils.CrudUtils.BeanUpdateComponent; import com.ikanow.aleph2.data_model.utils.CrudUtils.MultiQueryComponent; import com.ikanow.aleph2.data_model.utils.CrudUtils.Operator; import com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent; import com.ikanow.aleph2.data_model.utils.CrudUtils.SingleQueryComponent; import com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent; import com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateOperator; import com.ikanow.aleph2.data_model.utils.BeanTemplateUtils.BeanTemplate; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; public class TestCrudUtils { // Some utility code (which actually end up being the basis for the mongodb crud operator...) private static BasicDBObject operatorToMongoKey(String field, Tuple2<Operator, Tuple2<Object, Object>> operator_args) { return Patterns.match(operator_args).<BasicDBObject>andReturn() .when(op_args -> Operator.exists == op_args._1(), op_args -> new BasicDBObject(field, new BasicDBObject("$exists", op_args._2()._1()))) .when(op_args -> (Operator.any_of == op_args._1()), op_args -> new BasicDBObject(field, new BasicDBObject("$in", op_args._2()._1()))) .when(op_args -> (Operator.all_of == op_args._1()), op_args -> new BasicDBObject(field, new BasicDBObject("$all", op_args._2()._1()))) .when(op_args -> (Operator.equals == op_args._1()) && (null != op_args._2()._2()), op_args -> new BasicDBObject(field, new BasicDBObject("$ne", op_args._2()._2()))) .when(op_args -> (Operator.equals == op_args._1()), op_args -> new BasicDBObject(field, op_args._2()._1())) .when(op_args -> Operator.range_open_open == op_args._1(), op_args -> { QueryBuilder qb = QueryBuilder.start(field); if (null != op_args._2()._1()) qb = qb.greaterThan(op_args._2()._1()); if (null != op_args._2()._2()) qb = qb.lessThan(op_args._2()._2()); return (BasicDBObject) qb.get(); }).when(op_args -> Operator.range_open_closed == op_args._1(), op_args -> { QueryBuilder qb = QueryBuilder.start(field); if (null != op_args._2()._1()) qb = qb.greaterThan(op_args._2()._1()); if (null != op_args._2()._2()) qb = qb.lessThanEquals(op_args._2()._2()); return (BasicDBObject) qb.get(); }).when(op_args -> Operator.range_closed_closed == op_args._1(), op_args -> { QueryBuilder qb = QueryBuilder.start(field); if (null != op_args._2()._1()) qb = qb.greaterThanEquals(op_args._2()._1()); if (null != op_args._2()._2()) qb = qb.lessThanEquals(op_args._2()._2()); return (BasicDBObject) qb.get(); }).when(op_args -> Operator.range_closed_open == op_args._1(), op_args -> { QueryBuilder qb = QueryBuilder.start(field); if (null != op_args._2()._1()) qb = qb.greaterThanEquals(op_args._2()._1()); if (null != op_args._2()._2()) qb = qb.lessThan(op_args._2()._2()); return (BasicDBObject) qb.get(); }).otherwise(op_args -> new BasicDBObject()); } private static String getOperatorName(Operator op_in) { return Patterns.match(op_in).<String>andReturn().when(op -> Operator.any_of == op, op -> "$or") .when(op -> Operator.all_of == op, op -> "$and").otherwise(op -> "$and"); } @SuppressWarnings("unchecked") private static <T> Tuple2<DBObject, DBObject> convertToMongoQuery(QueryComponent<T> query_in) { final String andVsOr = getOperatorName(query_in.getOp()); final DBObject query_out = Patterns.match(query_in).<DBObject>andReturn() .when((Class<SingleQueryComponent<T>>) (Class<?>) SingleQueryComponent.class, q -> convertToMongoQuery_single(andVsOr, q)) .when((Class<MultiQueryComponent<T>>) (Class<?>) MultiQueryComponent.class, q -> convertToMongoQuery_multi(andVsOr, q)) .otherwise(q -> (DBObject) new BasicDBObject()); // Meta commands BasicDBObject meta = new BasicDBObject(); if (null != query_in.getLimit()) meta.put("$limit", query_in.getLimit()); final BasicDBObject sort = Patterns.match(query_in.getOrderBy()).<BasicDBObject>andReturn() .when(l -> l == null, l -> null).otherwise(l -> { BasicDBObject s = new BasicDBObject(); l.stream().forEach(field_order -> s.put(field_order._1(), field_order._2())); return s; }); if (null != sort) meta.put("$sort", sort); return Tuples._2T(query_out, meta); } @SuppressWarnings("unchecked") protected static <T> DBObject convertToMongoQuery_multi(final String andVsOr, final MultiQueryComponent<T> query_in) { return Patterns.match(query_in.getElements()).<DBObject>andReturn() .when(f -> f.isEmpty(), f -> new BasicDBObject()) .otherwise(f -> f.stream().collect(Collector.of(BasicDBList::new, (acc, entry) -> { Patterns.match(entry).andAct() .when(SingleQueryComponent.class, e -> acc.add(convertToMongoQuery_single(getOperatorName(e.getOp()), e))) .when(MultiQueryComponent.class, e -> acc.add(convertToMongoQuery_multi( getOperatorName(((MultiQueryComponent<?>) e).getOp()), (MultiQueryComponent<?>) e))); //(at various other points in the code, oraclej has complained about this though ecj is happy, so just added these casts for safety) }, (a, b) -> { a.addAll(b); return a; }, acc -> (DBObject) new BasicDBObject(andVsOr, acc), Characteristics.UNORDERED))); } private static <T> DBObject convertToMongoQuery_single(String andVsOr, SingleQueryComponent<T> query_in) { LinkedHashMultimap<String, Tuple2<Operator, Tuple2<Object, Object>>> fields = query_in.getAll(); // The actual query: return Patterns.match(fields).<DBObject>andReturn().when(f -> f.isEmpty(), f -> new BasicDBObject()) .otherwise(f -> f.asMap().entrySet().stream() .<Tuple2<String, Tuple2<Operator, Tuple2<Object, Object>>>>flatMap( entry -> entry.getValue().stream().map(val -> Tuples._2T(entry.getKey(), val))) .collect( new Collector<Tuple2<String, Tuple2<Operator, Tuple2<Object, Object>>>, BasicDBObject, DBObject>() { @Override public Supplier<BasicDBObject> supplier() { return BasicDBObject::new; } @Override public BiConsumer<BasicDBObject, Tuple2<String, Tuple2<Operator, Tuple2<Object, Object>>>> accumulator() { return (acc, entry) -> { Patterns.match(acc.get(andVsOr)).andAct().when(l -> (null == l), l -> { BasicDBList dbl = new BasicDBList(); dbl.add(operatorToMongoKey(entry._1(), entry._2())); acc.put(andVsOr, dbl); }).when(BasicDBList.class, l -> l.add(operatorToMongoKey(entry._1(), entry._2()))) .otherwise(l -> { }); }; } // Boilerplate: @Override public BinaryOperator<BasicDBObject> combiner() { return (a, b) -> { a.putAll(b.toMap()); return a; }; } @Override public Function<BasicDBObject, DBObject> finisher() { return acc -> acc; } @Override public Set<java.util.stream.Collector.Characteristics> characteristics() { return EnumSet.of(Characteristics.UNORDERED); } })); } // Test objects public static class TestBean { public static class NestedNestedTestBean { public String nested_nested_string_field() { return nested_nested_string_field; } private String nested_nested_string_field; } public static class NestedTestBean { public String nested_string_field() { return nested_string_field; } public NestedNestedTestBean nested_object() { return nested_object; } public List<String> nested_string_list() { return nested_string_list; } private String nested_string_field; private List<String> nested_string_list; private NestedNestedTestBean nested_object; } public String string_field() { return string_field; } public List<String> string_fields() { return string_fields; } public Boolean bool_field() { return bool_field; } public Long long_field() { return long_field; } public List<NestedTestBean> nested_list() { return nested_list; } public Map<String, String> map() { return map; } public NestedTestBean nested_object() { return nested_object; } protected TestBean() { } private String string_field; private List<String> string_fields; private Boolean bool_field; private Long long_field; private List<NestedTestBean> nested_list; private NestedTestBean nested_object; private Map<String, String> map; } @Test public void emptyQuery() { // No meta: final SingleQueryComponent<TestBean> query_comp_1 = CrudUtils.allOf(BeanTemplate.of(new TestBean())); System.out.println(query_comp_1.toString()); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); assertEquals(null, query_comp_1.getExtra()); assertEquals("{ }", query_meta_1._1().toString()); assertEquals("{ }", query_meta_1._2().toString()); // Meta fields BeanTemplate<TestBean> template2 = BeanTemplateUtils.build(TestBean.class) .with(TestBean::string_field, null).done(); final SingleQueryComponent<TestBean> query_comp_2 = CrudUtils.anyOf(template2) .orderBy(Tuples._2T("test_field_1", 1), Tuples._2T("test_field_2", -1)); System.out.println(query_comp_2.toString()); assertEquals(template2.get(), query_comp_2.getElement()); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(query_comp_2); assertEquals("{ }", query_meta_2._1().toString()); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_1", 1); expected_meta_nested.put("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$sort", expected_meta_nested); assertEquals(expected_meta.toString(), query_meta_2._2().toString()); } @Test public void basicSingleTest() { // Queries starting with allOf // Very simple BeanTemplate<TestBean> template1 = BeanTemplateUtils.build(TestBean.class) .with(TestBean::string_field, "string_field").done(); final SingleQueryComponent<TestBean> query_comp_1 = CrudUtils.allOf(template1).when(TestBean::bool_field, true); System.out.println(query_comp_1.toString()); final SingleQueryComponent<JsonNode> query_comp_1_json1 = query_comp_1.toJson(); System.out.println(query_comp_1_json1.toString()); final SingleQueryComponent<TestBean> query_comp_1b = CrudUtils.allOf(TestBean.class) .when("bool_field", true).when("string_field", "string_field"); System.out.println(query_comp_1b.toString()); final SingleQueryComponent<JsonNode> query_comp_1b_json1 = query_comp_1b.toJson(); System.out.println(query_comp_1b_json1.toString()); final SingleQueryComponent<JsonNode> query_comp_1b_json2 = CrudUtils.allOf().when("bool_field", true) .when("string_field", "string_field"); System.out.println(query_comp_1b_json2.toString()); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); final Tuple2<DBObject, DBObject> query_meta_1_json = convertToMongoQuery(query_comp_1_json1); final Tuple2<DBObject, DBObject> query_meta_1b = convertToMongoQuery(query_comp_1b); final Tuple2<DBObject, DBObject> query_meta_1b_json1 = convertToMongoQuery(query_comp_1b_json1); final Tuple2<DBObject, DBObject> query_meta_1b_json2 = convertToMongoQuery(query_comp_1b_json2); final DBObject expected_1 = QueryBuilder.start().and(QueryBuilder.start("bool_field").is(true).get(), QueryBuilder.start("string_field").is("string_field").get()).get(); assertEquals(expected_1.toString(), query_meta_1._1().toString()); assertEquals(expected_1.toString(), query_meta_1_json._1().toString()); assertEquals(expected_1.toString(), query_meta_1b._1().toString()); assertEquals(expected_1.toString(), query_meta_1b_json1._1().toString()); assertEquals(expected_1.toString(), query_meta_1b_json2._1().toString()); assertEquals("{ }", query_meta_1._2().toString()); // Includes extra + all the checks except the range checks final SingleQueryComponent<TestBean> query_comp_2 = CrudUtils.anyOf(TestBean.class) .when(TestBean::string_field, "string_field").withPresent(TestBean::bool_field) .withNotPresent(TestBean::long_field) .withAny(TestBean::string_field, Arrays.asList("test1a", "test1b")) .withAll(TestBean::long_field, Arrays.asList(10, 11, 12)).whenNot(TestBean::long_field, 13) .limit(100); final SingleQueryComponent<JsonNode> query_comp_2_json1 = query_comp_2.toJson(); final SingleQueryComponent<TestBean> query_comp_2b = CrudUtils.anyOf(TestBean.class) .when("string_field", "string_field").withPresent("bool_field").withNotPresent("long_field") .withAny("string_field", Arrays.asList("test1a", "test1b")) .withAll("long_field", Arrays.asList(10, 11, 12)).whenNot("long_field", 13).limit(100); final SingleQueryComponent<JsonNode> query_comp_2b_json1 = query_comp_2b.toJson(); final SingleQueryComponent<JsonNode> query_comp_2b_json2 = CrudUtils.anyOf() .when("string_field", "string_field").withPresent("bool_field").withNotPresent("long_field") .withAny("string_field", Arrays.asList("test1a", "test1b")) .withAll("long_field", Arrays.asList(10, 11, 12)).whenNot("long_field", 13).limit(100); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(query_comp_2); final Tuple2<DBObject, DBObject> query_meta_2_json1 = convertToMongoQuery(query_comp_2_json1); final Tuple2<DBObject, DBObject> query_meta_2b = convertToMongoQuery(query_comp_2b); final Tuple2<DBObject, DBObject> query_meta_2b_json1 = convertToMongoQuery(query_comp_2b_json1); final Tuple2<DBObject, DBObject> query_meta_2b_json2 = convertToMongoQuery(query_comp_2b_json2); final DBObject expected_2 = QueryBuilder.start() .or(QueryBuilder.start("string_field").is("string_field").get(), QueryBuilder.start("string_field").in(Arrays.asList("test1a", "test1b")).get(), QueryBuilder.start("bool_field").exists(true).get(), QueryBuilder.start("long_field").exists(false).get(), QueryBuilder.start("long_field").all(Arrays.asList(10, 11, 12)).get(), QueryBuilder.start("long_field").notEquals(13).get()) .get(); assertEquals(expected_2.toString(), query_meta_2._1().toString()); assertEquals(expected_2.toString(), query_meta_2_json1._1().toString()); assertEquals(expected_2.toString(), query_meta_2b._1().toString()); assertEquals(expected_2.toString(), query_meta_2b_json1._1().toString()); assertEquals(expected_2.toString(), query_meta_2b_json2._1().toString()); assertEquals("{ \"$limit\" : 100}", query_meta_2._2().toString()); assertEquals("{ \"$limit\" : 100}", query_meta_2_json1._2().toString()); } @Test public void testAllTheRangeQueries() { final SingleQueryComponent<TestBean> query_comp_1 = CrudUtils.allOf(TestBean.class) .rangeAbove(TestBean::string_field, "bbb", true).rangeBelow(TestBean::string_field, "fff", false) .rangeIn(TestBean::string_field, "ccc", false, "ddd", true) .rangeIn(TestBean::string_field, "xxx", false, "yyy", false) .rangeAbove(TestBean::long_field, 1000, false).rangeBelow(TestBean::long_field, 10000, true) .rangeIn(TestBean::long_field, 2000, true, 20000, true) .rangeIn(TestBean::long_field, 3000, true, 30000, false) .orderBy(Tuples._2T("test_field_1", 1), Tuples._2T("test_field_2", -1)).limit(200); final SingleQueryComponent<TestBean> query_comp_1b = CrudUtils.allOf(TestBean.class) .rangeAbove("string_field", "bbb", true).rangeBelow("string_field", "fff", false) .rangeIn("string_field", "ccc", false, "ddd", true) .rangeIn("string_field", "xxx", false, "yyy", false) .rangeAbove("long_field", 1000, false).rangeBelow("long_field", 10000, true) .rangeIn("long_field", 2000, true, 20000, true).rangeIn("long_field", 3000, true, 30000, false) .orderBy(Tuples._2T("test_field_1", 1)).orderBy(Tuples._2T("test_field_2", -1)).limit(200); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); final Tuple2<DBObject, DBObject> query_meta_1b = convertToMongoQuery(query_comp_1b); final DBObject expected_1 = QueryBuilder.start() .and(QueryBuilder.start("string_field").greaterThan("bbb").get(), QueryBuilder.start("string_field").lessThanEquals("fff").get(), QueryBuilder.start("string_field").greaterThanEquals("ccc").lessThan("ddd").get(), QueryBuilder.start("string_field").greaterThanEquals("xxx").lessThanEquals("yyy").get(), QueryBuilder.start("long_field").greaterThanEquals(1000).get(), QueryBuilder.start("long_field").lessThan(10000).get(), QueryBuilder.start("long_field").greaterThan(2000).lessThan(20000).get(), QueryBuilder.start("long_field").greaterThan(3000).lessThanEquals(30000).get() ).get(); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_1", 1); expected_meta_nested.put("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$limit", 200); expected_meta.put("$sort", expected_meta_nested); assertEquals(expected_1.toString(), query_meta_1._1().toString()); assertEquals(expected_1.toString(), query_meta_1b._1().toString()); assertEquals(expected_meta.toString(), query_meta_1._2().toString()); } @Test public void testNestedQueries() { // 1 level of nesting final SingleQueryComponent<TestBean> query_comp_1 = CrudUtils.allOf(TestBean.class) .when(TestBean::string_field, "a") .nested(TestBean::nested_list, CrudUtils.anyOf(TestBean.NestedTestBean.class) .when(TestBean.NestedTestBean::nested_string_field, "x") .withAny(TestBean.NestedTestBean::nested_string_field, Arrays.asList("x", "y")) .rangeIn("nested_string_field", "ccc", false, "ddd", true).limit(1000) // (should be ignored) .orderBy(Tuples._2T("test_field_1", 1)) // (should be ignored) ).withPresent("long_field").limit(5).orderBy(Tuples._2T("test_field_2", -1)); System.out.println(query_comp_1.toString()); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); final DBObject expected_1 = QueryBuilder.start() .and(QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("nested_list.nested_string_field").is("x").get(), QueryBuilder.start("nested_list.nested_string_field").in(Arrays.asList("x", "y")).get(), QueryBuilder.start("nested_list.nested_string_field").greaterThanEquals("ccc") .lessThan("ddd").get(), QueryBuilder.start("long_field").exists(true).get()) .get(); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$limit", 5); expected_meta.put("$sort", expected_meta_nested); assertEquals(expected_1.toString(), query_meta_1._1().toString()); assertEquals(expected_meta.toString(), query_meta_1._2().toString()); // 2 levels of nesting BeanTemplate<TestBean.NestedTestBean> nestedBean = BeanTemplateUtils.build(TestBean.NestedTestBean.class) .with("nested_string_field", "x").done(); final SingleQueryComponent<TestBean> query_comp_2 = CrudUtils.allOf(TestBean.class) .when(TestBean::string_field, "a") .nested(TestBean::nested_list, CrudUtils.anyOf(nestedBean) .when(TestBean.NestedTestBean::nested_string_field, "y") .nested(TestBean.NestedTestBean::nested_object, CrudUtils.allOf(TestBean.NestedNestedTestBean.class) .when(TestBean.NestedNestedTestBean::nested_nested_string_field, "z") .withNotPresent(TestBean.NestedNestedTestBean::nested_nested_string_field) .limit(1000) // (should be ignored) .orderBy(Tuples._2T("test_field_1", 1)) // (should be ignored) ).withAny(TestBean.NestedTestBean::nested_string_field, Arrays.asList("x", "y")) .rangeIn("nested_string_field", "ccc", false, "ddd", true)) .withPresent("long_field"); System.out.println(query_comp_2.toString()); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(query_comp_2); final DBObject expected_2 = QueryBuilder.start().and(QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("nested_list.nested_string_field").is("x").get(), QueryBuilder.start("nested_list.nested_string_field").is("y").get(), QueryBuilder.start("nested_list.nested_string_field").in(Arrays.asList("x", "y")).get(), QueryBuilder.start("nested_list.nested_string_field").greaterThanEquals("ccc").lessThan("ddd") .get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").is("z").get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").exists(false).get(), QueryBuilder.start("long_field").exists(true).get()).get(); assertEquals(expected_2.toString(), query_meta_2._1().toString()); assertEquals("{ }", query_meta_2._2().toString()); // Nested objects TestBean t = new TestBean(); t.string_field = "a"; t.map = new HashMap<String, String>(); t.bool_field = true; t.long_field = 1L; t.nested_list = Arrays.asList(); TestBean.NestedTestBean nt2 = new TestBean.NestedTestBean(); nt2.nested_string_field = "xx"; t.nested_object = nt2; TestBean.NestedNestedTestBean nnt = new TestBean.NestedNestedTestBean(); nnt.nested_nested_string_field = "i"; nt2.nested_object = nnt; final SingleQueryComponent<TestBean> query_comp_3 = CrudUtils.allOf(BeanTemplate.of(t)) .when(TestBean::long_field, 2).nested(TestBean::nested_list, CrudUtils.allOf(TestBean.NestedTestBean.class) .when(TestBean.NestedTestBean::nested_string_field, "y") .nested(TestBean.NestedTestBean::nested_object, CrudUtils.allOf(TestBean.NestedNestedTestBean.class).withNotPresent( TestBean.NestedNestedTestBean::nested_nested_string_field))); final Tuple2<DBObject, DBObject> query_meta_3 = convertToMongoQuery(query_comp_3); final DBObject expected_3 = QueryBuilder.start() .and(QueryBuilder.start("long_field").is(2).get(), QueryBuilder.start("long_field").is(1).get(), QueryBuilder.start("nested_list.nested_string_field").is("y").get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").exists(false) .get(), QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("bool_field").is(true).get(), QueryBuilder.start("nested_list").is(Arrays.asList()).get(), QueryBuilder.start("nested_object.nested_string_field").is("xx").get(), QueryBuilder.start("nested_object.nested_object.nested_nested_string_field").is("i").get(), QueryBuilder.start("map").is(new HashMap<String, String>()).get() ).get(); assertEquals(expected_3.toString(), query_meta_3._1().toString()); assertEquals("{ }", query_meta_3._2().toString()); } @Test public void testMultipleQueries() { // Just to test .. single node versions final SingleQueryComponent<TestBean> query_comp_1 = CrudUtils.allOf(TestBean.class) .rangeAbove(TestBean::string_field, "bbb", true).rangeBelow(TestBean::string_field, "fff", false) .rangeIn(TestBean::string_field, "ccc", false, "ddd", true) .rangeIn(TestBean::string_field, "xxx", false, "yyy", false) .rangeAbove(TestBean::long_field, 1000, false).rangeBelow(TestBean::long_field, 10000, true) .rangeIn(TestBean::long_field, 2000, true, 20000, true) .rangeIn(TestBean::long_field, 3000, true, 30000, false) .orderBy(Tuples._2T("test_field_1", 1)) // should be ignored .limit(200); // should be ignored final MultiQueryComponent<TestBean> multi_query_1 = CrudUtils.<TestBean>allOf(query_comp_1) .orderBy(Tuples._2T("test_field_2", -1)).limit(5); final MultiQueryComponent<TestBean> multi_query_2 = CrudUtils.<TestBean>anyOf(query_comp_1); System.out.println(multi_query_1.toString()); System.out.println(multi_query_2.toString()); final QueryBuilder expected_1 = QueryBuilder.start().and( QueryBuilder.start("string_field").greaterThan("bbb").get(), QueryBuilder.start("string_field").lessThanEquals("fff").get(), QueryBuilder.start("string_field").greaterThanEquals("ccc").lessThan("ddd").get(), QueryBuilder.start("string_field").greaterThanEquals("xxx").lessThanEquals("yyy").get(), QueryBuilder.start("long_field").greaterThanEquals(1000).get(), QueryBuilder.start("long_field").lessThan(10000).get(), QueryBuilder.start("long_field").greaterThan(2000).lessThan(20000).get(), QueryBuilder.start("long_field").greaterThan(3000).lessThanEquals(30000).get() ); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(multi_query_1); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(multi_query_2); final DBObject multi_expected_1 = QueryBuilder.start().and((DBObject) expected_1.get()).get(); final DBObject multi_expected_2 = QueryBuilder.start().or((DBObject) expected_1.get()).get(); assertEquals(multi_expected_1.toString(), query_meta_1._1().toString()); assertEquals(multi_expected_2.toString(), query_meta_2._1().toString()); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$limit", 5); expected_meta.put("$sort", expected_meta_nested); assertEquals(expected_meta.toString(), query_meta_1._2().toString()); assertEquals("{ }", query_meta_2._2().toString()); // Multiple nested final SingleQueryComponent<TestBean> query_comp_2 = CrudUtils.allOf(TestBean.class) .when(TestBean::string_field, "a") .nested(TestBean::nested_list, CrudUtils.anyOf(TestBean.NestedTestBean.class) .when(TestBean.NestedTestBean::nested_string_field, "x") .nested(TestBean.NestedTestBean::nested_object, CrudUtils.allOf(TestBean.NestedNestedTestBean.class) .when(TestBean.NestedNestedTestBean::nested_nested_string_field, "z") .withNotPresent(TestBean.NestedNestedTestBean::nested_nested_string_field) .limit(1000) // (should be ignored) .orderBy(Tuples._2T("test_field_1", 1)) // (should be ignored) ).withAny(TestBean.NestedTestBean::nested_string_field, Arrays.asList("x", "y")) .rangeIn("nested_string_field", "ccc", false, "ddd", true)) .withPresent("long_field"); final MultiQueryComponent<TestBean> multi_query_3 = CrudUtils.allOf(query_comp_1, query_comp_2).limit(5); final MultiQueryComponent<TestBean> multi_query_4 = CrudUtils.anyOf(query_comp_1, query_comp_2) .orderBy(Tuples._2T("test_field_2", -1)); System.out.println(multi_query_3.toString()); System.out.println(multi_query_4.toString()); final MultiQueryComponent<TestBean> multi_query_5 = CrudUtils.<TestBean>allOf(Stream.of(query_comp_1)) .also(query_comp_2).limit(5); final MultiQueryComponent<TestBean> multi_query_6 = CrudUtils.<TestBean>anyOf(query_comp_1) .also(query_comp_2).orderBy().orderBy(Tuples._2T("test_field_2", -1)); System.out.println(multi_query_5.toString()); System.out.println(multi_query_6.toString()); final Tuple2<DBObject, DBObject> query_meta_3 = convertToMongoQuery(multi_query_3); final Tuple2<DBObject, DBObject> query_meta_4 = convertToMongoQuery(multi_query_4); final Tuple2<DBObject, DBObject> query_meta_5 = convertToMongoQuery(multi_query_5); final Tuple2<DBObject, DBObject> query_meta_6 = convertToMongoQuery(multi_query_6); final QueryBuilder expected_2 = QueryBuilder.start().and(QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("nested_list.nested_string_field").is("x").get(), QueryBuilder.start("nested_list.nested_string_field").in(Arrays.asList("x", "y")).get(), QueryBuilder.start("nested_list.nested_string_field").greaterThanEquals("ccc").lessThan("ddd") .get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").is("z").get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").exists(false).get(), QueryBuilder.start("long_field").exists(true).get()); final DBObject multi_expected_3 = QueryBuilder.start() .and((DBObject) expected_1.get(), (DBObject) expected_2.get()).get(); final DBObject multi_expected_4 = QueryBuilder.start() .or((DBObject) expected_1.get(), (DBObject) expected_2.get()).get(); assertEquals(multi_expected_3.toString(), query_meta_3._1().toString()); assertEquals(multi_expected_4.toString(), query_meta_4._1().toString()); assertEquals(multi_expected_3.toString(), query_meta_5._1().toString()); assertEquals(multi_expected_4.toString(), query_meta_6._1().toString()); final BasicDBObject expected_meta_nested_2 = new BasicDBObject("test_field_2", -1); final BasicDBObject expected_meta_2 = new BasicDBObject("$sort", expected_meta_nested_2); assertEquals("{ \"$limit\" : 5}", query_meta_3._2().toString()); assertEquals(expected_meta_2.toString(), query_meta_4._2().toString()); assertEquals("{ \"$limit\" : 5}", query_meta_5._2().toString()); assertEquals(expected_meta_2.toString(), query_meta_6._2().toString()); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // JSON TESTING (C/P FROM ABOVE) @Test public void emptyQuery_json() { // No meta: final SingleQueryComponent<JsonNode> query_comp_1 = CrudUtils.allOf_json(BeanTemplate.of(new TestBean())); System.out.println(query_comp_1.toString()); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); assertEquals(null, query_comp_1.getExtra()); assertEquals("{ }", query_meta_1._1().toString()); assertEquals("{ }", query_meta_1._2().toString()); // Meta fields BeanTemplate<TestBean> template2 = BeanTemplateUtils.build(TestBean.class) .with(TestBean::string_field, null).done(); final SingleQueryComponent<JsonNode> query_comp_2 = CrudUtils.anyOf_json(template2) .orderBy(Tuples._2T("test_field_1", 1), Tuples._2T("test_field_2", -1)); assertEquals(template2.get(), query_comp_2.getElement()); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(query_comp_2); assertEquals("{ }", query_meta_2._1().toString()); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_1", 1); expected_meta_nested.put("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$sort", expected_meta_nested); assertEquals(expected_meta.toString(), query_meta_2._2().toString()); } @Test public void basicSingleTest_json() { // Queries starting with allOf // Very simple BeanTemplate<TestBean> template1 = BeanTemplateUtils.build(TestBean.class) .with(TestBean::string_field, "string_field").done(); final SingleQueryComponent<JsonNode> query_comp_1 = CrudUtils.allOf_json(template1) .when(TestBean::bool_field, true); final SingleQueryComponent<JsonNode> query_comp_1b = CrudUtils.allOf_json(TestBean.class) .when("bool_field", true).when("string_field", "string_field"); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); final Tuple2<DBObject, DBObject> query_meta_1b = convertToMongoQuery(query_comp_1b); final DBObject expected_1 = QueryBuilder.start().and(QueryBuilder.start("bool_field").is(true).get(), QueryBuilder.start("string_field").is("string_field").get()).get(); assertEquals(expected_1.toString(), query_meta_1._1().toString()); assertEquals(expected_1.toString(), query_meta_1b._1().toString()); assertEquals("{ }", query_meta_1._2().toString()); // Includes extra + all the checks except the range checks final SingleQueryComponent<JsonNode> query_comp_2 = CrudUtils.anyOf_json(TestBean.class) .when(TestBean::string_field, "string_field").withPresent(TestBean::bool_field) .withNotPresent(TestBean::long_field) .withAny(TestBean::string_field, Arrays.asList("test1a", "test1b")) .withAll(TestBean::long_field, Arrays.asList(10, 11, 12)).whenNot(TestBean::long_field, 13) .limit(100); final SingleQueryComponent<JsonNode> query_comp_2b = CrudUtils.anyOf_json(TestBean.class) .when("string_field", "string_field").withPresent("bool_field").withNotPresent("long_field") .withAny("string_field", Arrays.asList("test1a", "test1b")) .withAll("long_field", Arrays.asList(10, 11, 12)).whenNot("long_field", 13).limit(100); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(query_comp_2); final Tuple2<DBObject, DBObject> query_meta_2b = convertToMongoQuery(query_comp_2b); final DBObject expected_2 = QueryBuilder.start() .or(QueryBuilder.start("string_field").is("string_field").get(), QueryBuilder.start("string_field").in(Arrays.asList("test1a", "test1b")).get(), QueryBuilder.start("bool_field").exists(true).get(), QueryBuilder.start("long_field").exists(false).get(), QueryBuilder.start("long_field").all(Arrays.asList(10, 11, 12)).get(), QueryBuilder.start("long_field").notEquals(13).get()) .get(); assertEquals(expected_2.toString(), query_meta_2._1().toString()); assertEquals(expected_2.toString(), query_meta_2b._1().toString()); assertEquals("{ \"$limit\" : 100}", query_meta_2._2().toString()); } @Test public void testAllTheRangeQueries_json() { final SingleQueryComponent<JsonNode> query_comp_1 = CrudUtils.allOf_json(TestBean.class) .rangeAbove(TestBean::string_field, "bbb", true).rangeBelow(TestBean::string_field, "fff", false) .rangeIn(TestBean::string_field, "ccc", false, "ddd", true) .rangeIn(TestBean::string_field, "xxx", false, "yyy", false) .rangeAbove(TestBean::long_field, 1000, false).rangeBelow(TestBean::long_field, 10000, true) .rangeIn(TestBean::long_field, 2000, true, 20000, true) .rangeIn(TestBean::long_field, 3000, true, 30000, false) .orderBy(Tuples._2T("test_field_1", 1), Tuples._2T("test_field_2", -1)).limit(200); final SingleQueryComponent<JsonNode> query_comp_1b = CrudUtils.allOf_json(TestBean.class) .rangeAbove("string_field", "bbb", true).rangeBelow("string_field", "fff", false) .rangeIn("string_field", "ccc", false, "ddd", true) .rangeIn("string_field", "xxx", false, "yyy", false) .rangeAbove("long_field", 1000, false).rangeBelow("long_field", 10000, true) .rangeIn("long_field", 2000, true, 20000, true).rangeIn("long_field", 3000, true, 30000, false) .orderBy(Tuples._2T("test_field_1", 1)).orderBy(Tuples._2T("test_field_2", -1)).limit(200); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); final Tuple2<DBObject, DBObject> query_meta_1b = convertToMongoQuery(query_comp_1b); final DBObject expected_1 = QueryBuilder.start() .and(QueryBuilder.start("string_field").greaterThan("bbb").get(), QueryBuilder.start("string_field").lessThanEquals("fff").get(), QueryBuilder.start("string_field").greaterThanEquals("ccc").lessThan("ddd").get(), QueryBuilder.start("string_field").greaterThanEquals("xxx").lessThanEquals("yyy").get(), QueryBuilder.start("long_field").greaterThanEquals(1000).get(), QueryBuilder.start("long_field").lessThan(10000).get(), QueryBuilder.start("long_field").greaterThan(2000).lessThan(20000).get(), QueryBuilder.start("long_field").greaterThan(3000).lessThanEquals(30000).get() ).get(); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_1", 1); expected_meta_nested.put("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$limit", 200); expected_meta.put("$sort", expected_meta_nested); assertEquals(expected_1.toString(), query_meta_1._1().toString()); assertEquals(expected_1.toString(), query_meta_1b._1().toString()); assertEquals(expected_meta.toString(), query_meta_1._2().toString()); } @Test public void testNestedQueries_json() { // 1 level of nesting final SingleQueryComponent<JsonNode> query_comp_1 = CrudUtils.allOf_json(TestBean.class) .when(TestBean::string_field, "a") .nested(TestBean::nested_list, CrudUtils.anyOf_json(TestBean.NestedTestBean.class) .when(TestBean.NestedTestBean::nested_string_field, "x") .withAny(TestBean.NestedTestBean::nested_string_field, Arrays.asList("x", "y")) .rangeIn("nested_string_field", "ccc", false, "ddd", true).limit(1000) // (should be ignored) .orderBy(Tuples._2T("test_field_1", 1)) // (should be ignored) ).withPresent("long_field").limit(5).orderBy(Tuples._2T("test_field_2", -1)); System.out.println(query_comp_1.toString()); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(query_comp_1); final DBObject expected_1 = QueryBuilder.start() .and(QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("nested_list.nested_string_field").is("x").get(), QueryBuilder.start("nested_list.nested_string_field").in(Arrays.asList("x", "y")).get(), QueryBuilder.start("nested_list.nested_string_field").greaterThanEquals("ccc") .lessThan("ddd").get(), QueryBuilder.start("long_field").exists(true).get()) .get(); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$limit", 5); expected_meta.put("$sort", expected_meta_nested); assertEquals(expected_1.toString(), query_meta_1._1().toString()); assertEquals(expected_meta.toString(), query_meta_1._2().toString()); // 2 levels of nesting BeanTemplate<TestBean.NestedTestBean> nestedBean = BeanTemplateUtils.build(TestBean.NestedTestBean.class) .with("nested_string_field", "x").done(); final SingleQueryComponent<JsonNode> query_comp_2 = CrudUtils.allOf_json(TestBean.class) .when(TestBean::string_field, "a") .nested(TestBean::nested_list, CrudUtils.anyOf_json(nestedBean) .when(TestBean.NestedTestBean::nested_string_field, "y") .nested(TestBean.NestedTestBean::nested_object, CrudUtils.allOf_json(TestBean.NestedNestedTestBean.class) .when(TestBean.NestedNestedTestBean::nested_nested_string_field, "z") .withNotPresent(TestBean.NestedNestedTestBean::nested_nested_string_field) .limit(1000) // (should be ignored) .orderBy(Tuples._2T("test_field_1", 1)) // (should be ignored) ).withAny(TestBean.NestedTestBean::nested_string_field, Arrays.asList("x", "y")) .rangeIn("nested_string_field", "ccc", false, "ddd", true)) .withPresent("long_field"); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(query_comp_2); final DBObject expected_2 = QueryBuilder.start().and(QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("nested_list.nested_string_field").is("x").get(), QueryBuilder.start("nested_list.nested_string_field").is("y").get(), QueryBuilder.start("nested_list.nested_string_field").in(Arrays.asList("x", "y")).get(), QueryBuilder.start("nested_list.nested_string_field").greaterThanEquals("ccc").lessThan("ddd") .get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").is("z").get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").exists(false).get(), QueryBuilder.start("long_field").exists(true).get()).get(); assertEquals(expected_2.toString(), query_meta_2._1().toString()); assertEquals("{ }", query_meta_2._2().toString()); // Nested objects TestBean t = new TestBean(); t.string_field = "a"; t.map = new HashMap<String, String>(); t.bool_field = true; t.long_field = 1L; t.nested_list = Arrays.asList(); TestBean.NestedTestBean nt2 = new TestBean.NestedTestBean(); nt2.nested_string_field = "xx"; t.nested_object = nt2; TestBean.NestedNestedTestBean nnt = new TestBean.NestedNestedTestBean(); nnt.nested_nested_string_field = "i"; nt2.nested_object = nnt; final SingleQueryComponent<JsonNode> query_comp_3 = CrudUtils.allOf_json(BeanTemplate.of(t)) .when(TestBean::long_field, 2).nested(TestBean::nested_list, CrudUtils.allOf_json(TestBean.NestedTestBean.class) .when(TestBean.NestedTestBean::nested_string_field, "y") .nested(TestBean.NestedTestBean::nested_object, CrudUtils.allOf_json(TestBean.NestedNestedTestBean.class).withNotPresent( TestBean.NestedNestedTestBean::nested_nested_string_field))); final Tuple2<DBObject, DBObject> query_meta_3 = convertToMongoQuery(query_comp_3); final DBObject expected_3 = QueryBuilder.start() .and(QueryBuilder.start("long_field").is(2).get(), QueryBuilder.start("long_field").is(1).get(), QueryBuilder.start("nested_list.nested_string_field").is("y").get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").exists(false) .get(), QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("bool_field").is(true).get(), QueryBuilder.start("nested_list").is(Arrays.asList()).get(), QueryBuilder.start("nested_object.nested_string_field").is("xx").get(), QueryBuilder.start("nested_object.nested_object.nested_nested_string_field").is("i").get(), QueryBuilder.start("map").is(new HashMap<String, String>()).get() ).get(); assertEquals(expected_3.toString(), query_meta_3._1().toString()); assertEquals("{ }", query_meta_3._2().toString()); } @Test public void testMultipleQueries_json() { // Just to test .. single node versions final SingleQueryComponent<JsonNode> query_comp_1 = CrudUtils.allOf_json(TestBean.class) .rangeAbove(TestBean::string_field, "bbb", true).rangeBelow(TestBean::string_field, "fff", false) .rangeIn(TestBean::string_field, "ccc", false, "ddd", true) .rangeIn(TestBean::string_field, "xxx", false, "yyy", false) .rangeAbove(TestBean::long_field, 1000, false).rangeBelow(TestBean::long_field, 10000, true) .rangeIn(TestBean::long_field, 2000, true, 20000, true) .rangeIn(TestBean::long_field, 3000, true, 30000, false) .orderBy(Tuples._2T("test_field_1", 1)) // should be ignored .limit(200); // should be ignored final MultiQueryComponent<JsonNode> multi_query_1 = CrudUtils.<JsonNode>allOf(query_comp_1) .orderBy(Tuples._2T("test_field_2", -1)).limit(5); final MultiQueryComponent<JsonNode> multi_query_2 = CrudUtils.<JsonNode>anyOf(Stream.of(query_comp_1)); System.out.println(multi_query_1.toString()); System.out.println(multi_query_2.toString()); final QueryBuilder expected_1 = QueryBuilder.start().and( QueryBuilder.start("string_field").greaterThan("bbb").get(), QueryBuilder.start("string_field").lessThanEquals("fff").get(), QueryBuilder.start("string_field").greaterThanEquals("ccc").lessThan("ddd").get(), QueryBuilder.start("string_field").greaterThanEquals("xxx").lessThanEquals("yyy").get(), QueryBuilder.start("long_field").greaterThanEquals(1000).get(), QueryBuilder.start("long_field").lessThan(10000).get(), QueryBuilder.start("long_field").greaterThan(2000).lessThan(20000).get(), QueryBuilder.start("long_field").greaterThan(3000).lessThanEquals(30000).get() ); final Tuple2<DBObject, DBObject> query_meta_1 = convertToMongoQuery(multi_query_1); final Tuple2<DBObject, DBObject> query_meta_2 = convertToMongoQuery(multi_query_2); final DBObject multi_expected_1 = QueryBuilder.start().and((DBObject) expected_1.get()).get(); final DBObject multi_expected_2 = QueryBuilder.start().or((DBObject) expected_1.get()).get(); assertEquals(multi_expected_1.toString(), query_meta_1._1().toString()); assertEquals(multi_expected_2.toString(), query_meta_2._1().toString()); final BasicDBObject expected_meta_nested = new BasicDBObject("test_field_2", -1); final BasicDBObject expected_meta = new BasicDBObject("$limit", 5); expected_meta.put("$sort", expected_meta_nested); assertEquals(expected_meta.toString(), query_meta_1._2().toString()); assertEquals("{ }", query_meta_2._2().toString()); // Multiple nested final SingleQueryComponent<JsonNode> query_comp_2 = CrudUtils.allOf_json(TestBean.class) .when(TestBean::string_field, "a") .nested(TestBean::nested_list, CrudUtils.anyOf_json(TestBean.NestedTestBean.class) .when(TestBean.NestedTestBean::nested_string_field, "x") .nested(TestBean.NestedTestBean::nested_object, CrudUtils.allOf_json(TestBean.NestedNestedTestBean.class) .when(TestBean.NestedNestedTestBean::nested_nested_string_field, "z") .withNotPresent(TestBean.NestedNestedTestBean::nested_nested_string_field) .limit(1000) // (should be ignored) .orderBy(Tuples._2T("test_field_1", 1)) // (should be ignored) ).withAny(TestBean.NestedTestBean::nested_string_field, Arrays.asList("x", "y")) .rangeIn("nested_string_field", "ccc", false, "ddd", true)) .withPresent("long_field"); final MultiQueryComponent<JsonNode> multi_query_3 = CrudUtils .allOf(Arrays.asList(query_comp_1, query_comp_2)).limit(5); final MultiQueryComponent<JsonNode> multi_query_4 = CrudUtils .anyOf(Arrays.asList(query_comp_1, query_comp_2)).orderBy(Tuples._2T("test_field_2", -1)); System.out.println(multi_query_3.toString()); System.out.println(multi_query_4.toString()); final MultiQueryComponent<JsonNode> multi_query_5 = CrudUtils.<JsonNode>allOf(query_comp_1) .also(query_comp_2).limit(5); final MultiQueryComponent<JsonNode> multi_query_6 = CrudUtils.<JsonNode>anyOf(query_comp_1) .also(query_comp_2).orderBy().orderBy(Tuples._2T("test_field_2", -1)); final Tuple2<DBObject, DBObject> query_meta_3 = convertToMongoQuery(multi_query_3); final Tuple2<DBObject, DBObject> query_meta_4 = convertToMongoQuery(multi_query_4); final Tuple2<DBObject, DBObject> query_meta_5 = convertToMongoQuery(multi_query_5); final Tuple2<DBObject, DBObject> query_meta_6 = convertToMongoQuery(multi_query_6); final QueryBuilder expected_2 = QueryBuilder.start().and(QueryBuilder.start("string_field").is("a").get(), QueryBuilder.start("nested_list.nested_string_field").is("x").get(), QueryBuilder.start("nested_list.nested_string_field").in(Arrays.asList("x", "y")).get(), QueryBuilder.start("nested_list.nested_string_field").greaterThanEquals("ccc").lessThan("ddd") .get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").is("z").get(), QueryBuilder.start("nested_list.nested_object.nested_nested_string_field").exists(false).get(), QueryBuilder.start("long_field").exists(true).get()); final DBObject multi_expected_3 = QueryBuilder.start() .and((DBObject) expected_1.get(), (DBObject) expected_2.get()).get(); final DBObject multi_expected_4 = QueryBuilder.start() .or((DBObject) expected_1.get(), (DBObject) expected_2.get()).get(); assertEquals(multi_expected_3.toString(), query_meta_3._1().toString()); assertEquals(multi_expected_4.toString(), query_meta_4._1().toString()); assertEquals(multi_expected_3.toString(), query_meta_5._1().toString()); assertEquals(multi_expected_4.toString(), query_meta_6._1().toString()); final BasicDBObject expected_meta_nested_2 = new BasicDBObject("test_field_2", -1); final BasicDBObject expected_meta_2 = new BasicDBObject("$sort", expected_meta_nested_2); assertEquals("{ \"$limit\" : 5}", query_meta_3._2().toString()); assertEquals(expected_meta_2.toString(), query_meta_4._2().toString()); assertEquals("{ \"$limit\" : 5}", query_meta_5._2().toString()); assertEquals(expected_meta_2.toString(), query_meta_6._2().toString()); } @Test public void testNestedMultiQuery() throws IOException { // Mainly just testing toJson here final BeanTemplate<TestBean> template1a = BeanTemplateUtils.build(TestBean.class) .with(TestBean::string_field, "string_field").done(); final SingleQueryComponent<TestBean> query_comp_1a = CrudUtils.anyOf(template1a); final SingleQueryComponent<TestBean> query_comp_2a = CrudUtils.allOf(TestBean.class) .when(TestBean::bool_field, true); final MultiQueryComponent<TestBean> multi_query_1 = CrudUtils .allOf(Arrays.asList(query_comp_1a, query_comp_2a)); final BeanTemplate<TestBean> template1b = BeanTemplateUtils.build(TestBean.class) .with(TestBean::string_field, "string_field_b").done(); final SingleQueryComponent<TestBean> query_comp_1b = CrudUtils.allOf(template1b); final SingleQueryComponent<TestBean> query_comp_2b = CrudUtils.anyOf(TestBean.class) .when(TestBean::bool_field, false); final MultiQueryComponent<TestBean> multi_query_2 = CrudUtils.allOf(query_comp_1b, query_comp_2b); final MultiQueryComponent<JsonNode> multi_query_test_1 = CrudUtils.anyOf(multi_query_1, multi_query_2) .toJson(); final MultiQueryComponent<JsonNode> multi_query_test_2 = CrudUtils.allOf(multi_query_1, query_comp_1b) .toJson(); System.out.println(multi_query_test_1.toString()); System.out.println(multi_query_test_2.toString()); final Tuple2<DBObject, DBObject> multi_query_meta_1 = convertToMongoQuery(multi_query_test_1); final Tuple2<DBObject, DBObject> multi_query_meta_2 = convertToMongoQuery(multi_query_test_2); final DBObject expected_1 = QueryBuilder .start().or( QueryBuilder.start() .and(QueryBuilder.start() .or(QueryBuilder.start("string_field").is("string_field").get()).get(), QueryBuilder.start().and(QueryBuilder.start("bool_field").is(true).get()) .get()) .get(), QueryBuilder.start().and( QueryBuilder.start() .and(QueryBuilder.start("string_field").is("string_field_b").get()).get(), QueryBuilder.start().or(QueryBuilder.start("bool_field").is(false).get()).get()) .get()) .get(); final DBObject expected_2 = QueryBuilder .start().and( QueryBuilder.start().and( QueryBuilder.start().or(QueryBuilder.start("string_field").is("string_field").get()) .get(), QueryBuilder.start().and(QueryBuilder.start("bool_field").is(true).get()).get()) .get(), QueryBuilder.start().and(QueryBuilder.start("string_field").is("string_field_b").get()) .get()) .get(); assertEquals(expected_1.toString(), multi_query_meta_1._1().toString()); assertEquals(expected_2.toString(), multi_query_meta_2._1().toString()); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // public static class TestBean { // public static class NestedNestedTestBean { // public String nested_nested_string_field() { return nested_nested_string_field; } // // private String nested_nested_string_field; // } // public static class NestedTestBean { // public String nested_string_field() { return nested_string_field; } // public NestedNestedTestBean nested_object() { return nested_object; } // // private String nested_string_field; // private NestedNestedTestBean nested_object; // } // public String string_field() { return string_field; } // public Boolean bool_field() { return bool_field; } // public Long long_field() { return long_field; } // public List<NestedTestBean> nested_list() { return nested_list; } // public Map<String, String> map() { return map; } // public NestedTestBean nested_object() { return nested_object; } // // protected TestBean() {} // private String string_field; // private Boolean bool_field; // private Long long_field; // private List<NestedTestBean> nested_list; // private NestedTestBean nested_object; // private Map<String, String> map; // } // UPDATE TESTING - BEAN @Test public void updateBeanTest() { // Test 1 - getter fields final BeanTemplate<TestBean.NestedTestBean> nested1 = BeanTemplateUtils.build(TestBean.NestedTestBean.class) .with("nested_string_field", "test1").done(); //(2) final BeanUpdateComponent<TestBean> test1 = CrudUtils.update(TestBean.class) .add(TestBean::string_fields, "AA", false) //(0) .increment(TestBean::long_field, 4) //(1) .nested(TestBean::nested_list, CrudUtils.update(nested1) //(2) .unset(TestBean.NestedTestBean::nested_string_field) //(3a) .remove(TestBean.NestedTestBean::nested_string_list, Arrays.asList("x", "y", "z")) //(4) .add(TestBean.NestedTestBean::nested_string_list, "A", true) // (5) ).unset(TestBean::bool_field) //(3b) .unset(TestBean::nested_object) //(3c) .remove(TestBean::nested_list, CrudUtils.allOf(TestBean.NestedTestBean.class).when("nested_string_field", "1")) //6) ; assertEquals(TestBean.class, test1.getElementClass()); final UpdateComponent<JsonNode> test1_json = test1.toJson(); System.out.println(test1_json.toString()); final DBObject result1 = createUpdateObject(test1); final DBObject result1_json = createUpdateObject(test1_json); final String expected1 = "{ \"$push\" : { \"string_fields\" : \"AA\"} , \"$inc\" : { \"long_field\" : 4} , \"$set\" : { \"nested_list.nested_string_field\" : \"test1\"} , \"$unset\" : { \"nested_list.nested_string_field\" : 1 , \"bool_field\" : 1 , \"nested_object\" : 1} , \"$pullAll\" : { \"nested_list.nested_string_list\" : [ \"x\" , \"y\" , \"z\"]} , \"$addToSet\" : { \"nested_list.nested_string_list\" : \"A\"} , \"$pull\" : { \"nested_list\" : { \"$and\" : [ { \"nested_string_field\" : \"1\"}]}}}"; // (see above for analysis of results) assertEquals(expected1, result1.toString()); assertEquals(expected1, result1_json.toString()); // Test 1b - string fields final BeanTemplate<TestBean.NestedTestBean> nested1b = BeanTemplateUtils .build(TestBean.NestedTestBean.class).with("nested_string_field", "test1").done(); //(2) final UpdateComponent<TestBean> test1b = CrudUtils.update(TestBean.class).add("string_fields", "AA", false) //(0) .increment("long_field", 4) //(1) .nested("nested_list", CrudUtils.update(nested1b) //(2) .unset("nested_string_field") //(3a) .remove("nested_string_list", Arrays.asList("x", "y", "z")) //(4) .add("nested_string_list", "A", true) // (5) ).unset("bool_field") //(3b) .unset("nested_object") //(3c) .remove("nested_list", CrudUtils.allOf(TestBean.NestedTestBean.class).when("nested_string_field", "1")) //6) ; final UpdateComponent<JsonNode> test1b_json1 = CrudUtils.update().add("string_fields", "AA", false) //(0) .increment("long_field", 4) //(1) .nested("nested_list", CrudUtils.update(nested1b) //(2) .unset("nested_string_field") //(3a) .remove("nested_string_list", Arrays.asList("x", "y", "z")) //(4) .add("nested_string_list", "A", true) // (5) ).unset("bool_field") //(3b) .unset("nested_object") //(3c) .remove("nested_list", CrudUtils.allOf(TestBean.NestedTestBean.class).when("nested_string_field", "1")) //6) ; final UpdateComponent<JsonNode> test1b_json2 = test1b.toJson(); System.out.println(test1b.toString()); System.out.println(test1b_json2.toString()); final DBObject result1b = createUpdateObject(test1b); final DBObject result1b_json1 = createUpdateObject(test1b_json1); final DBObject result1b_json2 = createUpdateObject(test1b_json2); final String expected1b = "{ \"$push\" : { \"string_fields\" : \"AA\"} , \"$inc\" : { \"long_field\" : 4} , \"$set\" : { \"nested_list.nested_string_field\" : \"test1\"} , \"$unset\" : { \"nested_list.nested_string_field\" : 1 , \"bool_field\" : 1 , \"nested_object\" : 1} , \"$pullAll\" : { \"nested_list.nested_string_list\" : [ \"x\" , \"y\" , \"z\"]} , \"$addToSet\" : { \"nested_list.nested_string_list\" : \"A\"} , \"$pull\" : { \"nested_list\" : { \"$and\" : [ { \"nested_string_field\" : \"1\"}]}}}"; // (see above for analysis of results) assertEquals(expected1b, result1b.toString()); assertEquals(expected1b, result1b_json1.toString()); assertEquals(expected1b, result1b_json2.toString()); // Test2 - more coverage final BeanTemplate<TestBean> testbean2 = BeanTemplateUtils.build(TestBean.class) .with(TestBean::map, ImmutableMap.<String, String>builder().put("a", "b").build()).done(); final UpdateComponent<TestBean> test2 = CrudUtils.update(testbean2) //(3) .set(TestBean::string_fields, "A").add(TestBean::string_fields, Arrays.asList("a", "b", "c"), true) //(1) .set("long_field", 1L).nested("nested_list", CrudUtils.update(TestBean.NestedTestBean.class) .add(TestBean.NestedTestBean::nested_string_list, "A", false) // (will be overwritten) ).add("nested_list.nested_string_list", Arrays.asList("x", "y"), false); //(2) final DBObject result2 = createUpdateObject(test2); final String expected2 = "{ \"$set\" : { \"string_fields\" : \"A\" , \"long_field\" : 1 , \"map\" : { \"a\" : \"b\"}} , \"$addToSet\" : { \"string_fields\" : { \"$each\" : [ \"a\" , \"b\" , \"c\"]}} , \"$push\" : { \"nested_list.nested_string_list\" : { \"$each\" : [ \"x\" , \"y\"]}}}"; // (see above for analysis of results) assertEquals(expected2, result2.toString()); // Test3 - delete object final UpdateComponent<TestBean> test3 = CrudUtils.update(TestBean.class).deleteObject(); final DBObject result3 = createUpdateObject(test3); assertEquals("{ \"$unset\" : null }", result3.toString()); // Test4 - bean templates as JsonNode (see _json for the other way round!) final BeanTemplate<TestBean.NestedTestBean> nested4a = BeanTemplateUtils .build(TestBean.NestedTestBean.class).with("nested_string_field", "test4a").done(); //(2) final BeanTemplate<TestBean.NestedTestBean> nested4b = BeanTemplateUtils .build(TestBean.NestedTestBean.class).with("nested_string_field", "test4b").done(); //(2) //convert to JsonNode: final ObjectMapper object_mapper = MongoJackModule .configure(BeanTemplateUtils.configureMapper(Optional.empty())); JsonNode nested4a_json = object_mapper.valueToTree(nested4a.get()); JsonNode nested4b_json = object_mapper.valueToTree(nested4b.get()); final UpdateComponent<TestBean> test4 = CrudUtils.update(TestBean.class) .set(TestBean::nested_object, nested4a_json) .add(TestBean::nested_list, Arrays.asList(nested4a_json, nested4b_json), false); final UpdateComponent<JsonNode> test4_json = CrudUtils.update().set("nested_object", nested4a_json) .add("nested_list", Arrays.asList(nested4a_json, nested4b_json), false); final DBObject result4 = createUpdateObject(test4); final DBObject result4_json = createUpdateObject(test4_json); String expected4 = "{ \"$set\" : { \"nested_object\" : { \"nested_string_field\" : \"test4a\"}} , \"$push\" : { \"nested_list\" : { \"$each\" : [ { \"nested_string_field\" : \"test4a\"} , { \"nested_string_field\" : \"test4b\"}]}}}"; assertEquals(expected4, result4.toString()); assertEquals(expected4, result4_json.toString()); } ////////////////////////// @Test public void updateBeanTest_json() { // Test 1 - getters final BeanTemplate<TestBean.NestedTestBean> nested1 = BeanTemplateUtils.build(TestBean.NestedTestBean.class) .with("nested_string_field", "test1").done(); //(2) final UpdateComponent<JsonNode> test1 = CrudUtils.update_json(TestBean.class) .add(TestBean::string_fields, "AA", false) //(0) .increment(TestBean::long_field, 4) //(1) .nested(TestBean::nested_list, CrudUtils.update(nested1) //(2) .unset(TestBean.NestedTestBean::nested_string_field) //(3a) .remove(TestBean.NestedTestBean::nested_string_list, Arrays.asList("x", "y", "z")) //(4) .add(TestBean.NestedTestBean::nested_string_list, "A", true) // (5) ).unset(TestBean::bool_field) //(3b) .unset(TestBean::nested_object) //(3c) .remove(TestBean::nested_list, CrudUtils.allOf(TestBean.NestedTestBean.class).when("nested_string_field", "1")) //6) ; System.out.println(test1.toString()); final DBObject result1 = createUpdateObject(test1); final String expected1 = "{ \"$push\" : { \"string_fields\" : \"AA\"} , \"$inc\" : { \"long_field\" : 4} , \"$set\" : { \"nested_list.nested_string_field\" : \"test1\"} , \"$unset\" : { \"nested_list.nested_string_field\" : 1 , \"bool_field\" : 1 , \"nested_object\" : 1} , \"$pullAll\" : { \"nested_list.nested_string_list\" : [ \"x\" , \"y\" , \"z\"]} , \"$addToSet\" : { \"nested_list.nested_string_list\" : \"A\"} , \"$pull\" : { \"nested_list\" : { \"$and\" : [ { \"nested_string_field\" : \"1\"}]}}}"; // (see above for analysis of results) assertEquals(expected1, result1.toString()); // Test 1b - string fields final BeanTemplate<TestBean.NestedTestBean> nested1b = BeanTemplateUtils .build(TestBean.NestedTestBean.class).with("nested_string_field", "test1").done(); //(2) final UpdateComponent<JsonNode> test1b = CrudUtils.update_json(TestBean.class) .add("string_fields", "AA", false) //(0) .increment("long_field", 4) //(1) .nested("nested_list", CrudUtils.update(nested1b) //(2) .unset("nested_string_field") //(3a) .remove("nested_string_list", "x") //(4) .add("nested_string_list", "A", true) // (5) ).unset("bool_field") //(3b) .unset("nested_object") //(3c) .remove("nested_list", CrudUtils.allOf(TestBean.NestedTestBean.class).when("nested_string_field", "1")) //6) ; System.out.println(test1b.toString()); final DBObject result1b = createUpdateObject(test1b); final String expected1b = "{ \"$push\" : { \"string_fields\" : \"AA\"} , \"$inc\" : { \"long_field\" : 4} , \"$set\" : { \"nested_list.nested_string_field\" : \"test1\"} , \"$unset\" : { \"nested_list.nested_string_field\" : 1 , \"bool_field\" : 1 , \"nested_object\" : 1} , \"$pullAll\" : { \"nested_list.nested_string_list\" : [ \"x\"]} , \"$addToSet\" : { \"nested_list.nested_string_list\" : \"A\"} , \"$pull\" : { \"nested_list\" : { \"$and\" : [ { \"nested_string_field\" : \"1\"}]}}}"; // (see above for analysis of results) assertEquals(expected1b, result1b.toString()); // Test2 - More coverage final BeanTemplate<TestBean> testbean2 = BeanTemplateUtils.build(TestBean.class) .with(TestBean::map, ImmutableMap.<String, String>builder().put("a", "b").build()).done(); final UpdateComponent<JsonNode> test2 = CrudUtils.update_json(testbean2) //(3) .set(TestBean::string_fields, "A").add(TestBean::string_fields, Arrays.asList("a", "b", "c"), true) //(1) .set("long_field", 1L).nested("nested_list", CrudUtils.update(TestBean.NestedTestBean.class) .add(TestBean.NestedTestBean::nested_string_list, "A", false) // (will be overwritten) ).add("nested_list.nested_string_list", Arrays.asList("x", "y"), false); //(2) System.out.println(test2.toString()); final DBObject result2 = createUpdateObject(test2); final String expected2 = "{ \"$set\" : { \"string_fields\" : \"A\" , \"long_field\" : 1 , \"map\" : { \"a\" : \"b\"}} , \"$addToSet\" : { \"string_fields\" : { \"$each\" : [ \"a\" , \"b\" , \"c\"]}} , \"$push\" : { \"nested_list.nested_string_list\" : { \"$each\" : [ \"x\" , \"y\"]}}}"; // (see above for analysis of results) assertEquals(expected2, result2.toString()); // Test3 - delete object final UpdateComponent<JsonNode> test3 = CrudUtils.update_json(TestBean.class).deleteObject(); final DBObject result3 = createUpdateObject(test3); assertEquals("{ \"$unset\" : null }", result3.toString()); // Test4 - bean templates final BeanTemplate<TestBean.NestedTestBean> nested4a = BeanTemplateUtils .build(TestBean.NestedTestBean.class).with("nested_string_field", "test4a").done(); //(2) final BeanTemplate<TestBean.NestedTestBean> nested4b = BeanTemplateUtils .build(TestBean.NestedTestBean.class).with("nested_string_field", "test4b").done(); //(2) final UpdateComponent<JsonNode> test4 = CrudUtils.update_json(TestBean.class) .set(TestBean::nested_object, nested4a) .add(TestBean::nested_list, Arrays.asList(nested4a, nested4b), false); final DBObject result4 = createUpdateObject(test4); assertEquals( "{ \"$set\" : { \"nested_object\" : { \"nested_string_field\" : \"test4a\"}} , \"$push\" : { \"nested_list\" : { \"$each\" : [ { \"nested_string_field\" : \"test4a\"} , { \"nested_string_field\" : \"test4b\"}]}}}", result4.toString()); } ////////////////////////// // Utils // (c/p from MongoDbUtils) /** Create a DB object from a bean template * @param bean_template * @return * @throws IOException * @throws JsonMappingException * @throws JsonGenerationException */ public static DBObject convertBeanTemplate(BeanTemplate<Object> bean_template, ObjectMapper object_mapper) { try { final BsonObjectGenerator generator = new BsonObjectGenerator(); object_mapper.writeValue(generator, bean_template.get()); return generator.getDBObject(); } catch (Exception e) { throw new RuntimeException(e); } } /** Create a DB object from a JsonNode * @param bean_template * @return * @throws IOException * @throws JsonMappingException * @throws JsonGenerationException */ public static DBObject convertJsonBean(JsonNode json, ObjectMapper object_mapper) { try { final BsonObjectGenerator generator = new BsonObjectGenerator(); object_mapper.writeTree(generator, json); return generator.getDBObject(); } catch (Exception e) { throw new RuntimeException(e); } } /** Create a MongoDB update object * @param update - the generic specification * @param add increments numbers or adds to sets/lists * @param remove decrements numbers of removes from sets/lists * @return the mongodb object * @throws IOException * @throws JsonMappingException * @throws JsonGenerationException */ @SuppressWarnings("unchecked") public static <O> DBObject createUpdateObject(final UpdateComponent<O> update) { final ObjectMapper object_mapper = MongoJackModule .configure(BeanTemplateUtils.configureMapper(Optional.empty())); return update.getAll().entries().stream() .map(kv -> Patterns .match(kv.getValue()._2()).<Map.Entry<String, Tuple2<UpdateOperator, Object>>>andReturn() // Special case, handle bean template .when(e -> null == e, __ -> kv) .when(JsonNode.class, j -> Maps.immutableEntry(kv.getKey(), Tuples._2T(kv.getValue()._1(), convertJsonBean(j, object_mapper)))) .when(BeanTemplate.class, e -> Maps.immutableEntry(kv.getKey(), Tuples._2T(kv.getValue()._1(), convertBeanTemplate(e, object_mapper)))) // Special case, handle list of bean templates .when(Collection.class, l -> !l.isEmpty() && (l.iterator().next() instanceof JsonNode), l -> Maps.immutableEntry(kv.getKey(), Tuples._2T(kv.getValue()._1(), l.stream().map(j -> convertJsonBean((JsonNode) j, object_mapper)) .collect(Collectors.toList())))) .when(Collection.class, l -> !l.isEmpty() && (l.iterator().next() instanceof BeanTemplate), l -> Maps.immutableEntry(kv.getKey(), Tuples._2T(kv.getValue()._1(), l.stream() .map(e -> convertBeanTemplate((BeanTemplate<Object>) e, object_mapper)) .collect(Collectors.toList())))) .otherwise(() -> kv)) .collect(Collector.of(BasicDBObject::new, (acc, kv) -> { Patterns.match(kv.getValue()._2()).andAct() // Delete operator, bunch of things have to happen for safety .when(o -> ((UpdateOperator.unset == kv.getValue()._1()) && kv.getKey().isEmpty() && (null == kv.getValue()._2())), o -> acc.put("$unset", null)) //Increment .when(Number.class, n -> (UpdateOperator.increment == kv.getValue()._1()), n -> nestedPut(acc, "$inc", kv.getKey(), n)) // Set .when(o -> (UpdateOperator.set == kv.getValue()._1()), o -> nestedPut(acc, "$set", kv.getKey(), o)) // Unset .when(o -> (UpdateOperator.unset == kv.getValue()._1()), o -> nestedPut(acc, "$unset", kv.getKey(), 1)) // Add items/item to list .when(Collection.class, c -> (UpdateOperator.add == kv.getValue()._1()), c -> nestedPut(acc, "$push", kv.getKey(), new BasicDBObject("$each", c))) .when(o -> (UpdateOperator.add == kv.getValue()._1()), o -> nestedPut(acc, "$push", kv.getKey(), o)) // Add item/items to set .when(Collection.class, c -> (UpdateOperator.add_deduplicate == kv.getValue()._1()), c -> nestedPut(acc, "$addToSet", kv.getKey(), new BasicDBObject("$each", c))) .when(o -> (UpdateOperator.add_deduplicate == kv.getValue()._1()), o -> nestedPut(acc, "$addToSet", kv.getKey(), o)) // Remove items from list by query .when(QueryComponent.class, q -> (UpdateOperator.remove == kv.getValue()._1()), q -> nestedPut(acc, "$pull", kv.getKey(), convertToMongoQuery(q)._1())) // Remove items/item from list .when(Collection.class, c -> (UpdateOperator.remove == kv.getValue()._1()), c -> nestedPut(acc, "$pullAll", kv.getKey(), c)) .when(o -> (UpdateOperator.remove == kv.getValue()._1()), o -> nestedPut(acc, "$pullAll", kv.getKey(), Arrays.asList(o))) .otherwise(() -> { }); // (do nothing) }, (a, b) -> { a.putAll(b.toMap()); return a; }, Characteristics.UNORDERED)); } /** Inserts an object into field1.field2, creating objects along the way * @param mutable the mutable object into which the the nested field is inserted * @param parent the top level fieldname * @param nested the nested fieldname * @param to_insert the object to insert */ protected static void nestedPut(final BasicDBObject mutable, final String parent, final String nested, final Object to_insert) { final DBObject dbo = (DBObject) mutable.get(parent); if (null != dbo) { dbo.put(nested, to_insert); } else { BasicDBObject new_dbo = new BasicDBObject(); new_dbo.put(nested, to_insert); mutable.put(parent, new_dbo); } } }