Skip to content

Commit

Permalink
Introduce new space-context "super" pointing to the extended space of…
Browse files Browse the repository at this point in the history
… a composite space

- Add a new value "super" for query param "context" to read from the space being extended through a composite space
- Writing / Deleting through that new context is not permitted through a composite space
- Add tests for new super context

Signed-off-by: Benjamin Rögner <[email protected]>
  • Loading branch information
roegi committed Nov 10, 2022
1 parent 6c8c978 commit 76d6c19
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package com.here.xyz.hub.rest;

import static com.here.xyz.events.ContextAwareEvent.SpaceContext.DEFAULT;
import static com.here.xyz.events.ContextAwareEvent.SpaceContext.SUPER;
import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_GEO_JSON;
import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_JSON;
import static com.here.xyz.hub.rest.ApiParam.Query.FORCE_2D;
Expand Down Expand Up @@ -87,7 +88,7 @@ private ApiResponseType getEmptyResponseTypeOr(final RoutingContext context, Api
private void getFeature(final RoutingContext context) {
final boolean skipCache = Query.getBoolean(context, SKIP_CACHE, false);
final boolean force2D = Query.getBoolean(context, FORCE_2D, false);
final SpaceContext spaceContext = SpaceContext.of(Query.getString(context, Query.CONTEXT, DEFAULT.toString()).toUpperCase());
final SpaceContext spaceContext = getSpaceContext(context);

final GetFeaturesByIdEvent event = new GetFeaturesByIdEvent()
.withIds(Collections.singletonList(context.pathParam(Path.FEATURE_ID)))
Expand All @@ -105,7 +106,7 @@ private void getFeature(final RoutingContext context) {
private void getFeatures(final RoutingContext context) {
final boolean skipCache = Query.getBoolean(context, SKIP_CACHE, false);
final boolean force2D = Query.getBoolean(context, FORCE_2D, false);
final SpaceContext spaceContext = SpaceContext.of(Query.getString(context, Query.CONTEXT, DEFAULT.toString()).toUpperCase());
final SpaceContext spaceContext = getSpaceContext(context);

final GetFeaturesByIdEvent event = new GetFeaturesByIdEvent()
.withIds(Query.queryParam(Query.FEATURE_ID, context))
Expand Down Expand Up @@ -159,7 +160,7 @@ private void postFeatures(final RoutingContext context) {
private void deleteFeature(final RoutingContext context) {
Map<String, Object> featureModification = Collections.singletonMap("featureIds",
Collections.singletonList(context.pathParam(Path.FEATURE_ID)));
final SpaceContext spaceContext = SpaceContext.of(Query.getString(context, Query.CONTEXT, DEFAULT.toString()).toUpperCase());
final SpaceContext spaceContext = getSpaceContext(context);
executeConditionalOperationChain(true, context, ApiResponseType.EMPTY, IfExists.DELETE, IfNotExists.RETAIN, true, ConflictResolution.ERROR,
Collections.singletonList(featureModification), spaceContext);
}
Expand All @@ -173,7 +174,7 @@ private void deleteFeatures(final RoutingContext context) {
final String accept = context.request().getHeader(ACCEPT);
final ApiResponseType responseType = APPLICATION_GEO_JSON.equals(accept) || APPLICATION_JSON.equals(accept)
? ApiResponseType.FEATURE_COLLECTION : ApiResponseType.EMPTY;
final SpaceContext spaceContext = SpaceContext.of(Query.getString(context, Query.CONTEXT, DEFAULT.toString()).toUpperCase());
final SpaceContext spaceContext = getSpaceContext(context);

//Delete features by IDs
if (featureIds != null && !featureIds.isEmpty()) {
Expand All @@ -185,6 +186,8 @@ private void deleteFeatures(final RoutingContext context) {

//Delete features by tags
else if (!tags.isEmpty()) {
if (checkModificationOnSuper(context, spaceContext))
return;
DeleteFeaturesByTagEvent event = new DeleteFeaturesByTagEvent();
if (!tags.containsWildcard()) {
event.setTags(tags);
Expand All @@ -202,12 +205,17 @@ else if (!tags.isEmpty()) {
*/
private void executeConditionalOperationChain(boolean requireResourceExists, final RoutingContext context,
ApiResponseType apiResponseTypeType, IfExists ifExists, IfNotExists ifNotExists, boolean transactional, ConflictResolution cr) {
if (checkModificationOnSuper(context, getSpaceContext(context)))
return;
executeConditionalOperationChain(requireResourceExists, context, apiResponseTypeType, ifExists, ifNotExists, transactional, cr, null, DEFAULT);
}

private void executeConditionalOperationChain(boolean requireResourceExists, final RoutingContext context,
ApiResponseType apiResponseTypeType, IfExists ifExists, IfNotExists ifNotExists, boolean transactional, ConflictResolution cr,
List<Map<String, Object>> featureModifications, SpaceContext spaceContext) {
if (checkModificationOnSuper(context, spaceContext))
return;

ModifyFeaturesEvent event = new ModifyFeaturesEvent().withTransaction(transactional).withContext(spaceContext);
int bodySize = context.getBody() != null ? context.getBody().length() : 0;
ConditionalOperation task = buildConditionalOperation(event, context, apiResponseTypeType, featureModifications, ifNotExists, ifExists, transactional, cr, requireResourceExists, bodySize);
Expand All @@ -221,6 +229,15 @@ private void executeConditionalOperationChain(boolean requireResourceExists, fin
task.execute(this::sendResponse, this::sendErrorResponse);
}

private static boolean checkModificationOnSuper(RoutingContext context, SpaceContext spaceContext) {
if (spaceContext != null && spaceContext.equals(SUPER)) {
context.fail(
new HttpException(HttpResponseStatus.FORBIDDEN, "It's not permitted to perform modifications through context " + SUPER + "."));
return true;
}
return false;
}

private ConditionalOperation buildConditionalOperation(
ModifyFeaturesEvent event,
RoutingContext context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import com.here.xyz.events.IterateFeaturesEvent;
import com.here.xyz.events.PropertiesQuery;
import com.here.xyz.events.SearchForFeaturesEvent;
import com.here.xyz.events.SelectiveEvent;
import com.here.xyz.hub.Service;
import com.here.xyz.hub.rest.ApiParam.Path;
import com.here.xyz.hub.rest.ApiParam.Query;
Expand Down Expand Up @@ -164,10 +163,6 @@ private void iterateFeatures(final RoutingContext context) {
}
}

private static SpaceContext getSpaceContext(RoutingContext context) {
return SpaceContext.of(Query.getString(context, Query.CONTEXT, SpaceContext.DEFAULT.toString()).toUpperCase());
}

/**
* Retrieves the features by intersecting with the provided geometry.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;

import com.here.xyz.events.ContextAwareEvent.SpaceContext;
import com.here.xyz.hub.rest.ApiParam.Query;
import com.here.xyz.hub.task.FeatureTaskHandler.InvalidStorageException;
import com.here.xyz.hub.task.Task;
import com.here.xyz.responses.ErrorResponse;
Expand All @@ -16,6 +18,10 @@ public abstract class SpaceBasedApi extends Api {
protected final static int MIN_LIMIT = 1;
protected final static int HARD_LIMIT = 100_000;

protected static SpaceContext getSpaceContext(RoutingContext context) {
return SpaceContext.of(Query.getString(context, Query.CONTEXT, SpaceContext.DEFAULT.toString()).toUpperCase());
}

/**
* Send an error response to the client when an exception occurred while processing a task.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package com.here.xyz.hub.task;

import static com.here.xyz.events.ContextAwareEvent.SpaceContext.DEFAULT;
import static com.here.xyz.events.ContextAwareEvent.SpaceContext.SUPER;
import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_VND_HERE_FEATURE_MODIFICATION_LIST;
import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_VND_MAPBOX_VECTOR_TILE;
import static com.here.xyz.hub.rest.ApiResponseType.MVT;
Expand All @@ -41,6 +43,7 @@
import com.here.xyz.Payload;
import com.here.xyz.XyzSerializable;
import com.here.xyz.events.ContentModifiedNotification;
import com.here.xyz.events.ContextAwareEvent;
import com.here.xyz.events.Event;
import com.here.xyz.events.Event.TrustedParams;
import com.here.xyz.events.EventNotification;
Expand Down Expand Up @@ -773,8 +776,10 @@ private static <X extends FeatureTask> Future<Space> resolveSpace(final X task)
return Space.resolveSpace(task.getMarker(), task.getEvent().getSpace())
.compose(
space -> {
task.space = space;
if (space != null) {
if (space.getExtension() != null && task.getEvent() instanceof ContextAwareEvent && SUPER.equals(((ContextAwareEvent<?>) task.getEvent()).getContext()))
return switchToSuperSpace(task, space);
task.space = space;
//Inject the extension-map
return space.resolveCompositeParams(task.getMarker()).compose(resolvedExtensions -> {
Map<String, Object> storageParams = new HashMap<>();
Expand All @@ -800,6 +805,15 @@ private static <X extends FeatureTask> Future<Space> resolveSpace(final X task)
}
}

private static <X extends FeatureTask> Future<Space> switchToSuperSpace(X task, Space space) {
//Overwrite the event's space ID to be the ID of the extended (super) space ...
task.getEvent().setSpace(space.getExtension().getSpaceId());
//also overwrite the space context to be DEFAULT now ...
((ContextAwareEvent<?>) task.getEvent()).setContext(DEFAULT);
//... and resolve the extended (super) space instead
return resolveSpace(task);
}

private static <X extends FeatureTask> Future<Space> resolveExtendedSpaces(X task, Space extendingSpace) {
if (extendingSpace == null)
return Future.succeededFuture();
Expand Down
13 changes: 9 additions & 4 deletions xyz-hub-service/src/main/resources/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1193,18 +1193,23 @@ components:
name: context
in: query
description: |
The context where the operation will be performed when the space extends
another space. If not specified, the operation occurs based on the extension
rules. For additional information, see Space.extends.
The context where the operation will be performed on a composite space.
If not specified, the operation occurs based on the extension rules.
For additional information, see Space.extends.
Available context are:
|Context|Description|
|-------|-----------|
|extension|The operation will be executed only in the space which extends another and no operation will be performed in the extended space.
|default|The default value if none is given. For composite spaces the operation occurs based on the extension rules. For normal spaces this is the only valid context.|
|extension|The operation will be executed only in the extension and no operation will be performed in the extended space.|
|super|Only applicable for read-operations. The operation will be executed only in the space being extended (super space).|
schema:
type: string
enum:
- default
- extension
- super
default: default

East:
name: east
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
package com.here.xyz.hub.rest;

import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_GEO_JSON;
import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_JSON;
import static com.jayway.restassured.RestAssured.given;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.NO_CONTENT;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
Expand Down Expand Up @@ -102,26 +102,80 @@ public void updateOnDelta() {

@Test
public void getOnlyOnDelta() {
Feature f1 = newFeature();
Feature f2 = newFeature();
// FIXME in order to get the extending space to be created, a read or write operation must be executed, otherwise a 504 is returned
postFeature("x-psql-test", f1);
postFeature("x-psql-test-ext", f2);
Feature feature = newFeature();
postFeature("x-psql-test-ext", feature);

given()
.headers(getAuthHeaders(AuthProfile.ACCESS_OWNER_1_ADMIN))
.when()
.get("/spaces/x-psql-test/features/" + f2.getId())
.get("/spaces/x-psql-test/features/" + feature.getId())
.then()
.statusCode(NOT_FOUND.code());

given()
.headers(getAuthHeaders(AuthProfile.ACCESS_OWNER_1_ADMIN))
.when()
.get("/spaces/x-psql-test-ext/features/" + f2.getId())
.get("/spaces/x-psql-test-ext/features/" + feature.getId())
.then()
.statusCode(OK.code())
.body("id", equalTo(f2.getId()));
.body("id", equalTo(feature.getId()));
}

@Test
public void getOnlyFromSuper() {
Feature feature = newFeature();
postFeature("x-psql-test", feature.withProperties(new Properties().with("name", "abc")));
postFeature("x-psql-test-ext", feature.withProperties(new Properties().with("name", "xyz")));

given()
.headers(getAuthHeaders(AuthProfile.ACCESS_OWNER_1_ADMIN))
.when()
.get("/spaces/x-psql-test-ext/features/" + feature.getId() + "?context=super")
.then()
.statusCode(OK.code())
.body("id", equalTo(feature.getId()))
.body("properties.name", equalTo("abc"));
}

@Test
public void createSuperNegative() {
Feature feature = newFeature();

given()
.headers(getAuthHeaders(AuthProfile.ACCESS_OWNER_1_ADMIN))
.body(feature.serialize())
.when()
.post("/spaces/x-psql-test-ext/features?context=super")
.then()
.statusCode(FORBIDDEN.code());
}

@Test
public void updateSuperNegative() {
Feature feature = newFeature();
postFeature("x-psql-test", feature);

given()
.headers(getAuthHeaders(AuthProfile.ACCESS_OWNER_1_ADMIN))
.contentType("application/geo+json")
.body(feature.withProperties(new Properties().with("name", "abc")).serialize())
.when()
.patch("/spaces/x-psql-test-ext/features/" + feature.getId() + "?context=super")
.then()
.statusCode(FORBIDDEN.code());
}

@Test
public void deleteSuperNegative() {
Feature feature = newFeature();
postFeature("x-psql-test", feature);

given()
.headers(getAuthHeaders(AuthProfile.ACCESS_OWNER_1_ADMIN))
.when()
.delete("/spaces/x-psql-test-ext/features/" + feature.getId() + "?context=super")
.then()
.statusCode(FORBIDDEN.code());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@

import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_JSON;
import static com.jayway.restassured.RestAssured.given;
import static io.netty.handler.codec.http.HttpResponseStatus.NO_CONTENT;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.hamcrest.Matchers.equalTo;

import com.jayway.restassured.response.ValidatableResponse;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;

public class TestCompositeSpace extends TestSpaceWithFeature {

Expand All @@ -41,7 +37,7 @@ public void setup() {
createSpaceWithExtension("x-psql-test");
createSpaceWithExtension("x-psql-test-ext");

// FIXME avoid 504
//FIXME: in order to get the extending space to be created, a read or write operation must be executed, otherwise a 504 is returned
getFeature("x-psql-test", "F1");
getFeature("x-psql-test-2", "F1");
getFeature("x-psql-test-ext", "F1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public abstract class ContextAwareEvent<T extends Event> extends Event<T> {

public enum SpaceContext {
EXTENSION,
SUPER,
DEFAULT;

public static SpaceContext of(String value) {
Expand Down

0 comments on commit 76d6c19

Please sign in to comment.