Parcourir la source

Validation over Campaign from_time and to_time for new telemetries in the API 'campaignIdUnitsObservationsPOST'

Lukas Cerny il y a 1 an
Parent
commit
eac7319f24

+ 3 - 0
src/main/java/cz/senslog/telemetry/database/repository/MapLogRepository.java

@@ -87,6 +87,9 @@ public class MapLogRepository implements SensLogRepository {
 
     @Override
     public Future<Integer> saveAllTelemetry(List<UnitTelemetry> data) {
+        if (data == null || data.isEmpty()) {
+            return Future.succeededFuture(0);
+        }
         List<Tuple> tuples = data.stream().map(d -> Tuple.of(
                 d.getUnitId(),
                 d.getTimestamp(),

+ 30 - 0
src/main/java/cz/senslog/telemetry/database/validation/UnitTelemetryValidation.java

@@ -0,0 +1,30 @@
+package cz.senslog.telemetry.database.validation;
+
+import cz.senslog.telemetry.database.domain.Campaign;
+import cz.senslog.telemetry.database.domain.UnitTelemetry;
+import cz.senslog.telemetry.utils.ComparisonOperators;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static cz.senslog.telemetry.utils.ComparisonOperators.ofBoundary;
+import static java.util.stream.Collectors.toList;
+
+public class UnitTelemetryValidation {
+
+    public static boolean isTelemetryWithinCampaign(Campaign campaign, UnitTelemetry telemetry) {
+        return ofBoundary(campaign.getFromTime(), campaign.getToTime()).isWithinEqual(telemetry.getTimestamp());
+    }
+
+    public static List<UnitTelemetry> telemetriesWithinCampaign(Campaign campaign, List<UnitTelemetry> telemetries) {
+        ComparisonOperators.BoundaryComparison campBound = ofBoundary(campaign.getFromTime(), campaign.getToTime());
+        OffsetDateTime firstTelTime = telemetries.get(0).getTimestamp();
+        OffsetDateTime lastTelTime = telemetries.get(telemetries.size() - 1).getTimestamp();
+
+        if (campBound.isWithinEqual(firstTelTime) && campBound.isWithinEqual(lastTelTime)) {
+            return telemetries;
+        }
+
+        return telemetries.stream().filter(v -> campBound.isWithinEqual(v.getTimestamp())).collect(toList());
+    }
+}

+ 30 - 0
src/main/java/cz/senslog/telemetry/server/ws/ContentType.java

@@ -0,0 +1,30 @@
+package cz.senslog.telemetry.server.ws;
+
+public enum ContentType {
+    JSON    ("application/json"),
+    GEOJSON ("application/geo+json")
+    ;
+
+    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;
+    }
+}

+ 3 - 1
src/main/java/cz/senslog/telemetry/server/ws/ExceptionHandler.java

@@ -9,6 +9,8 @@ import org.apache.logging.log4j.Logger;
 
 import java.util.Optional;
 
+import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
+
 
 public final class ExceptionHandler implements io.vertx.core.Handler<RoutingContext> {
 
@@ -38,7 +40,7 @@ public final class ExceptionHandler implements io.vertx.core.Handler<RoutingCont
 
         logger.error(message);
         rc.response()
-                .putHeader("Content-Type", "application/json")
+                .putHeader(CONTENT_TYPE, ContentType.JSON.contentType())
                 .setStatusCode(rc.statusCode())
                 .end(JsonObject.of(
                         "code", code,

+ 54 - 69
src/main/java/cz/senslog/telemetry/server/ws/OpenAPIHandler.java

@@ -7,6 +7,8 @@ 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.database.validation.UnitTelemetryValidation;
+import cz.senslog.telemetry.utils.FluentInvoke;
 import cz.senslog.telemetry.utils.TernaryCondition;
 import io.vertx.core.http.HttpServerRequest;
 import io.vertx.core.json.JsonArray;
@@ -20,47 +22,23 @@ import java.util.*;
 import java.util.function.*;
 import java.util.stream.Collectors;
 
-import static cz.senslog.telemetry.server.ws.OpenAPIHandler.ContentType.GEOJSON;
-import static cz.senslog.telemetry.server.ws.OpenAPIHandler.ContentType.JSON;
+import static cz.senslog.telemetry.database.validation.UnitTelemetryValidation.telemetriesWithinCampaign;
+import static cz.senslog.telemetry.server.ws.ContentType.GEOJSON;
+import static cz.senslog.telemetry.server.ws.ContentType.JSON;
+import static cz.senslog.telemetry.utils.ComparisonOperators.*;
+import static cz.senslog.telemetry.utils.FluentInvoke.fluentlyOf;
+import static cz.senslog.telemetry.utils.TernaryCondition.ternaryIf;
 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;
+import static java.util.Comparator.comparing;
 import static java.util.Optional.ofNullable;
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toList;
 
 public class OpenAPIHandler {
 
-    protected enum ContentType {
-        JSON    ("application/json"),
-        GEOJSON ("application/geo+json")
-        ;
-
-        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;
@@ -1544,49 +1522,56 @@ public class OpenAPIHandler {
     }
 
     public void campaignIdUnitsObservationsPOST(RoutingContext rc) {
+        long campaignId = Long.parseLong(rc.pathParam("campaignId"));
+
         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 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")
+                            )).sorted(comparing(UnitTelemetry::getTimestamp)).collect(toList()))
+                    .ifPresent(orgTels -> repo.findCampaignById(campaignId)
+                            .onSuccess(campaign -> fluentlyOf(telemetriesWithinCampaign(campaign, orgTels))
+                                        .then(flTels -> repo.saveAllTelemetry(flTels)
+                                                .onSuccess(count -> rc.response().end(JsonObject.of(
+                                                        "saved", count,
+                                                        "errors", orgTels.size() - count
+                                                ).encode()))
+                                                .onFailure(rc::fail))
+                            )
+                    .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)
-                    );
+                                    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")
+                            )).sorted(comparing(UnitTelemetry::getTimestamp)).collect(toList()))
+                    .ifPresent(orgTels -> repo.findCampaignById(campaignId)
+                            .onSuccess(campaign -> fluentlyOf(telemetriesWithinCampaign(campaign, orgTels))
+                                    .then(flTels -> repo.saveAllTelemetry(flTels)
+                                            .onSuccess(count -> rc.response().end(JsonObject.of(
+                                                    "saved", count,
+                                                    "errors", orgTels.size() - count
+                                            ).encode()))
+                                            .onFailure(rc::fail))
+                            )
+                            .onFailure(rc::fail));
         }
     }
 }

