Skip to content

Commit

Permalink
Mcpods 6515 update apis (#97)
Browse files Browse the repository at this point in the history
* WIP api implementation

* implement update multiple features, one test

* wip

* update one feature by id

* add test

* add test

* add test

* align tests, autoformat

* allow returned feature collection to specify updated

* revert previous change, extract features twice in transformWriteResultToXyzCollectionResponse

* adjust result helper to not rely on cursor reposition

* switch to test helper class

* add deleted features readout ability

* decouple update feature test resource from create

* add test

* add test

* add test

* explicit imports

* autoformat

* renaming correctly to upsert

* fix cherry pick error

* autoformat

* decouple upsert and update tests completely from create

* improve test assertion

* change mock and test to return 404 for updating non existent features

* fix conflict with maintenance branch

* update openapi yaml

* fix mock

* clean up

* reformat

* adjust description in openapi yaml
  • Loading branch information
gunplar authored Nov 28, 2023
1 parent 67d4e21 commit 9880206
Showing 24 changed files with 893 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
*/
package com.here.naksha.app.service.http.apis;

import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.CREATE_FEATURES;
import static com.here.naksha.app.service.http.tasks.WriteFeatureApiTask.WriteFeatureApiReqType.*;

import com.here.naksha.app.service.http.NakshaHttpVerticle;
import com.here.naksha.app.service.http.tasks.WriteFeatureApiTask;
@@ -41,6 +41,8 @@ public WriteFeatureApi(final @NotNull NakshaHttpVerticle verticle) {
@Override
public void addOperations(final @NotNull RouterBuilder rb) {
rb.operation("postFeatures").handler(this::createFeatures);
rb.operation("putFeatures").handler(this::upsertFeatures);
rb.operation("putFeature").handler(this::updateFeature);
}

@Override
@@ -50,6 +52,14 @@ private void createFeatures(final @NotNull RoutingContext routingContext) {
startWriteFeatureApiTask(CREATE_FEATURES, routingContext);
}

private void upsertFeatures(final @NotNull RoutingContext routingContext) {
startWriteFeatureApiTask(UPSERT_FEATURES, routingContext);
}

private void updateFeature(final @NotNull RoutingContext routingContext) {
startWriteFeatureApiTask(UPDATE_BY_ID, routingContext);
}

private void startWriteFeatureApiTask(WriteFeatureApiReqType reqType, RoutingContext routingContext) {
new WriteFeatureApiTask<>(
reqType, verticle, naksha(), routingContext, verticle.createNakshaContext(routingContext))
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
package com.here.naksha.app.service.http.tasks;

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

import com.here.naksha.app.service.http.HttpResponseType;
@@ -32,14 +33,12 @@
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.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.models.storage.*;
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.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -162,11 +161,17 @@ protected AbstractApiTask(
return verticle.sendErrorResponse(routingContext, er.reason, er.message);
} else {
try {
List<R> features = readFeaturesFromResult(wrResult, type);
final Map<EExecutedOp, List<R>> featureMap = readFeaturesGroupedByOp(wrResult, type);
final List<R> insertedFeatures = featureMap.get(EExecutedOp.CREATED);
final List<R> updatedFeatures = featureMap.get(EExecutedOp.UPDATED);
final List<R> deletedFeatures = featureMap.get(EExecutedOp.DELETED);
return verticle.sendXyzResponse(
routingContext,
HttpResponseType.FEATURE_COLLECTION,
new XyzFeatureCollection().withInsertedFeatures(features));
new XyzFeatureCollection()
.withInsertedFeatures(insertedFeatures)
.withUpdatedFeatures(updatedFeatures)
.withDeletedFeatures(deletedFeatures));
} catch (NoCursor | NoSuchElementException emptyException) {
return verticle.sendErrorResponse(
routingContext, XyzError.EXCEPTION, "Unexpected empty result from ResultCursor");
Original file line number Diff line number Diff line change
@@ -19,11 +19,13 @@
package com.here.naksha.app.service.http.tasks;

import static com.here.naksha.app.service.http.apis.ApiParams.ADD_TAGS;
import static com.here.naksha.app.service.http.apis.ApiParams.FEATURE_ID;
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.fasterxml.jackson.core.JsonProcessingException;
import com.here.naksha.app.service.http.NakshaHttpVerticle;
import com.here.naksha.app.service.models.FeatureCollectionRequest;
import com.here.naksha.lib.core.INaksha;
@@ -37,6 +39,7 @@
import com.here.naksha.lib.core.util.json.Json;
import com.here.naksha.lib.core.util.storage.RequestHelper;
import com.here.naksha.lib.core.view.ViewDeserialize;
import com.here.naksha.lib.core.view.ViewDeserialize.User;
import io.vertx.ext.web.RoutingContext;
import java.util.List;
import org.jetbrains.annotations.NotNull;
@@ -50,7 +53,9 @@ public class WriteFeatureApiTask<T extends XyzResponse> extends AbstractApiTask<

public enum WriteFeatureApiReqType {
CREATE_FEATURES,
MODIFY_FEATURES
UPSERT_FEATURES,
UPDATE_BY_ID,
DELETE_FEATURES
}

public WriteFeatureApiTask(
@@ -79,12 +84,12 @@ protected void init() {}
logger.info("Received Http request {}", this.reqType);
// Custom execute logic to process input API request based on reqType
try {
switch (this.reqType) {
case CREATE_FEATURES:
return executeCreateFeatures();
default:
return executeUnsupported();
}
return switch (this.reqType) {
case CREATE_FEATURES -> executeCreateFeatures();
case UPSERT_FEATURES -> executeUpsertFeatures();
case UPDATE_BY_ID -> executeUpdateFeature();
default -> executeUnsupported();
};
} catch (Exception ex) {
// unexpected exception
logger.error("Exception processing Http request. ", ex);
@@ -95,13 +100,7 @@ protected void init() {}

private @NotNull XyzResponse executeCreateFeatures() throws Exception {
// Deserialize input request
final FeatureCollectionRequest collectionRequest;
try (final Json json = Json.get()) {
final String bodyJson = routingContext.body().asString();
collectionRequest = json.reader(ViewDeserialize.User.class)
.forType(FeatureCollectionRequest.class)
.readValue(bodyJson);
}
final FeatureCollectionRequest collectionRequest = featuresFromRequestBody();
final List<XyzFeature> features = (List<XyzFeature>) collectionRequest.getFeatures();
if (features.isEmpty()) {
return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Can't create empty features");
@@ -123,17 +122,121 @@ protected void init() {}
}

// as applicable, modify features based on parameters supplied
if (prefixId != null || addTags != null || removeTags != null) {
for (final XyzFeature feature : features) {
feature.setIdPrefix(prefixId);
feature.getProperties().getXyzNamespace().addTags(addTags, true).removeTags(removeTags, true);
}

for (final XyzFeature feature : features) {
feature.setIdPrefix(prefixId);
addTagsToFeature(feature, addTags);
removeTagsFromFeature(feature, removeTags);
}

final WriteXyzFeatures wrRequest = RequestHelper.createFeaturesRequest(spaceId, features);

// Forward request to NH Space Storage writer instance
final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest);
// transform WriteResult to Http FeatureCollection response
return transformWriteResultToXyzCollectionResponse(wrResult, XyzFeature.class);
}

private @NotNull XyzResponse executeUpsertFeatures() throws Exception {
// Deserialize input request
final FeatureCollectionRequest collectionRequest = featuresFromRequestBody();
final List<XyzFeature> features = (List<XyzFeature>) collectionRequest.getFeatures();
if (features.isEmpty()) {
return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Can't update empty features");
}

// Parse API parameters
final String spaceId = pathParam(routingContext, SPACE_ID);
final QueryParameterList queryParams = (routingContext.request().query() != null)
? new QueryParameterList(routingContext.request().query())
: null;
final List<String> addTags = (queryParams != null) ? queryParams.collectAllOf(ADD_TAGS, String.class) : null;
final List<String> removeTags =
(queryParams != null) ? queryParams.collectAllOf(REMOVE_TAGS, String.class) : null;

// Validate parameters
if (spaceId == null || spaceId.isEmpty()) {
return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing spaceId parameter");
}

// as applicable, modify features based on parameters supplied
for (final XyzFeature feature : features) {
addTagsToFeature(feature, addTags);
removeTagsFromFeature(feature, removeTags);
}
final WriteXyzFeatures wrRequest = RequestHelper.upsertFeaturesRequest(spaceId, features);

// Forward request to NH Space Storage writer instance
final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest);
// transform WriteResult to Http FeatureCollection response
return transformWriteResultToXyzCollectionResponse(wrResult, XyzFeature.class);
}

private @NotNull XyzResponse executeUpdateFeature() throws Exception {
// Deserialize input request
XyzFeature feature;
try (final Json json = Json.get()) {
final String bodyJson = routingContext.body().asString();
feature = json.reader(User.class).forType(XyzFeature.class).readValue(bodyJson);
}

// Parse API parameters
final String spaceId = pathParam(routingContext, SPACE_ID);
final String featureId = pathParam(routingContext, FEATURE_ID);

final QueryParameterList queryParams = (routingContext.request().query() != null)
? new QueryParameterList(routingContext.request().query())
: null;
final List<String> addTags = (queryParams != null) ? queryParams.collectAllOf(ADD_TAGS, String.class) : null;
final List<String> removeTags =
(queryParams != null) ? queryParams.collectAllOf(REMOVE_TAGS, String.class) : null;

// Validate parameters
if (spaceId == null || spaceId.isEmpty()) {
return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing spaceId parameter");
}

if (featureId == null || featureId.isEmpty()) {
return verticle.sendErrorResponse(routingContext, XyzError.ILLEGAL_ARGUMENT, "Missing featureId parameter");
}

if (!featureId.equals(feature.getId())) {
return verticle.sendErrorResponse(
routingContext,
XyzError.ILLEGAL_ARGUMENT,
"URI path parameter featureId is not the same as id in feature request body.");
}

// as applicable, modify features based on parameters supplied
addTagsToFeature(feature, addTags);
removeTagsFromFeature(feature, removeTags);

final WriteXyzFeatures wrRequest = RequestHelper.updateFeatureRequest(spaceId, feature);

// Forward request to NH Space Storage writer instance
final Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest);
// transform WriteResult to Http FeatureCollection response
return transformWriteResultToXyzFeatureResponse(wrResult, XyzFeature.class);
}

private @NotNull FeatureCollectionRequest featuresFromRequestBody() throws JsonProcessingException {
try (final Json json = Json.get()) {
final String bodyJson = routingContext.body().asString();
return json.reader(ViewDeserialize.User.class)
.forType(FeatureCollectionRequest.class)
.readValue(bodyJson);
}
}

private void addTagsToFeature(XyzFeature feature, List<String> addTags) {
if (addTags != null) {
feature.getProperties().getXyzNamespace().addTags(addTags, true);
}
}

private void removeTagsFromFeature(XyzFeature feature, List<String> removeTags) {
if (removeTags != null) {
feature.getProperties().getXyzNamespace().removeTags(removeTags, true);
}
}
}
Loading

0 comments on commit 9880206

Please sign in to comment.