Skip to content

Commit

Permalink
Merge pull request #92 from xeus2001/MCPODS-6583-result-hierarchy-ali…
Browse files Browse the repository at this point in the history
…gnment

MCPODS-6583 aligned naksha hub with Result-related changes
  • Loading branch information
Amaneusz authored Nov 16, 2023
2 parents 51b4b15 + 1675b8f commit 82e31f1
Show file tree
Hide file tree
Showing 29 changed files with 1,009 additions and 293 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -524,19 +524,30 @@ public void sendErrorResponse(@NotNull RoutingContext routingContext, @NotNull T
}

private @NotNull HttpResponseStatus mapErrorToHttpStatus(final @NotNull XyzError xyzError) {
return switch (xyzError) {
case EXCEPTION -> HttpResponseStatus.INTERNAL_SERVER_ERROR;
case NOT_IMPLEMENTED -> HttpResponseStatus.NOT_IMPLEMENTED;
case ILLEGAL_ARGUMENT -> HttpResponseStatus.BAD_REQUEST;
case PAYLOAD_TOO_LARGE -> HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE;
case BAD_GATEWAY -> HttpResponseStatus.BAD_GATEWAY;
case CONFLICT -> HttpResponseStatus.CONFLICT;
case UNAUTHORIZED -> HttpResponseStatus.UNAUTHORIZED;
case FORBIDDEN -> HttpResponseStatus.FORBIDDEN;
case TOO_MANY_REQUESTS -> HttpResponseStatus.TOO_MANY_REQUESTS;
case TIMEOUT -> HttpResponseStatus.GATEWAY_TIMEOUT;
case NOT_FOUND -> HttpResponseStatus.NOT_FOUND;
};
if (xyzError.equals(XyzError.EXCEPTION)) {
return HttpResponseStatus.INTERNAL_SERVER_ERROR;
} else if (xyzError.equals(XyzError.NOT_IMPLEMENTED)) {
return HttpResponseStatus.NOT_IMPLEMENTED;
} else if (xyzError.equals(ILLEGAL_ARGUMENT)) {
return HttpResponseStatus.BAD_REQUEST;
} else if (xyzError.equals(XyzError.PAYLOAD_TOO_LARGE)) {
return HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE;
} else if (xyzError.equals(XyzError.BAD_GATEWAY)) {
return HttpResponseStatus.BAD_GATEWAY;
} else if (xyzError.equals(XyzError.CONFLICT)) {
return HttpResponseStatus.CONFLICT;
} else if (xyzError.equals(XyzError.UNAUTHORIZED)) {
return HttpResponseStatus.UNAUTHORIZED;
} else if (xyzError.equals(XyzError.FORBIDDEN)) {
return HttpResponseStatus.FORBIDDEN;
} else if (xyzError.equals(XyzError.TOO_MANY_REQUESTS)) {
return HttpResponseStatus.TOO_MANY_REQUESTS;
} else if (xyzError.equals(XyzError.TIMEOUT)) {
return HttpResponseStatus.GATEWAY_TIMEOUT;
} else if (xyzError.equals(XyzError.NOT_FOUND)) {
return NOT_FOUND;
}
throw new IllegalArgumentException();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,36 @@
*/
package com.here.naksha.app.service.http.tasks;

import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeaturesFromResult;
import static java.util.Collections.emptyList;

import com.here.naksha.app.service.http.HttpResponseType;
import com.here.naksha.app.service.http.NakshaHttpVerticle;
import com.here.naksha.lib.core.AbstractTask;
import com.here.naksha.lib.core.INaksha;
import com.here.naksha.lib.core.NakshaContext;
import com.here.naksha.lib.core.exceptions.NoCursor;
import com.here.naksha.lib.core.lambdas.F0;
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.geojson.implementation.XyzFeatureCollection;
import com.here.naksha.lib.core.models.payload.XyzResponse;
import com.here.naksha.lib.core.models.storage.*;
import com.here.naksha.lib.core.models.storage.ErrorResult;
import com.here.naksha.lib.core.models.storage.ReadFeatures;
import com.here.naksha.lib.core.models.storage.Result;
import com.here.naksha.lib.core.models.storage.WriteFeatures;
import com.here.naksha.lib.core.storage.IReadSession;
import com.here.naksha.lib.core.storage.IWriteSession;
import io.vertx.ext.web.RoutingContext;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* An abstract class that can be used for all Http API specific custom Task implementations.
*
*/
public abstract class AbstractApiTask<T extends XyzResponse>
extends AbstractTask<XyzResponse, AbstractApiTask<XyzResponse>> {
Expand Down Expand Up @@ -70,93 +76,72 @@ protected AbstractApiTask(
return verticle.sendErrorResponse(routingContext, XyzError.NOT_IMPLEMENTED, "Unsupported operation!");
}

protected <R extends XyzFeature> @NotNull XyzResponse transformReadResultToXyzCollectionResponse(
final @Nullable Result rdResult, final @NotNull Class<R> type) {
if (rdResult == null) {
// return empty collection
logger.warn("Unexpected null result, returning empty collection.");
return verticle.sendXyzResponse(
routingContext, HttpResponseType.FEATURE_COLLECTION, new XyzFeatureCollection());
} else if (rdResult instanceof ErrorResult er) {
// In case of error, convert result to ErrorResponse
logger.error("Received error result {}", er);
return verticle.sendErrorResponse(routingContext, er.reason, er.message);
} else if (rdResult instanceof ReadResult<?> rr) {
// In case of success, convert result to success XyzResponse
final List<R> features = new ArrayList<>();
int cnt = 0;
for (final R feature : rr.withFeatureType(type)) {
features.add(feature);
if (++cnt >= 1000) {
break; // TODO : can be improved later (perhaps by accepting limit as an input)
}
}
rr.close();
final XyzFeatureCollection response = new XyzFeatureCollection().withFeatures(features);
return verticle.sendXyzResponse(routingContext, HttpResponseType.FEATURE_COLLECTION, response);
}
// unexpected result type
logger.error("Unexpected result type {}", rdResult.getClass().getSimpleName());
return verticle.sendErrorResponse(
routingContext,
XyzError.EXCEPTION,
"Unsupported result type : " + rdResult.getClass().getSimpleName());
}

protected <R extends XyzFeature> @NotNull XyzResponse transformReadResultToXyzFeatureResponse(
final @NotNull Result rdResult, final @NotNull Class<R> type) {
if (rdResult == null) {
return transformResultToXyzFeatureResponse(
rdResult,
type,
() -> verticle.sendErrorResponse(
routingContext, XyzError.NOT_FOUND, "The desired feature does not exist."));
}

protected <R extends XyzFeature> @NotNull XyzResponse transformWriteResultToXyzFeatureResponse(
final @Nullable Result wrResult, final @NotNull Class<R> type) {
return transformResultToXyzFeatureResponse(
wrResult,
type,
() -> verticle.sendErrorResponse(
routingContext,
XyzError.EXCEPTION,
"Unexpected error while saving feature, the result cursor is empty / does not exist"));
}

private <R extends XyzFeature> @NotNull XyzResponse transformResultToXyzFeatureResponse(
final @Nullable Result result,
final @NotNull Class<R> type,
final @NotNull F0<XyzResponse> onNoElementsReturned) {
if (result == null) {
logger.error("Unexpected null result!");
return verticle.sendErrorResponse(routingContext, XyzError.EXCEPTION, "Unexpected null result!");
} else if (rdResult instanceof ErrorResult er) {
} else if (result instanceof ErrorResult er) {
// In case of error, convert result to ErrorResponse
logger.error("Received error result {}", er);
return verticle.sendErrorResponse(routingContext, er.reason, er.message);
} else if (rdResult instanceof ReadResult<?> rr) {
// In case of success, convert result to success XyzResponse
final Iterator<R> iterator = rr.withFeatureType(type).iterator();
if (!iterator.hasNext())
return verticle.sendErrorResponse(
routingContext, XyzError.NOT_FOUND, "The desired feature does not exist.");
final List<R> features = new ArrayList<>();
features.add(iterator.next());
rr.close();
final XyzFeatureCollection featureResponse = new XyzFeatureCollection().withFeatures(features);
return verticle.sendXyzResponse(routingContext, HttpResponseType.FEATURE, featureResponse);
} else {
try {
List<R> features = readFeaturesFromResult(result, type);
final XyzFeatureCollection featureResponse = new XyzFeatureCollection().withFeatures(features);
return verticle.sendXyzResponse(routingContext, HttpResponseType.FEATURE, featureResponse);
} catch (NoCursor | NoSuchElementException emptyException) {
return onNoElementsReturned.call();
}
}
// unexpected result type
logger.error("Unexpected result type {}", rdResult.getClass().getSimpleName());
return verticle.sendErrorResponse(
routingContext,
XyzError.EXCEPTION,
"Unsupported result type : " + rdResult.getClass().getSimpleName());
}

protected <R extends XyzFeature> @NotNull XyzResponse transformWriteResultToXyzFeatureResponse(
final @Nullable Result wrResult, final @NotNull Class<R> type) {
if (wrResult == null) {
// unexpected null response
logger.error("Unexpected null result!");
return verticle.sendErrorResponse(routingContext, XyzError.EXCEPTION, "Unexpected null result!");
} else if (wrResult instanceof ErrorResult er) {
protected <R extends XyzFeature> @NotNull XyzResponse transformReadResultToXyzCollectionResponse(
final @Nullable Result rdResult, final @NotNull Class<R> type) {
if (rdResult == null) {
// return empty collection
logger.warn("Unexpected null result, returning empty collection.");
return verticle.sendXyzResponse(
routingContext, HttpResponseType.FEATURE_COLLECTION, new XyzFeatureCollection());
} else if (rdResult instanceof ErrorResult er) {
// In case of error, convert result to ErrorResponse
logger.error("Received error result {}", er);
return verticle.sendErrorResponse(routingContext, er.reason, er.message);
} else if (wrResult instanceof WriteResult<?> wr) {
// In case of success, convert result to success XyzResponse
//noinspection unchecked
final WriteResult<R> featureWR = (WriteResult<R>) wr;
final List<R> features =
featureWR.results.stream().map(op -> op.object).toList();
final XyzFeatureCollection featureResponse = new XyzFeatureCollection().withFeatures(features);
return verticle.sendXyzResponse(routingContext, HttpResponseType.FEATURE, featureResponse);
} else {
try {
List<R> features = readFeaturesFromResult(rdResult, type, 1000);
return verticle.sendXyzResponse(
routingContext,
HttpResponseType.FEATURE_COLLECTION,
new XyzFeatureCollection().withInsertedFeatures(features));
} catch (NoCursor | NoSuchElementException emptyException) {
logger.info("No data found in ResultCursor, returning empty collection");
return verticle.sendXyzResponse(
routingContext, HttpResponseType.FEATURE_COLLECTION, emptyFeatureCollection());
}
}
// unexpected result type
logger.error("Unexpected result type {}", wrResult.getClass().getSimpleName());
return verticle.sendErrorResponse(
routingContext,
XyzError.EXCEPTION,
"Unsupported result type : " + wrResult.getClass().getSimpleName());
}

protected <R extends XyzFeature> @NotNull XyzResponse transformWriteResultToXyzCollectionResponse(
Expand All @@ -169,23 +154,18 @@ protected AbstractApiTask(
// In case of error, convert result to ErrorResponse
logger.error("Received error result {}", er);
return verticle.sendErrorResponse(routingContext, er.reason, er.message);
} else if (wrResult instanceof WriteResult<?> wr) {
// In case of success, convert result to success XyzResponse
//noinspection unchecked
final WriteResult<R> featureWR = (WriteResult<R>) wr;
final List<R> features =
featureWR.results.stream().map(op -> op.object).toList();
return verticle.sendXyzResponse(
routingContext,
HttpResponseType.FEATURE_COLLECTION,
new XyzFeatureCollection().withInsertedFeatures(features));
} else {
try {
List<R> features = readFeaturesFromResult(wrResult, type);
return verticle.sendXyzResponse(
routingContext,
HttpResponseType.FEATURE_COLLECTION,
new XyzFeatureCollection().withInsertedFeatures(features));
} catch (NoCursor | NoSuchElementException emptyException) {
return verticle.sendErrorResponse(
routingContext, XyzError.EXCEPTION, "Unexpected empty result from ResultCursor");
}
}
// unexpected result type
logger.error("Unexpected result type {}", wrResult.getClass().getSimpleName());
return verticle.sendErrorResponse(
routingContext,
XyzError.EXCEPTION,
"Unsupported result type : " + wrResult.getClass().getSimpleName());
}

protected Result executeReadRequestFromSpaceStorage(ReadFeatures readRequest) {
Expand All @@ -199,4 +179,8 @@ protected Result executeWriteRequestFromSpaceStorage(WriteFeatures<?> writeReque
return writer.execute(writeRequest);
}
}

private XyzFeatureCollection emptyFeatureCollection() {
return new XyzFeatureCollection().withFeatures(emptyList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
*/
package com.here.naksha.app.service.http.tasks;

import static com.here.naksha.app.service.http.apis.ApiParams.*;
import static com.here.naksha.app.service.http.apis.ApiParams.ADD_TAGS;
import static com.here.naksha.app.service.http.apis.ApiParams.PREFIX_ID;
import static com.here.naksha.app.service.http.apis.ApiParams.REMOVE_TAGS;
import static com.here.naksha.app.service.http.apis.ApiParams.SPACE_ID;
import static com.here.naksha.app.service.http.apis.ApiParams.pathParam;

import com.here.naksha.app.service.http.NakshaHttpVerticle;
import com.here.naksha.app.service.models.FeatureCollectionRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ public void tc0300_testCreateFeaturesWithNewIds() throws Exception {

// Given: Create Features request (against above Space)
final String bodyJson = loadFileOrFail("TC0300_createFeaturesWithNewIds/create_features.json");
final String expectedBodyPart = loadFileOrFail("TC0300_createFeaturesWithNewIds/feature_response_part.json");
// TODO: include geometry after Cursor-related changes ->
// loadFileOrFail("TC0300_createFeaturesWithNewIds/feature_response_part.json");
final String expectedBodyPart =
loadFileOrFail("TC0300_createFeaturesWithNewIds/feature_response_part_without_geometry.json");
streamId = UUID.randomUUID().toString();

// When: Create Features request is submitted to NakshaHub Space Storage instance
Expand Down Expand Up @@ -185,7 +188,10 @@ public void tc0301_testCreateFeaturesWithGivenIds() throws Exception {
final String spaceId = "um-mod-topology-dev";
// Given: Create Features request
final String bodyJson = loadFileOrFail("TC0301_createFeaturesWithGivenIds/create_features.json");
final String expectedBodyPart = loadFileOrFail("TC0301_createFeaturesWithGivenIds/feature_response_part.json");
// TODO: include geometry after Cursor-related changes ->
// loadFileOrFail("TC0301_createFeaturesWithGivenIds/feature_response_part.json");
final String expectedBodyPart =
loadFileOrFail("TC0301_createFeaturesWithGivenIds/feature_response_part_without_geometry.json");
streamId = UUID.randomUUID().toString();

// When: Create Features request is submitted to NakshaHub Space Storage instance
Expand Down Expand Up @@ -218,7 +224,10 @@ public void tc0302_testCreateFeaturesWithPrefixId() throws Exception {
final String prefixId = "my-custom-prefix:";
// Given: Create Features request
final String bodyJson = loadFileOrFail("TC0302_createFeaturesWithPrefixId/create_features.json");
final String expectedBodyPart = loadFileOrFail("TC0302_createFeaturesWithPrefixId/feature_response_part.json");
// TODO: include geometry after Cursor-related changes ->
// loadFileOrFail("TC0302_createFeaturesWithPrefixId/feature_response_part.json");
final String expectedBodyPart =
loadFileOrFail("TC0302_createFeaturesWithPrefixId/feature_response_part_without_geometry.json");
streamId = UUID.randomUUID().toString();

// When: Create Features request is submitted to NakshaHub Space Storage instance
Expand Down Expand Up @@ -255,7 +264,10 @@ public void tc0303_testCreateFeaturesWithAddTags() throws Exception {
+ "&addTags=" + URLEncoder.encode("@Existing_Non_Normalized_Tag", UTF_8);
// Given: Create Features request
final String bodyJson = loadFileOrFail("TC0303_createFeaturesWithAddTags/create_features.json");
final String expectedBodyPart = loadFileOrFail("TC0303_createFeaturesWithAddTags/feature_response_part.json");
// TODO: include geometry after Cursor-related changes ->
// loadFileOrFail("TC0303_createFeaturesWithAddTags/feature_response_part.json");
final String expectedBodyPart =
loadFileOrFail("TC0303_createFeaturesWithAddTags/feature_response_part_without_geometry.json");
streamId = UUID.randomUUID().toString();

// When: Create Features request is submitted to NakshaHub Space Storage instance
Expand Down Expand Up @@ -290,8 +302,10 @@ public void tc0304_testCreateFeaturesWithRemoveTags() throws Exception {
+ "&removeTags=" + URLEncoder.encode("@Existing_Non_Normalized_Tag", UTF_8);
// Given: Create Features request
final String bodyJson = loadFileOrFail("TC0304_createFeaturesWithRemoveTags/create_features.json");
// TODO: include geometry after Cursor-related changes ->
// loadFileOrFail("TC0304_createFeaturesWithRemoveTags/feature_response_part.json");
final String expectedBodyPart =
loadFileOrFail("TC0304_createFeaturesWithRemoveTags/feature_response_part.json");
loadFileOrFail("TC0304_createFeaturesWithRemoveTags/feature_response_part_without_geometry.json");
streamId = UUID.randomUUID().toString();

// When: Create Features request is submitted to NakshaHub Space Storage instance
Expand Down
Loading

0 comments on commit 82e31f1

Please sign in to comment.