From b5e1f7593bcad982a4d52575c5eeeb17e4fc5596 Mon Sep 17 00:00:00 2001 From: phmai Date: Mon, 16 Dec 2024 12:09:43 +0100 Subject: [PATCH 01/11] separate recursion of SourceIdHandler from util Signed-off-by: phmai --- .../naksha/lib/handlers/SourceIdHandler.java | 61 +++- .../lib/handlers/SourceIdHandlerUnitTest.java | 338 ++++++++++-------- 2 files changed, 237 insertions(+), 162 deletions(-) diff --git a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java index 5e8065598..5bdafb5f3 100644 --- a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java +++ b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java @@ -39,7 +39,7 @@ public class SourceIdHandler extends AbstractEventHandler { private static final Logger logger = LoggerFactory.getLogger(SourceIdHandler.class); - private static final String TAG_PREFIX = "xyz_source_id_"; // TODO decide CASL-710 + private static final String TAG_PREFIX = "xyz_source_id_"; private static final String SOURCE_ID = "sourceId"; public static final int PREF_PATHS_SIZE = 2; @@ -88,9 +88,10 @@ private void transformPropertyOperation(ReadFeatures readRequest) { IPropertyQuery propertyOp = readRequest.getQuery().getProperties(); - // TODO fix it CASL-710 - // PropertyOperationUtil.transformPropertyInPropertyOperationTree( - // propertyOp, SourceIdHandler::mapIntoTagOperation); + final Optional tagQuery = mapIntoTagOperation(propertyOp); + if (tagQuery.isPresent()) { + readRequest.getQuery().setTags(tagQuery.get()); + } } private void setSourceIdTags(NakshaFeature feature) { @@ -114,14 +115,52 @@ private Optional getSourceIdFromFeature(NakshaProperties properties) { } } - public static Optional mapIntoTagOperation(PQuery propertyOperation) { - - if (sourceIdTransformationCapable(propertyOperation) && operationTypeAllowed(propertyOperation)) { - final TagExists tagQuery = new TagExists(TAG_PREFIX + propertyOperation.getValue()); - return Optional.of(tagQuery); + public static Optional mapIntoTagOperation(IPropertyQuery propertyOperation) { + if (propertyOperation instanceof PAnd pAnd) { + final TagAnd tagAnd = new TagAnd(); + for (IPropertyQuery component : pAnd) { + final Optional tagComponent = mapIntoTagOperation(component); + if (tagComponent.isPresent()) { + tagAnd.add(tagComponent.get()); + } + else { + return Optional.empty(); + } + } + return Optional.of(tagAnd); + } + else if (propertyOperation instanceof POr pOr) { + final TagOr tagOr = new TagOr(); + for (IPropertyQuery component : pOr) { + final Optional tagComponent = mapIntoTagOperation(component); + if (tagComponent.isPresent()) { + tagOr.add(tagComponent.get()); + } + else { + return Optional.empty(); + } + } + return Optional.of(tagOr); + } + else if (propertyOperation instanceof PNot pNot) { + final Optional tagComponent = mapIntoTagOperation(pNot.getQuery()); + if (tagComponent.isPresent()) { + return Optional.of(new TagNot(tagComponent.get())); + } + else { + return Optional.empty(); + } + } + else if (propertyOperation instanceof PQuery pQuery) { + if (sourceIdTransformationCapable(pQuery) && operationTypeAllowed(pQuery)) { + final TagExists tagQuery = new TagExists(TAG_PREFIX + pQuery.getValue()); + return Optional.of(tagQuery); + } + return Optional.empty(); + } + else { + throw new IllegalArgumentException("Unknown property operation type: " + propertyOperation.getClass().getSimpleName()); } - - return Optional.empty(); } private static boolean propertyReferenceEqualsSourceId(Property pRef) { diff --git a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java index bca79d731..c771a1186 100644 --- a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java +++ b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java @@ -1,157 +1,193 @@ package com.here.naksha.lib.handlers; +import com.here.naksha.lib.handlers.util.PropertyOperationUtil; +import naksha.model.objects.NakshaProperties; +import naksha.model.request.RequestQuery; +import naksha.model.request.query.*; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + class SourceIdHandlerUnitTest { - //TODO fix these tests when source id handler is fixed CASL-710 -// @Test -// void tc2002_testMapEqToContainsTag() { -// //given -// final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); -// PQuery given = new PQuery(property, StringOp.EQUALS,"task_1"); -// //when -// -// Optional result = SourceIdHandler.mapIntoTagOperation(given); -// //then -// -// assertTrue(result.isPresent()); -// assertEquals("xyz_source_id_task_1", result.get().getName()); -// } -// -// @Test -// void tc2003_testMapNotEqToNotContainsTag() { -// //given -// NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); -// POp given = POp.not(POp.eq(pRef, "task_1")); -// //when -// -// PropertyOperationUtil.transformPropertyInPropertyOperationTree(given, SourceIdHandler::mapIntoTagOperation); -// //then -// -// assertFalse(given.children().isEmpty()); -// -// POp nestedPop = given.children().get(0); -// assertEquals(nestedPop.getPropertyRef().getTagName(), "xyz_source_id_task_1"); -// assertEquals(nestedPop.op(), POpType.EXISTS); -// } -// -// @Test -// void tc2004_testMapContainsToContainsTag() { -// //given -// NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); -// POp given = POp.contains(pRef, "task_1"); -// //when -// -// Optional result = SourceIdHandler.mapIntoTagOperation(given); -// //then -// -// assertTrue(result.isPresent()); -// assertEquals(result.get().getPropertyRef().getTagName(), "xyz_source_id_task_1"); -// assertEquals(result.get().op(), POpType.EXISTS); -// } -// -// @Test -// void tc2005_testMapOnlyCorrectPref() { -// //given -// NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "WrongPRef"); -// POp given = POp.eq(pRef, "task_1"); -// //when -// -// Optional result = SourceIdHandler.mapIntoTagOperation(given); -// //then -// -// assertTrue(result.isEmpty()); -// } -// -// @Test -// void tc2006_testMapsCorrectlyCombinedOperation () { -// //given -// NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); -// POp given = POp.and(POp.not(POp.eq(pRef, "task_1")), POp.contains(PRef.tag("funnyTag"), "4")); -// //when -// -// PropertyOperationUtil.transformPropertyInPropertyOperationTree(given, SourceIdHandler::mapIntoTagOperation); -// //then -// -// assertEquals(given.op(), OpType.AND); -// assertFalse(given.children().isEmpty()); -// assertEquals(given.children().size(), 2); -// assertEquals(given.children().get(0).op(), OpType.NOT); -// -// POp nestedPop = given.children().get(0).children().get(0); -// assertEquals(nestedPop.getPropertyRef().getTagName(), "xyz_source_id_task_1"); -// assertEquals(nestedPop.op(), POpType.EXISTS); -// -// assertEquals(given.children().get(1).op(), POpType.CONTAINS); -// } -// @Test -// void tc2007_testMapEqToContainsTagWithoutNormalization() { -// //given -// NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); -// POp given = POp.eq(pRef, "tAskK_1"); -// -// //when -// Optional result = SourceIdHandler.mapIntoTagOperation(given); -// -// //then -// assertTrue(result.isPresent()); -// assertEquals(result.get().getPropertyRef().getTagName(), "xyz_source_id_tAskK_1"); -// assertEquals(result.get().op(), POpType.EXISTS); -// } -// -// @ParameterizedTest -// @MethodSource("writeRequestTestParams") -// void testWriteRequestTagPopulation(final WriteFeatures wf, final String expectedFeatureJson) throws JSONException { -// // Given: Mocking in place -// final INaksha naksha = mock(INaksha.class); -// final IEvent event = mock(IEvent.class); -// when(event.getRequest()).thenReturn((Request)wf); -// when(event.sendUpstream(any())).thenReturn(new SuccessResult()); -// -// // Given: Handler initialization -// final EventHandler e = new EventHandler(SourceIdHandler.class, "some_id"); -// final SourceIdHandler sourceIdHandler = new SourceIdHandler(naksha); -// -// // When: handler processing logic is invoked -// try (final Result result = sourceIdHandler.process(event)) { -// assertTrue(result instanceof SuccessResult, "SuccessResult was expected"); -// } -// // Then: validate that the feature in the original request is modified as per expectation -// assertNotNull(wf.features.get(0)); -// assertNotNull(wf.features.get(0).getFeature()); -// JSONAssert.assertEquals("Output Feature not as expected", expectedFeatureJson, wf.features.get(0).getFeature().serialize(), JSONCompareMode.STRICT); -// } -// -// private static Stream writeRequestTestParams() { -// // Common parameters across tests -// final String commonFilePath = "SourceIdFilter/testWriteFeatureTagPopulation/input_feature.json"; -// final String expectedFeatureJson = loadFileOrFail("SourceIdFilter/testWriteFeatureTagPopulation/output_feature.json"); -// -// return Stream.of( -// Arguments.arguments( -// Named.named( -// "WriteXyzFeatures tag population", -// createWriteXyzFeaturesFromFile(commonFilePath) -// ), -// expectedFeatureJson -// ), -// Arguments.arguments( -// Named.named( -// "ContextWriteXyzFeatures tag population", -// createContextWriteXyzFeaturesFromFile(commonFilePath) -// ), -// expectedFeatureJson -// ) -// ); -// } -// -// private static WriteFeatures createWriteXyzFeaturesFromFile(final String filePath) { -// final XyzFeature feature = parseJsonFileOrFail(filePath, XyzFeature.class); -// return new WriteXyzFeatures("some_collection").add(EWriteOp.CREATE, feature); -// } -// -// private static WriteFeatures createContextWriteXyzFeaturesFromFile(final String filePath) { -// final XyzFeature feature = parseJsonFileOrFail(filePath, XyzFeature.class); -// return new ContextWriteXyzFeatures("some_collection").add(EWriteOp.CREATE, feature); -// } + @Test + void tc2002_testMapEqToContainsTag() { + //given + final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); + final PQuery given = new PQuery(property, StringOp.EQUALS,"task_1"); + //when + + Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + //then + + assertTrue(tagQuery.isPresent()); + assertInstanceOf(TagExists.class,tagQuery.get()); + final TagExists exists = (TagExists) tagQuery.get(); + assertEquals("xyz_source_id_task_1", exists.getName()); + } + + @Test + void tc2003_testMapNotEqToNotContainsTag() { + //given + final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); + final IPropertyQuery given = new PNot(new PQuery(property, StringOp.EQUALS,"task_1")); + //when + + Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + //then + + assertTrue(tagQuery.isPresent()); + assertInstanceOf(TagNot.class,tagQuery.get()); + final TagNot tagNot = (TagNot) tagQuery.get(); + assertInstanceOf(TagExists.class,tagNot.getQuery()); + final TagExists exists = (TagExists) tagNot.getQuery(); + assertEquals("xyz_source_id_task_1", exists.getName()); + } + + @Test + void tc2004_testMapContainsToContainsTag() { + //given + final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); + final PQuery given = new PQuery(property, StringOp.CONTAINS,"task_1"); + //when + + final Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + //then + + assertTrue(tagQuery.isPresent()); + assertInstanceOf(TagExists.class,tagQuery.get()); + final TagExists exists = (TagExists) tagQuery.get(); + assertEquals("xyz_source_id_task_1", exists.getName()); + } + + @Test + void tc2005_testMapOnlyCorrectPref() { + //given + final Property property = new Property(NakshaProperties.META_KEY, "WrongProperty"); + final PQuery given = new PQuery(property, StringOp.EQUALS,"task_1"); + //when + + final Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + //then + + assertFalse(tagQuery.isPresent()); + } + + @Test + void tc2006_testMapsCorrectlyCombinedOperation () { + //given + NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); + POp given = POp.and(POp.not(POp.eq(pRef, "task_1")), POp.contains(PRef.tag("funnyTag"), "4")); + //when + + PropertyOperationUtil.transformPropertyInPropertyOperationTree(given, SourceIdHandler::mapIntoTagOperation); + //then + + assertEquals(given.op(), OpType.AND); + assertFalse(given.children().isEmpty()); + assertEquals(given.children().size(), 2); + assertEquals(given.children().get(0).op(), OpType.NOT); + + POp nestedPop = given.children().get(0).children().get(0); + assertEquals(nestedPop.getPropertyRef().getTagName(), "xyz_source_id_task_1"); + assertEquals(nestedPop.op(), POpType.EXISTS); + + assertEquals(given.children().get(1).op(), POpType.CONTAINS); + + //given + final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); + final Property property2 = new Property(NakshaProperties.META_KEY, "funnyTag"); + final PAnd given = new PAnd(); + given.add(new PNot(new PQuery(property, StringOp.EQUALS,"task_1"))); + given.add(new PQuery(property2, StringOp.CONTAINS,"4")); + + final RequestQuery query = new RequestQuery(); + query.setProperties(given); + //when + + PropertyOperationUtil.transformPropertyInPropertyOperationTree(given,query, (propertyOperation, query1) -> SourceIdHandler.mapIntoTagOperation(propertyOperation)); + //then + + assertInstanceOf(TagAnd.class,query.getTags()); + final TagAnd tagAnd = (TagAnd) query.getTags(); + assertEquals(2, tagAnd.size()); + assertInstanceOf(TagNot.class,tagAnd.get(0)); + final TagNot nestedTagNot = (TagNot) tagAnd.get(0); + assertInstanceOf(TagExists.class,nestedTagNot.getQuery()); + final TagExists nestedTagExist = (TagExists) nestedTagNot.getQuery(); + assertEquals("xyz_source_id_task_1", nestedTagExist.getName()); + } + @Test + void tc2007_testMapEqToContainsTagWithoutNormalization() { + //given + NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); + POp given = POp.eq(pRef, "tAskK_1"); + + //when + Optional result = SourceIdHandler.mapIntoTagOperation(given); + + //then + assertTrue(result.isPresent()); + assertEquals(result.get().getPropertyRef().getTagName(), "xyz_source_id_tAskK_1"); + assertEquals(result.get().op(), POpType.EXISTS); + } + + @ParameterizedTest + @MethodSource("writeRequestTestParams") + void testWriteRequestTagPopulation(final WriteFeatures wf, final String expectedFeatureJson) throws JSONException { + // Given: Mocking in place + final INaksha naksha = mock(INaksha.class); + final IEvent event = mock(IEvent.class); + when(event.getRequest()).thenReturn((Request)wf); + when(event.sendUpstream(any())).thenReturn(new SuccessResult()); + + // Given: Handler initialization + final EventHandler e = new EventHandler(SourceIdHandler.class, "some_id"); + final SourceIdHandler sourceIdHandler = new SourceIdHandler(naksha); + + // When: handler processing logic is invoked + try (final Result result = sourceIdHandler.process(event)) { + assertTrue(result instanceof SuccessResult, "SuccessResult was expected"); + } + // Then: validate that the feature in the original request is modified as per expectation + assertNotNull(wf.features.get(0)); + assertNotNull(wf.features.get(0).getFeature()); + JSONAssert.assertEquals("Output Feature not as expected", expectedFeatureJson, wf.features.get(0).getFeature().serialize(), JSONCompareMode.STRICT); + } + + private static Stream writeRequestTestParams() { + // Common parameters across tests + final String commonFilePath = "SourceIdFilter/testWriteFeatureTagPopulation/input_feature.json"; + final String expectedFeatureJson = loadFileOrFail("SourceIdFilter/testWriteFeatureTagPopulation/output_feature.json"); + + return Stream.of( + Arguments.arguments( + Named.named( + "WriteXyzFeatures tag population", + createWriteXyzFeaturesFromFile(commonFilePath) + ), + expectedFeatureJson + ), + Arguments.arguments( + Named.named( + "ContextWriteXyzFeatures tag population", + createContextWriteXyzFeaturesFromFile(commonFilePath) + ), + expectedFeatureJson + ) + ); + } + + private static WriteFeatures createWriteXyzFeaturesFromFile(final String filePath) { + final XyzFeature feature = parseJsonFileOrFail(filePath, XyzFeature.class); + return new WriteXyzFeatures("some_collection").add(EWriteOp.CREATE, feature); + } + + private static WriteFeatures createContextWriteXyzFeaturesFromFile(final String filePath) { + final XyzFeature feature = parseJsonFileOrFail(filePath, XyzFeature.class); + return new ContextWriteXyzFeatures("some_collection").add(EWriteOp.CREATE, feature); + } } \ No newline at end of file From e8458e6241075db5ce8b60c30aff614e2226c3a5 Mon Sep 17 00:00:00 2001 From: phmai Date: Tue, 17 Dec 2024 14:00:37 +0100 Subject: [PATCH 02/11] note for future REST API plans Signed-off-by: phmai --- .../here/naksha/app/service/http/ops/PropertySearchUtil.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java index bfdd7babc..b4385e0c9 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java @@ -62,6 +62,11 @@ // * Multiple parameter values concatenated with "," (COMMA) delimiter, will result into OR list. // *
// * So, "p.prop_1=value_1,value_11" will form OR condition as (p.prop_1=value_1 OR p.prop_1=value_11). +// *
+// * NOTE that OR condition is supported only for the same one key and multiple values only, not for multiple key value pairs. +// * The reason is to prevent complication when transformation between property search and other types of search like tag search is employed (for example through Source ID Handler). +// * So, "?p.property_name_1=value_1 OR p.@ns:com:here:meta.sourceId=abc" through Source ID Handler would then become an OR between a property search (the first clause unchanged) and a tag search (the second clause transformed), which is not supported. +// * Only AND relation is supported between different types of search (property, tag, spatial,...). // *

// * // * @param queryParams API query parameter from where property search params need to be extracted From 746c4f22f6b0581cd2ade6be43944af599ffb3e2 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 14:11:12 +0100 Subject: [PATCH 03/11] WIP Signed-off-by: phmai --- .../naksha/lib/handlers/SourceIdHandler.java | 74 +++++++++++++------ .../lib/handlers/SourceIdHandlerUnitTest.java | 33 ++------- 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java index 5bdafb5f3..6d28c9cbf 100644 --- a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java +++ b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java @@ -24,10 +24,9 @@ import com.here.naksha.lib.core.IEvent; import com.here.naksha.lib.core.INaksha; import com.here.naksha.lib.core.models.storage.ContextWriteXyzFeatures; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; + import naksha.model.objects.NakshaFeature; import naksha.model.objects.NakshaProperties; import naksha.model.request.*; @@ -91,6 +90,13 @@ private void transformPropertyOperation(ReadFeatures readRequest) { final Optional tagQuery = mapIntoTagOperation(propertyOp); if (tagQuery.isPresent()) { readRequest.getQuery().setTags(tagQuery.get()); + if (isFullyConvertedToITagQuery(propertyOp)) { + readRequest.getQuery().setProperties(null); + } + // Unwrap the query if it is an AND clause with only 1 remaining sub-clause + else if ((propertyOp instanceof PAnd canBeSimplified) && (canBeSimplified.size() == 1)) { + readRequest.getQuery().setProperties(canBeSimplified.get(0)); + } } } @@ -115,41 +121,63 @@ private Optional getSourceIdFromFeature(NakshaProperties properties) { } } + private static boolean isFullyConvertedToITagQuery(IPropertyQuery propertyQuery) { + return (propertyQuery instanceof PQuery) || (propertyQuery instanceof PNot) + || (propertyQuery instanceof POr) + || ((propertyQuery instanceof PAnd pAnd) && (pAnd.isEmpty())); + } + + /** + * For the AND case, any property query sub-clause that can be converted into tag query will be converted and returned, leaving the remaining inconvertible clauses intact. + *
+ * For the OR case, it is required that every sub-clause must be convertible, else the whole clause will not be converted. + * This is because OR relation between types of queries (property, tag, spatial,...) is not supported, only AND is supported and applied at the very end of the request. + */ public static Optional mapIntoTagOperation(IPropertyQuery propertyOperation) { if (propertyOperation instanceof PAnd pAnd) { final TagAnd tagAnd = new TagAnd(); - for (IPropertyQuery component : pAnd) { - final Optional tagComponent = mapIntoTagOperation(component); + // List of successfully transformed property queries to be removed at the end, so as not to disrupt the loop + final List toRemove = new ArrayList<>(); + final int size = pAnd.size(); + for (int i = 0; i < size; i++) { + final IPropertyQuery propertyComponent = pAnd.get(i); + final Optional tagComponent = mapIntoTagOperation(propertyComponent); if (tagComponent.isPresent()) { tagAnd.add(tagComponent.get()); + if (isFullyConvertedToITagQuery(propertyComponent)) { + toRemove.add(propertyComponent); + } + // Unwrap the query if it is an AND clause with only 1 remaining sub-clause + else if ((propertyComponent instanceof PAnd canBeSimplified) && (canBeSimplified.size() == 1)) { + pAnd.set(i, canBeSimplified.get(0)); + } } - else { - return Optional.empty(); - } + } + pAnd.removeAll(toRemove); + if (tagAnd.isEmpty()) { + return Optional.empty(); + } + else if (tagAnd.size() == 1) { + // Unwrap this one single query in an AND clause + return Optional.of(tagAnd.get(0)); } return Optional.of(tagAnd); } else if (propertyOperation instanceof POr pOr) { final TagOr tagOr = new TagOr(); - for (IPropertyQuery component : pOr) { - final Optional tagComponent = mapIntoTagOperation(component); - if (tagComponent.isPresent()) { - tagOr.add(tagComponent.get()); - } - else { - return Optional.empty(); + for (IPropertyQuery iPropertyQuery : pOr) { + final Optional tagComponent = mapIntoTagOperation(iPropertyQuery); + if (tagComponent.isEmpty()) { + // At least one sub-clause in an OR clause cannot be converted, hence abort and leave the whole OR as is + return Optional.empty(); + } + tagOr.add(tagComponent.get()); } - } return Optional.of(tagOr); } else if (propertyOperation instanceof PNot pNot) { final Optional tagComponent = mapIntoTagOperation(pNot.getQuery()); - if (tagComponent.isPresent()) { - return Optional.of(new TagNot(tagComponent.get())); - } - else { - return Optional.empty(); - } + return tagComponent.map(TagNot::new); } else if (propertyOperation instanceof PQuery pQuery) { if (sourceIdTransformationCapable(pQuery) && operationTypeAllowed(pQuery)) { diff --git a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java index c771a1186..96f467250 100644 --- a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java +++ b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java @@ -77,25 +77,6 @@ void tc2005_testMapOnlyCorrectPref() { @Test void tc2006_testMapsCorrectlyCombinedOperation () { - //given - NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); - POp given = POp.and(POp.not(POp.eq(pRef, "task_1")), POp.contains(PRef.tag("funnyTag"), "4")); - //when - - PropertyOperationUtil.transformPropertyInPropertyOperationTree(given, SourceIdHandler::mapIntoTagOperation); - //then - - assertEquals(given.op(), OpType.AND); - assertFalse(given.children().isEmpty()); - assertEquals(given.children().size(), 2); - assertEquals(given.children().get(0).op(), OpType.NOT); - - POp nestedPop = given.children().get(0).children().get(0); - assertEquals(nestedPop.getPropertyRef().getTagName(), "xyz_source_id_task_1"); - assertEquals(nestedPop.op(), POpType.EXISTS); - - assertEquals(given.children().get(1).op(), POpType.CONTAINS); - //given final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); final Property property2 = new Property(NakshaProperties.META_KEY, "funnyTag"); @@ -103,21 +84,23 @@ void tc2006_testMapsCorrectlyCombinedOperation () { given.add(new PNot(new PQuery(property, StringOp.EQUALS,"task_1"))); given.add(new PQuery(property2, StringOp.CONTAINS,"4")); - final RequestQuery query = new RequestQuery(); - query.setProperties(given); //when - PropertyOperationUtil.transformPropertyInPropertyOperationTree(given,query, (propertyOperation, query1) -> SourceIdHandler.mapIntoTagOperation(propertyOperation)); + final Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); //then - assertInstanceOf(TagAnd.class,query.getTags()); - final TagAnd tagAnd = (TagAnd) query.getTags(); - assertEquals(2, tagAnd.size()); + assertTrue(tagQuery.isPresent()); + assertInstanceOf(TagAnd.class,tagQuery.get()); + final TagAnd tagAnd = (TagAnd) tagQuery.get(); + assertEquals(1, tagAnd.size()); assertInstanceOf(TagNot.class,tagAnd.get(0)); final TagNot nestedTagNot = (TagNot) tagAnd.get(0); assertInstanceOf(TagExists.class,nestedTagNot.getQuery()); final TagExists nestedTagExist = (TagExists) nestedTagNot.getQuery(); assertEquals("xyz_source_id_task_1", nestedTagExist.getName()); + assertEquals(1, given.size()); + assertInstanceOf(PQuery.class, given.get(0)); + assertEquals(StringOp.CONTAINS,((PQuery) given.get(0)).getOp()); } @Test void tc2007_testMapEqToContainsTagWithoutNormalization() { From 919fb90b35ef837d5fd545996e38ebf671913f74 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 14:26:36 +0100 Subject: [PATCH 04/11] WIP Signed-off-by: phmai --- .../naksha/lib/handlers/SourceIdHandler.java | 26 +++---- .../lib/handlers/SourceIdHandlerUnitTest.java | 78 ++++++++++++------- 2 files changed, 63 insertions(+), 41 deletions(-) diff --git a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java index 6d28c9cbf..baa38188e 100644 --- a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java +++ b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java @@ -61,7 +61,7 @@ protected EventProcessingStrategy processingStrategyFor(IEvent event) { logger.info("Handler received request {}", request.getClass().getSimpleName()); if (request instanceof ReadFeatures readRequest) { // Read request - transformPropertyOperation(readRequest); + mapIntoTagOperation(readRequest); } else if (request instanceof WriteRequest wr) { // Write request WriteList codecList = wr.getWrites(); @@ -79,7 +79,13 @@ protected EventProcessingStrategy processingStrategyFor(IEvent event) { return event.sendUpstream(request); } - private void transformPropertyOperation(ReadFeatures readRequest) { + /** + * For the AND case, any property query sub-clause that can be converted into tag query will be converted and returned, leaving the remaining inconvertible clauses intact. + *
+ * For the OR case, it is required that every sub-clause must be convertible, else the whole clause will not be converted. + * This is because OR relation between types of queries (property, tag, spatial,...) is not supported, only AND is supported and applied at the very end of the request. + */ + public static void mapIntoTagOperation(ReadFeatures readRequest) { if (readRequest.getQuery().getProperties() == null) { return; @@ -87,7 +93,7 @@ private void transformPropertyOperation(ReadFeatures readRequest) { IPropertyQuery propertyOp = readRequest.getQuery().getProperties(); - final Optional tagQuery = mapIntoTagOperation(propertyOp); + final Optional tagQuery = transformPropertyOperation(propertyOp); if (tagQuery.isPresent()) { readRequest.getQuery().setTags(tagQuery.get()); if (isFullyConvertedToITagQuery(propertyOp)) { @@ -127,13 +133,7 @@ private static boolean isFullyConvertedToITagQuery(IPropertyQuery propertyQuery) || ((propertyQuery instanceof PAnd pAnd) && (pAnd.isEmpty())); } - /** - * For the AND case, any property query sub-clause that can be converted into tag query will be converted and returned, leaving the remaining inconvertible clauses intact. - *
- * For the OR case, it is required that every sub-clause must be convertible, else the whole clause will not be converted. - * This is because OR relation between types of queries (property, tag, spatial,...) is not supported, only AND is supported and applied at the very end of the request. - */ - public static Optional mapIntoTagOperation(IPropertyQuery propertyOperation) { + private static Optional transformPropertyOperation(IPropertyQuery propertyOperation) { if (propertyOperation instanceof PAnd pAnd) { final TagAnd tagAnd = new TagAnd(); // List of successfully transformed property queries to be removed at the end, so as not to disrupt the loop @@ -141,7 +141,7 @@ public static Optional mapIntoTagOperation(IPropertyQuery propertyOpe final int size = pAnd.size(); for (int i = 0; i < size; i++) { final IPropertyQuery propertyComponent = pAnd.get(i); - final Optional tagComponent = mapIntoTagOperation(propertyComponent); + final Optional tagComponent = transformPropertyOperation(propertyComponent); if (tagComponent.isPresent()) { tagAnd.add(tagComponent.get()); if (isFullyConvertedToITagQuery(propertyComponent)) { @@ -166,7 +166,7 @@ else if (tagAnd.size() == 1) { else if (propertyOperation instanceof POr pOr) { final TagOr tagOr = new TagOr(); for (IPropertyQuery iPropertyQuery : pOr) { - final Optional tagComponent = mapIntoTagOperation(iPropertyQuery); + final Optional tagComponent = transformPropertyOperation(iPropertyQuery); if (tagComponent.isEmpty()) { // At least one sub-clause in an OR clause cannot be converted, hence abort and leave the whole OR as is return Optional.empty(); @@ -176,7 +176,7 @@ else if (propertyOperation instanceof POr pOr) { return Optional.of(tagOr); } else if (propertyOperation instanceof PNot pNot) { - final Optional tagComponent = mapIntoTagOperation(pNot.getQuery()); + final Optional tagComponent = transformPropertyOperation(pNot.getQuery()); return tagComponent.map(TagNot::new); } else if (propertyOperation instanceof PQuery pQuery) { diff --git a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java index 96f467250..7e8a03074 100644 --- a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java +++ b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java @@ -1,12 +1,15 @@ package com.here.naksha.lib.handlers; -import com.here.naksha.lib.handlers.util.PropertyOperationUtil; import naksha.model.objects.NakshaProperties; -import naksha.model.request.RequestQuery; +import naksha.model.request.ReadFeatures; import naksha.model.request.query.*; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Optional; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -17,14 +20,17 @@ void tc2002_testMapEqToContainsTag() { //given final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); final PQuery given = new PQuery(property, StringOp.EQUALS,"task_1"); + final ReadFeatures readFeatures = new ReadFeatures(); + readFeatures.getQuery().setProperties(given); //when - Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + SourceIdHandler.mapIntoTagOperation(readFeatures); //then - assertTrue(tagQuery.isPresent()); - assertInstanceOf(TagExists.class,tagQuery.get()); - final TagExists exists = (TagExists) tagQuery.get(); + final ITagQuery tagQuery = readFeatures.getQuery().getTags(); + + assertInstanceOf(TagExists.class,tagQuery); + final TagExists exists = (TagExists) tagQuery; assertEquals("xyz_source_id_task_1", exists.getName()); } @@ -33,14 +39,17 @@ void tc2003_testMapNotEqToNotContainsTag() { //given final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); final IPropertyQuery given = new PNot(new PQuery(property, StringOp.EQUALS,"task_1")); + final ReadFeatures readFeatures = new ReadFeatures(); + readFeatures.getQuery().setProperties(given); //when - Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + SourceIdHandler.mapIntoTagOperation(readFeatures); //then - assertTrue(tagQuery.isPresent()); - assertInstanceOf(TagNot.class,tagQuery.get()); - final TagNot tagNot = (TagNot) tagQuery.get(); + final ITagQuery tagQuery = readFeatures.getQuery().getTags(); + + assertInstanceOf(TagNot.class,tagQuery); + final TagNot tagNot = (TagNot) tagQuery; assertInstanceOf(TagExists.class,tagNot.getQuery()); final TagExists exists = (TagExists) tagNot.getQuery(); assertEquals("xyz_source_id_task_1", exists.getName()); @@ -51,14 +60,17 @@ void tc2004_testMapContainsToContainsTag() { //given final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); final PQuery given = new PQuery(property, StringOp.CONTAINS,"task_1"); + final ReadFeatures readFeatures = new ReadFeatures(); + readFeatures.getQuery().setProperties(given); //when - final Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + SourceIdHandler.mapIntoTagOperation(readFeatures); //then - assertTrue(tagQuery.isPresent()); - assertInstanceOf(TagExists.class,tagQuery.get()); - final TagExists exists = (TagExists) tagQuery.get(); + final ITagQuery tagQuery = readFeatures.getQuery().getTags(); + + assertInstanceOf(TagExists.class,tagQuery); + final TagExists exists = (TagExists) tagQuery; assertEquals("xyz_source_id_task_1", exists.getName()); } @@ -67,12 +79,16 @@ void tc2005_testMapOnlyCorrectPref() { //given final Property property = new Property(NakshaProperties.META_KEY, "WrongProperty"); final PQuery given = new PQuery(property, StringOp.EQUALS,"task_1"); + final ReadFeatures readFeatures = new ReadFeatures(); + readFeatures.getQuery().setProperties(given); //when - final Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + SourceIdHandler.mapIntoTagOperation(readFeatures); //then - assertFalse(tagQuery.isPresent()); + final ITagQuery tagQuery = readFeatures.getQuery().getTags(); + + assertNull(tagQuery); } @Test @@ -83,15 +99,17 @@ void tc2006_testMapsCorrectlyCombinedOperation () { final PAnd given = new PAnd(); given.add(new PNot(new PQuery(property, StringOp.EQUALS,"task_1"))); given.add(new PQuery(property2, StringOp.CONTAINS,"4")); - + final ReadFeatures readFeatures = new ReadFeatures(); + readFeatures.getQuery().setProperties(given); //when - final Optional tagQuery = SourceIdHandler.mapIntoTagOperation(given); + SourceIdHandler.mapIntoTagOperation(readFeatures); //then - assertTrue(tagQuery.isPresent()); - assertInstanceOf(TagAnd.class,tagQuery.get()); - final TagAnd tagAnd = (TagAnd) tagQuery.get(); + final ITagQuery tagQuery = readFeatures.getQuery().getTags(); + + assertInstanceOf(TagAnd.class,tagQuery); + final TagAnd tagAnd = (TagAnd) tagQuery; assertEquals(1, tagAnd.size()); assertInstanceOf(TagNot.class,tagAnd.get(0)); final TagNot nestedTagNot = (TagNot) tagAnd.get(0); @@ -105,16 +123,20 @@ void tc2006_testMapsCorrectlyCombinedOperation () { @Test void tc2007_testMapEqToContainsTagWithoutNormalization() { //given - NonIndexedPRef pRef = new NonIndexedPRef(XyzFeature.PROPERTIES, XyzProperties.HERE_META_NS, "sourceId"); - POp given = POp.eq(pRef, "tAskK_1"); - + final Property property = new Property(NakshaProperties.META_KEY, "sourceId"); + final PQuery given = new PQuery(property, StringOp.CONTAINS,"tAskK_1"); + final ReadFeatures readFeatures = new ReadFeatures(); + readFeatures.getQuery().setProperties(given); //when - Optional result = SourceIdHandler.mapIntoTagOperation(given); + SourceIdHandler.mapIntoTagOperation(readFeatures); //then - assertTrue(result.isPresent()); - assertEquals(result.get().getPropertyRef().getTagName(), "xyz_source_id_tAskK_1"); - assertEquals(result.get().op(), POpType.EXISTS); + + final ITagQuery tagQuery = readFeatures.getQuery().getTags(); + + assertInstanceOf(TagExists.class,tagQuery); + final TagExists exists = (TagExists) tagQuery; + assertEquals("xyz_source_id_tAskK_1", exists.getName()); } @ParameterizedTest From 276bc1dec471022aa5206ca9bda83f63d1721fbc Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 14:55:17 +0100 Subject: [PATCH 05/11] WIP Signed-off-by: phmai --- .../com/here/naksha/test/common/JsonUtil.java | 17 +++--- .../naksha/lib/handlers/SourceIdHandler.java | 46 +++++++------- .../lib/handlers/SourceIdHandlerUnitTest.java | 60 ++++++++++++------- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java b/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java index d26e19f75..a27d4c64a 100644 --- a/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java +++ b/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java @@ -38,23 +38,20 @@ import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; -import com.here.naksha.lib.core.util.json.Json; -import com.here.naksha.lib.core.view.ViewDeserialize; -import com.here.naksha.lib.core.view.ViewSerialize; +import naksha.base.FromJsonOptions; +import naksha.base.Platform; +import naksha.base.ToJsonOptions; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; -/** - * @deprecated use {@link naksha.base.Platform} .fromJson() and .toJson() where possible. - */ public class JsonUtil { private JsonUtil() {} public static T parseJson(final @NotNull String jsonStr, final @NotNull Class type) { T obj = null; - try (final Json json = Json.get()) { - obj = json.reader(ViewDeserialize.Storage.class).forType(type).readValue(jsonStr); + try { + obj = type.cast(Platform.fromJSON(jsonStr, FromJsonOptions.DEFAULT)); } catch (Exception ex) { Assertions.fail("Unable tor parse jsonStr " + jsonStr, ex); return null; @@ -64,8 +61,8 @@ public static T parseJson(final @NotNull String jsonStr, final @NotNull Clas public static String toJson(final @NotNull Object obj) { String jsonStr = null; - try (final Json json = Json.get()) { - jsonStr = json.writer(ViewSerialize.Storage.class).writeValueAsString(obj); + try { + jsonStr = Platform.toJSON(obj, ToJsonOptions.DEFAULT); } catch (Exception ex) { throw unchecked(ex); } diff --git a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java index baa38188e..daa79bdbf 100644 --- a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java +++ b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java @@ -24,9 +24,7 @@ import com.here.naksha.lib.core.IEvent; import com.here.naksha.lib.core.INaksha; import com.here.naksha.lib.core.models.storage.ContextWriteXyzFeatures; - import java.util.*; - import naksha.model.objects.NakshaFeature; import naksha.model.objects.NakshaProperties; import naksha.model.request.*; @@ -128,9 +126,10 @@ private Optional getSourceIdFromFeature(NakshaProperties properties) { } private static boolean isFullyConvertedToITagQuery(IPropertyQuery propertyQuery) { - return (propertyQuery instanceof PQuery) || (propertyQuery instanceof PNot) - || (propertyQuery instanceof POr) - || ((propertyQuery instanceof PAnd pAnd) && (pAnd.isEmpty())); + return (propertyQuery instanceof PQuery) + || (propertyQuery instanceof PNot) + || (propertyQuery instanceof POr) + || ((propertyQuery instanceof PAnd pAnd) && (pAnd.isEmpty())); } private static Optional transformPropertyOperation(IPropertyQuery propertyOperation) { @@ -156,38 +155,35 @@ else if ((propertyComponent instanceof PAnd canBeSimplified) && (canBeSimplified pAnd.removeAll(toRemove); if (tagAnd.isEmpty()) { return Optional.empty(); - } - else if (tagAnd.size() == 1) { + } else if (tagAnd.size() == 1) { // Unwrap this one single query in an AND clause - return Optional.of(tagAnd.get(0)); + return Optional.of(tagAnd.get(0)); } return Optional.of(tagAnd); - } - else if (propertyOperation instanceof POr pOr) { + } else if (propertyOperation instanceof POr pOr) { final TagOr tagOr = new TagOr(); - for (IPropertyQuery iPropertyQuery : pOr) { - final Optional tagComponent = transformPropertyOperation(iPropertyQuery); - if (tagComponent.isEmpty()) { - // At least one sub-clause in an OR clause cannot be converted, hence abort and leave the whole OR as is - return Optional.empty(); - } - tagOr.add(tagComponent.get()); + for (IPropertyQuery iPropertyQuery : pOr) { + final Optional tagComponent = transformPropertyOperation(iPropertyQuery); + if (tagComponent.isEmpty()) { + // At least one sub-clause in an OR clause cannot be converted, hence abort and leave the whole OR + // as is + return Optional.empty(); } + tagOr.add(tagComponent.get()); + } return Optional.of(tagOr); - } - else if (propertyOperation instanceof PNot pNot) { + } else if (propertyOperation instanceof PNot pNot) { final Optional tagComponent = transformPropertyOperation(pNot.getQuery()); - return tagComponent.map(TagNot::new); - } - else if (propertyOperation instanceof PQuery pQuery) { + return tagComponent.map(TagNot::new); + } else if (propertyOperation instanceof PQuery pQuery) { if (sourceIdTransformationCapable(pQuery) && operationTypeAllowed(pQuery)) { final TagExists tagQuery = new TagExists(TAG_PREFIX + pQuery.getValue()); return Optional.of(tagQuery); } return Optional.empty(); - } - else { - throw new IllegalArgumentException("Unknown property operation type: " + propertyOperation.getClass().getSimpleName()); + } else { + throw new IllegalArgumentException("Unknown property operation type: " + + propertyOperation.getClass().getSimpleName()); } } diff --git a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java index 7e8a03074..d4bc8c27a 100644 --- a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java +++ b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java @@ -1,17 +1,32 @@ package com.here.naksha.lib.handlers; +import com.here.naksha.lib.core.IEvent; +import com.here.naksha.lib.core.INaksha; +import com.here.naksha.lib.core.models.naksha.EventHandler; +import com.here.naksha.lib.core.models.storage.ContextWriteXyzFeatures; +import naksha.base.Platform; +import naksha.base.ToJsonOptions; +import naksha.model.objects.NakshaFeature; import naksha.model.objects.NakshaProperties; -import naksha.model.request.ReadFeatures; +import naksha.model.request.*; import naksha.model.request.query.*; +import org.json.JSONException; +import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; -import java.util.Optional; import java.util.stream.Stream; +import static com.here.naksha.test.common.FileUtil.loadFileOrFail; +import static com.here.naksha.test.common.FileUtil.parseJsonFileOrFail; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class SourceIdHandlerUnitTest { @@ -108,11 +123,8 @@ void tc2006_testMapsCorrectlyCombinedOperation () { final ITagQuery tagQuery = readFeatures.getQuery().getTags(); - assertInstanceOf(TagAnd.class,tagQuery); - final TagAnd tagAnd = (TagAnd) tagQuery; - assertEquals(1, tagAnd.size()); - assertInstanceOf(TagNot.class,tagAnd.get(0)); - final TagNot nestedTagNot = (TagNot) tagAnd.get(0); + assertInstanceOf(TagNot.class,tagQuery); + final TagNot nestedTagNot = (TagNot) tagQuery; assertInstanceOf(TagExists.class,nestedTagNot.getQuery()); final TagExists nestedTagExist = (TagExists) nestedTagNot.getQuery(); assertEquals("xyz_source_id_task_1", nestedTagExist.getName()); @@ -141,25 +153,26 @@ void tc2007_testMapEqToContainsTagWithoutNormalization() { @ParameterizedTest @MethodSource("writeRequestTestParams") - void testWriteRequestTagPopulation(final WriteFeatures wf, final String expectedFeatureJson) throws JSONException { + void testWriteRequestTagPopulation(final WriteRequest wf, final String expectedFeatureJson) throws JSONException { // Given: Mocking in place final INaksha naksha = mock(INaksha.class); final IEvent event = mock(IEvent.class); when(event.getRequest()).thenReturn((Request)wf); - when(event.sendUpstream(any())).thenReturn(new SuccessResult()); + when(event.sendUpstream(any())).thenReturn(new SuccessResponse()); // Given: Handler initialization final EventHandler e = new EventHandler(SourceIdHandler.class, "some_id"); final SourceIdHandler sourceIdHandler = new SourceIdHandler(naksha); // When: handler processing logic is invoked - try (final Result result = sourceIdHandler.process(event)) { - assertTrue(result instanceof SuccessResult, "SuccessResult was expected"); - } + final Response result = sourceIdHandler.process(event); + assertTrue(result instanceof SuccessResponse, "SuccessResult was expected"); + // Then: validate that the feature in the original request is modified as per expectation - assertNotNull(wf.features.get(0)); - assertNotNull(wf.features.get(0).getFeature()); - JSONAssert.assertEquals("Output Feature not as expected", expectedFeatureJson, wf.features.get(0).getFeature().serialize(), JSONCompareMode.STRICT); + assertNotNull(wf.getWrites().get(0)); + assertNotNull(wf.getWrites().get(0).getFeature()); + final String featureString = Platform.toJSON(wf.getWrites().get(0).getFeature(), ToJsonOptions.DEFAULT); + JSONAssert.assertEquals("Output Feature not as expected", expectedFeatureJson, featureString, JSONCompareMode.STRICT); } private static Stream writeRequestTestParams() { @@ -185,14 +198,17 @@ private static Stream writeRequestTestParams() { ); } - private static WriteFeatures createWriteXyzFeaturesFromFile(final String filePath) { - final XyzFeature feature = parseJsonFileOrFail(filePath, XyzFeature.class); - return new WriteXyzFeatures("some_collection").add(EWriteOp.CREATE, feature); + private static WriteRequest createWriteXyzFeaturesFromFile(final String filePath) { + final NakshaFeature feature = parseJsonFileOrFail(filePath, NakshaFeature.class); + final WriteRequest writeRequest = new WriteRequest(); + writeRequest.add(new Write().createFeature(null, "some_collection", feature)); + return writeRequest; } - private static WriteFeatures createContextWriteXyzFeaturesFromFile(final String filePath) { - final XyzFeature feature = parseJsonFileOrFail(filePath, XyzFeature.class); - return new ContextWriteXyzFeatures("some_collection").add(EWriteOp.CREATE, feature); + private static ContextWriteXyzFeatures createContextWriteXyzFeaturesFromFile(final String filePath) { + final NakshaFeature feature = parseJsonFileOrFail(filePath, NakshaFeature.class); + final ContextWriteXyzFeatures writeXyzFeatures = new ContextWriteXyzFeatures(); + writeXyzFeatures.add(new Write().createFeature(null, "some_collection", feature)); + return writeXyzFeatures; } - } \ No newline at end of file From b8280186bd47051aa3a62acbc0f5b6c77def0091 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 15:42:16 +0100 Subject: [PATCH 06/11] WIP Signed-off-by: phmai --- .../java/com/here/naksha/test/common/FileUtil.java | 6 ++++-- .../java/com/here/naksha/test/common/JsonUtil.java | 10 ++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/FileUtil.java b/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/FileUtil.java index 8216919ab..e244c3f11 100644 --- a/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/FileUtil.java +++ b/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/FileUtil.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import naksha.base.AnyObject; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -65,11 +66,12 @@ public static String loadFileOrFail(final @NotNull String fileName) { return loadFileOrFail(TEST_DATA_FOLDER, fileName); } - public static T parseJsonFileOrFail(final @NotNull String fileName, final @NotNull Class type) { + public static T parseJsonFileOrFail( + final @NotNull String fileName, final @NotNull Class type) { return parseJson(loadFileOrFail(fileName), type); } - public static T parseJsonFileOrFail( + public static T parseJsonFileOrFail( final @NotNull String rootPath, final @NotNull String fileName, final @NotNull Class type) { return parseJson(loadFileOrFail(rootPath, fileName), type); } diff --git a/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java b/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java index a27d4c64a..264f4b81a 100644 --- a/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java +++ b/here-naksha-lib-core/src/testFixtures/java/com/here/naksha/test/common/JsonUtil.java @@ -38,9 +38,7 @@ import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; -import naksha.base.FromJsonOptions; -import naksha.base.Platform; -import naksha.base.ToJsonOptions; +import naksha.base.*; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Assertions; @@ -48,12 +46,12 @@ public class JsonUtil { private JsonUtil() {} - public static T parseJson(final @NotNull String jsonStr, final @NotNull Class type) { + public static T parseJson(final @NotNull String jsonStr, final @NotNull Class type) { T obj = null; try { - obj = type.cast(Platform.fromJSON(jsonStr, FromJsonOptions.DEFAULT)); + obj = JvmProxyUtil.box(Platform.fromJSON(jsonStr, FromJsonOptions.DEFAULT), type); } catch (Exception ex) { - Assertions.fail("Unable tor parse jsonStr " + jsonStr, ex); + Assertions.fail("Unable to parse jsonStr " + jsonStr, ex); return null; } return obj; From 4952d66f7d26839267e8e3821f57c0f949dce49c Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 15:44:19 +0100 Subject: [PATCH 07/11] fix meta namespace Signed-off-by: phmai --- .../commonMain/kotlin/naksha/model/objects/NakshaProperties.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt b/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt index 44c4d2ddd..05c7ac059 100644 --- a/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt +++ b/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt @@ -22,7 +22,7 @@ open class NakshaProperties : AnyObject() { const val FEATURE_TYPE = "featureType" const val XYZ_KEY = "@ns:com:here:xyz" const val DELTA_KEY = "@ns:com:here:delta" - const val META_KEY = "@ns:com:here:meta" + const val META_KEY = "@ns:com:here:mom:meta" private val XYZ = NotNullProperty(XyzNs::class, name = XYZ_KEY) private val DELTA_PROXY_NULL = NullableProperty(MomDeltaNs::class, name = DELTA_KEY) From cda44c66ccf6cb0afe9ba2d4220c37d67e4529b6 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 15:49:07 +0100 Subject: [PATCH 08/11] fix meta namespace Signed-off-by: phmai --- .../here/naksha/app/service/http/ops/PropertySearchUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java index b4385e0c9..8dc02326b 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/ops/PropertySearchUtil.java @@ -65,7 +65,7 @@ // *
// * NOTE that OR condition is supported only for the same one key and multiple values only, not for multiple key value pairs. // * The reason is to prevent complication when transformation between property search and other types of search like tag search is employed (for example through Source ID Handler). -// * So, "?p.property_name_1=value_1 OR p.@ns:com:here:meta.sourceId=abc" through Source ID Handler would then become an OR between a property search (the first clause unchanged) and a tag search (the second clause transformed), which is not supported. +// * So, "?p.property_name_1=value_1 OR p.@ns:com:here:mom:meta.sourceId=abc" through Source ID Handler would then become an OR between a property search (the first clause unchanged) and a tag search (the second clause transformed), which is not supported. // * Only AND relation is supported between different types of search (property, tag, spatial,...). // *

// * From a5ade36b7434d641a133fff664f12236e561a794 Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 16:23:37 +0100 Subject: [PATCH 09/11] fix meta and delta namespaces Signed-off-by: phmai --- docs/NAMESPACES.md | 4 ++-- docs/diagrams/architecture_base.drawio | 2 +- here-naksha-lib-base/README.md | 2 +- .../kotlin/naksha/model/objects/NakshaProperties.kt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/NAMESPACES.md b/docs/NAMESPACES.md index 23383351d..325b4296d 100644 --- a/docs/NAMESPACES.md +++ b/docs/NAMESPACES.md @@ -32,7 +32,7 @@ In the tags Naksha stores: **Note**: The **grid** is automatically set by the Naksha storage engine at part of the normal triggers. The **grid** is based upon the mass center of the geometry (`ST_GeoHash(ST_Centroid(geo),14)`). If the feature does not have a geometry, the `id` is used to create a geo-hash replacement. It can be used for work distribution. -## MOM-Metadata [`@ns:com:here:meta`] +## MOM-Metadata [`@ns:com:here:mom:meta`] The MOM metadata was traditionally stored in the base-collection of the Data-Hub and extended the data originating from the RMOB. The Data-Hub was the predecessor of XYZ-Hub, which is the predecessor of the Interactive-Map-Service. Naksha will continue to support this namespace, but only through the `lib-naksha-moderation`. The updates to this namespace are now-a-days done by the `Wikvaya` service (aka Map-Creator Middleware), which eventually will be replaced by the `lib-naksha-moderation`. @@ -50,7 +50,7 @@ The MOM metadata was traditionally stored in the base-collection of the Data-Hub The **sourceId** is used differently in different contexts. For example, in Map-Creator, for normal edits, the app and date are stored here, like `COM_1000001_20220713`. For the UTM (User-Task-Management) the task-id is stored in it, for example `MapTask:LqBzOJyAo2pTa12l`. In other contexts it is used to logically group parts of a distributed transaction to later query all collections that have features belonging to the same logical transaction. -## Moderation-Metadata [`@ns:com:here:delta`] +## Moderation-Metadata [`@ns:com:here:mom:delta`] The moderation metadata was historically managed by the Map-Creator Middleware and were part of the moderation process. This namespace only exists for features being in the moderation process, it does not exist in the base collections (so not in the consistent store). The updates to this namespace are now-a-days done by the `Wikvaya` service (aka Map-Creator Middleware), which eventually will be replaced by the `lib-naksha-moderation`. diff --git a/docs/diagrams/architecture_base.drawio b/docs/diagrams/architecture_base.drawio index f291187e3..e6df1c315 100644 --- a/docs/diagrams/architecture_base.drawio +++ b/docs/diagrams/architecture_base.drawio @@ -640,7 +640,7 @@ - + diff --git a/here-naksha-lib-base/README.md b/here-naksha-lib-base/README.md index ea1465422..e85c0aa44 100644 --- a/here-naksha-lib-base/README.md +++ b/here-naksha-lib-base/README.md @@ -31,7 +31,7 @@ All proxies should end with the postfix `{name}Proxy`, for example `XyzFeaturePr ## Late binding Proxies are late bound. For this purpose the parameterless primary constructor is invoked (via reflection), when the proxy is dynamically bound to an existing native object. This situation can be handled by the object through overriding of the `bind` method. -For example, in the Naksha-Hub pipelines all features are exposed as `XyzFeatureProxy` instances. However, these are only proxies created at the underlying in-memory data via `Base.proxy(data, XyzFeatureProxy::class)`. So, when a handler needs values from `properties.@ns:com:here:delta`, these are available through the standard data mode implemented in `XyzFeatureProxy` and can be used directly like `feature.getProperties().getDelta()`. +For example, in the Naksha-Hub pipelines all features are exposed as `XyzFeatureProxy` instances. However, these are only proxies created at the underlying in-memory data via `Base.proxy(data, XyzFeatureProxy::class)`. So, when a handler needs values from `properties.@ns:com:here:mom:delta`, these are available through the standard data mode implemented in `XyzFeatureProxy` and can be used directly like `feature.getProperties().getDelta()`. However, if a handler is part of a custom extension, it may want to access a custom namespace, for example `properties.@ns:com:customData`. The default `XyzFeatureProxy` does not expose it. If the default **XyzFeatureProxy** would be a normal [POJO](https://en.wikipedia.org/wiki/Plain_old_Java_object) the handler would need to first convert the Xyz-Feature into some proprietary object, then it could modify it. However, after doing so, it would have to convert the proprietary object back into the XYZ-Feature for the next handler. This even would require to keep all unknown properties intact and unchanged. The effort to do this is immense and the code will become very slow due to all the data transformations. To solve this problem (and many other), `lib-base` comes with an abstraction between the in memory data storage and the data model. When being based upon `lib-base`, the handler only need to create his own data model, for example: diff --git a/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt b/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt index 05c7ac059..b26a853bc 100644 --- a/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt +++ b/here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaProperties.kt @@ -21,7 +21,7 @@ open class NakshaProperties : AnyObject() { companion object { const val FEATURE_TYPE = "featureType" const val XYZ_KEY = "@ns:com:here:xyz" - const val DELTA_KEY = "@ns:com:here:delta" + const val DELTA_KEY = "@ns:com:here:mom:delta" const val META_KEY = "@ns:com:here:mom:meta" private val XYZ = NotNullProperty(XyzNs::class, name = XYZ_KEY) From 6498c7e8f7aeb3d509bfef346eec2a9b8090c7bd Mon Sep 17 00:00:00 2001 From: phmai Date: Thu, 19 Dec 2024 17:23:50 +0100 Subject: [PATCH 10/11] redundant cast Signed-off-by: phmai --- .../com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java index d4bc8c27a..8b50ca47c 100644 --- a/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java +++ b/here-naksha-lib-handlers/src/test/java/com/here/naksha/lib/handlers/SourceIdHandlerUnitTest.java @@ -157,7 +157,7 @@ void testWriteRequestTagPopulation(final WriteRequest wf, final String expectedF // Given: Mocking in place final INaksha naksha = mock(INaksha.class); final IEvent event = mock(IEvent.class); - when(event.getRequest()).thenReturn((Request)wf); + when(event.getRequest()).thenReturn(wf); when(event.sendUpstream(any())).thenReturn(new SuccessResponse()); // Given: Handler initialization From 113152fe575181b2a5c69e4a1f31499c9a045f0b Mon Sep 17 00:00:00 2001 From: phmai Date: Fri, 20 Dec 2024 12:34:02 +0100 Subject: [PATCH 11/11] review comment Signed-off-by: phmai --- .../here/naksha/lib/handlers/SourceIdHandler.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java index daa79bdbf..7ec06a145 100644 --- a/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java +++ b/here-naksha-lib-handlers/src/main/java/com/here/naksha/lib/handlers/SourceIdHandler.java @@ -92,8 +92,15 @@ public static void mapIntoTagOperation(ReadFeatures readRequest) { IPropertyQuery propertyOp = readRequest.getQuery().getProperties(); final Optional tagQuery = transformPropertyOperation(propertyOp); - if (tagQuery.isPresent()) { - readRequest.getQuery().setTags(tagQuery.get()); + tagQuery.ifPresent(presentTagQuery -> { + // Set tag query to request, combining with the already existing tag query if given + final ITagQuery existingTagQuery = readRequest.getQuery().getTags(); + if (existingTagQuery != null) { + readRequest.getQuery().setTags(new TagAnd(presentTagQuery, existingTagQuery)); + } else { + readRequest.getQuery().setTags(presentTagQuery); + } + // Clean up property query if it has already been transformed into tag query if (isFullyConvertedToITagQuery(propertyOp)) { readRequest.getQuery().setProperties(null); } @@ -101,7 +108,7 @@ public static void mapIntoTagOperation(ReadFeatures readRequest) { else if ((propertyOp instanceof PAnd canBeSimplified) && (canBeSimplified.size() == 1)) { readRequest.getQuery().setProperties(canBeSimplified.get(0)); } - } + }); } private void setSourceIdTags(NakshaFeature feature) {