Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.jackson.DatabindCodec;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.openapi.router.RouterBuilder;
import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -170,6 +172,36 @@ private void validateRequestBody(Subscription subscription) throws HttpException

if (subscription.getConfig() == null || subscription.getConfig().getType() == null)
throw new HttpException(BAD_REQUEST, "Validation failed. The property config 'type' cannot be empty.");

validateFilter(subscription.getFilter());
}

private void validateFilter(Subscription.SubscriptionFilter filter) throws HttpException {
if (filter == null)
return;

if (filter.getJsonPaths() != null) {
for (String jsonPath : filter.getJsonPaths()) {
if (jsonPath == null || jsonPath.isEmpty())
throw new HttpException(BAD_REQUEST, "Validation failed. JSON path cannot be null or empty.");
try {
JsonPath.compile(jsonPath);
} catch (InvalidPathException e) {
throw new HttpException(BAD_REQUEST, "Validation failed. Invalid JSON path '" + jsonPath + "': " + e.getMessage());
}
}
}

if (filter.getGeometry() != null) {
try {
filter.getGeometry().validate();
} catch (Exception e) {
throw new HttpException(BAD_REQUEST, "Validation failed. Invalid geometry in filter: " + e.getMessage());
}
}

if (filter.getRadius() < 0)
throw new HttpException(BAD_REQUEST, "Validation failed. The filter 'radius' cannot be negative.");
}

private Future<Void> validateSubscriptionDestination(Subscription subscription) {
Expand Down
26 changes: 26 additions & 0 deletions xyz-hub-service/src/main/resources/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4046,6 +4046,32 @@ components:
description: "The reason for the current state."
type: string
example: ""
filter:
$ref: '#/components/schemas/SubscriptionFilter'
SubscriptionFilter:
type: object
description: Filter criteria for subscription notifications.
properties:
jsonPaths:
description: >-
List of JSON path expressions to filter features.
type: array
items:
type: string
example: ["$.properties.name", "$.properties.type"]
geometry:
$ref: '#/components/schemas/Geometry'
radius:
description: >-
Radius in meters which defines the diameter of the search request.
type: integer
default: 0
clip:
description: >-
If set to true the features' geometries are clipped to the geometry of
the tile, bounding box or input geometry. Default is false.
type: boolean
default: false
SubscriptionConfig:
type: object
description: The subscription configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,28 @@ public void createSubscriptionWithoutId() {
.statusCode(BAD_REQUEST.code());
}

@Test
public void createSubscriptionWithFilter() {
addSubscription(AuthProfile.ACCESS_SPACE_1_MANAGE_SPACES, "/xyz/hub/createSubscriptionWithFilter.json")
.statusCode(CREATED.code())
.body("id", equalTo("test-subscription-1"))
.body("filter.jsonPaths[0]", equalTo("$.properties.name"))
.body("filter.radius", equalTo(100))
.body("filter.clip", equalTo(true));
}

@Test
public void createSubscriptionWithInvalidJsonPath() {
addSubscription(AuthProfile.ACCESS_SPACE_1_MANAGE_SPACES, "/xyz/hub/createSubscriptionWithInvalidJsonPath.json")
.statusCode(BAD_REQUEST.code());
}

@Test
public void createSubscriptionWithInvalidGeometry() {
addSubscription(AuthProfile.ACCESS_SPACE_1_MANAGE_SPACES, "/xyz/hub/createSubscriptionWithInvalidGeometry.json")
.statusCode(BAD_REQUEST.code());
}

@Test
public void createSubscriptionWithSameId() {
addSubscription(AuthProfile.ACCESS_SPACE_1_MANAGE_SPACES, "/xyz/hub/createSubscription.json")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"id": "test-subscription-1",
"destination": "some-destination",
"config": {
"type": "PER_FEATURE",
"params": {
"destinationType": "stream"
}
},
"filter": {
"jsonPaths": ["$.properties.name", "$[?(@.properties.type == 'building')]"],
"geometry": {
"type": "Polygon",
"coordinates": [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]
},
"radius": 100,
"clip": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"id": "test-subscription-1",
"destination": "some-destination",
"config": {
"type": "PER_FEATURE"
},
"filter": {
"geometry": {
"type": "Polygon",
"coordinates": [[[0, 0], [0, 1], [1, 1]]]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "test-subscription-1",
"destination": "some-destination",
"config": {
"type": "PER_FEATURE"
},
"filter": {
"jsonPaths": ["$[invalid json path syntax"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
package com.here.xyz.models.hub;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.here.xyz.models.geojson.implementation.Geometry;

import java.util.List;
import java.util.Map;

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down Expand Up @@ -48,6 +50,8 @@ public class Subscription {

private SubscriptionStatus status;

private SubscriptionFilter filter;

public String getId() {
return id;
}
Expand Down Expand Up @@ -113,6 +117,83 @@ public Subscription withStatus(SubscriptionStatus status) {
return this;
}

public SubscriptionFilter getFilter() {
return filter;
}

public void setFilter(SubscriptionFilter filter) {
this.filter = filter;
}

public Subscription withFilter(SubscriptionFilter filter) {
this.filter = filter;
return this;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class SubscriptionFilter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we re-use the SpatialFilter here, and have a common "filter" object across all the places ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally yes, that's what I tried to do initially. But the SpatialFilter object is part of xyz-util and if we try to import it we will get ourselves into a circular dependency situation. I tried moving it in this module too and it's a mess. So the best option is to simply have these values as separate fields


private List<String> jsonPaths;

private Geometry geometry;

private int radius;

private boolean clip;

public List<String> getJsonPaths() {
return jsonPaths;
}

public void setJsonPaths(List<String> jsonPaths) {
this.jsonPaths = jsonPaths;
}

public SubscriptionFilter withJsonPaths(List<String> jsonPaths) {
this.jsonPaths = jsonPaths;
return this;
}

public Geometry getGeometry() {
return geometry;
}

public void setGeometry(Geometry geometry) {
this.geometry = geometry;
}

public SubscriptionFilter withGeometry(Geometry geometry) {
this.geometry = geometry;
return this;
}

public int getRadius() {
return radius;
}

public void setRadius(int radius) {
this.radius = radius;
}

public SubscriptionFilter withRadius(int radius) {
this.radius = radius;
return this;
}

public boolean isClip() {
return clip;
}

public void setClip(boolean clip) {
this.clip = clip;
}

public SubscriptionFilter withClip(boolean clip) {
this.clip = clip;
return this;
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class SubscriptionConfig {

Expand Down