Przeglądaj źródła

refactored scheduler, refactored gson to vertx json

Lukas Cerny 5 lat temu
rodzic
commit
50a09a280d

+ 1 - 1
config/test.yaml

@@ -57,7 +57,7 @@ services:
 
 scheduler:
 #  senslog1:
-#      period: 30
+#      period: 15
 #      consumer: "test"
 #      config:
 #        startDate: "2020-08-05T12:24:17[Europe/Prague]"

+ 2 - 72
connector-core/src/main/java/io/connector/core/AbstractGateway.java

@@ -145,8 +145,9 @@ public abstract class AbstractGateway {
                 eventBus.request(schedulerConsumerAddr, message.body(), schedulerConsumerReply -> {
                     if (schedulerConsumerReply.succeeded()) {
                         io.vertx.core.eventbus.Message<Object> schedulerConsumerMsg = schedulerConsumerReply.result();
+                        DeliveryOptions options = new DeliveryOptions().setHeaders(schedulerConsumerMsg.headers());
                         logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, schedulerConsumerMsg.address());
-                        message.reply(schedulerConsumerMsg.body());
+                        message.reply(schedulerConsumerMsg.body(), options);
                     } else {
                         logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, schedulerConsumerAddr);
                         message.fail(400, schedulerConsumerReply.cause().getMessage());
@@ -154,77 +155,6 @@ public abstract class AbstractGateway {
                 });
             }
         });
-
-        /*
-        eventBus.consumer(gatewayAddr, message -> {
-            logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, message.address());
-            if (isDefault) {
-                String proxyConsumerName = message.headers().get(ADDRESS);
-                // this is a provider and must request a local consumer
-                DeliveryOptions proxyOptions = new DeliveryOptions().setHeaders(message.headers());
-                // request proxy consumer (eq scheduler-observations)
-                String proxyAddr = createAddress(proxyConsumerName);
-                logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, proxyAddr);
-                eventBus.request(proxyAddr, message.body(), proxyOptions, proxyReply -> {
-                    if (proxyReply.succeeded()) {
-                        io.vertx.core.eventbus.Message<Object> proxyMsg = proxyReply.result();
-                        logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, proxyMsg.address());
-                        String providerProxyName = proxyMsg.headers().get(ADDRESS); // hit: consumerName is the same as providerProxyName
-                        String providerName = schedulerMapping.get(providerProxyName);
-                        if (providerName != null) {
-                            DeliveryOptions providerOptions = new DeliveryOptions().setHeaders(proxyMsg.headers());
-                            // request provider consumer (eq observations)
-                            String providerAddr = createAddress(providerName);
-                            logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, providerAddr);
-                            eventBus.request(providerAddr, proxyMsg.body(), providerOptions, providerReply -> {
-                                if (providerReply.succeeded()) {
-                                    io.vertx.core.eventbus.Message<Object> providerMsg = providerReply.result();
-                                    logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, providerMsg.address());
-                                    DeliveryOptions replyOptions = new DeliveryOptions().setHeaders(providerMsg.headers());
-                                    logger.info("[{}/{}] Reply of the message {}.", moduleId, gatewayId, message.address());
-                                    message.reply(providerMsg.body(), replyOptions);
-                                }else {
-                                    logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
-                                    message.fail(400, providerReply.cause().getMessage());
-                                }
-                            });
-                        } else {
-                            logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
-                            message.fail(400, "No conversion endpoint for " + providerName);
-                        }
-                    } else {
-                        logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
-                        message.fail(400, proxyReply.cause().getMessage());
-                    }
-                });
-            } else {
-                String providerName = message.headers().get(ADDRESS);
-                String consumerName = schedulerMapping.get(providerName);
-                if (consumerName != null) {
-                    DeliveryOptions consumerOptions = new DeliveryOptions().setHeaders(message.headers());
-                    String consumerAddr = createAddress(consumerName);
-                    logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, consumerAddr);
-                    eventBus.request(consumerAddr, message.body(), consumerOptions, consumerReply -> {
-                       if (consumerReply.succeeded()) {
-                           io.vertx.core.eventbus.Message<Object> consumerMsg = consumerReply.result();
-                           logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, consumerMsg.address());
-                           DeliveryOptions replyOptions = new DeliveryOptions().setHeaders(consumerMsg.headers());
-                           logger.info("[{}/{}] Reply of the message {}.", moduleId, gatewayId, message.address());
-                           message.reply(consumerMsg.body(), replyOptions);
-                       } else {
-                           logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
-                           logger.catching(consumerReply.cause());
-                           message.fail(400, consumerReply.cause().getMessage());
-                       }
-                    });
-                } else {
-                    logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
-                    message.fail(400, "No conversion endpoint for " + providerName);
-                }
-            }
-        });
-
-         */
     }
 
 

