From f51509f3c5697d309f0a0d77f55a46a37a394d99 Mon Sep 17 00:00:00 2001 From: "Adamczyk, Tomasz" Date: Wed, 25 Sep 2024 11:01:20 +0200 Subject: [PATCH 1/7] CASL-533 GetFeaturesByIdEvent POC Signed-off-by: Adamczyk, Tomasz --- .../naksha/storage/http/HttpInterface.java | 24 +++++++ .../here/naksha/storage/http/HttpStorage.java | 5 +- .../storage/http/HttpStorageProperties.java | 22 +++++- .../storage/http/HttpStorageReadSession.java | 20 +++++- .../naksha/storage/http/PrepareResult.java | 6 +- .../naksha/storage/http/RequestSender.java | 4 +- .../ConnectorInterfaceReadExecute.java | 68 +++++++++++++++++++ .../FfwInterfaceReadExecute.java} | 11 +-- .../http/{ => ffw}/POpToQueryConverter.java | 2 +- .../{ => ffw}/POpToQueryConverterTest.java | 6 +- 10 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpInterface.java create mode 100644 here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java rename here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/{HttpStorageReadExecute.java => ffw/FfwInterfaceReadExecute.java} (94%) rename here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/{ => ffw}/POpToQueryConverter.java (99%) rename here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/{ => ffw}/POpToQueryConverterTest.java (96%) diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpInterface.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpInterface.java new file mode 100644 index 000000000..0dccf1f06 --- /dev/null +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpInterface.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017-2024 HERE Europe B.V. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.storage.http; + +public enum HttpInterface { + ffwAdapter, + dataHubConnector +} diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorage.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorage.java index 8743ac59e..9a776914e 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorage.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorage.java @@ -39,9 +39,10 @@ public class HttpStorage implements IStorage { private static final Logger log = LoggerFactory.getLogger(HttpStorage.class); private final RequestSender requestSender; + private final HttpStorageProperties properties; public HttpStorage(@NotNull Storage storage) { - HttpStorageProperties properties = HttpStorage.getProperties(storage); + properties = HttpStorage.getProperties(storage); requestSender = RequestSenderCache.getInstance() .getSenderWith(new KeyProperties( storage.getId(), @@ -53,7 +54,7 @@ public HttpStorage(@NotNull Storage storage) { @Override public @NotNull IReadSession newReadSession(@Nullable NakshaContext context, boolean useMaster) { - return new HttpStorageReadSession(context, useMaster, requestSender); + return new HttpStorageReadSession(context, useMaster, requestSender, properties.getProtocol()); } @Override diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java index 91a4589e5..86be05d0b 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java @@ -44,6 +44,9 @@ public class HttpStorageProperties extends XyzProperties { private static final String SOCKET_TIMEOUT = "socketTimeout"; private static final String HEADERS = "headers"; + private static final String HTTP_INTERFACE = "httpInterface"; + private static final HttpInterface DEFAULT_XYZ_PROTOCOL = HttpInterface.ffwAdapter; + @JsonProperty(URL) private @NotNull String url; @@ -56,16 +59,29 @@ public class HttpStorageProperties extends XyzProperties { @JsonProperty(HEADERS) private @NotNull Map headers; + @JsonProperty(HTTP_INTERFACE) + private @NotNull HttpInterface httpInterface; + @JsonCreator public HttpStorageProperties( @JsonProperty(value = URL, required = true) @NotNull String url, @JsonProperty(CONNECTION_TIMEOUT) @Nullable Long connectTimeout, @JsonProperty(SOCKET_TIMEOUT) @Nullable Long socketTimeout, - @JsonProperty(HEADERS) @Nullable Map headers) { + @JsonProperty(HEADERS) @Nullable Map headers, + @JsonProperty(HTTP_INTERFACE) @Nullable HttpInterface httpInterface) { this.url = url; this.connectTimeout = connectTimeout == null ? DEF_CONNECTION_TIMEOUT_SEC : connectTimeout; this.socketTimeout = socketTimeout == null ? DEF_SOCKET_TIMEOUT_SEC : socketTimeout; this.headers = headers == null ? DEFAULT_HEADERS : headers; + this.httpInterface = httpInterface == null ? DEFAULT_XYZ_PROTOCOL : httpInterface; + } + + public HttpStorageProperties( + @JsonProperty(value = URL, required = true) @NotNull String url, + @JsonProperty(CONNECTION_TIMEOUT) @Nullable Long connectTimeout, + @JsonProperty(SOCKET_TIMEOUT) @Nullable Long socketTimeout, + @JsonProperty(HEADERS) @Nullable Map headers) { + this(url, connectTimeout, socketTimeout, headers, DEFAULT_XYZ_PROTOCOL); } /** @@ -86,4 +102,8 @@ public HttpStorageProperties( public @NotNull Map getHeaders() { return headers; } + + public @NotNull HttpInterface getProtocol() { + return httpInterface; + } } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java index 4064ca1bf..937d059cc 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java @@ -22,6 +22,8 @@ import com.here.naksha.lib.core.models.XyzError; import com.here.naksha.lib.core.models.storage.*; import com.here.naksha.lib.core.storage.IReadSession; +import com.here.naksha.storage.http.connector.ConnectorInterfaceReadExecute; +import com.here.naksha.storage.http.ffw.FfwInterfaceReadExecute; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; @@ -41,10 +43,18 @@ public final class HttpStorageReadSession implements IReadSession { @NotNull private final RequestSender requestSender; - HttpStorageReadSession(@Nullable NakshaContext context, boolean useMaster, @NotNull RequestSender requestSender) { + @NotNull + private final HttpInterface httpInterface; + + HttpStorageReadSession( + @Nullable NakshaContext context, + boolean useMaster, + @NotNull RequestSender requestSender, + @NotNull HttpInterface httpInterface) { this.context = context == null ? NakshaContext.currentContext() : context; this.useMaster = useMaster; this.requestSender = requestSender; + this.httpInterface = httpInterface; } @Override @@ -90,7 +100,13 @@ public void setLockTimeout(long timeout, @NotNull TimeUnit timeUnit) { @Override public @NotNull Result execute(@NotNull ReadRequest readRequest) { try { - return HttpStorageReadExecute.execute(context, (ReadFeaturesProxyWrapper) readRequest, requestSender); + return switch (httpInterface) { + case ffwAdapter -> FfwInterfaceReadExecute.execute( + context, (ReadFeaturesProxyWrapper) readRequest, requestSender); + case dataHubConnector -> ConnectorInterfaceReadExecute.execute( + (ReadFeaturesProxyWrapper) readRequest, requestSender); + }; + } catch (Exception e) { log.warn("We got exception while executing Read request.", e); return new ErrorResult(XyzError.EXCEPTION, e.getMessage(), e); diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java index b810e26db..b77347a06 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java @@ -41,13 +41,13 @@ /** * Builds a {@link Result} from {@link HttpResponse} */ -class PrepareResult { +public class PrepareResult { - static Result prepareResult(List featureList) { + public static Result prepareResult(List featureList) { return createHttpResultFromFeatureList(featureList); } - static Result prepareResult( + public static Result prepareResult( HttpResponse httpResponse, Class httpResponseType, Function> typedResponseToFeatureList) { diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/RequestSender.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/RequestSender.java index 9fe7e9e35..867449c25 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/RequestSender.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/RequestSender.java @@ -57,11 +57,11 @@ public RequestSender(@NotNull RequestSender.KeyProperties keyProps) { * @param endpoint does not contain host:port part, starts with "/". * @param addHeaders headers to be added to the ones defines {@link KeyProperties#defaultHeaders}. */ - HttpResponse sendRequest(@NotNull String endpoint, @Nullable Map addHeaders) { + public HttpResponse sendRequest(@NotNull String endpoint, @Nullable Map addHeaders) { return sendRequest(endpoint, true, addHeaders, null, null); } - HttpResponse sendRequest( + public HttpResponse sendRequest( @NotNull String endpoint, boolean keepDefHeaders, @Nullable Map headers, diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java new file mode 100644 index 000000000..5432a4a0e --- /dev/null +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2024 HERE Europe B.V. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.storage.http.connector; + +import static com.here.naksha.common.http.apis.ApiParamsConst.FEATURE_IDS; + +import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; +import com.here.naksha.lib.core.models.naksha.Space; +import com.here.naksha.lib.core.models.payload.Event; +import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByIdEvent; +import com.here.naksha.lib.core.models.storage.ReadFeaturesProxyWrapper; +import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.lib.core.util.json.JsonSerializable; +import com.here.naksha.storage.http.PrepareResult; +import com.here.naksha.storage.http.RequestSender; +import java.net.http.HttpResponse; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public class ConnectorInterfaceReadExecute { + + @NotNull + public static Result execute(ReadFeaturesProxyWrapper request, RequestSender sender) { + return switch (request.getReadRequestType()) { + case GET_BY_IDS -> executeFeaturesByIds(request, sender); + default -> throw new IllegalStateException("Unexpected value: " + request.getReadRequestType()); + }; + } + + private static Result executeFeaturesByIds(ReadFeaturesProxyWrapper request, RequestSender sender) { + Event event = createFeaturesByIdsEvent(request); + + String jsonEvent = JsonSerializable.serialize(event); + HttpResponse httpResponse = post(sender, jsonEvent); + + return PrepareResult.prepareResult(httpResponse, XyzFeatureCollection.class, XyzFeatureCollection::getFeatures); + } + + private static Event createFeaturesByIdsEvent(ReadFeaturesProxyWrapper request) { + String dataHubSpaceName = request.getCollections().get(0); + Space dataHubSpace = new Space(dataHubSpaceName); + List id = request.getQueryParameter(FEATURE_IDS); + Event getFeaturesByIdEvent = new GetFeaturesByIdEvent().withIds(id); + + getFeaturesByIdEvent.setSpace(dataHubSpace); + return getFeaturesByIdEvent; + } + + private static HttpResponse post(RequestSender sender, String body) { + return sender.sendRequest("", true, null, "POST", body); + } +} diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java similarity index 94% rename from here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadExecute.java rename to here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java index ed93601a8..9c157ef25 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java @@ -16,10 +16,10 @@ * SPDX-License-Identifier: Apache-2.0 * License-Filename: LICENSE */ -package com.here.naksha.storage.http; +package com.here.naksha.storage.http.ffw; import static com.here.naksha.common.http.apis.ApiParamsConst.*; -import static com.here.naksha.storage.http.PrepareResult.prepareResult; +import static com.here.naksha.storage.http.PrepareResult.*; import static java.lang.String.format; import com.here.naksha.lib.core.NakshaContext; @@ -30,6 +30,7 @@ import com.here.naksha.lib.core.models.storage.POp; import com.here.naksha.lib.core.models.storage.ReadFeaturesProxyWrapper; import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.storage.http.RequestSender; import java.net.HttpURLConnection; import java.net.http.HttpResponse; import java.util.Arrays; @@ -41,13 +42,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class HttpStorageReadExecute { +public class FfwInterfaceReadExecute { - private static final Logger log = LoggerFactory.getLogger(HttpStorageReadExecute.class); private static final String HDR_STREAM_ID = "Stream-Id"; @NotNull - static Result execute(@NotNull NakshaContext context, ReadFeaturesProxyWrapper request, RequestSender sender) { + public static Result execute( + @NotNull NakshaContext context, ReadFeaturesProxyWrapper request, RequestSender sender) { return switch (request.getReadRequestType()) { case GET_BY_ID -> executeFeatureById(context, request, sender); diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/POpToQueryConverter.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java similarity index 99% rename from here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/POpToQueryConverter.java rename to here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java index f65df90c0..365b7dfc5 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/POpToQueryConverter.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 * License-Filename: LICENSE */ -package com.here.naksha.storage.http; +package com.here.naksha.storage.http.ffw; import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; import static com.here.naksha.lib.core.models.storage.POpType.*; diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/POpToQueryConverterTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java similarity index 96% rename from here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/POpToQueryConverterTest.java rename to here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java index f30e0a758..434156004 100644 --- a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/POpToQueryConverterTest.java +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java @@ -1,9 +1,9 @@ -package com.here.naksha.storage.http; +package com.here.naksha.storage.http.ffw; import com.here.naksha.lib.core.models.storage.POp; import com.here.naksha.lib.core.models.storage.PRef; import com.here.naksha.lib.core.util.storage.RequestHelper; -import com.here.naksha.storage.http.POpToQueryConverter.POpToQueryConversionException; +import com.here.naksha.storage.http.ffw.POpToQueryConverter.POpToQueryConversionException; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -12,7 +12,7 @@ import java.net.URLEncoder; import static com.here.naksha.lib.core.models.storage.POp.*; -import static com.here.naksha.storage.http.POpToQueryConverter.p0pToQuery; +import static com.here.naksha.storage.http.ffw.POpToQueryConverter.p0pToQuery; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; From 91cea2b0cc17b131370b5ee9c595072e29768561 Mon Sep 17 00:00:00 2001 From: adamczyk-HERE Date: Wed, 2 Oct 2024 11:35:14 +0200 Subject: [PATCH 2/7] CASL-533 StreamId tracking added Signed-off-by: adamczyk-HERE --- .../naksha/lib/core/models/payload/Event.java | 2 +- .../naksha/lib/core/models/payload/Payload.java | 13 ------------- .../here/naksha/lib/core/events/EventTest.java | 15 --------------- .../storage/http/HttpStorageProperties.java | 8 ++++---- .../storage/http/HttpStorageReadSession.java | 2 +- .../connector/ConnectorInterfaceReadExecute.java | 13 ++++++++----- .../storage/http/ffw/FfwInterfaceReadExecute.java | 2 -- 7 files changed, 14 insertions(+), 41 deletions(-) diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Event.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Event.java index cc51ffd97..132f1ba93 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Event.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Event.java @@ -146,7 +146,7 @@ public long remaining(@NotNull TimeUnit timeUnit) { return remaining; } - @JsonView(ExcludeFromHash.class) + @JsonProperty("streamId") private String streamId; @JsonView(ExcludeFromHash.class) diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Payload.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Payload.java index 9d37de35a..9ee842041 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Payload.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/Payload.java @@ -23,7 +23,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.io.ByteStreams; import com.here.naksha.lib.core.models.Typed; -import com.here.naksha.lib.core.util.Hasher; import com.here.naksha.lib.core.util.json.JsonObject; import com.here.naksha.lib.core.util.json.JsonSerializable; import java.io.BufferedInputStream; @@ -139,18 +138,6 @@ public static int compareVersions(String versionA, String versionB) { return 0; } - /** - * Returns the hash of the event as a base64 string. - */ - @JsonIgnore - public String getHash() { - try { - return Hasher.getHash(getCacheString()); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - @SuppressWarnings("WeakerAccess") @JsonIgnore @Deprecated diff --git a/here-naksha-lib-core/src/test/java/com/here/naksha/lib/core/events/EventTest.java b/here-naksha-lib-core/src/test/java/com/here/naksha/lib/core/events/EventTest.java index 7444dc4de..994752cce 100644 --- a/here-naksha-lib-core/src/test/java/com/here/naksha/lib/core/events/EventTest.java +++ b/here-naksha-lib-core/src/test/java/com/here/naksha/lib/core/events/EventTest.java @@ -91,19 +91,4 @@ public void getFeaturesByTileEventTest() throws Exception { assertEquals(-77.080078125D, event.getBbox().getWest(), 0); } - @Test - public void checkHash() throws Exception { - GetFeaturesByTileEvent event1 = - JsonSerializable.deserialize(LazyParsedFeatureCollectionTest.class.getResourceAsStream( - "/com/here/xyz/test/GetFeaturesByTileEvent.json")); - GetFeaturesByTileEvent event2 = - JsonSerializable.deserialize(LazyParsedFeatureCollectionTest.class.getResourceAsStream( - "/com/here/xyz/test/GetFeaturesByTileEvent2.json")); - GetFeaturesByTileEvent event3 = - JsonSerializable.deserialize(LazyParsedFeatureCollectionTest.class.getResourceAsStream( - "/com/here/xyz/test/GetFeaturesByTileEvent3.json")); - - assertEquals(event1.getHash(), event2.getHash()); - assertNotEquals(event1.getHash(), event3.getHash()); - } } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java index 86be05d0b..035f7d49a 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageProperties.java @@ -77,10 +77,10 @@ public HttpStorageProperties( } public HttpStorageProperties( - @JsonProperty(value = URL, required = true) @NotNull String url, - @JsonProperty(CONNECTION_TIMEOUT) @Nullable Long connectTimeout, - @JsonProperty(SOCKET_TIMEOUT) @Nullable Long socketTimeout, - @JsonProperty(HEADERS) @Nullable Map headers) { + @JsonProperty(value = URL, required = true) @NotNull String url, + @JsonProperty(CONNECTION_TIMEOUT) @Nullable Long connectTimeout, + @JsonProperty(SOCKET_TIMEOUT) @Nullable Long socketTimeout, + @JsonProperty(HEADERS) @Nullable Map headers) { this(url, connectTimeout, socketTimeout, headers, DEFAULT_XYZ_PROTOCOL); } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java index 937d059cc..91b3314ae 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/HttpStorageReadSession.java @@ -104,7 +104,7 @@ public void setLockTimeout(long timeout, @NotNull TimeUnit timeUnit) { case ffwAdapter -> FfwInterfaceReadExecute.execute( context, (ReadFeaturesProxyWrapper) readRequest, requestSender); case dataHubConnector -> ConnectorInterfaceReadExecute.execute( - (ReadFeaturesProxyWrapper) readRequest, requestSender); + context, (ReadFeaturesProxyWrapper) readRequest, requestSender); }; } catch (Exception e) { diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java index 5432a4a0e..61b358805 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java @@ -20,6 +20,7 @@ import static com.here.naksha.common.http.apis.ApiParamsConst.FEATURE_IDS; +import com.here.naksha.lib.core.NakshaContext; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.naksha.Space; import com.here.naksha.lib.core.models.payload.Event; @@ -36,15 +37,16 @@ public class ConnectorInterfaceReadExecute { @NotNull - public static Result execute(ReadFeaturesProxyWrapper request, RequestSender sender) { + public static Result execute(NakshaContext context, ReadFeaturesProxyWrapper request, RequestSender sender) { return switch (request.getReadRequestType()) { - case GET_BY_IDS -> executeFeaturesByIds(request, sender); + case GET_BY_IDS -> executeFeaturesByIds(context, request, sender); default -> throw new IllegalStateException("Unexpected value: " + request.getReadRequestType()); }; } - private static Result executeFeaturesByIds(ReadFeaturesProxyWrapper request, RequestSender sender) { - Event event = createFeaturesByIdsEvent(request); + private static Result executeFeaturesByIds( + NakshaContext context, ReadFeaturesProxyWrapper request, RequestSender sender) { + Event event = createFeaturesByIdsEvent(request, context.getStreamId()); String jsonEvent = JsonSerializable.serialize(event); HttpResponse httpResponse = post(sender, jsonEvent); @@ -52,13 +54,14 @@ private static Result executeFeaturesByIds(ReadFeaturesProxyWrapper request, Req return PrepareResult.prepareResult(httpResponse, XyzFeatureCollection.class, XyzFeatureCollection::getFeatures); } - private static Event createFeaturesByIdsEvent(ReadFeaturesProxyWrapper request) { + private static Event createFeaturesByIdsEvent(ReadFeaturesProxyWrapper request, String streamId) { String dataHubSpaceName = request.getCollections().get(0); Space dataHubSpace = new Space(dataHubSpaceName); List id = request.getQueryParameter(FEATURE_IDS); Event getFeaturesByIdEvent = new GetFeaturesByIdEvent().withIds(id); getFeaturesByIdEvent.setSpace(dataHubSpace); + getFeaturesByIdEvent.setStreamId(streamId); return getFeaturesByIdEvent; } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java index 9c157ef25..237c299d0 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java @@ -39,8 +39,6 @@ import java.util.Map; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class FfwInterfaceReadExecute { From 03b886b4799be7bc64d4406b187e917073fce723 Mon Sep 17 00:00:00 2001 From: adamczyk-HERE Date: Mon, 7 Oct 2024 15:10:36 +0200 Subject: [PATCH 3/7] CASL-576 Property Search --- .../http/tasks/ReadFeatureApiTask.java | 1 + .../models/payload/events/PropertyQuery.java | 17 +- .../ConnectorInterfaceReadExecute.java | 83 ++++- .../http/connector/POpToQueryConverter.java | 170 +++++++++ .../http/ffw/FfwInterfaceReadExecute.java | 2 +- .../storage/http/ffw/POpToQueryConverter.java | 4 +- .../connector/POpToQueryConverterTest.java | 344 ++++++++++++++++++ .../http/ffw/POpToQueryConverterTest.java | 44 +-- 8 files changed, 621 insertions(+), 44 deletions(-) create mode 100644 here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java create mode 100644 here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java index 810bf730b..67c7b4dde 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java @@ -225,6 +225,7 @@ protected void init() {} queryParamsMap.put(EAST, east); queryParamsMap.put(SOUTH, south); queryParamsMap.put(LIMIT, limit); + queryParamsMap.put(CLIP_GEO, clip); if (propSearchOp != null) { queryParamsMap.put(PROPERTY_SEARCH_OP, propSearchOp); } diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/PropertyQuery.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/PropertyQuery.java index 8ecf6910f..46ecba4b3 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/PropertyQuery.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/PropertyQuery.java @@ -34,9 +34,10 @@ @JsonTypeName(value = "PropertyQuery") public class PropertyQuery { - public PropertyQuery() {} - - public PropertyQuery(@NotNull String key, @NotNull QueryOperation op) {} + public PropertyQuery(@NotNull String key, @NotNull QueryOperation op) { + this.key = key; + this.operation = op; + } /** The property key as JSON path. */ private String key; @@ -93,4 +94,14 @@ public void setValues(@NotNull List<@Nullable Object> values) { this.values = values; return this; } + + public enum QueryOperation { + EQUALS, + NOT_EQUALS, + LESS_THAN, + GREATER_THAN, + LESS_THAN_OR_EQUALS, + GREATER_THAN_OR_EQUALS, + CONTAINS + } } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java index 61b358805..5d99ae11a 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java @@ -18,13 +18,17 @@ */ package com.here.naksha.storage.http.connector; -import static com.here.naksha.common.http.apis.ApiParamsConst.FEATURE_IDS; +import static com.here.naksha.common.http.apis.ApiParamsConst.*; import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.models.geojson.coordinates.BBox; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.naksha.Space; import com.here.naksha.lib.core.models.payload.Event; +import com.here.naksha.lib.core.models.payload.events.PropertyQueryOr; +import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByBBoxEvent; import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByIdEvent; +import com.here.naksha.lib.core.models.storage.POp; import com.here.naksha.lib.core.models.storage.ReadFeaturesProxyWrapper; import com.here.naksha.lib.core.models.storage.Result; import com.here.naksha.lib.core.util.json.JsonSerializable; @@ -32,21 +36,28 @@ import com.here.naksha.storage.http.RequestSender; import java.net.http.HttpResponse; import java.util.List; +import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; public class ConnectorInterfaceReadExecute { @NotNull public static Result execute(NakshaContext context, ReadFeaturesProxyWrapper request, RequestSender sender) { - return switch (request.getReadRequestType()) { - case GET_BY_IDS -> executeFeaturesByIds(context, request, sender); - default -> throw new IllegalStateException("Unexpected value: " + request.getReadRequestType()); - }; - } + String streamId = context.getStreamId(); + String dataHubSpaceName = request.getCollections().get(0); + Space dataHubSpace = new Space(dataHubSpaceName); + + Event event = + switch (request.getReadRequestType()) { + case GET_BY_ID -> createFeatureByIdEvent(request); + case GET_BY_IDS -> createFeaturesByIdsEvent(request); + case GET_BY_BBOX -> createFeatureByBBoxEvent(request); + case GET_BY_TILE -> createFeaturesByTileEvent(request); + default -> throw new IllegalStateException("Unexpected value: " + request.getReadRequestType()); + }; - private static Result executeFeaturesByIds( - NakshaContext context, ReadFeaturesProxyWrapper request, RequestSender sender) { - Event event = createFeaturesByIdsEvent(request, context.getStreamId()); + event.setSpace(dataHubSpace); + event.setStreamId(streamId); String jsonEvent = JsonSerializable.serialize(event); HttpResponse httpResponse = post(sender, jsonEvent); @@ -54,15 +65,55 @@ private static Result executeFeaturesByIds( return PrepareResult.prepareResult(httpResponse, XyzFeatureCollection.class, XyzFeatureCollection::getFeatures); } - private static Event createFeaturesByIdsEvent(ReadFeaturesProxyWrapper request, String streamId) { - String dataHubSpaceName = request.getCollections().get(0); - Space dataHubSpace = new Space(dataHubSpaceName); + private static Event createFeaturesByIdsEvent(ReadFeaturesProxyWrapper request) { List id = request.getQueryParameter(FEATURE_IDS); - Event getFeaturesByIdEvent = new GetFeaturesByIdEvent().withIds(id); + return new GetFeaturesByIdEvent().withIds(id); + } + + private static Event createFeatureByIdEvent(ReadFeaturesProxyWrapper request) { + String id = request.getQueryParameter(FEATURE_ID); + return new GetFeaturesByIdEvent().withIds(List.of(id)); + } + + private static Event createFeatureByBBoxEvent(ReadFeaturesProxyWrapper request) { + BBox bBox = new BBox( + request.getQueryParameter(WEST), + request.getQueryParameter(SOUTH), + request.getQueryParameter(EAST), + request.getQueryParameter(NORTH)); + Long limit = request.getQueryParameter(LIMIT); + boolean clip = request.getQueryParameter(CLIP_GEO); + POp propertyOp = request.getPropertyOp(); + + GetFeaturesByBBoxEvent getFeaturesByBBoxEvent = new GetFeaturesByBBoxEvent(); + getFeaturesByBBoxEvent.setLimit(limit); + getFeaturesByBBoxEvent.setBbox(bBox); + getFeaturesByBBoxEvent.setClip(clip); + if (propertyOp != null) { + PropertyQueryOr propertiesQuery = new PropertyQueryOr(); + propertiesQuery.add(POpToQueryConverter.pOpToQuery(propertyOp)); + getFeaturesByBBoxEvent.setPropertiesQuery(propertiesQuery); + } + + return getFeaturesByBBoxEvent; + } + + private static Event createFeaturesByTileEvent(ReadFeaturesProxyWrapper request) throws NotImplementedException { + // Long margin = request.getQueryParameter(MARGIN); + // Long limit = request.getQueryParameter(LIMIT); + // String tileType = request.getQueryParameter(TILE_TYPE); + // String tileId = request.getQueryParameter(TILE_ID); + // + // if (tileType != null && !tileType.equals(TILE_TYPE_QUADKEY)) + // throw new NotImplementedException("Tile type other than " + TILE_TYPE_QUADKEY); + + // + // GetFeaturesByTileEvent getFeaturesByTileEvent = new GetFeaturesByTileEvent(); + // getFeaturesByTileEvent.setHereTileFlag(false); + // getFeaturesByTileEvent.setMargin(margin.intValue()); + // getFeaturesByTileEvent.setLimit(limit); - getFeaturesByIdEvent.setSpace(dataHubSpace); - getFeaturesByIdEvent.setStreamId(streamId); - return getFeaturesByIdEvent; + throw new NotImplementedException(); } private static HttpResponse post(RequestSender sender, String body) { diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java new file mode 100644 index 000000000..37893b277 --- /dev/null +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017-2024 HERE Europe B.V. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.storage.http.connector; + +import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; +import static com.here.naksha.lib.core.models.payload.events.PropertyQuery.QueryOperation.*; +import static com.here.naksha.lib.core.models.payload.events.PropertyQuery.QueryOperation.CONTAINS; +import static com.here.naksha.lib.core.models.storage.POpType.*; + +import com.here.naksha.lib.core.models.payload.events.PropertyQuery; +import com.here.naksha.lib.core.models.payload.events.PropertyQueryAnd; +import com.here.naksha.lib.core.models.storage.POp; +import com.here.naksha.lib.core.models.storage.POpType; +import java.util.*; +import org.jetbrains.annotations.NotNull; + +public class POpToQueryConverter { + + public static final String NULL = null; + public static final String PATH_SEGMENT_DELIMITER = "."; + + private static final Map SIMPLE_LEAF_OPERATORS = Map.of( + POpType.EQ, EQUALS, + POpType.GT, GREATER_THAN, + POpType.GTE, GREATER_THAN_OR_EQUALS, + POpType.LT, LESS_THAN, + POpType.LTE, LESS_THAN_OR_EQUALS, + POpType.CONTAINS, CONTAINS // Connector cannot handle this operation type, but DataHub works the same + ); + + private POpToQueryConverter() {} + + static PropertyQueryAnd pOpToQuery(POp pOp) { + if (pOp.op() == AND) { + return and(pOp); + } else { + PropertyQueryAnd propertyQueries = new PropertyQueryAnd(); + propertyQueries.add(pOpToMultiValueComparison(pOp)); + return propertyQueries; + } + } + + private static PropertyQueryAnd and(POp pOp) { + assertHasAtLeastOneChildren(pOp); + List list = pOp.children().stream() + .flatMap((child) -> pOpToQuery(child).stream()) + .toList(); + PropertyQueryAnd propertyQueries = new PropertyQueryAnd(); + propertyQueries.addAll(list); + return propertyQueries; + } + + private static PropertyQuery pOpToMultiValueComparison(POp pOp) { + if (pOp.op() == AND) throw unsupportedOperation("AND can be only a top level operation"); + if (pOp.op() == OR) return or(pOp); + if (pOp.op() == NOT) return not(pOp); + if (pOp.op() == EXISTS) return exists(pOp); + return simpleLeafOperator(pOp); + } + + private static PropertyQuery or(POp pOp) { + assertHasAtLeastOneChildren(pOp); + + return pOp.children().stream() + .map(POpToQueryConverter::pOpToMultiValueComparison) + .reduce((PropertyQuery l, PropertyQuery r) -> { + if (!Objects.equals(l.getOperation(), r.getOperation())) + throw unsupportedOperation( + "Operators " + l.getOperation() + " and " + r.getOperation() + " combined in one OR"); + if (!Objects.equals(l.getKey(), r.getKey())) + throw unsupportedOperation( + "Operator OR with dwo different keys: " + l.getKey() + " and " + r.getKey()); + List list = + new ArrayList<>(l.getValues().size() + r.getValues().size()); + list.addAll(l.getValues()); + list.addAll(r.getValues()); + return new PropertyQuery(l.getKey(), l.getOperation()).withValues(list); + }) + .orElseThrow(() -> new IllegalStateException("Should not reach here.")); + } + + private static PropertyQuery not(POp pOp) { + assertHasNChildren(pOp, 1); + PropertyQuery multiValueComparison = + pOpToMultiValueComparison(pOp.children().get(0)); + PropertyQuery.QueryOperation newOperator; + if (multiValueComparison.getOperation().equals(EQUALS)) { + newOperator = NOT_EQUALS; + } else if (multiValueComparison.getOperation().equals(NOT_EQUALS)) { + newOperator = EQUALS; + } else { + throw unsupportedOperation("Cannot negate operation: " + multiValueComparison.getOperation()); + } + return new PropertyQuery(multiValueComparison.getKey(), newOperator) + .withValues(multiValueComparison.getValues()); + } + + private static PropertyQuery exists(POp pOp) { + assertHasNChildren(pOp, 0); + assertHasPathSet(pOp); + + return new PropertyQuery(joinPath(pOp.getPropertyRef().getPath()), NOT_EQUALS) + .withValues(Collections.singletonList(NULL)); + } + + private static PropertyQuery simpleLeafOperator(POp pOp) { + assertHasNChildren(pOp, 0); + assertHasPathSet(pOp); + assertHasValueSet(pOp); + + PropertyQuery.QueryOperation operator = SIMPLE_LEAF_OPERATORS.get(pOp.op()); + if (operator == null) throw unsupportedOperation(pOp.op() + " not supported"); + String propertyPath = joinPath(pOp.getPropertyRef().getPath()); + return new PropertyQuery(propertyPath, operator).withValues(List.of(pOp.getValue())); + } + + private static void assertHasNChildren(POp pOp, int count) { + List<@NotNull POp> children = pOp.children(); + if (children == null && count == 0) return; + if (children != null && children.size() == count) return; + throw unsupportedOperation("Operation must have exactly" + count + "children"); + } + + private static void assertHasAtLeastOneChildren(POp pOp) { + if (pOp.children() == null || pOp.children().isEmpty()) + throw unsupportedOperation("Operation must have at least one children"); + } + + private static void assertHasPathSet(POp pOp) { + if (pOp.getPropertyRef() == null + || pOp.getPropertyRef().getPath() == null + || pOp.getPropertyRef().getPath().isEmpty()) + throw unsupportedOperation("PropertyRef Path is not present"); + } + + private static void assertHasValueSet(POp pOp) { + if (pOp.getValue() == null || pOp.getValue().toString().isEmpty()) + throw unsupportedOperation("Value is not present"); + } + + private static RuntimeException unsupportedOperation(String msg) { + return unchecked(new POpToQueryConversionException(msg)); + } + + private static String joinPath(List strings) { + return String.join(PATH_SEGMENT_DELIMITER, strings); + } + + static class POpToQueryConversionException extends UnsupportedOperationException { + public POpToQueryConversionException(String message) { + super(message); + } + } +} diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java index 237c299d0..c1dc5b146 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/FfwInterfaceReadExecute.java @@ -129,7 +129,7 @@ private static Result executeIterate( */ private static String getPOpQueryOrEmpty(ReadFeaturesProxyWrapper readRequest) { POp pOp = readRequest.getQueryParameter(PROPERTY_SEARCH_OP); - return pOp == null ? "" : "&" + POpToQueryConverter.p0pToQuery(pOp); + return pOp == null ? "" : "&" + POpToQueryConverter.pOpToQuery(pOp); } /** diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java index 365b7dfc5..e8a0c32c3 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/ffw/POpToQueryConverter.java @@ -52,14 +52,14 @@ public class POpToQueryConverter { private POpToQueryConverter() {} - static String p0pToQuery(POp pOp) { + static String pOpToQuery(POp pOp) { if (pOp.op() == AND) return and(pOp); else return pOpToMultiValueComparison(pOp).resolve(); } private static String and(POp pOp) { assertHasAtLeastOneChildren(pOp); - return pOp.children().stream().map(POpToQueryConverter::p0pToQuery).collect(Collectors.joining(AND_DELIMITER)); + return pOp.children().stream().map(POpToQueryConverter::pOpToQuery).collect(Collectors.joining(AND_DELIMITER)); } private static MultiValueComparison pOpToMultiValueComparison(POp pOp) { diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java new file mode 100644 index 000000000..e8eacd16b --- /dev/null +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java @@ -0,0 +1,344 @@ +package com.here.naksha.storage.http.connector; + +import com.here.naksha.lib.core.models.payload.events.PropertyQueryAnd; +import com.here.naksha.lib.core.models.storage.POp; +import com.here.naksha.lib.core.models.storage.PRef; +import com.here.naksha.lib.core.util.json.JsonSerializable; +import com.here.naksha.lib.core.util.storage.RequestHelper; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static com.here.naksha.lib.core.models.storage.POp.*; +import static com.here.naksha.storage.http.connector.POpToQueryConverter.*; +import static com.here.naksha.storage.http.connector.POpToQueryConverter.pOpToQuery; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class POpToQueryConverterTest { + + @Test + void andSingle() { + POp pOp = and( + eq(propRef("prop_1"), "1") + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals( """ + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1"]} + ]""", + query); + } + + + @Test + void andDiffProp() { + POp pOp = and( + eq(propRef("prop_1"), "1"), + eq(propRef("prop_2"), 2) + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals( """ + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1"]}, + {"key":"property.prop_2","operation":"EQUALS","values":[2]} + ]""", + query); + } + + @Test + void andSameProp() { + POp pOp = and( + eq(propRef("prop_1"), "1"), + eq(propRef("prop_1"), "2") + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1"]}, + {"key":"property.prop_1","operation":"EQUALS","values":["2"]} + ]""", + query); + } + + @Test + void andManyChildren() { + POp pOp = and( + eq(propRef("prop_1"), "1"), + eq(propRef("prop_2"), "2"), + eq(propRef("prop_3"), "3"), + eq(propRef("prop_4"), "4"), + eq(propRef("prop_5"), "5") + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals( + """ + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1"]}, + {"key":"property.prop_2","operation":"EQUALS","values":["2"]}, + {"key":"property.prop_3","operation":"EQUALS","values":["3"]}, + {"key":"property.prop_4","operation":"EQUALS","values":["4"]}, + {"key":"property.prop_5","operation":"EQUALS","values":["5"]} + ]""", + query); + } + + void assertQueryEquals(String expectedJson, PropertyQueryAnd actualQuery) { + assertEquals( + expectedJson.replace(System.lineSeparator(), ""), + JsonSerializable.serialize(actualQuery) + ); + } + + + @Test + void orSingle() { + POp pOp = or( + eq(propRef("prop_1"), "1") + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1"]} + ]""", + query); + } + + @Test + void orSameProp() { + POp pOp = or( + eq(propRef("prop_1"), "1"), + eq(propRef("prop_1"), "2") + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1","2"]} + ]""", + query); + } + + @Test + void orDiffProp_throw() { + POp pOp = or( + eq(propRef("prop_1"), "1"), + eq(propRef("prop_2"), "2") + ); + + assertThrows( + POpToQueryConversionException.class, + () -> pOpToQuery(pOp), + "Operator OR with dwo different keys: property.prop_1 and property.prop_2" + ); + } + + @Test + void orIncompatibleOps_throw() { + POp pOp = or( + eq(propRef("prop_1"), 1), + gt(propRef("prop_2"), 2) + ); + + assertThrows( + POpToQueryConversionException.class, + () -> pOpToQuery(pOp), + "Operators EQUALS and GREATER_THAN combined in one OR" + ); + } + + + @Test + void orManyChildren() { + POp pOp = or( + eq(propRef("prop_1"), "1"), + eq(propRef("prop_1"), "2"), + eq(propRef("prop_1"), "3"), + eq(propRef("prop_1"), "4"), + eq(propRef("prop_1"), "5") + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1","2","3","4","5"]} + ]""", + query); + } + + @Test + void nullOrValue() { + POp pOp = or( + eq(propRef("prop_1"), 1), + not(exists(propRef("prop_1"))) + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":[1,null]} + ]""", + query); + } + + @Test + void equals() { + POp pOp = eq(propRef("prop_1"), "1"); + + PropertyQueryAnd query = pOpToQuery(pOp); + + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":["1"]} + ]""", + query); + } + + @Test + void notEquals() { + POp pOp = not(eq(propRef("prop_1"), "1")); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"NOT_EQUALS","values":["1"]} + ]""", + query); + } + + @ParameterizedTest + @MethodSource("getOpsIncompatibleWithNot") + void notWithIncompatibleOperation_throw(POp incompatibleOp) { + POp pOp = not(incompatibleOp); + + assertThrows(POpToQueryConversionException.class, () -> pOpToQuery(pOp)); + } + + public static POp[] getOpsIncompatibleWithNot() { + return new POp[]{ + or(gt(propRef("prop_1"), 1)), + and(eq(propRef("prop_1"), "1")), + gt(propRef("prop_1"), 1), + gte(propRef("prop_1"), 1), + lt(propRef("prop_1"), 1), + lte(propRef("prop_1"), 1), + contains(propRef("prop_1"), "{}") + }; + } + + @Test + void existsSingle() { + POp pOp = exists(propRef("prop_1")); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"NOT_EQUALS","values":[null]} + ]""", + query); + } + + @Test + void notExistsSingle() { + POp pOp = not(exists(propRef("prop_1"))); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"EQUALS","values":[null]} + ]""", + query); + } + + @Test + void containsJson() { + String json = "{\"num\":1,\"str\":\"str1\",\"arr\":[1,2,3],\"obj\":{}}"; + POp pOp = contains(propRef("prop_1"), json); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals(""" + [ + {"key":"property.prop_1","operation":"CONTAINS","values":["{\\"num\\":1,\\"str\\":\\"str1\\",\\"arr\\":[1,2,3],\\"obj\\":{}}"]} + ]""", + query); + } + + @Test + void simpleLeafOperations() { + POp pOp = and( + eq(propRef("prop_1"), 1), + gt(propRef("prop_2"), 2), + gte(propRef("prop_3"), 3), + lt(propRef("prop_4"), 4), + lte(propRef("prop_5"), 5) + ); + + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals( + """ + [ + {"key":"property.prop_1","operation":"EQUALS","values":[1]}, + {"key":"property.prop_2","operation":"GREATER_THAN","values":[2]}, + {"key":"property.prop_3","operation":"GREATER_THAN_OR_EQUALS","values":[3]}, + {"key":"property.prop_4","operation":"LESS_THAN","values":[4]}, + {"key":"property.prop_5","operation":"LESS_THAN_OR_EQUALS","values":[5]} + ]""", + query); + } + + @ParameterizedTest + @MethodSource("getNotSupportedOps") + void notSupportedOps_throw(POp notSupportedOp) { + POp pOp = not(notSupportedOp); + + assertThrows(POpToQueryConversionException.class, () -> pOpToQuery(pOp)); + } + + public static POp[] getNotSupportedOps() { + PRef prop1Ref = propRef("prop_1"); + return new POp[]{ + startsWith(prop1Ref, "1"), + isNull(prop1Ref), + isNotNull(prop1Ref), + }; + } + + @Test + void dontAddPrefixToIdProp() { + POp pOp = and( + eq(RequestHelper.pRefFromPropPath(new String[]{"id"}), "1") + ); + PropertyQueryAnd query = pOpToQuery(pOp); + + assertQueryEquals( + """ + [ + {"key":"id","operation":"EQUALS","values":["1"]} + ]""", + query); + } + + private static @NotNull PRef propRef(String propName) { + return RequestHelper.pRefFromPropPath(new String[]{"property", propName}); + } +} \ No newline at end of file diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java index 434156004..368e4a57b 100644 --- a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/ffw/POpToQueryConverterTest.java @@ -12,7 +12,7 @@ import java.net.URLEncoder; import static com.here.naksha.lib.core.models.storage.POp.*; -import static com.here.naksha.storage.http.ffw.POpToQueryConverter.p0pToQuery; +import static com.here.naksha.storage.http.ffw.POpToQueryConverter.pOpToQuery; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -25,7 +25,7 @@ void andSingle() { eq(propRef("prop_1"), "1") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1"); } @@ -37,7 +37,7 @@ void andDiffProp() { eq(propRef("prop_2"), "2") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1&property.prop_2=2"); } @@ -49,7 +49,7 @@ void andSameProp() { eq(propRef("prop_1"), "2") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1&property.prop_1=2"); } @@ -64,7 +64,7 @@ void andManyChildren() { eq(propRef("prop_5"), "5") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1&property.prop_2=2&property.prop_3=3&property.prop_4=4&property.prop_5=5"); } @@ -79,7 +79,7 @@ void orSingle() { eq(propRef("prop_1"), "1") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1"); } @@ -91,7 +91,7 @@ void orSameProp() { eq(propRef("prop_1"), "2") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1,2"); } @@ -103,7 +103,7 @@ void orDiffProp_throw() { eq(propRef("prop_2"), "2") ); - assertThrows(POpToQueryConversionException.class, () -> p0pToQuery(pOp)); + assertThrows(POpToQueryConversionException.class, () -> pOpToQuery(pOp)); } @Test @@ -113,7 +113,7 @@ void orIncompatibleOps_throw() { gt(propRef("prop_2"), 2) ); - assertThrows(POpToQueryConversionException.class, () -> p0pToQuery(pOp)); + assertThrows(POpToQueryConversionException.class, () -> pOpToQuery(pOp)); } @Test @@ -126,7 +126,7 @@ void orManyChildren() { eq(propRef("prop_1"), "5") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1,2,3,4,5"); } @@ -138,7 +138,7 @@ void nullOrValue() { not(exists(propRef("prop_1"))) ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1,.null"); } @@ -147,7 +147,7 @@ void nullOrValue() { void equals() { POp pOp = eq(propRef("prop_1"), "1"); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1"); } @@ -156,7 +156,7 @@ void equals() { void notEquals() { POp pOp = not(eq(propRef("prop_1"), "1")); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1!=1"); } @@ -166,7 +166,7 @@ void notEquals() { void notWithIncompatibleOperation_throw(POp incompatibleOp) { POp pOp = not(incompatibleOp); - assertThrows(POpToQueryConversionException.class, () -> p0pToQuery(pOp)); + assertThrows(POpToQueryConversionException.class, () -> pOpToQuery(pOp)); } public static POp[] getOpsIncompatibleWithNot() { @@ -185,7 +185,7 @@ public static POp[] getOpsIncompatibleWithNot() { void existsSingle() { POp pOp = exists(propRef("prop_1")); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1!=.null"); } @@ -194,7 +194,7 @@ void existsSingle() { void notExistsSingle() { POp pOp = not(exists(propRef("prop_1"))); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=.null"); } @@ -204,7 +204,7 @@ void containsJson() { String json = "{\"num\":1,\"str\":\"str1\",\"arr\":[1,2,3],\"obj\":{}}"; POp pOp = contains(propRef("prop_1"), json); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=cs=" + urlEncoded(json)); } @@ -219,7 +219,7 @@ void simpleLeafOperations() { lte(propRef("prop_5"), 5) ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "property.prop_1=1" + @@ -234,7 +234,7 @@ void simpleLeafOperations() { void notSupportedOps_throw(POp notSupportedOp) { POp pOp = not(notSupportedOp); - assertThrows(POpToQueryConversionException.class, () -> p0pToQuery(pOp)); + assertThrows(POpToQueryConversionException.class, () -> pOpToQuery(pOp)); } public static POp[] getNotSupportedOps() { @@ -247,11 +247,11 @@ public static POp[] getNotSupportedOps() { } @Test - void translateIdProp() { + void addPrefixToIdProp() { POp pOp = and( - eq(RequestHelper.pRefFromPropPath(new String[]{"f","id"}), "1") + eq(RequestHelper.pRefFromPropPath(new String[]{"id"}), "1") ); - String query = p0pToQuery(pOp); + String query = pOpToQuery(pOp); assertEquals(query, "f.id=1"); } From 5db45e4f481ed476f84481e85c1f1b9e7034bb3c Mon Sep 17 00:00:00 2001 From: adamczyk-HERE Date: Wed, 16 Oct 2024 13:00:05 +0200 Subject: [PATCH 4/7] Integrations tests Signed-off-by: adamczyk-HERE --- build.gradle.kts | 5 +- .../ConnectorInterfaceReadExecute.java | 4 - .../http/connector/POpToQueryConverter.java | 3 +- .../connector/POpToQueryConverterTest.java | 1 - .../http/connector/integration/BBoxTest.java | 48 +++++++++ .../http/connector/integration/Commons.java | 73 ++++++++++++++ .../connector/integration/PropSearchTest.java | 99 +++++++++++++++++++ .../connector/integration/bbox/feature_1.json | 37 +++++++ .../connector/integration/bbox/feature_2.json | 37 +++++++ .../propsearch/feature_template.json | 37 +++++++ 10 files changed, 337 insertions(+), 7 deletions(-) create mode 100644 here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/BBoxTest.java create mode 100644 here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java create mode 100644 here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java create mode 100644 here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_1.json create mode 100644 here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_2.json create mode 100644 here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/propsearch/feature_template.json diff --git a/build.gradle.kts b/build.gradle.kts index 4534d59da..70939d4e7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -366,9 +366,12 @@ project(":here-naksha-storage-http") { implementation(commons_lang3) testImplementation(mockito) + testImplementation("io.rest-assured:rest-assured:5.5.0") + } + tasks.withType { + exclude("**/integration/**") } setOverallCoverage(0.0) // only increasing allowed! - } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java index 5d99ae11a..abfc8bc02 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java @@ -103,10 +103,6 @@ private static Event createFeaturesByTileEvent(ReadFeaturesProxyWrapper request) // Long limit = request.getQueryParameter(LIMIT); // String tileType = request.getQueryParameter(TILE_TYPE); // String tileId = request.getQueryParameter(TILE_ID); - // - // if (tileType != null && !tileType.equals(TILE_TYPE_QUADKEY)) - // throw new NotImplementedException("Tile type other than " + TILE_TYPE_QUADKEY); - // // GetFeaturesByTileEvent getFeaturesByTileEvent = new GetFeaturesByTileEvent(); // getFeaturesByTileEvent.setHereTileFlag(false); diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java index 37893b277..00fab30c8 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/POpToQueryConverter.java @@ -25,6 +25,7 @@ import com.here.naksha.lib.core.models.payload.events.PropertyQuery; import com.here.naksha.lib.core.models.payload.events.PropertyQueryAnd; +import com.here.naksha.lib.core.models.storage.OpType; import com.here.naksha.lib.core.models.storage.POp; import com.here.naksha.lib.core.models.storage.POpType; import java.util.*; @@ -35,7 +36,7 @@ public class POpToQueryConverter { public static final String NULL = null; public static final String PATH_SEGMENT_DELIMITER = "."; - private static final Map SIMPLE_LEAF_OPERATORS = Map.of( + private static final Map SIMPLE_LEAF_OPERATORS = Map.of( POpType.EQ, EQUALS, POpType.GT, GREATER_THAN, POpType.GTE, GREATER_THAN_OR_EQUALS, diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java index e8eacd16b..cb471fe4f 100644 --- a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/POpToQueryConverterTest.java @@ -12,7 +12,6 @@ import static com.here.naksha.lib.core.models.storage.POp.*; import static com.here.naksha.storage.http.connector.POpToQueryConverter.*; -import static com.here.naksha.storage.http.connector.POpToQueryConverter.pOpToQuery; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/BBoxTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/BBoxTest.java new file mode 100644 index 000000000..ffb288767 --- /dev/null +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/BBoxTest.java @@ -0,0 +1,48 @@ +package com.here.naksha.storage.http.connector.integration; + +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; + +import static com.here.naksha.storage.http.connector.integration.Commons.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BBoxTest { + + @BeforeEach + void rmFeatures() { + rmAllFeatures(); + } + + @Test + void bbox_notContains() throws URISyntaxException { + createFromJsonFile("bbox/feature_1.json"); + + String bbox = "bbox?west=-1&north=0&east=0&south=-3"; + Response dhResponse = dataHub().get(bbox); + Response nResponse = naksha().get(bbox); + assertSameIds(dhResponse, nResponse); + + List features = dhResponse.body().jsonPath().getList("features", Map.class); + assertEquals(0, features.size()); + } + + @Test + void bbox_contains() throws URISyntaxException { + createFromJsonFile("bbox/feature_1.json"); + + String bbox = "bbox?west=-3&north=0&east=0&south=-1"; + Response dhResponse = Commons.dataHub().get(bbox); + Response nResponse = naksha().get(bbox); + assertSameIds(dhResponse, nResponse); + + List features = dhResponse.body().jsonPath().getList("features", Map.class); + assertEquals(1, features.size()); + } + + +} diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java new file mode 100644 index 000000000..04ef63272 --- /dev/null +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java @@ -0,0 +1,73 @@ +package com.here.naksha.storage.http.connector.integration; + +import io.restassured.RestAssured; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; +import org.hamcrest.Matchers; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class Commons { + + // Use you values + private static final String NAKSHA_SPACE = System.getenv("nakshaSpace"); + private static final String DH_SPACE = System.getenv("dataHubSpace"); + public static final String TOKEN = System.getenv("dataHubToken"); + + static void rmAllFeatures() { + Response iterateResponse = dataHub().get("iterate"); + List featuresIds = responseToIds(iterateResponse); + dataHub().with().queryParam("id", featuresIds).delete("features"); + dataHub().get("iterate").then().body("features", Matchers.hasSize(0)); + } + + static void assertSameIds(Response dhResponse, Response nResponse) { + List nResponseMap = responseToIds(nResponse); + List dhResponseMap = responseToIds(dhResponse); + assertEquals(nResponseMap,dhResponseMap); + } + + static RequestSpecification dataHub() { + String token = new String(Base64.getDecoder().decode(TOKEN)); + return RestAssured + .given() + .header("Authorization", "Bearer " + token) + .baseUri("https://xyz.api.here.com/hub/spaces/" + DH_SPACE); + } + + static RequestSpecification naksha() { + return RestAssured.given().baseUri("http://localhost:8080/hub/spaces/" + NAKSHA_SPACE); + } + + static List responseToIds(Response response){ + return response.body().jsonPath().getList("features").stream().map(e -> ((Map) e).get("id").toString()).toList(); + } + + static void createFromJsonFile(String pathInIntegrationResources) throws URISyntaxException { + String pathInResources = "com/here/naksha/storage/http/connector/integration/" + pathInIntegrationResources; + URI feature1 = ClassLoader.getSystemResource(pathInResources).toURI(); + dataHub().body(new File(feature1)).post("features").then().statusCode(200); + } + + static void createFromJsonFileFormatted(String pathInIntegrationResources, String... args) { + try { + String pathInResources = "com/here/naksha/storage/http/connector/integration/" + pathInIntegrationResources; + Path featureTemplatePath = Path.of(ClassLoader.getSystemResource(pathInResources).toURI()); + String body = Files.readString(featureTemplatePath).formatted(args); + dataHub().body(body).post("features").then().statusCode(200); + } catch (URISyntaxException | IOException e) { + fail(e); + } + } +} \ No newline at end of file diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java new file mode 100644 index 000000000..bfb641727 --- /dev/null +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java @@ -0,0 +1,99 @@ +package com.here.naksha.storage.http.connector.integration; + +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.here.naksha.storage.http.connector.integration.Commons.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PropSearchTest { + + public static final String BBOX_PATH_AND_PARAMS = "bbox?west=-10&north=10&east=10&south=-10&"; + + @BeforeEach + void setUp() { + rmAllFeatures(); + } + + @Test + void singleOperations() { + createFromJsonFileFormatted("propsearch/feature_template.json", "1", """ + "prop1" : 1""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "2", """ + "prop1" : 2""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "3", """ + "prop1" : 3""" + ); + + assertPropSearchHasShortIds("p.prop1=1", List.of("1")); + assertPropSearchHasShortIds("p.prop1=2", List.of("2")); + assertPropSearchHasShortIds("p.prop1!=1", List.of("2","3")); + + assertPropSearchHasShortIds("p.prop1=gt=2", List.of("3")); + assertPropSearchHasShortIds("p.prop1=gte=2", List.of("2", "3")); + + assertPropSearchHasShortIds("p.prop1=lt=2", List.of("1")); + assertPropSearchHasShortIds("p.prop1=lte=2", List.of("1", "2")); + } + + @Test + void combinedOperations() { + createFromJsonFileFormatted("propsearch/feature_template.json", "A1", """ + "prop1" : "A", "prop2" : 1""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "A2", """ + "prop1" : "A", "prop2" : 2""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "B1", """ + "prop1" : "B", "prop2" : 1""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "B2", """ + "prop1" : "B", "prop2" : 2""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "C1", """ + "prop1" : "C", "prop2" : 1""" + ); + createFromJsonFileFormatted("propsearch/feature_template.json", "C2", """ + "prop1" : "C", "prop2" : 2""" + ); + + assertPropSearchHasShortIds("p.prop1=A&p.prop2=1", List.of("A1")); // and + assertPropSearchHasShortIds("p.prop1=A,B", List.of("A1","A2","B1","B2")); // or + assertPropSearchHasShortIds("p.prop1=A,B&p.prop2=1", List.of("A1","B1")); // or, and + assertPropSearchHasShortIds("p.prop1!=A,B&p.prop2=1", List.of("A1","B1", "C1")); // or != (always true), and + assertPropSearchHasShortIds("p.prop1!=A&p.prop1!=B&p.prop2=1", List.of("C1")); // and != + assertPropSearchHasShortIds("p.prop2=gt=1&p.prop2=lte=3", List.of("A2","B2","C2")); + + } + + @Test + void notSupportedOperations(){ + createFromJsonFileFormatted("propsearch/feature_template.json", "1", """ + "prop1" : 1""" + ); + + String params = BBOX_PATH_AND_PARAMS + "p.prop1=cs=1"; + Response nakshaResponse = naksha().urlEncodingEnabled(false).get(params); + Response dataHubResponse = dataHub().urlEncodingEnabled(false).get(params); + System.out.println(); + } + + void assertPropSearchHasShortIds(String propsearch, List shortIds) { + String params = BBOX_PATH_AND_PARAMS + propsearch; + Response nakshaResponse = naksha().urlEncodingEnabled(false).get(params); + Response dataHubResponse = dataHub().urlEncodingEnabled(false).get(params); + assertTrue(responseHasExactShortIds(shortIds, nakshaResponse)); + assertTrue(responseHasExactShortIds(shortIds, dataHubResponse)); + } + + boolean responseHasExactShortIds(List expectedShortIds, Response response) { + List expectedIds = expectedShortIds.stream().map(e -> "urn:here::here:landmark3d.Landmark3dPhotoreal:" + e).toList(); + List responseIds = responseToIds(response); + return expectedIds.equals(responseIds); + } +} diff --git a/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_1.json b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_1.json new file mode 100644 index 000000000..63f4f6429 --- /dev/null +++ b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_1.json @@ -0,0 +1,37 @@ +{ + "id": "urn:here::here:landmark3d.Landmark3dPhotoreal:1", + "type": "Feature", + "momType": "landmark3d.Landmark3dPhotoreal", + "properties": { + "prop1": "1" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -3, + -1, + 0 + ], + [ + -3, + -2, + 0 + ], + [ + -2, + -2, + 0 + ], + [ + -3, + -1, + 0 + ] + ] + ] + ] + } +} \ No newline at end of file diff --git a/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_2.json b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_2.json new file mode 100644 index 000000000..9ca9dfbe4 --- /dev/null +++ b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/bbox/feature_2.json @@ -0,0 +1,37 @@ +{ + "id": "urn:here::here:landmark3d.Landmark3dPhotoreal:2", + "type": "Feature", + "momType": "landmark3d.Landmark3dPhotoreal", + "properties": { + "prop1": "1" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 10, + 10, + 0 + ], + [ + 10, + 11, + 0 + ], + [ + 12, + 0, + 0 + ], + [ + 10, + 10, + 0 + ] + ] + ] + ] + } +} \ No newline at end of file diff --git a/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/propsearch/feature_template.json b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/propsearch/feature_template.json new file mode 100644 index 000000000..67d460677 --- /dev/null +++ b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/propsearch/feature_template.json @@ -0,0 +1,37 @@ +{ + "id": "urn:here::here:landmark3d.Landmark3dPhotoreal:%s", + "type": "Feature", + "momType": "landmark3d.Landmark3dPhotoreal", + "properties": { + %s + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -3, + -1, + 0 + ], + [ + -3, + -2, + 0 + ], + [ + -2, + -2, + 0 + ], + [ + -3, + -1, + 0 + ] + ] + ] + ] + } +} \ No newline at end of file From 09a4c7575a6a1ae372bc4d1e2d365e826df7ebee Mon Sep 17 00:00:00 2001 From: adamczyk-HERE Date: Wed, 16 Oct 2024 15:25:00 +0200 Subject: [PATCH 5/7] CASL-595 Error response handling Signed-off-by: adamczyk-HERE --- .../com/here/naksha/storage/http/PrepareResult.java | 12 ++++++++++-- .../http/connector/integration/PropSearchTest.java | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java index b77347a06..d7a35e45e 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/PrepareResult.java @@ -23,11 +23,13 @@ import com.here.naksha.lib.core.models.Typed; import com.here.naksha.lib.core.models.XyzError; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; +import com.here.naksha.lib.core.models.payload.responses.ErrorResponse; import com.here.naksha.lib.core.models.storage.*; import com.here.naksha.lib.core.util.json.JsonSerializable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.HttpURLConnection; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; @@ -55,8 +57,14 @@ public static Result prepareResult( XyzError error = mapHttpStatusToErrorOrNull(httpResponse.statusCode()); if (error != null) return new ErrorResult(error, "Response http status code: " + httpResponse.statusCode()); - T resultFeatures = JsonSerializable.deserialize(prepareBody(httpResponse), httpResponseType); - return prepareResult(typedResponseToFeatureList.apply(resultFeatures)); + String preapredBody = prepareBody(httpResponse); + try { + T resultFeatures = JsonSerializable.deserialize(preapredBody, httpResponseType); + return prepareResult(typedResponseToFeatureList.apply(resultFeatures)); + } catch (UncheckedIOException e) { + ErrorResponse errorResponse = JsonSerializable.deserialize(preapredBody, ErrorResponse.class); + return new ErrorResult(errorResponse.getError(), "Error response : " + errorResponse.getErrorMessage()); + } } private static String prepareBody(HttpResponse response) { diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java index bfb641727..f9c5f7e90 100644 --- a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java @@ -7,6 +7,7 @@ import java.util.List; import static com.here.naksha.storage.http.connector.integration.Commons.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class PropSearchTest { @@ -79,7 +80,8 @@ void notSupportedOperations(){ String params = BBOX_PATH_AND_PARAMS + "p.prop1=cs=1"; Response nakshaResponse = naksha().urlEncodingEnabled(false).get(params); - Response dataHubResponse = dataHub().urlEncodingEnabled(false).get(params); + assertEquals(nakshaResponse.jsonPath().getString("type"),"ErrorResponse"); + assertEquals(nakshaResponse.jsonPath().getString("error"),"Exception"); System.out.println(); } From cafe7b5276662026928a43fff6d5316364f575a5 Mon Sep 17 00:00:00 2001 From: adamczyk-HERE Date: Fri, 18 Oct 2024 16:01:21 +0200 Subject: [PATCH 6/7] CASL-603 Read Features - tile (quadkey) Signed-off-by: adamczyk-HERE --- build.gradle.kts | 4 +- .../http/tasks/ReadFeatureApiTask.java | 1 + .../core/models/geojson/WebMercatorTile.java | 3 +- .../feature/GetFeaturesByTileEvent.java | 17 --- .../ConnectorInterfaceReadExecute.java | 54 +++++++--- .../http/connector/integration/Commons.java | 22 +++- .../connector/integration/PropSearchTest.java | 6 +- .../http/connector/integration/TileTest.java | 100 ++++++++++++++++++ .../integration/tile/feature_template.json | 15 +++ 9 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/TileTest.java create mode 100644 here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/tile/feature_template.json diff --git a/build.gradle.kts b/build.gradle.kts index 70939d4e7..2e9de66ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -369,7 +369,9 @@ project(":here-naksha-storage-http") { testImplementation("io.rest-assured:rest-assured:5.5.0") } tasks.withType { - exclude("**/integration/**") + if (System.getenv("runConnectorIntegrationTests")?.toBoolean() != true) { + exclude("**/integration/**") + } } setOverallCoverage(0.0) // only increasing allowed! } diff --git a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java index 67c7b4dde..0bd4563a5 100644 --- a/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java +++ b/here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/tasks/ReadFeatureApiTask.java @@ -276,6 +276,7 @@ protected void init() {} queryParamsMap.put(LIMIT, limit); queryParamsMap.put(TILE_TYPE, tileType); queryParamsMap.put(TILE_ID, tileId); + queryParamsMap.put(CLIP_GEO, clip); if (propSearchOp != null) { queryParamsMap.put(PROPERTY_SEARCH_OP, propSearchOp); } diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/WebMercatorTile.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/WebMercatorTile.java index efae64a80..ee24833d6 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/WebMercatorTile.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/geojson/WebMercatorTile.java @@ -23,6 +23,7 @@ import com.here.naksha.lib.core.models.geojson.declaration.ILonLat; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.prep.PreparedGeometry; import org.locationtech.jts.geom.prep.PreparedGeometryFactory; @@ -380,7 +381,7 @@ public static long quadKeyToTile(String quadKey) throws NullPointerException, Il * * @param quadKey the quaddKey that represents */ - public static WebMercatorTile forQuadkey(String quadKey) throws IllegalArgumentException { + public static @NotNull WebMercatorTile forQuadkey(String quadKey) throws IllegalArgumentException { if (!QUADKEY_REGEXP.matcher(quadKey).matches()) { throw new IllegalArgumentException("Invalid quadkey."); } diff --git a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/feature/GetFeaturesByTileEvent.java b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/feature/GetFeaturesByTileEvent.java index 1e3b6aeb7..9f7a29bd7 100644 --- a/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/feature/GetFeaturesByTileEvent.java +++ b/here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/models/payload/events/feature/GetFeaturesByTileEvent.java @@ -18,11 +18,8 @@ */ package com.here.naksha.lib.core.models.payload.events.feature; -import static com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByTileResponseType.GEO_JSON; - import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonTypeName; -import org.jetbrains.annotations.NotNull; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeName(value = "GetFeaturesByTileEvent") @@ -34,7 +31,6 @@ public final class GetFeaturesByTileEvent extends GetFeaturesByBBoxEvent { private int y; private String quadkey; private int margin; - private @NotNull GetFeaturesByTileResponseType responseType = GEO_JSON; private boolean hereTileFlag; public int getLevel() { @@ -114,17 +110,4 @@ public GetFeaturesByTileEvent withMargin(int margin) { setMargin(margin); return this; } - - public @NotNull GetFeaturesByTileResponseType getResponseType() { - return responseType; - } - - public void setResponseType(@NotNull GetFeaturesByTileResponseType responseType) { - this.responseType = responseType; - } - - public GetFeaturesByTileEvent withResponseType(GetFeaturesByTileResponseType responseType) { - setResponseType(responseType); - return this; - } } diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java index abfc8bc02..9f34c6ca1 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java @@ -21,6 +21,7 @@ import static com.here.naksha.common.http.apis.ApiParamsConst.*; import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.models.geojson.WebMercatorTile; import com.here.naksha.lib.core.models.geojson.coordinates.BBox; import com.here.naksha.lib.core.models.geojson.implementation.XyzFeatureCollection; import com.here.naksha.lib.core.models.naksha.Space; @@ -28,7 +29,10 @@ import com.here.naksha.lib.core.models.payload.events.PropertyQueryOr; import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByBBoxEvent; import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByIdEvent; +import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByTileEvent; +import com.here.naksha.lib.core.models.payload.events.feature.QueryEvent; import com.here.naksha.lib.core.models.storage.POp; +import com.here.naksha.lib.core.models.storage.ReadFeatures; import com.here.naksha.lib.core.models.storage.ReadFeaturesProxyWrapper; import com.here.naksha.lib.core.models.storage.Result; import com.here.naksha.lib.core.util.json.JsonSerializable; @@ -83,36 +87,52 @@ private static Event createFeatureByBBoxEvent(ReadFeaturesProxyWrapper request) request.getQueryParameter(NORTH)); Long limit = request.getQueryParameter(LIMIT); boolean clip = request.getQueryParameter(CLIP_GEO); - POp propertyOp = request.getPropertyOp(); GetFeaturesByBBoxEvent getFeaturesByBBoxEvent = new GetFeaturesByBBoxEvent(); getFeaturesByBBoxEvent.setLimit(limit); getFeaturesByBBoxEvent.setBbox(bBox); getFeaturesByBBoxEvent.setClip(clip); + setPropertyOp(request, getFeaturesByBBoxEvent); + + return getFeaturesByBBoxEvent; + } + + static void setPropertyOp(ReadFeatures request, QueryEvent getFeaturesByBBoxEvent) { + POp propertyOp = request.getPropertyOp(); if (propertyOp != null) { PropertyQueryOr propertiesQuery = new PropertyQueryOr(); propertiesQuery.add(POpToQueryConverter.pOpToQuery(propertyOp)); getFeaturesByBBoxEvent.setPropertiesQuery(propertiesQuery); } - - return getFeaturesByBBoxEvent; - } - - private static Event createFeaturesByTileEvent(ReadFeaturesProxyWrapper request) throws NotImplementedException { - // Long margin = request.getQueryParameter(MARGIN); - // Long limit = request.getQueryParameter(LIMIT); - // String tileType = request.getQueryParameter(TILE_TYPE); - // String tileId = request.getQueryParameter(TILE_ID); - // - // GetFeaturesByTileEvent getFeaturesByTileEvent = new GetFeaturesByTileEvent(); - // getFeaturesByTileEvent.setHereTileFlag(false); - // getFeaturesByTileEvent.setMargin(margin.intValue()); - // getFeaturesByTileEvent.setLimit(limit); - - throw new NotImplementedException(); } private static HttpResponse post(RequestSender sender, String body) { return sender.sendRequest("", true, null, "POST", body); } + + private static Event createFeaturesByTileEvent(ReadFeaturesProxyWrapper readRequest) { + String tileType = readRequest.getQueryParameter(TILE_TYPE); + if (TILE_TYPE_QUADKEY.equals(tileType)) { + long margin = readRequest.getQueryParameter(MARGIN); + long limit = readRequest.getQueryParameter(LIMIT); + String tileId = readRequest.getQueryParameter(TILE_ID); + boolean clip = readRequest.getQueryParameter(CLIP_GEO); + + GetFeaturesByTileEvent event = new GetFeaturesByTileEvent(); + event.setMargin((int) margin); // TODO-a + event.setLimit(limit); + event.setClip(clip); + setPropertyOp(readRequest, event); + + WebMercatorTile tileAddress = WebMercatorTile.forQuadkey(tileId); + event.setBbox(tileAddress.getExtendedBBox(event.getMargin())); + event.setLevel(tileAddress.level); + event.setX(tileAddress.x); + event.setY(tileAddress.y); + event.setQuadkey(tileAddress.asQuadkey()); + return event; + } else { + throw new NotImplementedException("Tile type other than " + TILE_TYPE_QUADKEY); + } + } } diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java index 04ef63272..316541819 100644 --- a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/Commons.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; +import static org.hamcrest.Matchers.hasKey; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -51,13 +52,20 @@ static RequestSpecification naksha() { } static List responseToIds(Response response){ + response. + then().assertThat().body("$",hasKey("features")) + .and().log().ifValidationFails(); return response.body().jsonPath().getList("features").stream().map(e -> ((Map) e).get("id").toString()).toList(); } static void createFromJsonFile(String pathInIntegrationResources) throws URISyntaxException { String pathInResources = "com/here/naksha/storage/http/connector/integration/" + pathInIntegrationResources; URI feature1 = ClassLoader.getSystemResource(pathInResources).toURI(); - dataHub().body(new File(feature1)).post("features").then().statusCode(200); + dataHub().with().body(new File(feature1)) + .when().post("features") + .then() + .assertThat().statusCode(200) + .and().log().ifValidationFails(); } static void createFromJsonFileFormatted(String pathInIntegrationResources, String... args) { @@ -65,9 +73,19 @@ static void createFromJsonFileFormatted(String pathInIntegrationResources, Strin String pathInResources = "com/here/naksha/storage/http/connector/integration/" + pathInIntegrationResources; Path featureTemplatePath = Path.of(ClassLoader.getSystemResource(pathInResources).toURI()); String body = Files.readString(featureTemplatePath).formatted(args); - dataHub().body(body).post("features").then().statusCode(200); + dataHub().with().body(body) + .when().post("features") + .then() + .assertThat().statusCode(200) + .and().log().ifValidationFails(); } catch (URISyntaxException | IOException e) { fail(e); } } + + static boolean responseHasExactShortIds(List expectedShortIds, Response response) { + List expectedIds = expectedShortIds.stream().map(e -> "urn:here::here:landmark3d.Landmark3dPhotoreal:" + e).toList(); + List responseIds = responseToIds(response); + return expectedIds.equals(responseIds); + } } \ No newline at end of file diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java index f9c5f7e90..be30f1788 100644 --- a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/PropSearchTest.java @@ -93,9 +93,5 @@ void assertPropSearchHasShortIds(String propsearch, List shortIds) { assertTrue(responseHasExactShortIds(shortIds, dataHubResponse)); } - boolean responseHasExactShortIds(List expectedShortIds, Response response) { - List expectedIds = expectedShortIds.stream().map(e -> "urn:here::here:landmark3d.Landmark3dPhotoreal:" + e).toList(); - List responseIds = responseToIds(response); - return expectedIds.equals(responseIds); - } + } diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/TileTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/TileTest.java new file mode 100644 index 000000000..808c1c381 --- /dev/null +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/TileTest.java @@ -0,0 +1,100 @@ +package com.here.naksha.storage.http.connector.integration; + +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; + +import static com.here.naksha.storage.http.connector.integration.Commons.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TileTest { + @BeforeEach + void rmFeatures() { + rmAllFeatures(); + } + + @Test + void testTranslationToBBox() { + + createFromJsonFileFormatted( // clearly inside + "tile/feature_template.json", + "1", + "[3, -3, 0], [0, -3, 0], [0, 0, 0], [3, 0, 0], [3, -3, 0]" + ); + + createFromJsonFileFormatted( // clearly outside + "tile/feature_template.json", + "2", + "[-103, -3, 0], [-100, -3, 0], [-100, 0, 0], [-103, 0, 0], [-103, -3, 0]" + ); + + + createFromJsonFileFormatted( // inside, east edge (11.25E) + "tile/feature_template.json", + "3", + "[11.25, -3, 0], [12, -3, 0], [12, 0, 0], [11.25, 0, 0], [11.25, -3, 0]" + ); + + createFromJsonFileFormatted( // outside, east edge (11.25E) + "tile/feature_template.json", + "4", + "[11.251, -3, 0], [12, -3, 0], [12, 0, 0], [11.251, 0, 0], [11.251, -3, 0]" + ); + + createFromJsonFileFormatted( // inside, west edge (0W) + "tile/feature_template.json", + "5", + "[-3, -3, 0], [0, -3, 0], [0, 0, 0], [-3, 0, 0], [-3, -3, 0]" + ); + + + createFromJsonFileFormatted( // outside, west edge (0W) + "tile/feature_template.json", + "6", + "[-3, -3, 0], [-0.01, -3, 0], [-0.01, 0, 0], [-3, 0, 0], [-3, -3, 0]" + ); + + createFromJsonFileFormatted( // inside, north edge (0N) + "tile/feature_template.json", + "7", + "[0, 0, 0], [3, 0, 0], [3, 3, 0], [0, 3, 0], [0, 0, 0]" + ); + + + createFromJsonFileFormatted( // outside, north edge (0N) + "tile/feature_template.json", + "8", + "[0, 0.01, 0], [3, 0.01, 0], [3, 3, 0], [0, 3, 0], [0, 0.01, 0]" + ); + + String path = "tile/quadkey/30000"; // ~11.1784... S - 0 N; 11.250 W - 0E + Response dhResponse = dataHub().get(path); + Response nResponse = naksha().get(path); + assertTrue(responseHasExactShortIds(List.of("1","3","5","7"), dhResponse)); + assertTrue(responseHasExactShortIds(List.of("1","3","5","7"), nResponse)); + } + + @ParameterizedTest + @ValueSource(strings = {"here","tms"}) + void testUnsupportedTileType(String unsupportedTileType) { + + createFromJsonFileFormatted( // exemplary feature + "tile/feature_template.json", + "1", + "[3, -3, 0], [0, -3, 0], [0, 0, 0], [3, 0, 0], [3, -3, 0]" + ); + + String pathToUnsupportedTile = "tile/" + unsupportedTileType + "/30000"; + + Response response = naksha().get(pathToUnsupportedTile); + assertEquals("ErrorResponse", response.jsonPath().getString("type")); + assertEquals("IllegalArgument", response.jsonPath().getString("error")); + } +} diff --git a/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/tile/feature_template.json b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/tile/feature_template.json new file mode 100644 index 000000000..a5ebf4fa7 --- /dev/null +++ b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/tile/feature_template.json @@ -0,0 +1,15 @@ +{ + "id": "urn:here::here:landmark3d.Landmark3dPhotoreal:%s", + "type": "Feature", + "momType": "landmark3d.Landmark3dPhotoreal", + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [[ + + %s + + ]] + ] + } +} \ No newline at end of file From cdb38cbe75beaab56c423e2289c9f2c3bb234808 Mon Sep 17 00:00:00 2001 From: adamczyk-HERE Date: Mon, 21 Oct 2024 13:13:44 +0200 Subject: [PATCH 7/7] CASL-611 Iterate w/o handle Signed-off-by: adamczyk-HERE --- .../ConnectorInterfaceReadExecute.java | 12 ++++--- .../connector/integration/IterateTest.java | 34 +++++++++++++++++++ .../integration/iterate/feature_template.json | 11 ++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/IterateTest.java create mode 100644 here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/iterate/feature_template.json diff --git a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java index 9f34c6ca1..8594b358d 100644 --- a/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java +++ b/here-naksha-storage-http/src/main/java/com/here/naksha/storage/http/connector/ConnectorInterfaceReadExecute.java @@ -27,10 +27,7 @@ import com.here.naksha.lib.core.models.naksha.Space; import com.here.naksha.lib.core.models.payload.Event; import com.here.naksha.lib.core.models.payload.events.PropertyQueryOr; -import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByBBoxEvent; -import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByIdEvent; -import com.here.naksha.lib.core.models.payload.events.feature.GetFeaturesByTileEvent; -import com.here.naksha.lib.core.models.payload.events.feature.QueryEvent; +import com.here.naksha.lib.core.models.payload.events.feature.*; import com.here.naksha.lib.core.models.storage.POp; import com.here.naksha.lib.core.models.storage.ReadFeatures; import com.here.naksha.lib.core.models.storage.ReadFeaturesProxyWrapper; @@ -57,6 +54,7 @@ public static Result execute(NakshaContext context, ReadFeaturesProxyWrapper req case GET_BY_IDS -> createFeaturesByIdsEvent(request); case GET_BY_BBOX -> createFeatureByBBoxEvent(request); case GET_BY_TILE -> createFeaturesByTileEvent(request); + case ITERATE -> createIterateEvent(request); default -> throw new IllegalStateException("Unexpected value: " + request.getReadRequestType()); }; @@ -69,6 +67,10 @@ public static Result execute(NakshaContext context, ReadFeaturesProxyWrapper req return PrepareResult.prepareResult(httpResponse, XyzFeatureCollection.class, XyzFeatureCollection::getFeatures); } + private static Event createIterateEvent(ReadFeaturesProxyWrapper request) { + return new IterateFeaturesEvent(); + } + private static Event createFeaturesByIdsEvent(ReadFeaturesProxyWrapper request) { List id = request.getQueryParameter(FEATURE_IDS); return new GetFeaturesByIdEvent().withIds(id); @@ -119,7 +121,7 @@ private static Event createFeaturesByTileEvent(ReadFeaturesProxyWrapper readRequ boolean clip = readRequest.getQueryParameter(CLIP_GEO); GetFeaturesByTileEvent event = new GetFeaturesByTileEvent(); - event.setMargin((int) margin); // TODO-a + event.setMargin((int) margin); event.setLimit(limit); event.setClip(clip); setPropertyOp(readRequest, event); diff --git a/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/IterateTest.java b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/IterateTest.java new file mode 100644 index 000000000..47f215512 --- /dev/null +++ b/here-naksha-storage-http/src/test/java/com/here/naksha/storage/http/connector/integration/IterateTest.java @@ -0,0 +1,34 @@ +package com.here.naksha.storage.http.connector.integration; + +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; +import java.util.stream.IntStream; + +import static com.here.naksha.storage.http.connector.integration.Commons.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class IterateTest { + @BeforeEach + void rmFeatures() { + rmAllFeatures(); + } + + @Test + void test() { + IntStream.rangeClosed(1,5).forEach( i -> + createFromJsonFileFormatted("iterate/feature_template.json",String.valueOf(i) ) + ); + + String path = "iterate"; + Response dhResponse = dataHub().get(path); + Response nResponse = naksha().get(path); + assertTrue(responseHasExactShortIds(List.of("1","2","3","4","5"), dhResponse)); + assertTrue(responseHasExactShortIds(List.of("1","2","3","4","5"), nResponse)); + } +} diff --git a/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/iterate/feature_template.json b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/iterate/feature_template.json new file mode 100644 index 000000000..0f1622e50 --- /dev/null +++ b/here-naksha-storage-http/src/test/resources/com/here/naksha/storage/http/connector/integration/iterate/feature_template.json @@ -0,0 +1,11 @@ +{ + "id": "urn:here::here:landmark3d.Landmark3dPhotoreal:%s", + "type": "Feature", + "momType": "landmark3d.Landmark3dPhotoreal", + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [[[-3, -3, 0], [0, -3, 0], [0, 0, 0], [-3, 0, 0], [-3, -3, 0]]] + ] + } +} \ No newline at end of file