Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ project(":here-naksha-lib-handlers") {
implementation(project(":here-naksha-lib-psql"))
implementation(project(":here-naksha-lib-view"))
implementation(project(":here-naksha-storage-http"))
implementation(project(":here-naksha-lib-mom10"))

implementation(commons_lang3)
implementation(commons_dbutils)
Expand Down Expand Up @@ -517,6 +518,7 @@ project(":here-naksha-app-service") {
//implementation(project(":here-naksha-lib-extension"))
implementation(project(":here-naksha-lib-hub"))
implementation(project(":here-naksha-common-http"))
implementation(project(":here-naksha-lib-mom10"))

implementation(log4j_slf4j)
implementation(log4j_api)
Expand Down Expand Up @@ -547,7 +549,6 @@ project(":here-naksha-lib-mom10"){
description = "Naksha MOM 10 library"
dependencies {
implementation(project(":here-naksha-lib-core"))
implementation(project(":here-naksha-lib-handlers"))

testImplementation(mockito)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,41 @@
import static com.here.naksha.common.http.apis.ApiParamsConst.DEF_ADMIN_FEATURE_LIMIT;
import static com.here.naksha.lib.core.util.storage.ResultHelper.readFeatureFromResult;
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.fasterxml.jackson.core.JsonProcessingException;
import com.here.naksha.app.service.http.HttpResponseType;
import com.here.naksha.app.service.http.NakshaHttpVerticle;
import com.here.naksha.app.service.http.tasks.processor.FeaturePostProcessor;
import com.here.naksha.app.service.models.IterateHandle;
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.F1;
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.geojson.implementation.XyzGeometry;
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.ContextXyzFeatureResult;
import com.here.naksha.lib.core.models.storage.EExecutedOp;
import com.here.naksha.lib.core.models.storage.ErrorResult;
import com.here.naksha.lib.core.models.storage.ForwardCursor;
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.XyzFeatureCodec;
import com.here.naksha.lib.core.storage.IReadSession;
import com.here.naksha.lib.core.storage.IWriteSession;
import com.here.naksha.lib.core.util.PropertyPathUtil;
import com.here.naksha.lib.core.util.json.Json;
import com.here.naksha.lib.core.util.json.JsonSerializable;
import com.here.naksha.lib.core.view.ViewDeserialize;
import io.vertx.ext.web.RoutingContext;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.util.GeometryFixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -98,8 +103,10 @@ protected AbstractApiTask(
}

protected <R extends XyzFeature> @NotNull XyzResponse transformReadResultToXyzFeatureResponse(
final @NotNull Result rdResult, final @NotNull Class<R> type, @Nullable F1<R, R> preResponseProcessing) {
return transformResultToXyzFeatureResponse(rdResult, type, NOT_FOUND_ON_NO_ELEMENTS, preResponseProcessing);
final @NotNull Result rdResult,
final @NotNull Class<R> type,
@Nullable FeaturePostProcessor<R> postProcessor) {
return transformResultToXyzFeatureResponse(rdResult, type, NOT_FOUND_ON_NO_ELEMENTS, postProcessor);
}

protected <R extends XyzFeature> @NotNull XyzResponse transformWriteResultToXyzFeatureResponse(
Expand All @@ -108,8 +115,10 @@ protected AbstractApiTask(
}

protected <R extends XyzFeature> @NotNull XyzResponse transformWriteResultToXyzFeatureResponse(
final @Nullable Result wrResult, final @NotNull Class<R> type, @Nullable F1<R, R> preResponseProcessing) {
return transformResultToXyzFeatureResponse(wrResult, type, FAIL_ON_NO_ELEMENTS, preResponseProcessing);
final @Nullable Result wrResult,
final @NotNull Class<R> type,
@Nullable FeaturePostProcessor<R> postProcessor) {
return transformResultToXyzFeatureResponse(wrResult, type, FAIL_ON_NO_ELEMENTS, postProcessor);
}

protected <R extends XyzFeature> @NotNull XyzResponse transformDeleteResultToXyzFeatureResponse(
Expand All @@ -118,8 +127,10 @@ protected AbstractApiTask(
}

protected <R extends XyzFeature> @NotNull XyzResponse transformDeleteResultToXyzFeatureResponse(
final @Nullable Result wrResult, final @NotNull Class<R> type, @Nullable F1<R, R> postProcessing) {
return transformResultToXyzFeatureResponse(wrResult, type, NOT_FOUND_ON_NO_ELEMENTS, postProcessing);
final @Nullable Result wrResult,
final @NotNull Class<R> type,
@Nullable FeaturePostProcessor<R> postProcessor) {
return transformResultToXyzFeatureResponse(wrResult, type, NOT_FOUND_ON_NO_ELEMENTS, postProcessor);
}

protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
Expand All @@ -130,16 +141,16 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
final @Nullable Result result,
final @NotNull Class<R> type,
final @NotNull NoElementsStrategy noElementsStrategy,
final @Nullable F1<R, R> preResponseProcessing) {
final @Nullable FeaturePostProcessor<R> postProcessor) {
final XyzResponse validatedErrorResponse = validateErrorResult(result);
if (validatedErrorResponse != null) {
return validatedErrorResponse;
} else {
try {
final R feature = readFeatureFromResult(result, type);
R processedFeature = feature;
if (feature != null && preResponseProcessing != null) {
processedFeature = preResponseProcessing.call(feature);
if (feature != null && postProcessor != null) {
processedFeature = postProcessor.postProcess(feature);
}
if (processedFeature == null) {
return verticle.sendErrorResponse(
Expand All @@ -161,9 +172,9 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
protected <R extends XyzFeature> @NotNull XyzResponse transformReadResultToXyzCollectionResponse(
final @Nullable Result rdResult,
final @NotNull Class<R> type,
final @Nullable F1<R, R> preResponseProcessing) {
final @Nullable FeaturePostProcessor<R> featurePostProcessor) {
return transformReadResultToXyzCollectionResponse(
rdResult, type, 0, DEF_ADMIN_FEATURE_LIMIT, null, preResponseProcessing);
rdResult, type, 0, DEF_ADMIN_FEATURE_LIMIT, null, featurePostProcessor);
}

protected <R extends XyzFeature> @NotNull XyzResponse transformReadResultToXyzCollectionResponse(
Expand All @@ -182,18 +193,18 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
final long offset,
final long maxLimit,
final @Nullable IterateHandle handle,
final @Nullable F1<R, R> preResponseProcessing) {
final @Nullable FeaturePostProcessor<R> featurePostProcessor) {
final XyzResponse validatedErrorResponse = validateErrorResultEmptyCollection(rdResult);
if (validatedErrorResponse != null) {
return validatedErrorResponse;
} else {
try {
final List<R> features = readFeaturesFromResult(rdResult, type, offset, maxLimit);
List<R> processedFeatures = features;
if (preResponseProcessing != null) {
if (featurePostProcessor != null) {
processedFeatures = new ArrayList<>();
for (R feature : features) {
final R processedFeature = preResponseProcessing.call(feature);
final R processedFeature = featurePostProcessor.postProcess(feature);
if (processedFeature != null) {
processedFeatures.add(processedFeature);
}
Expand All @@ -218,20 +229,26 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
private static String getIterateHandleAsString(
long featuresFound, long crtOffset, long maxLimit, final @Nullable IterateHandle handle) {
// nothing to populate if handle is not provided OR if we don't have more features to iterate
if (handle == null || featuresFound < maxLimit) return null;
if (handle == null || featuresFound < maxLimit) {
return null;
}
handle.setOffset(crtOffset + featuresFound); // set offset for next iteration
handle.setLimit(maxLimit);
return handle.base64EncodedSerializedJson();
}

protected <R extends XyzFeature> @NotNull XyzResponse transformWriteResultToXyzCollectionResponse(
final @Nullable Result wrResult, final @NotNull Class<R> type, final boolean isDeleteOperation) {
final @Nullable Result wrResult,
final @NotNull Class<R> type,
final boolean isDeleteOperation,
final @Nullable FeaturePostProcessor<R> featurePostProcessor) {
final XyzResponse validatedErrorResponse = validateErrorResult(wrResult);
if (validatedErrorResponse != null) {
return validatedErrorResponse;
} else {
try {
final Map<EExecutedOp, List<R>> featureMap = readFeaturesGroupedByOp(wrResult, type);
final Map<EExecutedOp, List<R>> featureMap =
postProcessedFeaturesByOp(wrResult, type, featurePostProcessor);
final List<R> insertedFeatures = featureMap.get(EExecutedOp.CREATED);
final List<R> updatedFeatures = featureMap.get(EExecutedOp.UPDATED);
final List<R> deletedFeatures = featureMap.get(EExecutedOp.DELETED);
Expand Down Expand Up @@ -260,6 +277,45 @@ private static String getIterateHandleAsString(
}
}

/**
* Helper method to fetch features from given Result and return a map of multiple lists grouped by {@link EExecutedOp} of features with
* type T. Returned lists are limited with respect to supplied `limit` parameter.
*
* @param result the Result which is to be read
* @param featureType the type of feature to be extracted from result
* @param <R> type of feature
* @return a map grouping the lists of features extracted from ReadResult
*/
private static <R extends XyzFeature> Map<EExecutedOp, List<R>> postProcessedFeaturesByOp(
Result result, Class<R> featureType, FeaturePostProcessor<R> postProcessor) throws NoCursor {
try (ForwardCursor<XyzFeature, XyzFeatureCodec> resultCursor = result.getXyzFeatureCursor()) {
final List<R> insertedFeatures = new ArrayList<>();
final List<R> updatedFeatures = new ArrayList<>();
final List<R> deletedFeatures = new ArrayList<>();
while (resultCursor.hasNext()) {
if (!resultCursor.next()) {
throw new RuntimeException("Unexpected invalid result");
}
R feature = featureType.cast(resultCursor.getFeature());
if (postProcessor != null) {
feature = postProcessor.postProcess(feature);
}
if (resultCursor.getOp().equals(EExecutedOp.CREATED)) {
insertedFeatures.add(feature);
} else if (resultCursor.getOp().equals(EExecutedOp.UPDATED)) {
updatedFeatures.add(feature);
} else if (resultCursor.getOp().equals(EExecutedOp.DELETED)) {
deletedFeatures.add(feature);
}
}
final Map<EExecutedOp, List<R>> features = new HashMap<>();
features.put(EExecutedOp.CREATED, insertedFeatures);
features.put(EExecutedOp.UPDATED, updatedFeatures);
features.put(EExecutedOp.DELETED, deletedFeatures);
return features;
}
}

protected Result executeReadRequestFromSpaceStorage(ReadFeatures readRequest) {
try (final IReadSession reader = naksha().getSpaceStorage().newReadSession(context(), false)) {
return reader.execute(readRequest);
Expand Down Expand Up @@ -309,38 +365,4 @@ XyzFeatureCollection emptyFeatureCollection() {
return json.reader(ViewDeserialize.User.class).forType(type).readValue(bodyJson);
}
}

protected <F extends XyzFeature> @Nullable F1<F, F> standardReadFeaturesPreResponseProcessing(
final @Nullable Set<String> propPaths, final boolean clip, final Geometry clipGeo) {
if (propPaths == null && !clip) return null;
return f -> {
F newF = f;
// Apply prop selection if enabled
if (propPaths != null) newF = applyPropertySelection(newF, propPaths);
// Apply geometry clipping if enabled
if (clip) applyGeometryClipping(newF, clipGeo);
return newF;
};
}

@SuppressWarnings("unchecked")
private <F extends XyzFeature> @NotNull F applyPropertySelection(
final @NotNull F f, final @NotNull Set<String> propPaths) {
final Map<String, Object> tgtMap = PropertyPathUtil.extractPropertyMapFromFeature(f, propPaths);
return (F) JsonSerializable.fromMap(tgtMap, f.getClass());
}

private <F extends XyzFeature> void applyGeometryClipping(final @NotNull F f, final Geometry clipGeo) {
// clip Feature geometry (if present) to a given clipGeo geometry
final XyzGeometry xyzGeo = f.getGeometry();
if (xyzGeo != null) {
// NOTE - in JTS when we say:
// GeometryFixer.fix(geom).intersection(bbox)
// it is the best available way of clipping geometry, equivalent to PostGIS approach of:
// ST_Intersection(ST_MakeValid(geo, 'method=structure'), bbox)
final Geometry clippedGeo =
GeometryFixer.fix(xyzGeo.getJTSGeometry()).intersection(clipGeo);
f.setGeometry(XyzGeometry.convertJTSGeometry(clippedGeo));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
*/
package com.here.naksha.app.service.http.tasks;

import static com.here.naksha.app.service.http.ops.MaskingUtil.maskProperties;
import static com.here.naksha.common.http.apis.ApiParamsConst.HANDLER_ID;
import static com.here.naksha.lib.core.NakshaAdminCollection.EVENT_HANDLERS;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.here.naksha.app.service.http.NakshaHttpVerticle;
import com.here.naksha.app.service.http.apis.ApiParams;
import com.here.naksha.app.service.http.tasks.processor.FeaturePostProcessor;
import com.here.naksha.app.service.http.tasks.processor.MaskingPostProcessor;
import com.here.naksha.lib.core.INaksha;
import com.here.naksha.lib.core.NakshaContext;
import com.here.naksha.lib.core.exceptions.XyzErrorException;
Expand All @@ -47,6 +48,7 @@
public class EventHandlerApiTask<T extends XyzResponse> extends AbstractApiTask<XyzResponse> {

private static final Logger logger = LoggerFactory.getLogger(EventHandlerApiTask.class);
private static final FeaturePostProcessor<EventHandler> HANDLER_MASKING = new MaskingPostProcessor<>();

private final @NotNull EventHandlerApiReqType reqType;

Expand Down Expand Up @@ -107,8 +109,7 @@ protected void init() {}
// Submit request to NH Space Storage
try (Result rdResult = executeReadRequestFromSpaceStorage(request)) {
// transform ReadResult to Http FeatureCollection response
return transformReadResultToXyzCollectionResponse(
rdResult, EventHandler.class, this::handlerWithMaskedSensitiveProperties);
return transformReadResultToXyzCollectionResponse(rdResult, EventHandler.class, HANDLER_MASKING);
}
}

Expand Down Expand Up @@ -136,33 +137,25 @@ protected void init() {}
final String handlerId = ApiParams.extractMandatoryPathParam(routingContext, HANDLER_ID);
final WriteXyzFeatures wrRequest = RequestHelper.deleteFeatureByIdRequest(EVENT_HANDLERS, handlerId);
try (Result wrResult = executeWriteRequestFromSpaceStorage(wrRequest)) {
return transformDeleteResultToXyzFeatureResponse(
wrResult, EventHandler.class, this::handlerWithMaskedSensitiveProperties);
return transformDeleteResultToXyzFeatureResponse(wrResult, EventHandler.class, HANDLER_MASKING);
}
}

@NotNull
private XyzResponse transformResponseFor(ReadFeatures rdRequest) {
try (Result rdResult = executeReadRequestFromSpaceStorage(rdRequest)) {
return transformReadResultToXyzFeatureResponse(
rdResult, EventHandler.class, this::handlerWithMaskedSensitiveProperties);
return transformReadResultToXyzFeatureResponse(rdResult, EventHandler.class, HANDLER_MASKING);
}
}

@NotNull
private XyzResponse transformResponseFor(WriteXyzFeatures updateHandlerReq) {
// persist new handler in Admin DB (if doesn't exist already)
try (Result updateHandlerResult = executeWriteRequestFromSpaceStorage(updateHandlerReq)) {
return transformWriteResultToXyzFeatureResponse(
updateHandlerResult, EventHandler.class, this::handlerWithMaskedSensitiveProperties);
return transformWriteResultToXyzFeatureResponse(updateHandlerResult, EventHandler.class, HANDLER_MASKING);
}
}

private EventHandler handlerWithMaskedSensitiveProperties(EventHandler handler) {
maskProperties(handler);
return handler;
}

private @NotNull EventHandler handlerFromRequestBody() throws JsonProcessingException {
try (final Json json = Json.get()) {
final String bodyJson = routingContext.body().asString();
Expand Down
Loading
Loading