+ 43 - 0
src/main/java/cz/senslog/telemetry/utils/ComparisonOperators.java

@@ -0,0 +1,43 @@
+package cz.senslog.telemetry.utils;
+
+import java.time.OffsetDateTime;
+
+public final class ComparisonOperators {
+
+    public static boolean lt(OffsetDateTime val1, OffsetDateTime val2) {
+        return val1.isBefore(val2);
+    }
+
+    public static boolean le(OffsetDateTime val1, OffsetDateTime val2) {
+        return val1.isBefore(val2) || val1.isEqual(val2);
+    }
+
+    public static boolean eq(OffsetDateTime val1, OffsetDateTime val2) {
+        return val1.isEqual(val2);
+    }
+
+    public static boolean ge(OffsetDateTime val1, OffsetDateTime val2) {
+        return val1.isAfter(val2) || val1.isEqual(val2);
+    }
+
+    public static boolean gt(OffsetDateTime val1, OffsetDateTime val2) {
+        return val1.isAfter(val2);
+    }
+
+    public static BoundaryComparison ofBoundary(OffsetDateTime start, OffsetDateTime end) {
+        return new BoundaryComparison(start, end);
+    }
+
+    public static class BoundaryComparison {
+        private final OffsetDateTime start, end;
+
+        private BoundaryComparison(OffsetDateTime start, OffsetDateTime end) {
+            this.start = start;
+            this.end = end;
+        }
+
+        public boolean isWithinEqual(OffsetDateTime val) {
+            return le(start, val) && ge(end, val);
+        }
+    }
+}

+ 2 - 2
src/main/java/cz/senslog/telemetry/utils/FluentInvoke.java

@@ -6,7 +6,7 @@ public final class FluentInvoke<T> {
 
     private final T object;
 
-    public static <T> FluentInvoke<T> of(T object) {
+    public static <T> FluentInvoke<T> fluentlyOf(T object) {
         return new FluentInvoke<>(object);
     }
 
@@ -15,6 +15,6 @@ public final class FluentInvoke<T> {
     }
 
     public <D> FluentInvoke<D> then(Function<T, D> fnc) {
-        return of(fnc.apply(object));
+        return fluentlyOf(fnc.apply(object));
     }
 }