diff --git a/agent/src/main/java/li/strolch/search/ExpressionBuilder.java b/agent/src/main/java/li/strolch/search/ExpressionBuilder.java index f12d1579b..b078a5bce 100644 --- a/agent/src/main/java/li/strolch/search/ExpressionBuilder.java +++ b/agent/src/main/java/li/strolch/search/ExpressionBuilder.java @@ -3,6 +3,8 @@ import li.strolch.model.StrolchRootElement; import li.strolch.utils.collections.DateRange; +import java.time.ZonedDateTime; + /** * An interface to add search expressions to easily discover the possible search expressions */ @@ -73,4 +75,12 @@ default SearchExpression isInIgnoreCase(Object default SearchExpression inRange(DateRange range) { return element -> PredicatesSupport.inRange(range).matches(extract(element)); } + + default SearchExpression isBefore(ZonedDateTime date, boolean inclusive) { + return element -> PredicatesSupport.isBefore(date, inclusive).matches(extract(element)); + } + + default SearchExpression isAfter(ZonedDateTime date, boolean inclusive) { + return element -> PredicatesSupport.isAfter(date, inclusive).matches(extract(element)); + } } diff --git a/agent/src/main/java/li/strolch/search/PredicatesSupport.java b/agent/src/main/java/li/strolch/search/PredicatesSupport.java index f0258e25f..f33a47cbb 100644 --- a/agent/src/main/java/li/strolch/search/PredicatesSupport.java +++ b/agent/src/main/java/li/strolch/search/PredicatesSupport.java @@ -3,6 +3,8 @@ import li.strolch.search.predicates.*; import li.strolch.utils.collections.DateRange; +import java.time.ZonedDateTime; + /** * Implements predicates to be used as static imports when writing searches */ @@ -67,4 +69,12 @@ public static SearchPredicate isInIgnoreCase(Object right) { public static SearchPredicate inRange(DateRange range) { return new InRangePredicate(range); } + + public static SearchPredicate isBefore(ZonedDateTime dateTime, boolean inclusive) { + return new IsBeforePredicate(dateTime, inclusive); + } + + public static SearchPredicate isAfter(ZonedDateTime dateTime, boolean inclusive) { + return new IsAfterPredicate(dateTime, inclusive); + } } diff --git a/agent/src/main/java/li/strolch/search/predicates/DatePredicate.java b/agent/src/main/java/li/strolch/search/predicates/DatePredicate.java new file mode 100644 index 000000000..e509217a4 --- /dev/null +++ b/agent/src/main/java/li/strolch/search/predicates/DatePredicate.java @@ -0,0 +1,29 @@ +package li.strolch.search.predicates; + +import li.strolch.search.SearchPredicate; +import li.strolch.search.ValueCoercer; + +import java.time.ZonedDateTime; +import java.util.Date; + +/** + *

A date predicate, concrete classes implement matching.

+ * + * Note: Can only be used with {@link Date} elements + */ +public abstract class DatePredicate implements SearchPredicate { + + protected final ZonedDateTime dateTime; + protected final boolean inclusive; + + public DatePredicate(ZonedDateTime dateTime, boolean inclusive) { + this.dateTime = dateTime; + this.inclusive = inclusive; + } + + @Override + public SearchPredicate coerce(ValueCoercer coercer) { + // nothing to coerce + return this; + } +} diff --git a/agent/src/main/java/li/strolch/search/predicates/InRangePredicate.java b/agent/src/main/java/li/strolch/search/predicates/InRangePredicate.java index 8a6d07417..953c64191 100644 --- a/agent/src/main/java/li/strolch/search/predicates/InRangePredicate.java +++ b/agent/src/main/java/li/strolch/search/predicates/InRangePredicate.java @@ -1,5 +1,6 @@ package li.strolch.search.predicates; +import java.time.ZonedDateTime; import java.util.Date; import li.strolch.search.SearchPredicate; @@ -9,7 +10,7 @@ /** *

Implements the date in range predicate.

* - * Note: Can only be used with {@link Date} elements + * Note: Can only be used with {@link Date} or {@link ZonedDateTime} objects */ public class InRangePredicate implements SearchPredicate { private final DateRange range; @@ -20,7 +21,11 @@ public InRangePredicate(DateRange range) { @Override public boolean matches(Object left) { - return range.contains((Date) left); + if (left instanceof Date) + return this.range.contains((Date) left); + else if (left instanceof ZonedDateTime) + return this.range.contains((ZonedDateTime) left); + throw new IllegalStateException("Unhandled object type " + left.getClass()); } @Override diff --git a/agent/src/main/java/li/strolch/search/predicates/IsAfterPredicate.java b/agent/src/main/java/li/strolch/search/predicates/IsAfterPredicate.java new file mode 100644 index 000000000..da4a18389 --- /dev/null +++ b/agent/src/main/java/li/strolch/search/predicates/IsAfterPredicate.java @@ -0,0 +1,31 @@ +package li.strolch.search.predicates; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +/** + *

Implements the date is after predicate.

+ * + * Note: Can only be used with {@link Date} or {@link ZonedDateTime} objects + */ +public class IsAfterPredicate extends DatePredicate { + public IsAfterPredicate(ZonedDateTime dateTime, boolean inclusive) { + super(dateTime, inclusive); + } + + @Override + public boolean matches(Object left) { + if (left instanceof Date other) { + ZonedDateTime zdt = ZonedDateTime.ofInstant(other.toInstant(), ZoneId.systemDefault()); + if (this.inclusive && this.dateTime.isEqual(zdt)) + return true; + return zdt.isAfter(this.dateTime); + } else if (left instanceof ZonedDateTime zdt) { + if (this.inclusive && this.dateTime.isEqual(zdt)) + return true; + return zdt.isAfter(this.dateTime); + } + throw new IllegalStateException("Unhandled object type " + left.getClass()); + } +} diff --git a/agent/src/main/java/li/strolch/search/predicates/IsBeforePredicate.java b/agent/src/main/java/li/strolch/search/predicates/IsBeforePredicate.java new file mode 100644 index 000000000..eac1cf492 --- /dev/null +++ b/agent/src/main/java/li/strolch/search/predicates/IsBeforePredicate.java @@ -0,0 +1,31 @@ +package li.strolch.search.predicates; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +/** + *

Implements the date is before predicate.

+ * + * Note: Can only be used with {@link Date} or {@link ZonedDateTime} objects + */ +public class IsBeforePredicate extends DatePredicate { + public IsBeforePredicate(ZonedDateTime dateTime, boolean inclusive) { + super(dateTime, inclusive); + } + + @Override + public boolean matches(Object left) { + if (left instanceof Date other) { + ZonedDateTime zdt = ZonedDateTime.ofInstant(other.toInstant(), ZoneId.systemDefault()); + if (this.inclusive && this.dateTime.isEqual(zdt)) + return true; + return zdt.isBefore(this.dateTime); + } else if (left instanceof ZonedDateTime zdt) { + if (this.inclusive && this.dateTime.isEqual(zdt)) + return true; + return zdt.isBefore(this.dateTime); + } + throw new IllegalStateException("Unhandled object type " + left.getClass()); + } +} diff --git a/agent/src/test/java/li/strolch/search/StrolchSearchTest.java b/agent/src/test/java/li/strolch/search/StrolchSearchTest.java index 66237c3bf..c434f6e42 100644 --- a/agent/src/test/java/li/strolch/search/StrolchSearchTest.java +++ b/agent/src/test/java/li/strolch/search/StrolchSearchTest.java @@ -1,20 +1,7 @@ package li.strolch.search; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static li.strolch.agent.api.StrolchAgent.getUniqueId; -import static li.strolch.model.ModelGenerator.*; -import static li.strolch.search.ExpressionsSupport.*; -import static li.strolch.search.PredicatesSupport.*; -import static org.junit.Assert.assertEquals; - -import java.util.Date; -import java.util.List; -import java.util.Map; - import com.google.gson.JsonObject; import li.strolch.RuntimeMock; -import li.strolch.agent.ParallelTests; import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Order; import li.strolch.model.ParameterBag; @@ -27,6 +14,7 @@ import li.strolch.persistence.api.StrolchTransaction; import li.strolch.privilege.model.Certificate; import li.strolch.utils.collections.DateRange; +import li.strolch.utils.iso8601.ISO8601; import li.strolch.utils.iso8601.ISO8601FormatFactory; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -34,12 +22,28 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static java.time.ZoneId.systemDefault; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static li.strolch.agent.api.StrolchAgent.getUniqueId; +import static li.strolch.model.ModelGenerator.*; +import static li.strolch.search.ExpressionsSupport.*; +import static li.strolch.search.PredicatesSupport.*; +import static org.junit.Assert.assertEquals; + public class StrolchSearchTest { public static final Logger logger = LoggerFactory.getLogger(StrolchSearchTest.class); private static final String TARGET_PATH = "target/" + StrolchSearchTest.class.getSimpleName(); private static final String SOURCE_PATH = "src/test/resources/transienttest"; + public static final String SORTING_TYPE = "SortingType"; private static RuntimeMock runtimeMock; private static Certificate cert; @@ -51,7 +55,7 @@ public static void beforeClass() { cert = runtimeMock.getPrivilegeHandler().authenticate("test", "test".toCharArray()); StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, false)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, false)) { { Resource ball = createResource("the-id", "Yellow Ball", "Ball"); @@ -83,15 +87,15 @@ public static void beforeClass() { String id; id = "ggg"; - tx.add(createOrder(id, id.toUpperCase(), "SortingType")); + tx.add(createOrder(id, id.toUpperCase(), SORTING_TYPE)); id = "ccc"; - tx.add(createOrder(id, id.toUpperCase(), "SortingType")); + tx.add(createOrder(id, id.toUpperCase(), SORTING_TYPE)); id = "aaa"; - tx.add(createOrder(id, id.toUpperCase(), "SortingType")); + tx.add(createOrder(id, id.toUpperCase(), SORTING_TYPE)); id = "bbb"; - tx.add(createOrder(id, id.toUpperCase(), "SortingType")); + tx.add(createOrder(id, id.toUpperCase(), SORTING_TYPE)); id = "ddd"; - tx.add(createOrder(id, id.toUpperCase(), "SortingType")); + tx.add(createOrder(id, id.toUpperCase(), SORTING_TYPE)); tx.commitOnClose(); } @@ -112,7 +116,7 @@ public void shouldSearchResources() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { List result = new BallSearch("the-id", "STATUS", "yellow") // do search, returns SearchResult @@ -133,11 +137,11 @@ public void shouldSearchResources1() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { - List result = new BallSearch("the-id", "STATUS", "yellow") - .where(element -> element.hasTimedState(STATE_FLOAT_ID)).search(tx) - .map(a -> a.accept(toJsonVisitor)).toList(); + List result = new BallSearch("the-id", "STATUS", "yellow").where( + element -> element.hasTimedState(STATE_FLOAT_ID)).search(tx).map(a -> a.accept(toJsonVisitor)) + .toList(); assertEquals(2, result.size()); } @@ -150,7 +154,7 @@ public void shouldSearchResources2() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { List result = new NewBallSearch().id("the-id").status("bla").color("yellow") @@ -168,7 +172,7 @@ public void shouldSearchResources2() { @Test public void shouldSearchResources3() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(4, new ResourceSearch().types().where(param(BAG_ID, PARAM_STRING_ID, contains("rol"))).search(tx) @@ -182,7 +186,7 @@ public void shouldSearchResources3() { @Test public void shouldSearchResources4() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(8, new ResourceSearch().types().search(tx).toList().size()); } @@ -191,7 +195,7 @@ public void shouldSearchResources4() { @Test public void shouldSearchResources5() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(2, new ResourceSearch().types("sdf", "Ball").search(tx).toList().size()); assertEquals(2, new ResourceSearch().types("Ball", "sdf").search(tx).toList().size()); @@ -202,7 +206,7 @@ public void shouldSearchResources5() { @Test public void shouldSearchResources6() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(1, new ResourceSearch().types("TestType") // .where(relationName(tx, "other").isEqualTo("Yellow")) // @@ -220,19 +224,19 @@ public void shouldSearchResources6() { @Test public void shouldSearchOrders() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { List result = new OrderSearch() { @Override public void define() { - DateRange dateRange = new DateRange() - .from(ISO8601FormatFactory.getInstance().parseDate("2012-01-01T00:00:00.000+01:00"), true) - .to(ISO8601FormatFactory.getInstance().parseDate("2013-01-01T00:00:00.000+01:00"), true); + DateRange dateRange = new DateRange().from(ISO8601.parseToZdt("2012-01-01T00:00:00.000+01:00"), + true).to(ISO8601.parseToZdt("2013-01-01T00:00:00.000+01:00"), true); - types().where(date(isEqualTo(new Date(1384929777699L))).or(state(isEqualTo(State.CREATED)) - .and(param(BAG_ID, PARAM_STRING_ID, isEqualTo("Strolch"))) - .and(param(BAG_ID, PARAM_DATE_ID, inRange(dateRange))))); + types().where(date().isEqualTo(Instant.ofEpochMilli(1384929777699L).atZone(systemDefault())) + .or(state().isEqualTo(State.CREATED) + .and(param(BAG_ID, PARAM_STRING_ID).isEqualTo("Strolch")) + .and(param(BAG_ID, PARAM_DATE_ID).inRange(dateRange)))); } }.search(tx).toList(); @@ -243,12 +247,12 @@ public void define() { @Test public void shouldSearchOrders1() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { StrolchSearch search = new OrderSearch() { @Override public void define() { - types("SortingType").where(state(isEqualTo(State.CREATED))); + types(SORTING_TYPE).where(state(isEqualTo(State.CREATED))); } }; @@ -267,19 +271,40 @@ public void define() { @Test public void shouldSearchOrders2() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { - assertEquals(5, new OrderSearch().types("sdf", "SortingType").search(tx).toList().size()); - assertEquals(5, new OrderSearch().types("SortingType", "sdf").search(tx).toList().size()); - assertEquals(5, new OrderSearch().types("4gdf", "SortingType", "sdf").search(tx).toList().size()); + assertEquals(5, new OrderSearch().types("sdf", SORTING_TYPE).search(tx).toList().size()); + assertEquals(5, new OrderSearch().types(SORTING_TYPE, "sdf").search(tx).toList().size()); + assertEquals(5, new OrderSearch().types("4gdf", SORTING_TYPE, "sdf").search(tx).toList().size()); assertEquals(7, new OrderSearch().types().search(tx).toList().size()); } } + @Test + public void shouldSearchOrders3() { + StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { + + ZonedDateTime dateTime = ISO8601.parseToZdt("2013-11-20T07:42:57.699+01:00"); + assertEquals(0, + new OrderSearch().types("TestType").where(date().isBefore(dateTime, false)).search(tx).toList() + .size()); + assertEquals(1, + new OrderSearch().types("TestType").where(date().isBefore(dateTime, true)).search(tx).toList() + .size()); + assertEquals(0, + new OrderSearch().types("TestType").where(date().isAfter(dateTime, false)).search(tx).toList() + .size()); + assertEquals(1, + new OrderSearch().types("TestType").where(date().isAfter(dateTime, true)).search(tx).toList() + .size()); + } + } + @Test public void shouldSearchActivities() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { Map states = new ActivitySearch() { @Override @@ -295,7 +320,7 @@ public void define() { @Test public void shouldSearchActivities1() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { Map states = new ActivitySearch() @@ -310,7 +335,7 @@ public void shouldSearchActivities1() { @Test public void shouldSearchActivities2() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(2, new ActivitySearch().types("sdf", "ActivityType").search(tx).toList().size()); assertEquals(2, new ActivitySearch().types("ActivityType", "sdf").search(tx).toList().size()); @@ -322,7 +347,7 @@ public void shouldSearchActivities2() { @Test public void shouldSearchActivities3() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(1, new ActivitySearch().types("sdf", "ActivityType") .where(element -> element.getActionsByType("Use").size() == 4).search(tx).toList().size()); @@ -332,10 +357,10 @@ public void shouldSearchActivities3() { @Test public void shouldSearchRootElements() { StrolchRealm realm = runtimeMock.getAgent().getContainer().getRealm(cert); - try (StrolchTransaction tx = realm.openTx(cert, ParallelTests.class, true)) { + try (StrolchTransaction tx = realm.openTx(cert, StrolchSearchTest.class, true)) { assertEquals(9, - new RootElementSearch().types("SortingType", "Ball", "ActivityType").search(tx).toList().size()); + new RootElementSearch().types(SORTING_TYPE, "Ball", "ActivityType").search(tx).toList().size()); assertEquals(17, new RootElementSearch().types().search(tx).toList().size()); assertEquals(2, new RootElementSearch().types("ActivityType").search(tx).toList().size()); } @@ -380,8 +405,8 @@ public BallSearch(String id, String status, String color) { public void define() { types("Ball").where(id(isEqualTo(this.id)).or( // - param("parameters", "status", isEqualTo(this.status)) - .and(not(param("parameters", "color", isEqualTo(this.color)))) + param("parameters", "status", isEqualTo(this.status)).and( + not(param("parameters", "color", isEqualTo(this.color)))) .and(param("parameters", "state", isEqualTo(State.EXECUTION))) .and(param("parameters", "state", isEqualToIgnoreCase(State.EXECUTION))) @@ -401,9 +426,9 @@ public void define() { .and(param(BAG_ID, PARAM_STRING_ID, isIn("Strolch", "sdf"))) .and(param(BAG_ID, PARAM_STRING_ID, isInIgnoreCase("strolch"))) .and(param(BAG_ID, PARAM_STRING_ID, isInIgnoreCase("strolch", "dfgdfg"))) - .and(param(BAG_ID, PARAM_STRING_ID, contains(new String[] { "Str", "rol" }))) + .and(param(BAG_ID, PARAM_STRING_ID, contains(new String[]{"Str", "rol"}))) .and(param(BAG_ID, PARAM_STRING_ID, containsIgnoreCase("ROL"))) - .and(param(BAG_ID, PARAM_STRING_ID, containsIgnoreCase(new String[] { "STR", "ROL" }))) + .and(param(BAG_ID, PARAM_STRING_ID, containsIgnoreCase(new String[]{"STR", "ROL"}))) .and(param(BAG_ID, PARAM_STRING_ID, startsWith("Str"))) .and(param(BAG_ID, PARAM_STRING_ID, startsWithIgnoreCase("str"))) .and(param(BAG_ID, PARAM_STRING_ID, endsWith("lch"))) @@ -433,9 +458,9 @@ public void define() { .and(param(BAG_ID, PARAM_LIST_LONG_ID, isEqualTo(asList(7L, 12L, 17L)))) .and(param(BAG_ID, PARAM_LIST_STRING_ID, isEqualTo(asList("Hello", "World")))) .and(param(BAG_ID, PARAM_LIST_STRING_ID, isEqualToIgnoreCase(asList("hello", "world")))) - .and(param(BAG_ID, PARAM_LIST_STRING_ID, contains(new String[] { "Hel", "wor" }))) - .and(param(BAG_ID, PARAM_LIST_STRING_ID, containsIgnoreCase(new String[] { "Hel", "wor" }))) - .and(param(BAG_ID, PARAM_LIST_STRING_ID, containsIgnoreCase(new String[] { "hel" }))) + .and(param(BAG_ID, PARAM_LIST_STRING_ID, contains(new String[]{"Hel", "wor"}))) + .and(param(BAG_ID, PARAM_LIST_STRING_ID, containsIgnoreCase(new String[]{"Hel", "wor"}))) + .and(param(BAG_ID, PARAM_LIST_STRING_ID, containsIgnoreCase(new String[]{"hel"}))) .and(param(BAG_ID, PARAM_LIST_STRING_ID, isIn(asList("Hello", "World")))) .and(param(BAG_ID, PARAM_LIST_STRING_ID, isIn(asList("Hello", "World", "Extra")))) .and(param(BAG_ID, PARAM_LIST_STRING_ID, isIn(asList("Extra", "Sauce")).not()))