2323import static com .here .naksha .common .http .apis .ApiParamsConst .DEF_ADMIN_FEATURE_LIMIT ;
2424import static com .here .naksha .lib .core .util .storage .ResultHelper .readFeatureFromResult ;
2525import static com .here .naksha .lib .core .util .storage .ResultHelper .readFeaturesFromResult ;
26- import static com .here .naksha .lib .core .util .storage .ResultHelper .readFeaturesGroupedByOp ;
2726import static java .util .Collections .emptyList ;
2827
2928import com .fasterxml .jackson .core .JsonProcessingException ;
3029import com .here .naksha .app .service .http .HttpResponseType ;
3130import com .here .naksha .app .service .http .NakshaHttpVerticle ;
31+ import com .here .naksha .app .service .http .tasks .processor .FeaturePostProcessor ;
3232import com .here .naksha .app .service .models .IterateHandle ;
3333import com .here .naksha .lib .core .AbstractTask ;
3434import com .here .naksha .lib .core .INaksha ;
3535import com .here .naksha .lib .core .NakshaContext ;
3636import com .here .naksha .lib .core .exceptions .NoCursor ;
37- import com .here .naksha .lib .core .lambdas .F1 ;
3837import com .here .naksha .lib .core .models .XyzError ;
3938import com .here .naksha .lib .core .models .geojson .implementation .XyzFeature ;
4039import com .here .naksha .lib .core .models .geojson .implementation .XyzFeatureCollection ;
41- import com .here .naksha .lib .core .models .geojson .implementation .XyzGeometry ;
4240import com .here .naksha .lib .core .models .payload .XyzResponse ;
43- import com .here .naksha .lib .core .models .storage .*;
41+ import com .here .naksha .lib .core .models .storage .ContextXyzFeatureResult ;
42+ import com .here .naksha .lib .core .models .storage .EExecutedOp ;
43+ import com .here .naksha .lib .core .models .storage .ErrorResult ;
44+ import com .here .naksha .lib .core .models .storage .ForwardCursor ;
45+ import com .here .naksha .lib .core .models .storage .ReadFeatures ;
46+ import com .here .naksha .lib .core .models .storage .Result ;
47+ import com .here .naksha .lib .core .models .storage .WriteFeatures ;
48+ import com .here .naksha .lib .core .models .storage .XyzFeatureCodec ;
4449import com .here .naksha .lib .core .storage .IReadSession ;
4550import com .here .naksha .lib .core .storage .IWriteSession ;
46- import com .here .naksha .lib .core .util .PropertyPathUtil ;
4751import com .here .naksha .lib .core .util .json .Json ;
48- import com .here .naksha .lib .core .util .json .JsonSerializable ;
4952import com .here .naksha .lib .core .view .ViewDeserialize ;
5053import io .vertx .ext .web .RoutingContext ;
51- import java .util .*;
54+ import java .util .ArrayList ;
55+ import java .util .HashMap ;
56+ import java .util .List ;
57+ import java .util .Map ;
58+ import java .util .NoSuchElementException ;
5259import org .jetbrains .annotations .NotNull ;
5360import org .jetbrains .annotations .Nullable ;
54- import org .locationtech .jts .geom .Geometry ;
55- import org .locationtech .jts .geom .util .GeometryFixer ;
5661import org .slf4j .Logger ;
5762import org .slf4j .LoggerFactory ;
5863
@@ -98,8 +103,10 @@ protected AbstractApiTask(
98103 }
99104
100105 protected <R extends XyzFeature > @ NotNull XyzResponse transformReadResultToXyzFeatureResponse (
101- final @ NotNull Result rdResult , final @ NotNull Class <R > type , @ Nullable F1 <R , R > preResponseProcessing ) {
102- return transformResultToXyzFeatureResponse (rdResult , type , NOT_FOUND_ON_NO_ELEMENTS , preResponseProcessing );
106+ final @ NotNull Result rdResult ,
107+ final @ NotNull Class <R > type ,
108+ @ Nullable FeaturePostProcessor <R > postProcessor ) {
109+ return transformResultToXyzFeatureResponse (rdResult , type , NOT_FOUND_ON_NO_ELEMENTS , postProcessor );
103110 }
104111
105112 protected <R extends XyzFeature > @ NotNull XyzResponse transformWriteResultToXyzFeatureResponse (
@@ -108,8 +115,10 @@ protected AbstractApiTask(
108115 }
109116
110117 protected <R extends XyzFeature > @ NotNull XyzResponse transformWriteResultToXyzFeatureResponse (
111- final @ Nullable Result wrResult , final @ NotNull Class <R > type , @ Nullable F1 <R , R > preResponseProcessing ) {
112- return transformResultToXyzFeatureResponse (wrResult , type , FAIL_ON_NO_ELEMENTS , preResponseProcessing );
118+ final @ Nullable Result wrResult ,
119+ final @ NotNull Class <R > type ,
120+ @ Nullable FeaturePostProcessor <R > postProcessor ) {
121+ return transformResultToXyzFeatureResponse (wrResult , type , FAIL_ON_NO_ELEMENTS , postProcessor );
113122 }
114123
115124 protected <R extends XyzFeature > @ NotNull XyzResponse transformDeleteResultToXyzFeatureResponse (
@@ -118,8 +127,10 @@ protected AbstractApiTask(
118127 }
119128
120129 protected <R extends XyzFeature > @ NotNull XyzResponse transformDeleteResultToXyzFeatureResponse (
121- final @ Nullable Result wrResult , final @ NotNull Class <R > type , @ Nullable F1 <R , R > postProcessing ) {
122- return transformResultToXyzFeatureResponse (wrResult , type , NOT_FOUND_ON_NO_ELEMENTS , postProcessing );
130+ final @ Nullable Result wrResult ,
131+ final @ NotNull Class <R > type ,
132+ @ Nullable FeaturePostProcessor <R > postProcessor ) {
133+ return transformResultToXyzFeatureResponse (wrResult , type , NOT_FOUND_ON_NO_ELEMENTS , postProcessor );
123134 }
124135
125136 protected XyzResponse handleNoElements (NoElementsStrategy noElementsStrategy ) {
@@ -130,16 +141,16 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
130141 final @ Nullable Result result ,
131142 final @ NotNull Class <R > type ,
132143 final @ NotNull NoElementsStrategy noElementsStrategy ,
133- final @ Nullable F1 < R , R > preResponseProcessing ) {
144+ final @ Nullable FeaturePostProcessor < R > postProcessor ) {
134145 final XyzResponse validatedErrorResponse = validateErrorResult (result );
135146 if (validatedErrorResponse != null ) {
136147 return validatedErrorResponse ;
137148 } else {
138149 try {
139150 final R feature = readFeatureFromResult (result , type );
140151 R processedFeature = feature ;
141- if (feature != null && preResponseProcessing != null ) {
142- processedFeature = preResponseProcessing . call (feature );
152+ if (feature != null && postProcessor != null ) {
153+ processedFeature = postProcessor . postProcess (feature );
143154 }
144155 if (processedFeature == null ) {
145156 return verticle .sendErrorResponse (
@@ -161,9 +172,9 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
161172 protected <R extends XyzFeature > @ NotNull XyzResponse transformReadResultToXyzCollectionResponse (
162173 final @ Nullable Result rdResult ,
163174 final @ NotNull Class <R > type ,
164- final @ Nullable F1 < R , R > preResponseProcessing ) {
175+ final @ Nullable FeaturePostProcessor < R > featurePostProcessor ) {
165176 return transformReadResultToXyzCollectionResponse (
166- rdResult , type , 0 , DEF_ADMIN_FEATURE_LIMIT , null , preResponseProcessing );
177+ rdResult , type , 0 , DEF_ADMIN_FEATURE_LIMIT , null , featurePostProcessor );
167178 }
168179
169180 protected <R extends XyzFeature > @ NotNull XyzResponse transformReadResultToXyzCollectionResponse (
@@ -182,18 +193,18 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
182193 final long offset ,
183194 final long maxLimit ,
184195 final @ Nullable IterateHandle handle ,
185- final @ Nullable F1 < R , R > preResponseProcessing ) {
196+ final @ Nullable FeaturePostProcessor < R > featurePostProcessor ) {
186197 final XyzResponse validatedErrorResponse = validateErrorResultEmptyCollection (rdResult );
187198 if (validatedErrorResponse != null ) {
188199 return validatedErrorResponse ;
189200 } else {
190201 try {
191202 final List <R > features = readFeaturesFromResult (rdResult , type , offset , maxLimit );
192203 List <R > processedFeatures = features ;
193- if (preResponseProcessing != null ) {
204+ if (featurePostProcessor != null ) {
194205 processedFeatures = new ArrayList <>();
195206 for (R feature : features ) {
196- final R processedFeature = preResponseProcessing . call (feature );
207+ final R processedFeature = featurePostProcessor . postProcess (feature );
197208 if (processedFeature != null ) {
198209 processedFeatures .add (processedFeature );
199210 }
@@ -218,20 +229,26 @@ protected XyzResponse handleNoElements(NoElementsStrategy noElementsStrategy) {
218229 private static String getIterateHandleAsString (
219230 long featuresFound , long crtOffset , long maxLimit , final @ Nullable IterateHandle handle ) {
220231 // nothing to populate if handle is not provided OR if we don't have more features to iterate
221- if (handle == null || featuresFound < maxLimit ) return null ;
232+ if (handle == null || featuresFound < maxLimit ) {
233+ return null ;
234+ }
222235 handle .setOffset (crtOffset + featuresFound ); // set offset for next iteration
223236 handle .setLimit (maxLimit );
224237 return handle .base64EncodedSerializedJson ();
225238 }
226239
227240 protected <R extends XyzFeature > @ NotNull XyzResponse transformWriteResultToXyzCollectionResponse (
228- final @ Nullable Result wrResult , final @ NotNull Class <R > type , final boolean isDeleteOperation ) {
241+ final @ Nullable Result wrResult ,
242+ final @ NotNull Class <R > type ,
243+ final boolean isDeleteOperation ,
244+ final @ Nullable FeaturePostProcessor <R > featurePostProcessor ) {
229245 final XyzResponse validatedErrorResponse = validateErrorResult (wrResult );
230246 if (validatedErrorResponse != null ) {
231247 return validatedErrorResponse ;
232248 } else {
233249 try {
234- final Map <EExecutedOp , List <R >> featureMap = readFeaturesGroupedByOp (wrResult , type );
250+ final Map <EExecutedOp , List <R >> featureMap =
251+ postProcessedFeaturesByOp (wrResult , type , featurePostProcessor );
235252 final List <R > insertedFeatures = featureMap .get (EExecutedOp .CREATED );
236253 final List <R > updatedFeatures = featureMap .get (EExecutedOp .UPDATED );
237254 final List <R > deletedFeatures = featureMap .get (EExecutedOp .DELETED );
@@ -260,6 +277,45 @@ private static String getIterateHandleAsString(
260277 }
261278 }
262279
280+ /**
281+ * Helper method to fetch features from given Result and return a map of multiple lists grouped by {@link EExecutedOp} of features with
282+ * type T. Returned lists are limited with respect to supplied `limit` parameter.
283+ *
284+ * @param result the Result which is to be read
285+ * @param featureType the type of feature to be extracted from result
286+ * @param <R> type of feature
287+ * @return a map grouping the lists of features extracted from ReadResult
288+ */
289+ private static <R extends XyzFeature > Map <EExecutedOp , List <R >> postProcessedFeaturesByOp (
290+ Result result , Class <R > featureType , FeaturePostProcessor <R > postProcessor ) throws NoCursor {
291+ try (ForwardCursor <XyzFeature , XyzFeatureCodec > resultCursor = result .getXyzFeatureCursor ()) {
292+ final List <R > insertedFeatures = new ArrayList <>();
293+ final List <R > updatedFeatures = new ArrayList <>();
294+ final List <R > deletedFeatures = new ArrayList <>();
295+ while (resultCursor .hasNext ()) {
296+ if (!resultCursor .next ()) {
297+ throw new RuntimeException ("Unexpected invalid result" );
298+ }
299+ R feature = featureType .cast (resultCursor .getFeature ());
300+ if (postProcessor != null ) {
301+ feature = postProcessor .postProcess (feature );
302+ }
303+ if (resultCursor .getOp ().equals (EExecutedOp .CREATED )) {
304+ insertedFeatures .add (feature );
305+ } else if (resultCursor .getOp ().equals (EExecutedOp .UPDATED )) {
306+ updatedFeatures .add (feature );
307+ } else if (resultCursor .getOp ().equals (EExecutedOp .DELETED )) {
308+ deletedFeatures .add (feature );
309+ }
310+ }
311+ final Map <EExecutedOp , List <R >> features = new HashMap <>();
312+ features .put (EExecutedOp .CREATED , insertedFeatures );
313+ features .put (EExecutedOp .UPDATED , updatedFeatures );
314+ features .put (EExecutedOp .DELETED , deletedFeatures );
315+ return features ;
316+ }
317+ }
318+
263319 protected Result executeReadRequestFromSpaceStorage (ReadFeatures readRequest ) {
264320 try (final IReadSession reader = naksha ().getSpaceStorage ().newReadSession (context (), false )) {
265321 return reader .execute (readRequest );
@@ -309,38 +365,4 @@ XyzFeatureCollection emptyFeatureCollection() {
309365 return json .reader (ViewDeserialize .User .class ).forType (type ).readValue (bodyJson );
310366 }
311367 }
312-
313- protected <F extends XyzFeature > @ Nullable F1 <F , F > standardReadFeaturesPreResponseProcessing (
314- final @ Nullable Set <String > propPaths , final boolean clip , final Geometry clipGeo ) {
315- if (propPaths == null && !clip ) return null ;
316- return f -> {
317- F newF = f ;
318- // Apply prop selection if enabled
319- if (propPaths != null ) newF = applyPropertySelection (newF , propPaths );
320- // Apply geometry clipping if enabled
321- if (clip ) applyGeometryClipping (newF , clipGeo );
322- return newF ;
323- };
324- }
325-
326- @ SuppressWarnings ("unchecked" )
327- private <F extends XyzFeature > @ NotNull F applyPropertySelection (
328- final @ NotNull F f , final @ NotNull Set <String > propPaths ) {
329- final Map <String , Object > tgtMap = PropertyPathUtil .extractPropertyMapFromFeature (f , propPaths );
330- return (F ) JsonSerializable .fromMap (tgtMap , f .getClass ());
331- }
332-
333- private <F extends XyzFeature > void applyGeometryClipping (final @ NotNull F f , final Geometry clipGeo ) {
334- // clip Feature geometry (if present) to a given clipGeo geometry
335- final XyzGeometry xyzGeo = f .getGeometry ();
336- if (xyzGeo != null ) {
337- // NOTE - in JTS when we say:
338- // GeometryFixer.fix(geom).intersection(bbox)
339- // it is the best available way of clipping geometry, equivalent to PostGIS approach of:
340- // ST_Intersection(ST_MakeValid(geo, 'method=structure'), bbox)
341- final Geometry clippedGeo =
342- GeometryFixer .fix (xyzGeo .getJTSGeometry ()).intersection (clipGeo );
343- f .setGeometry (XyzGeometry .convertJTSGeometry (clippedGeo ));
344- }
345- }
346368}
0 commit comments