+ 0 - 11
connector-core/src/main/java/io/connector/core/EventInfo.java

@@ -1,6 +1,5 @@
 package io.connector.core;
 
-import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 
 import java.util.List;
@@ -44,16 +43,6 @@ public class EventInfo {
     public void setData(Object data) {
         if (data instanceof Map) {
             this.data = JsonObject.mapFrom(data);
-            /*
-            Map<?, ?> dataMap = (Map<?, ?>)data;
-            JsonObject body = new JsonObject();
-            for (Map.Entry<?, ?> dataEntry : dataMap.entrySet()) {
-                if (dataEntry.getKey() instanceof String) {
-                    body.put((String)dataEntry.getKey(), dataEntry.getValue());
-                }
-            }
-            this.data = body;
-             */
         } else if (data instanceof List) {
             List<?> dataList = (List<?>)data;
             DataCollection<JsonObject> body = new DataCollection<>(dataList.size());

+ 2 - 2
connector-core/src/main/java/io/connector/core/Handler.java

@@ -5,8 +5,8 @@ import io.vertx.ext.web.RoutingContext;
 
 import java.util.function.Consumer;
 
-import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
-import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
+import static io.connector.core.http.ContentType.APPLICATION_JSON;
+import static io.connector.core.http.Header.CONTENT_TYPE;
 
 public final class Handler {
 

+ 5 - 21
connector-core/src/main/java/io/connector/core/VertxHttpServer.java

@@ -22,8 +22,9 @@ import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.List;
 
-import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
-import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
+import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
+import static io.connector.core.http.ContentType.APPLICATION_JSON;
+import static io.connector.core.http.Header.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static io.connector.core.AddressPath.Creator.createNormalized;
 import static io.connector.core.AddressPath.EVENT;
@@ -55,6 +56,7 @@ public class VertxHttpServer extends AbstractVerticle {
         eventBus.registerDefaultCodec(ModuleInfo.class, ModuleInfo.createCodec());
         ObjectMapper mapper = DatabindCodec.mapper();
         mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
         mapper.registerModule(JsonAttributeFormatter.builder()
                     .addSerializer(OffsetDateTime.class, val -> val.format(ISO_OFFSET_DATE_TIME))
               .getModule()
@@ -77,15 +79,6 @@ public class VertxHttpServer extends AbstractVerticle {
 
 
         router.get(create(domainPrefix, EVENT)).handler(BodyHandler.create()).handler(ctx -> {
-            /* trigger event:
-                    1. the same functionality as scheduler
-                        trigger event, fetch data a publish
-                        result will contain just OK (very time consuming)
-
-                    2. will be added to an event queue
-                        result will contain 'added to the event queue'
-             */
-
             HttpServerResponse response = ctx.response();
             response.putHeader(CONTENT_TYPE, APPLICATION_JSON);
 
@@ -93,16 +86,7 @@ public class VertxHttpServer extends AbstractVerticle {
             String address = createNormalized(info.getModule(), info.getGateway(), info.getAddress());
             DeliveryOptions options = new DeliveryOptions().addHeader(RESOURCE, HTTP_SERVER);
 
-//            eventBus.request(address, info.getData(), options, reply -> {
-//               if (reply.succeeded()) {
-//                   response.end(Json.encode(reply.result().body()));
-//               } else {
-//                   ctx.fail(reply.cause());
-//               }
-//            });
-
-            JsonObject responseBody = new JsonObject().put("message", "Events are not implemented yet.");
-            response.end(responseBody.encode());
+            response.end(new JsonObject().put("message", "Events are not implemented yet.").encode());
         });
 
         router.get(create(domainPrefix, INFO)).handler(ctx -> {

+ 3 - 9
connector-core/src/main/java/io/connector/core/VertxScheduler.java

@@ -16,8 +16,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
-import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
+import static io.connector.core.http.ContentType.APPLICATION_JSON;
+import static io.connector.core.http.Header.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static io.connector.core.AddressPath.Creator.createNormalized;
 import static io.connector.core.AddressPath.SCHEDULER_CONSUMER;
@@ -56,16 +56,11 @@ class VertxScheduler extends Module {
                         .addHeader(RESOURCE, SCHEDULER)
                         .addHeader(ADDRESS, config.getConsumer());
                 String providerAddress = createNormalized(SCHEDULER_PROVIDER, moduleId);
+                logger.info("Sending a message to the address {}.", providerAddress);
                 eventBus.request(providerAddress, config.getConfig(), fetcherOpt, reply -> {
                     if (reply.succeeded()) {
-
                         Message<Object> result = reply.result();
                         String source = result.headers().get(MODULE_TYPE);
-
-                        if (source == null) {
-                            logger.warn("Unknown module type of the address '{}'. The message will not be published.", providerAddress); return;
-                        }
-
                         DeliveryOptions pusherOpt = new DeliveryOptions();
                         MultiMap headers = result.headers().addAll(fetcherOpt.getHeaders());
                         for (Map.Entry<String, String> entryHeader : headers.entries()) {
@@ -73,7 +68,6 @@ class VertxScheduler extends Module {
                                 pusherOpt.addHeader(entryHeader.getKey(), entryHeader.getValue());
                             }
                         }
-
                         String consumerAddress = createNormalized(SCHEDULER_CONSUMER, source);
                         eventBus.publish(consumerAddress, result.body(), pusherOpt);
                     } else {

+ 17 - 42
connector-core/src/main/java/io/connector/core/config/PropertyConfig.java

@@ -1,8 +1,5 @@
 package io.connector.core.config;
 
-import cz.senslog.common.exception.PropertyNotFoundException;
-import cz.senslog.common.util.ClassUtils;
-
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
@@ -12,7 +9,6 @@ import java.util.*;
 
 import static java.lang.String.format;
 import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
-import static java.util.Collections.emptySet;
 import static java.util.Optional.ofNullable;
 
 public class PropertyConfig {
@@ -38,11 +34,9 @@ public class PropertyConfig {
     /**
      * Adds new property to properties.
      * @param name - name of new property.
-     * @param value - value of new property.
      */
-    public boolean setProperty(String name, Object value) {
-        Object res = properties.put(name, value);
-        return res == value;
+    public void setProperty(String name, Object value) {
+        properties.put(name, value);
     }
 
     /**
@@ -55,7 +49,7 @@ public class PropertyConfig {
             return properties.get(name);
         }
 
-        throw new PropertyNotFoundException(format(
+        throw new IllegalArgumentException(format(
                 "Property '%s' does not exist.", getNewPropertyId(name))
         );
     }
@@ -84,7 +78,13 @@ public class PropertyConfig {
      * @return string value.
      */
     public String getStringProperty(String name) {
-        return ClassUtils.cast(getProperty(name), String.class);
+        Object value = getProperty(name);
+        if (value instanceof String) {
+            return (String)value;
+        }
+        throw new ClassCastException(format(
+                "Value '%s' can not be cast to String", value
+        ));
     }
 
     /**
@@ -93,7 +93,13 @@ public class PropertyConfig {
      * @return integer value.
      */
     public Integer getIntegerProperty(String name) {
-        return ClassUtils.cast(getProperty(name), Integer.class);
+        Object value = getProperty(name);
+        if (value instanceof Integer) {
+            return (Integer)value;
+        }
+        throw new ClassCastException(format(
+                "Value '%s' can not be cast to Integer", value
+        ));
     }
 
     /**
@@ -141,37 +147,6 @@ public class PropertyConfig {
     }
 
     /**
-     * Returns property as a optional of LocalDateTime
-     * @param name - name of property.
-     * @return optional of localDateTime value.
-     */
-    public Optional<LocalDateTime> getOptionalLocalDateTimeProperty(String name) {
-        return properties.containsKey(name) ? Optional.of(getLocalDateTimeProperty(name)) : Optional.empty();
-    }
-
-    /**
-     * Returns property as a set of the 'type'.
-     * @param name - name of property.
-     * @param type - type of attributes
-     * @param <T> - generic type of attribute
-     * @return Set of attributes defined by type
-     */
-    public <T> Set<T> getSetProperty(String name, Class<T> type) {
-        Object value = properties.get(name);
-
-        if (value instanceof  List) {
-            List<?> list = (List<?>) value;
-            Set<T> res = new HashSet<>(list.size());
-            for (Object o : list) {
-                res.add(ClassUtils.cast(o, type));
-            }
-            return res;
-        }
-
-        return emptySet();
-    }
-
-    /**
      * Returns new node of configuration.
      * @param name - name of property.
      * @return node of configuration.

+ 1 - 2
connector-core/src/main/java/io/connector/core/config/file/FileConfigurationServiceImpl.java

@@ -1,6 +1,5 @@
 package io.connector.core.config.file;
 
-import cz.senslog.common.exception.UnsupportedFileException;
 import cz.senslog.common.util.StringUtils;
 import io.connector.core.ModuleDescriptor;
 import io.connector.core.config.DefaultConfig;
@@ -42,7 +41,7 @@ class FileConfigurationServiceImpl  implements FileConfigurationService {
         logger.info("Loading '{}' configuration file.", fileName);
 
         if (!fileName.toLowerCase().endsWith(".yaml")) {
-            throw new UnsupportedFileException(fileName + "does not contain .yaml extension.");
+            throw new IllegalArgumentException(fileName + "does not contain .yaml extension.");
         }
 
         Path filePath = Paths.get(fileName);

+ 20 - 0
connector-core/src/main/java/io/connector/core/http/ContentType.java

@@ -0,0 +1,20 @@
+package io.connector.core.http;
+
+public final class ContentType {
+
+    public static final String APPLICATION_ATOM_XML = "application/atom+xml";
+    public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
+    public static final String APPLICATION_JSON = "application/json";
+    public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
+    public static final String APPLICATION_SVG_XML = "application/svg+xml";
+    public static final String APPLICATION_XHTML_XML = "application/xhtml+xml";
+    public static final String APPLICATION_XML = "application/xml";
+    public static final String MULTIPART_FORM_DATA = "multipart/form-data";
+    public static final String TEXT_HTML = "text/html";
+    public static final String TEXT_PLAIN = "text/plain";
+    public static final String TEXT_XML = "text/xml";
+    public static final String WILDCARD = "*/*";
+
+    private ContentType() {}
+
+}

+ 11 - 0
connector-core/src/main/java/io/connector/core/http/Header.java

@@ -0,0 +1,11 @@
+package io.connector.core.http;
+
+public final class Header {
+
+    public static final String AUTHORIZATION = "Authorization";
+    public static final String DATE = "Date";
+    public static final String ACCEPT = "Accept";
+    public static final String CONTENT_TYPE = "Content-Type";
+
+    private Header() { }
+}

+ 40 - 22
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCClient.java

@@ -1,6 +1,5 @@
 package io.connector.module.afarcloud;
 
-import com.google.gson.reflect.TypeToken;
 import cz.senslog.common.http.HttpClient;
 import cz.senslog.common.http.HttpRequest;
 import cz.senslog.common.http.HttpResponse;
@@ -10,23 +9,42 @@ import io.connector.model.afarcloud.MultiSensor;
 import io.connector.model.afarcloud.MultiSimpleObservation;
 import io.connector.model.afarcloud.ResourceMeasurement;
 import io.connector.module.afarcloud.gateway.SensLog1Gateway;
-import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.Json;
 import io.vertx.core.json.JsonObject;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.lang.reflect.Type;
-import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
-import static cz.senslog.common.http.HttpContentType.TEXT_PLAIN;
-import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static io.connector.core.http.ContentType.TEXT_PLAIN;
 import static java.lang.String.format;
 
 public class AFCClient {
 
     private final static Logger logger = LogManager.getLogger(SensLog1Gateway.class);
 
+    private static class ResponseOfMeasurement {
+        private ResultOfMeasurement results;
+        public void setResults(ResultOfMeasurement results) {
+            this.results = results;
+        }
+    }
+
+    private static class ResultOfMeasurement {
+        private List<ResourceMeasurement> resources;
+        void setResources(List<ResourceMeasurement> resources) {
+            this.resources = resources;
+        }
+    }
+
+    private static class ResultOfMultiSensor {
+        private List<MultiSensor> sensors;
+        void setSensors(List<MultiSensor> sensors) {
+            this.sensors = sensors;
+        }
+    }
+
     private final AFCConfig config;
 
     private final HttpClient httpClient;
@@ -78,12 +96,11 @@ public class AFCClient {
             throw new RuntimeException(response.getBody());
         }
 
-        JsonObject jsonObject = new JsonObject(response.getBody());
-        JsonObject results = jsonObject.getJsonObject("results");
-        JsonArray resources = results.getJsonArray("resources");
-
-        final Type measurementType = new TypeToken<Collection<ResourceMeasurement>>() {}.getType();
-        return jsonToObject(resources.encode(), measurementType);
+        ResponseOfMeasurement responseModel = Json.decodeValue(response.getBody(), ResponseOfMeasurement.class);
+        if (responseModel == null || responseModel.results == null || responseModel.results.resources == null) {
+            return Collections.emptyList();
+        }
+        return responseModel.results.resources;
     }
 
     public List<ResourceMeasurement> getLatestObservationsBySensor(Filter filter) {
@@ -111,12 +128,11 @@ public class AFCClient {
             throw new RuntimeException(response.getBody());
         }
 
-        JsonObject jsonObject = new JsonObject(response.getBody());
-        JsonObject results = jsonObject.getJsonObject("results");
-        JsonArray resources = results.getJsonArray("resources");
-
-        final Type measurementType = new TypeToken<Collection<ResourceMeasurement>>() {}.getType();
-        return jsonToObject(resources.encode(), measurementType);
+        ResponseOfMeasurement responseModel = Json.decodeValue(response.getBody(), ResponseOfMeasurement.class);
+        if (responseModel == null || responseModel.results == null || responseModel.results.resources == null) {
+            return Collections.emptyList();
+        }
+        return responseModel.results.resources;
     }
 
     public List<MultiSensor> getAllMultiSensors() {
@@ -134,9 +150,11 @@ public class AFCClient {
             throw new RuntimeException(response.getBody());
         }
 
-        final Type unitType = new TypeToken<Collection<MultiSensor>>() {}.getType();
-        return jsonToObject(response.getBody(), unitType);
-
+        ResultOfMultiSensor resultModel = Json.decodeValue(format("{\"sensors\":%s}", response.getBody()), ResultOfMultiSensor.class);
+        if (resultModel == null || resultModel.sensors == null) {
+            return Collections.emptyList();
+        }
+        return resultModel.sensors;
     }
 
     public MultiSensor getSensorByResourceId(String resourceId) {
@@ -153,7 +171,7 @@ public class AFCClient {
             throw new RuntimeException(response.getBody());
         }
 
-        return jsonToObject(response.getBody(), MultiSensor.class);
+        return Json.decodeValue(response.getBody(), MultiSensor.class);
     }
 
     public MultiSensor getSensorByResourceUrn(String resourceUrn) {

+ 2 - 2
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java

@@ -14,8 +14,8 @@ import java.time.Instant;
 import java.util.*;
 import java.util.function.Supplier;
 
-import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
-import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
+import static io.connector.core.http.ContentType.APPLICATION_JSON;
+import static io.connector.core.http.Header.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static io.vertx.core.json.Json.encode;
 import static java.lang.Math.sqrt;

+ 2 - 2
connector-module-ogc-sensorthings/src/main/java/io/connector/module/ogc/sensorthings/gateway/AFarCloudGateway.java

@@ -12,8 +12,8 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
-import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
-import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
+import static io.connector.core.http.ContentType.APPLICATION_JSON;
+import static io.connector.core.http.Header.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static io.vertx.core.json.Json.encode;
 import static java.lang.Double.parseDouble;

+ 1 - 1
connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1HttpClient.java

@@ -15,7 +15,7 @@ import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
-import static cz.senslog.common.http.HttpContentType.TEXT_PLAIN;
+import static io.connector.core.http.ContentType.TEXT_PLAIN;
 import static java.lang.String.format;
 import static java.time.format.DateTimeFormatter.ofPattern;
 

+ 6 - 7
connector-module-senslog1/src/main/java/io/connector/module/senslog1/gateway/SensLog1Gateway.java

@@ -1,6 +1,5 @@
 package io.connector.module.senslog1.gateway;
 
-import cz.senslog.common.util.Tuple;
 import io.connector.core.AbstractGateway;
 import io.connector.core.DataCollection;
 import io.connector.core.Message;
@@ -68,9 +67,9 @@ public class SensLog1Gateway extends AbstractGateway {
         event().consume("observations-with-positions", message -> {
             MultiMap params = message.headers();
 
-            Tuple<OffsetDateTime, OffsetDateTime> timeRange = getTimeRangeFromParam(message);
+            OffsetDateTime[] timeRange = getTimeRangeFromParam(message);
             if (timeRange == null) { return; }
-            OffsetDateTime fromDate = timeRange.getItem1(), toDate = timeRange.getItem2();
+            OffsetDateTime fromDate = timeRange[0], toDate = timeRange[1];
 
             List<UnitData> positions, observations;
             if (params.contains("unitId")) {
@@ -89,9 +88,9 @@ public class SensLog1Gateway extends AbstractGateway {
         event().consume("positions", message -> {
             MultiMap params = message.headers();
 
-            Tuple<OffsetDateTime, OffsetDateTime> timeRange = getTimeRangeFromParam(message);
+            OffsetDateTime[] timeRange = getTimeRangeFromParam(message);
             if (timeRange == null) { return; }
-            OffsetDateTime fromDate = timeRange.getItem1(), toDate = timeRange.getItem2();
+            OffsetDateTime fromDate = timeRange[0], toDate = timeRange[1];
 
             List<UnitData> positions;
             if (params.contains("unitId")) {
@@ -144,7 +143,7 @@ public class SensLog1Gateway extends AbstractGateway {
         });
     }
 
-    private static <T> Tuple<OffsetDateTime, OffsetDateTime> getTimeRangeFromParam(Message<T> message) {
+    private static <T> OffsetDateTime[] getTimeRangeFromParam(Message<T> message) {
         MultiMap params = message.headers();
 
         OffsetDateTime fromDate;
@@ -161,7 +160,7 @@ public class SensLog1Gateway extends AbstractGateway {
             message.fail(400, "Attribute 'toDate' is required."); return null;
         }
 
-        return Tuple.of(fromDate, toDate);
+        return new OffsetDateTime[]{fromDate, toDate};
     }
 
     @SafeVarargs