|
|
@@ -2,7 +2,9 @@ package cz.senslog.telemetry.server.ws;
|
|
|
|
|
|
import cz.senslog.telemetry.app.Application;
|
|
|
import cz.senslog.telemetry.database.SortType;
|
|
|
+import cz.senslog.telemetry.database.domain.Location;
|
|
|
import cz.senslog.telemetry.database.domain.UnitLocation;
|
|
|
+import cz.senslog.telemetry.database.domain.UnitTelemetry;
|
|
|
import cz.senslog.telemetry.database.repository.SensLogRepository;
|
|
|
import cz.senslog.telemetry.database.domain.Filter;
|
|
|
import cz.senslog.telemetry.utils.TernaryCondition;
|
|
|
@@ -14,13 +16,13 @@ import io.vertx.ext.web.RoutingContext;
|
|
|
|
|
|
import java.time.OffsetDateTime;
|
|
|
import java.time.ZoneId;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Optional;
|
|
|
+import java.util.*;
|
|
|
import java.util.function.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
-import static cz.senslog.telemetry.utils.FluentInvoke.of;
|
|
|
+import static cz.senslog.telemetry.server.ws.OpenAPIHandler.ContentType.GEOJSON;
|
|
|
+import static cz.senslog.telemetry.server.ws.OpenAPIHandler.ContentType.JSON;
|
|
|
+import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
|
|
import static java.lang.Boolean.parseBoolean;
|
|
|
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
|
|
import static java.util.Collections.emptyList;
|
|
|
@@ -30,18 +32,39 @@ import static java.util.stream.Collectors.toList;
|
|
|
|
|
|
public class OpenAPIHandler {
|
|
|
|
|
|
- private enum ResponseFormat {
|
|
|
- JSON, GEOJSON
|
|
|
+ protected enum ContentType {
|
|
|
+ JSON ("application/json"),
|
|
|
+ GEOJSON ("application/geo+json")
|
|
|
;
|
|
|
- public static ResponseFormat of(String format) {
|
|
|
+
|
|
|
+ private final String contentType;
|
|
|
+
|
|
|
+ ContentType(String contentType) {
|
|
|
+ this.contentType = contentType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static ContentType of(String format) {
|
|
|
return valueOf(format.toUpperCase());
|
|
|
}
|
|
|
+
|
|
|
+ public static ContentType ofType(String contentType) {
|
|
|
+ for (ContentType value : values()) {
|
|
|
+ if (value.contentType.equalsIgnoreCase(contentType)) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new IllegalArgumentException(String.format("No enum constant %s for the type '%s'.", ContentType.class.getName(), contentType));
|
|
|
+ }
|
|
|
+
|
|
|
+ public String contentType() {
|
|
|
+ return contentType;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private static final int DEFAULT_MAX_DATA_LIMIT = 500;
|
|
|
private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("UTC");
|
|
|
private static final boolean DEFAULT_NAVIGATION_LINKS = true;
|
|
|
- private static final ResponseFormat DEFAULT_RESPONSE_FORMAT = ResponseFormat.JSON;
|
|
|
+ private static final ContentType DEFAULT_RESPONSE_FORMAT = JSON;
|
|
|
|
|
|
private static final BiFunction<OffsetDateTime, ZoneId, String> DATE_TIME_FORMATTER = (dateTime, zoneId) ->
|
|
|
OffsetDateTime.ofInstant(dateTime.toInstant(), zoneId).format(ISO_OFFSET_DATE_TIME);
|
|
|
@@ -182,9 +205,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -214,9 +237,10 @@ public class OpenAPIHandler {
|
|
|
|
|
|
switch (format) {
|
|
|
case JSON -> repo.findObservationsByCampaignIdWithPaging(campaignId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
- "next@NavigationLink", createNextNavLink.apply(paging.size())
|
|
|
- ) : JsonObject.of()).mergeIn(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType())
|
|
|
+ .end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
+ "next@NavigationLink", createNextNavLink.apply(paging.size())) : JsonObject.of())
|
|
|
+ .mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
"size", paging.size(),
|
|
|
"offset", offset,
|
|
|
@@ -234,7 +258,7 @@ public class OpenAPIHandler {
|
|
|
)).collect(toList())))).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON -> repo.findObservationsByCampaignIdWithPaging(campaignId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "FeatureCollection",
|
|
|
"metadata", JsonObject.of(
|
|
|
"size", paging.size(),
|
|
|
@@ -301,9 +325,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -334,7 +358,8 @@ public class OpenAPIHandler {
|
|
|
switch (format) {
|
|
|
case JSON ->
|
|
|
repo.findObservationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType())
|
|
|
+ .end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
"next@NavigationLink", createNextNavLink.apply(paging.size())
|
|
|
) : JsonObject.of()).mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
@@ -354,7 +379,7 @@ public class OpenAPIHandler {
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON ->
|
|
|
repo.findObservationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "FeatureCollection",
|
|
|
"metadata", JsonObject.of(
|
|
|
"size", paging.size(),
|
|
|
@@ -419,9 +444,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -442,7 +467,7 @@ public class OpenAPIHandler {
|
|
|
|
|
|
switch (format) {
|
|
|
case JSON -> repo.findUnitsLocationsByCampaignId(campaignId, limitPerUnit, from, to, zone, sortType, filters)
|
|
|
- .onSuccess(locations -> rc.response().end(navLinks.mergeIn(JsonObject.of(
|
|
|
+ .onSuccess(locations -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType()).end(navLinks.mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
"size", locations.size(),
|
|
|
"data", new JsonArray(locations.stream().map(l -> JsonObject.of(
|
|
|
@@ -455,7 +480,8 @@ public class OpenAPIHandler {
|
|
|
)).collect(toList())))).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON -> repo.findUnitsLocationsByCampaignId(campaignId, limitPerUnit, from, to, zone, sortType, filters)
|
|
|
- .onSuccess(data -> of(data.stream().collect(groupingBy(UnitLocation::getUnitId))).then(unitLocation -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(data -> Optional.of(data.stream().collect(groupingBy(UnitLocation::getUnitId))).ifPresent(unitLocation -> rc.response()
|
|
|
+ .putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "FeatureCollection",
|
|
|
"metadata", JsonObject.of(
|
|
|
"limitPerUnit", limitPerUnit
|
|
|
@@ -516,9 +542,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -549,7 +575,8 @@ public class OpenAPIHandler {
|
|
|
|
|
|
switch (format) {
|
|
|
case JSON -> repo.findLocationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType())
|
|
|
+ .end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
"next@NavigationLink", createNextNavLink.apply(paging.size())
|
|
|
) : JsonObject.of()).mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
@@ -566,7 +593,7 @@ public class OpenAPIHandler {
|
|
|
)).collect(toList())))).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON -> repo.findLocationsByCampaignIdAndUnitIdWithPaging(campaignId, unitId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "Feature",
|
|
|
"metadata", JsonObject.of(
|
|
|
"size", paging.size(),
|
|
|
@@ -912,9 +939,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -944,7 +971,8 @@ public class OpenAPIHandler {
|
|
|
|
|
|
switch (format) {
|
|
|
case JSON -> repo.findObservationsByCampaignIdAndUnitIdAndSensorIdWithPaging(campaignId, unitId, sensorId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType())
|
|
|
+ .end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
"next@NavigationLink", createNextNavLink.apply(paging.size())
|
|
|
) : JsonObject.of()).mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
@@ -963,7 +991,7 @@ public class OpenAPIHandler {
|
|
|
)).collect(toList())))).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON -> repo.findObservationsByCampaignIdAndUnitIdAndSensorIdWithPaging(campaignId, unitId, sensorId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "FeatureCollection",
|
|
|
"metadata", JsonObject.of(
|
|
|
"size", paging.size(),
|
|
|
@@ -1307,9 +1335,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -1339,7 +1367,8 @@ public class OpenAPIHandler {
|
|
|
|
|
|
switch (format) {
|
|
|
case JSON -> repo.findObservationsByEventIdWithPaging(eventId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType())
|
|
|
+ .end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
"next@NavigationLink", createNextNavLink.apply(paging.size())
|
|
|
) : JsonObject.of()).mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
@@ -1358,7 +1387,7 @@ public class OpenAPIHandler {
|
|
|
)).collect(toList())))).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON -> repo.findObservationsByEventIdWithPaging(eventId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "FeatureCollection",
|
|
|
"metadata", JsonObject.of(
|
|
|
"size", paging.size(),
|
|
|
@@ -1424,9 +1453,9 @@ public class OpenAPIHandler {
|
|
|
});
|
|
|
|
|
|
List<String> paramFormat = rc.queryParam("format");
|
|
|
- ResponseFormat format = TernaryCondition.<ResponseFormat>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
+ ContentType format = TernaryCondition.<ContentType>ternaryIf(paramFormat::isEmpty, DEFAULT_RESPONSE_FORMAT, () -> {
|
|
|
paramsJson.put("format", paramFormat.get(0));
|
|
|
- return ResponseFormat.of(paramFormat.get(0));
|
|
|
+ return ContentType.of(paramFormat.get(0));
|
|
|
});
|
|
|
|
|
|
List<String> paramFilters = rc.queryParam("filter");
|
|
|
@@ -1456,7 +1485,8 @@ public class OpenAPIHandler {
|
|
|
|
|
|
switch (format) {
|
|
|
case JSON -> repo.findLocationsByEventIdWithPaging(eventId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, JSON.contentType())
|
|
|
+ .end(navLinks.mergeIn(navigationLinks && paging.hasNext() ? JsonObject.of(
|
|
|
"next@NavigationLink", createNextNavLink.apply(paging.size())
|
|
|
) : JsonObject.of()).mergeIn(JsonObject.of(
|
|
|
"params", paramsJson,
|
|
|
@@ -1473,7 +1503,7 @@ public class OpenAPIHandler {
|
|
|
)).collect(toList())))).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
case GEOJSON -> repo.findLocationsByEventIdWithPaging(eventId, from, to, zone, offset, limit, filters)
|
|
|
- .onSuccess(paging -> rc.response().end(JsonObject.of(
|
|
|
+ .onSuccess(paging -> rc.response().putHeader(CONTENT_TYPE, GEOJSON.contentType()).end(JsonObject.of(
|
|
|
"type", "Feature",
|
|
|
"metadata", JsonObject.of(
|
|
|
"size", paging.size(),
|
|
|
@@ -1512,4 +1542,51 @@ public class OpenAPIHandler {
|
|
|
))).collect(toList())).encode()))
|
|
|
.onFailure(rc::fail);
|
|
|
}
|
|
|
+
|
|
|
+ public void campaignIdUnitsObservationsPOST(RoutingContext rc) {
|
|
|
+ switch (ContentType.ofType(rc.request().getHeader(CONTENT_TYPE))) {
|
|
|
+ case JSON -> Optional.of(rc.body().asJsonArray()).map(jsonArray -> jsonArray.stream().filter(JsonObject.class::isInstance).map(JsonObject.class::cast)
|
|
|
+ .map(f -> UnitTelemetry.of(
|
|
|
+ f.getLong("unitId"),
|
|
|
+ OffsetDateTime.parse(f.getString("timestamp")),
|
|
|
+ Location.of(
|
|
|
+ f.getJsonObject("location").getFloat("longitude"),
|
|
|
+ f.getJsonObject("location").getFloat("latitude"),
|
|
|
+ f.getJsonObject("location").getFloat("altitude")
|
|
|
+ ),
|
|
|
+ f.getInteger("speed"),
|
|
|
+ f.getJsonObject("observedValues")
|
|
|
+ )).collect(toList()))
|
|
|
+ .ifPresent(telemetries -> repo.saveAllTelemetry(telemetries)
|
|
|
+ .onSuccess(count -> rc.response()
|
|
|
+ .end(JsonObject.of(
|
|
|
+ "saved", count,
|
|
|
+ "errors", telemetries.size() - count
|
|
|
+ ).encode()))
|
|
|
+ .onFailure(rc::fail)
|
|
|
+ );
|
|
|
+
|
|
|
+ case GEOJSON -> Optional.of(rc.body().asJsonObject()).map(j -> j.getJsonArray("features"))
|
|
|
+ .map(jsonArray -> jsonArray.stream().filter(JsonObject.class::isInstance).map(JsonObject.class::cast)
|
|
|
+ .map(f -> UnitTelemetry.of(
|
|
|
+ f.getJsonObject("properties").getLong("unitId"),
|
|
|
+ OffsetDateTime.parse(f.getJsonObject("properties").getString("timestamp")),
|
|
|
+ Location.of(
|
|
|
+ f.getJsonObject("geometry").getJsonArray("coordinates").getFloat(0),
|
|
|
+ f.getJsonObject("geometry").getJsonArray("coordinates").getFloat(1),
|
|
|
+ f.getJsonObject("geometry").getJsonArray("coordinates").getFloat(2)
|
|
|
+ ),
|
|
|
+ f.getJsonObject("properties").getInteger("speed"),
|
|
|
+ f.getJsonObject("observations")
|
|
|
+ )).collect(toList()))
|
|
|
+ .ifPresent(telemetries -> repo.saveAllTelemetry(telemetries)
|
|
|
+ .onSuccess(count -> rc.response()
|
|
|
+ .end(JsonObject.of(
|
|
|
+ "saved", count,
|
|
|
+ "errors", telemetries.size() - count
|
|
|
+ ).encode()))
|
|
|
+ .onFailure(rc::fail)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|