Kaynağa Gözat

FieldClimate fix of timestamp and session for azure

Lukas Cerny 5 yıl önce
ebeveyn
işleme
89f7ae3070
38 değiştirilmiş dosya ile 654 ekleme ve 290 silme
  1. 7 2
      config/fieldclimateSenslog1.yaml
  2. 6 6
      config/lorawanSenslog1.yaml
  3. 2 2
      connector-app/src/test/java/cz/senslog/connector/app/config/ConnectorBuilderTest.java
  4. 8 0
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureConfig.java
  5. 52 32
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureFetcher.java
  6. 102 0
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureProxySession.java
  7. 81 0
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureSession.java
  8. 29 0
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureSessionProxyConfig.java
  9. 22 8
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/ConnectorFetchAzureProvider.java
  10. 5 5
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AuthenticationService.java
  11. 1 1
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AzureAuthConfig.java
  12. 83 45
      connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/AzureFetcherTest.java
  13. 8 8
      connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/auth/AuthenticationServiceTest.java
  14. 0 9
      connector-fetch-azure/src/test/resources/schema/sensorInfoSchema.json
  15. 17 0
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateConfig.java
  16. 36 25
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcher.java
  17. 5 7
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySession.java
  18. 7 7
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateSession.java
  19. 2 0
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/StationTimeRange.java
  20. 71 50
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcherTest.java
  21. 10 18
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySessionTest.java
  22. 10 10
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateSessionTest.java
  23. 5 4
      connector-model/src/main/java/cz/senslog/connector/model/api/AbstractModel.java
  24. 2 1
      connector-model/src/main/java/cz/senslog/connector/model/azure/AzureModel.java
  25. 4 3
      connector-model/src/main/java/cz/senslog/connector/model/azure/SensorData.java
  26. 4 3
      connector-model/src/main/java/cz/senslog/connector/model/azure/SensorInfo.java
  27. 36 7
      connector-model/src/main/java/cz/senslog/connector/model/config/PropertyConfig.java
  28. 0 2
      connector-model/src/main/java/cz/senslog/connector/model/converter/AzureUnitConverter.java
  29. 5 6
      connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV1ModelConverter.java
  30. 2 1
      connector-model/src/main/java/cz/senslog/connector/model/fieldclimate/FieldClimateModel.java
  31. 4 3
      connector-model/src/main/java/cz/senslog/connector/model/v1/Record.java
  32. 2 1
      connector-model/src/main/java/cz/senslog/connector/model/v1/SenslogV1Model.java
  33. 2 1
      connector-model/src/main/java/cz/senslog/connector/model/v2/SenslogV2Model.java
  34. 2 2
      connector-model/src/test/java/cz/senslog/connector/model/api/ConverterProviderTest.java
  35. 7 8
      connector-model/src/test/java/cz/senslog/connector/model/converter/AzureModelSenslogV1ModelConverterTest.java
  36. 8 5
      connector-model/src/test/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV1ModelConverterTest.java
  37. 4 2
      connector-push-rest-senslog-v1/src/main/java/cz/senslog/connector/push/rest/senslog/v1/SenslogV1Pusher.java
  38. 3 6
      connector-push-rest-senslog-v1/src/test/java/cz/senslog/connector/push/rest/senslog/v1/SenslogV1PusherTest.java

+ 7 - 2
config/fieldclimateSenslog1.yaml

@@ -15,8 +15,9 @@ settings:
   - Fieldclimate:
       name: "FieldClimate: Pessl Instruments"
       provider: "cz.senslog.connector.fetch.fieldclimate.ConnectorFetchFieldClimateProvider"
-      startDate: 2020-01-07T15:00:00.000
-      period: 5 # in hours
+      startDate: "2020-07-03T11:00:00.000" # yyyy-MM-DD hh:mm:ss.sss
+      timeZone: "Europe/Riga"
+      period: 1 # in hours (integer)
 
       authentication:
         publicKey: "3737ed4fe98fae975e54991216ed473c8d7db48662deff19"
@@ -34,6 +35,10 @@ settings:
         <<: *fieldClimateApiDomain
         path: "/data/{station_id}"
 
+      blockedStations:
+        - "0120821D"
+        - "0120821E"
+
       sessionProxy:
         user: "vilcini"
         group: "vilcini"

+ 6 - 6
config/lorawanSenslog1.yaml

@@ -28,12 +28,12 @@ settings:
             password: "SensLogIMA1"
             refreshPeriodIfFail: 10000
 
-        sessionProxy:
-            user: "afarcloud"
-            group: "afc"
-            lastObservationHost:
-              <<: *senslogApiDomain
-              path: "SensorService"
+#        sessionProxy:
+#            user: "afarcloud"
+#            group: "afc"
+#            lastObservationHost:
+#              <<: *senslogApiDomain
+#              path: "SensorService"
         
     - SenslogV1:
         name: "Senslog V1"

+ 2 - 2
connector-app/src/test/java/cz/senslog/connector/app/config/ConnectorBuilderTest.java

@@ -18,8 +18,8 @@ import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 
-import static java.time.LocalDateTime.MAX;
-import static java.time.LocalDateTime.MIN;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;

+ 8 - 0
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureConfig.java

@@ -21,6 +21,7 @@ public class AzureConfig {
     private final HostConfig sensorInfoHost;
     private final HostConfig sensorDataHost;
     private final AzureAuthConfig authentication;
+    private final AzureSessionProxyConfig sessionProxy;
 
     private LocalDateTime startDate;
     private LocalDateTime endDate;
@@ -38,6 +39,9 @@ public class AzureConfig {
 
         this.startDate = config.getLocalDateTimeProperty("startDate");
         this.endDate = config.getOptionalLocalDateTimeProperty("endDate").orElse(null);
+
+        this.sessionProxy = config.containsProperty("sessionProxy") ?
+                new AzureSessionProxyConfig(config.getPropertyConfig("sessionProxy")) : null;
     }
 
     public LocalDateTime getStartDate() {
@@ -64,6 +68,10 @@ public class AzureConfig {
         return endDate;
     }
 
+    public AzureSessionProxyConfig getSessionProxy() {
+        return sessionProxy;
+    }
+
     @Override
     public String toString() {
         return objectToJson(this);

+ 52 - 32
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureFetcher.java

@@ -5,7 +5,7 @@ import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.common.exception.ModuleInterruptedException;
 import cz.senslog.common.exception.SyntaxException;
 import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.azure.auth.AzureAuthenticationService;
+import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
 import cz.senslog.common.http.HttpClient;
 import cz.senslog.common.http.HttpRequest;
 import cz.senslog.common.http.HttpResponse;
@@ -20,13 +20,17 @@ import org.apache.logging.log4j.Logger;
 
 import java.lang.reflect.Type;
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.*;
 
 import static cz.senslog.common.http.HttpHeader.AUTHORIZATION;
 import static cz.senslog.common.json.BasicJson.jsonToObject;
 import static cz.senslog.common.util.StringUtils.isBlank;
 import static java.lang.String.format;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
 import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 import static java.util.Collections.emptyList;
 
 /**
@@ -49,7 +53,7 @@ import static java.util.Collections.emptyList;
  * @version 1.0
  * @since 1.0
  */
-public class AzureFetcher implements ConnectorFetcher<ProxySessionModel, AzureModel> {
+public class AzureFetcher implements ConnectorFetcher<AzureSession, AzureModel> {
 
     private static Logger logger = LogManager.getLogger(AzureFetcher.class);
 
@@ -65,33 +69,33 @@ public class AzureFetcher implements ConnectorFetcher<ProxySessionModel, AzureMo
     private final AzureConfig config;
 
     /** Service for authentication. */
-    private final AzureAuthenticationService authService;
+    private final AuthenticationService authService;
 
     /** Http client. */
     private final HttpClient httpClient;
 
-
-    /** Time when new data will be fetched. */
-    private LocalDateTime lastFetch;
-
     /** List of sensor information. */
     private List<SensorInfo> sensorInfos;
 
     /** Schema for sensor data. */
     private JsonSchema sensorDataSchema;
 
+    private AzureSession localSession;
+
+    public AzureFetcher() { this(null, null, null); }
+
     /**
      * Constructor of the class sets all attributes.
      * @param config - configuration for fetcher.
      * @param authService - authentication service.
      * @param httpClient - http client.
      */
-    AzureFetcher(AzureConfig config, AzureAuthenticationService authService, HttpClient httpClient) {
+    AzureFetcher(AzureConfig config, AuthenticationService authService, HttpClient httpClient) {
         this.config = config;
         this.authService = authService;
         this.httpClient = httpClient;
-        this.lastFetch = config.getStartDate();
         this.sensorInfos = new ArrayList<>();
+        this.localSession = AzureSession.emptySession();
     }
 
     @Override
@@ -161,32 +165,42 @@ public class AzureFetcher implements ConnectorFetcher<ProxySessionModel, AzureMo
     }
 
     @Override
-    public AzureModel fetch(Optional<ProxySessionModel> session) {
+    public AzureModel fetch(Optional<AzureSession> persistenceSession) {
 
         if (sensorInfos.isEmpty()) {
             logger.error("Sensors information were not loaded. Can not get detailed information.");
-            return new AzureModel(emptyList(), lastFetch, lastFetch);
+            OffsetDateTime now = OffsetDateTime.now();
+            return new AzureModel(emptyList(), now, now);
         }
 
+        AzureSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
+
         String accessToken = authService.getAccessToken();
 
-        LocalDateTime tempLastDate = lastFetch;
         int totalFetched = 0;
+        OffsetDateTime globalFrom = MAX, globalTo = MIN;
 
         for (SensorInfo sensorInfo : sensorInfos) {
             logger.info("Fetching data for the sensor {}.", sensorInfo.getEui());
+            String stationId = sensorInfo.getEui();
+
+            if (isBlank(stationId)) continue;
+
+            OffsetDateTime localFrom = session.getLastTimeForUnit(stationId, MIN);
+            if (localFrom.toLocalDateTime().isBefore(config.getStartDate())) {
+                localFrom = OffsetDateTime.of(config.getStartDate(), localFrom.getOffset());
+            }
 
-            if (isBlank(sensorInfo.getEui())) continue;
+            LocalDateTime endTime = config.getEndDate() != null ? config.getEndDate() : LocalDateTime.now();
+            OffsetDateTime localTo = OffsetDateTime.of(endTime, localFrom.getOffset());
 
             HostConfig sensorDataHost = config.getSensorDataHost();
             logger.info("Creating a http request to {}.", sensorDataHost);
-            String fromParam = lastFetch.format(ISO_DATE_TIME);
-            String toParam = config.getEndDate() != null ? config.getEndDate().format(ISO_DATE_TIME) : "";
             HttpRequest request = HttpRequest.newBuilder()
                     .url(URLBuilder.newBuilder(sensorDataHost.getDomain(), sensorDataHost.getPath())
-                            .addParam("eui", sensorInfo.getEui())
-                            .addParam("from", fromParam)
-                            .addParam("to", toParam)
+                            .addParam("eui", stationId)
+                            .addParam("from", localFrom.format(ISO_DATE_TIME))
+                            .addParam("to", localTo.format(ISO_DATE_TIME))
                             .addParam("limit", config.getLimitPerSensor())
                             .build())
                     .header(AUTHORIZATION, accessToken)
@@ -198,8 +212,7 @@ public class AzureFetcher implements ConnectorFetcher<ProxySessionModel, AzureMo
             logger.info("Received a response with a status: {}.", response.getStatus());
 
             if (response.isError()) {
-                logger.error("Can not get data of the sensor {}. Error {} {}",
-                        sensorInfo.getEui(), response.getStatus(), response.getBody());
+                logger.error("Can not get data of the sensor {}. Error {} {}", stationId, response.getStatus(), response.getBody());
                 continue;
             }
 
@@ -227,23 +240,33 @@ public class AzureFetcher implements ConnectorFetcher<ProxySessionModel, AzureMo
                 totalFetched += sensorInfoData.getData().size();
 
                 logger.debug("Getting last record of the sensor data.");
+                OffsetDateTime latestTimestamp = MIN;
                 List<SensorData> sensorData = sensorInfoData.getData();
                 if (!sensorData.isEmpty()) {
                     SensorData lastRecord = sensorData.get(sensorData.size() - 1);
-                    LocalDateTime lastRecordTime = lastRecord.getTime().toLocalDateTime();
-                    if (lastRecordTime.isAfter(tempLastDate)) {
-                        tempLastDate = lastRecordTime;
-                        logger.info("Time of the last fetched data was changed from {} to {}.",
-                                tempLastDate.format(ISO_DATE_TIME), lastRecordTime.format(ISO_DATE_TIME));
+                    OffsetDateTime lastRecordTime = lastRecord.getTime();
+                    if (lastRecordTime.isAfter(latestTimestamp)) {
+                        latestTimestamp = lastRecordTime;
                     }
                 }
+                localTo = latestTimestamp;
+
+                session.addLastUnitTime(stationId, localTo);
+
+                if (globalFrom.isAfter(localFrom)) {
+                    globalFrom = localFrom;
+                }
+
+                if (globalTo.isBefore(localTo)) {
+                    globalTo = localTo;
+                }
             }
         }
 
-        LocalDateTime from = lastFetch;
-        LocalDateTime to = tempLastDate;
-        logger.info("Fetched data from {} to {}.", from.format(ISO_DATE_TIME), to.format(ISO_DATE_TIME));
+
         logger.info("Total fetched {} records.", totalFetched);
+        logger.info("Fetched data from {} to {}.",
+                globalFrom.format(ISO_OFFSET_DATE_TIME), globalTo.format(ISO_OFFSET_DATE_TIME));
 
         if (totalFetched == 0 && config.getEndDate() != null) {
             throw new ModuleInterruptedException(format(
@@ -252,11 +275,8 @@ public class AzureFetcher implements ConnectorFetcher<ProxySessionModel, AzureMo
             ));
         }
 
-        logger.debug("Set new time of fetch from {} to {}.", lastFetch.format(ISO_DATE_TIME), tempLastDate.format(ISO_DATE_TIME));
-        lastFetch = tempLastDate;
-
         logger.debug("Creating a new instance of Azure model.");
-        AzureModel model = new AzureModel(sensorInfos, from, to);
+        AzureModel model = new AzureModel(sensorInfos, globalFrom, globalTo);
         logger.debug("Model was created successfully and sending it forward.");
 
         return model;

+ 102 - 0
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureProxySession.java

@@ -0,0 +1,102 @@
+package cz.senslog.connector.fetch.azure;
+
+import com.google.gson.reflect.TypeToken;
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import cz.senslog.common.util.Tuple;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.api.FetchProxySession;
+import cz.senslog.connector.model.azure.AzureModel;
+import cz.senslog.connector.model.config.HostConfig;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static cz.senslog.connector.fetch.azure.AzureSession.emptySession;
+import static cz.senslog.connector.model.converter.AzureUnitConverter.convertIdToAzureId;
+import static java.time.ZonedDateTime.parse;
+import static java.time.format.DateTimeFormatter.ofPattern;
+
+class AzureProxySession extends FetchProxySession<AzureSession, AzureModel> {
+
+    private static Logger logger = LogManager.getLogger(AzureProxySession.class);
+
+    protected static class ObservationInfo { OffsetDateTime timeStamp; Long unitId; }
+
+    private final HttpClient httpClient;
+    private final AzureSessionProxyConfig config;
+
+    public AzureProxySession(
+            ConnectorFetcher<AzureSession, AzureModel> instance,
+            AzureSessionProxyConfig config,
+            HttpClient httpClient
+    ) {
+        super(instance);
+        this.config = config;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    protected AzureSession preProcessing(Optional<AzureSession> previousSession) {
+
+        HostConfig host = config.getLastObservationHost();
+        logger.info("Getting last observations from {}.", host.getDomain());
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
+                        .addParam("Operation", "GetLastObservations")
+                        .addParam("group", config.getGroup())
+                        .addParam("user", config.getUser())
+                        .build())
+                .build();
+        logger.info("Creating a http request to {}.", request);
+
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
+
+        if (response.isError()) {
+            logger.error("Can not get data from the server {}. Error {} {}",
+                    host.getDomain(), response.getStatus(), response.getBody());
+            return emptySession();
+        }
+
+        logger.debug("Parsing body of the response to the list of class {}.", ObservationInfo.class);
+        Type lastObsType = new TypeToken<Collection<ObservationInfo>>() {}.getType();
+        List<ObservationInfo> lastObservations = jsonToObject(response.getBody(), lastObsType,
+                Tuple.of(OffsetDateTime.class, el -> OffsetDateTime.parse(el, ofPattern("yyyy-MM-dd HH:mm:ssX")))
+        );
+
+        AzureSession session = previousSession.filter(AzureSession::isActive)
+                .orElse(new AzureSession(true));
+        logger.debug("Created a new session of {}.", AzureSession.class);
+
+        logger.debug("Filling the new session of last timestamps from observations per each station.");
+        for (ObservationInfo info : lastObservations) {
+            String azureId = convertIdToAzureId(info.unitId);
+            if (azureId != null) {
+                session.addLastUnitTime(azureId, info.timeStamp);
+            }
+        }
+
+        logger.info("Loaded and successfully parsed {} last observations from stations.", lastObservations.size());
+        return session;
+    }
+
+    @Override
+    protected void postProcessing(Optional<AzureModel> returnedModel, Optional<AzureSession> currentSession) {
+        if (currentSession.isPresent() && currentSession.get().isActive()) {
+            AzureSession session = currentSession.get();
+            // TODO persistence the current session
+        }
+    }
+}

+ 81 - 0
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureSession.java

@@ -0,0 +1,81 @@
+package cz.senslog.connector.fetch.azure;
+
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.time.OffsetDateTime;
+import java.util.*;
+
+import static java.time.OffsetDateTime.MIN;
+
+class AzureSession extends ProxySessionModel {
+
+    static class UnitSession {
+        boolean used;
+
+        OffsetDateTime lastTime;
+
+        public UnitSession(OffsetDateTime lastTime) {
+            this.used = false;
+            this.lastTime = lastTime;
+        }
+    }
+
+    private Map<String, UnitSession> unitSessions;
+
+    public static AzureSession emptySession() {
+        return new AzureSession(false);
+    }
+
+    public AzureSession(boolean isActive) {
+        super(isActive);
+        this.unitSessions = new HashMap<>();
+    }
+
+    public void addLastUnitTime(String unitId, OffsetDateTime unitTime) {
+        String id = unitId.toUpperCase();
+        UnitSession unitSession = unitSessions.get(id);
+        if (unitSession == null) {
+            unitSession = new UnitSession(unitTime);
+            unitSessions.put(id, unitSession);
+        } else if (unitTime.isAfter(unitSession.lastTime)) {
+            unitSession.lastTime = unitTime;
+        }
+    }
+
+    public OffsetDateTime getLastTimeForUnit(String unitId, OffsetDateTime minUnitTime) {
+        OffsetDateTime savedLast = getLastTimeForUnit(unitId);
+        savedLast = savedLast == null ? MIN : savedLast;
+        return savedLast.isBefore(minUnitTime) ? minUnitTime : savedLast;
+    }
+
+    public OffsetDateTime getLastTimeForUnit(String unitId) {
+        String id = unitId.toUpperCase();
+        UnitSession unitSession = this.unitSessions.get(id);
+        if (unitSession != null) {
+            unitSession.used = true;
+            return unitSession.lastTime;
+        }
+
+        return null;
+    }
+
+    public String[] reduceLastUnits(String... validUnits) {
+        Set<String> unitsSet = new HashSet<>(Arrays.asList(validUnits));
+        List<String> removed = new ArrayList<>();
+        Iterator<Map.Entry<String, UnitSession>> unitIterator = unitSessions.entrySet().iterator();
+        while (unitIterator.hasNext()) {
+            Map.Entry<String, UnitSession> entry = unitIterator.next();
+            if (unitsSet.contains(entry.getKey())) {
+                removed.add(entry.getKey());
+                unitIterator.remove();
+            }
+        }
+        return removed.toArray(new String[0]);
+    }
+
+    @Override
+    public String toString() {
+        boolean isEmpty = !isActive() && unitSessions.isEmpty();
+        return "AzureSession." + (isEmpty ? "empty" : "active");
+    }
+}

+ 29 - 0
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureSessionProxyConfig.java

@@ -0,0 +1,29 @@
+package cz.senslog.connector.fetch.azure;
+
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.connector.model.config.PropertyConfig;
+
+public class AzureSessionProxyConfig {
+
+    private final HostConfig lastObservationHost;
+    private final String user;
+    private final String group;
+
+    public AzureSessionProxyConfig(PropertyConfig config) {
+        this.lastObservationHost = new HostConfig(config.getPropertyConfig("lastObservationHost"));
+        this.user = config.getStringProperty("user");
+        this.group = config.getStringProperty("group");
+    }
+
+    public HostConfig getLastObservationHost() {
+        return lastObservationHost;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+}

+ 22 - 8
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/ConnectorFetchAzureProvider.java

@@ -5,12 +5,12 @@ import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
 import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.fetch.api.ExecutableFetcher;
 import cz.senslog.connector.fetch.azure.auth.AzureAuthConfig;
-import cz.senslog.connector.fetch.azure.auth.AzureAuthenticationService;
+import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
 import cz.senslog.connector.model.azure.AzureModel;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import static cz.senslog.connector.fetch.azure.auth.AzureAuthenticationService.newAuthService;
+import static cz.senslog.connector.fetch.azure.auth.AuthenticationService.newAuthService;
 import static cz.senslog.common.http.HttpClient.newHttpClient;
 
 /**
@@ -26,23 +26,37 @@ public final class ConnectorFetchAzureProvider implements ConnectorFetchProvider
     private static Logger logger = LogManager.getLogger(ConnectorFetchAzureProvider.class);
 
     @Override
-    public ExecutableFetcher<AzureModel> createExecutableFetcher(DefaultConfig config) {
+    public ExecutableFetcher<AzureModel> createExecutableFetcher(DefaultConfig defaultConfig) {
         logger.info("Initialization a new fetch provider {}.", ConnectorFetchAzureProvider.class);
 
         logger.debug("Creating a new configuration.");
-        AzureConfig azureConfig = new AzureConfig(config);
+        AzureConfig config = new AzureConfig(defaultConfig);
         logger.info("Configuration for {} was created successfully.", ConnectorFetcher.class);
 
         logger.debug("Getting a configuration for authentication.");
-        AzureAuthConfig authConfig = azureConfig.getAuthentication();
+        AzureAuthConfig authConfig = config.getAuthentication();
 
         logger.info("Initialization a new Azure authentication service.");
-        AzureAuthenticationService authService = newAuthService(authConfig, newHttpClient());
+        AuthenticationService authService = newAuthService(authConfig, newHttpClient());
 
         logger.debug("Creating a new instance of {}.", AzureFetcher.class);
-        AzureFetcher fetcher = new AzureFetcher(azureConfig, authService, newHttpClient());
+        AzureFetcher fetcher = new AzureFetcher(config, authService, newHttpClient());
         logger.info("Fetcher for {} was created successfully.", AzureFetcher.class);
 
-        return ExecutableFetcher.create(fetcher);
+        logger.debug("Getting a configuration for proxy session.");
+        AzureSessionProxyConfig proxyConfig = config.getSessionProxy();
+
+        ExecutableFetcher<AzureModel> executor;
+        if (proxyConfig != null) {
+            logger.debug("Creating a new instance of {}.", AzureProxySession.class);
+            AzureProxySession proxySession = new AzureProxySession(fetcher, proxyConfig, newHttpClient());
+            logger.info("Fetcher session for {} was created successfully.", AzureProxySession.class);
+            executor = ExecutableFetcher.createWithProxySession(proxySession);
+        } else {
+            executor = ExecutableFetcher.create(fetcher);
+        }
+        logger.info("Fetcher executor for {} was created successfully.", AzureFetcher.class);
+
+        return executor;
     }
 }

+ 5 - 5
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AzureAuthenticationService.java → connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AuthenticationService.java

@@ -30,9 +30,9 @@ import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
  * @version 1.0
  * @since 1.0
  */
-public class AzureAuthenticationService {
+public class AuthenticationService {
 
-    private static Logger logger = LogManager.getLogger(AzureAuthenticationService.class);
+    private static Logger logger = LogManager.getLogger(AuthenticationService.class);
 
 
     /** Default period value if token can not be refreshed  */
@@ -55,8 +55,8 @@ public class AzureAuthenticationService {
      * @param httpClient - http client.
      * @return new instance of {@code AzureAuthenticationService}.
      */
-    public static AzureAuthenticationService newAuthService(AzureAuthConfig authConfig, HttpClient httpClient) {
-        return new AzureAuthenticationService(authConfig, httpClient);
+    public static AuthenticationService newAuthService(AzureAuthConfig authConfig, HttpClient httpClient) {
+        return new AuthenticationService(authConfig, httpClient);
     }
 
     /**
@@ -64,7 +64,7 @@ public class AzureAuthenticationService {
      * @param authConfig - authentication configuration.
      * @param httpClient - http client.
      */
-    private AzureAuthenticationService(AzureAuthConfig authConfig, HttpClient httpClient) {
+    private AuthenticationService(AzureAuthConfig authConfig, HttpClient httpClient) {
         this.authConfig = authConfig;
         this.httpClient = httpClient;
     }

+ 1 - 1
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AzureAuthConfig.java

@@ -8,7 +8,7 @@ import java.util.Optional;
 import static java.util.Optional.ofNullable;
 
 /**
- * The class {@code AzureAuthConfig} represents a configuration class for the {@link AzureAuthenticationService}.
+ * The class {@code AzureAuthConfig} represents a configuration class for the {@link AuthenticationService}.
  *
  * @author Lukas Cerny
  * @version 1.0

+ 83 - 45
connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/AzureFetcherTest.java

@@ -1,35 +1,35 @@
-/*
+
 package cz.senslog.connector.fetch.azure;
 
 
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
+import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.connector.fetch.azure.auth.AzureAuthenticationService;
 import cz.senslog.common.http.HttpClient;
 import cz.senslog.common.http.HttpCode;
 import cz.senslog.common.http.HttpRequest;
 import cz.senslog.common.http.HttpResponse;
 import cz.senslog.connector.model.azure.AzureModel;
-import cz.senslog.connector.model.azure.SensorData;
 import cz.senslog.connector.model.azure.SensorInfo;
+import cz.senslog.connector.model.config.PropertyConfig;
+import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
 import org.junit.jupiter.api.Test;
 import org.mockito.stubbing.Answer;
 
 import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
+import static cz.senslog.common.http.HttpClient.newHttpClient;
+import static cz.senslog.common.http.HttpClient.newHttpSSLClient;
 import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
 import static cz.senslog.common.json.BasicJson.objectToJson;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 class AzureFetcherTest {
 
-
     @Test
     void fetch_GetSensorsAndData_OneSensorOneData() throws Exception {
 
@@ -40,27 +40,30 @@ class AzureFetcherTest {
             if (path.equals("/sensorInfo")) {
                 SensorInfo sensorInfo = new SensorInfo();
                 sensorInfo.setEui("1234");
+                sensorInfo.setStatus(1);
                 List<SensorInfo> bodyList = new ArrayList<>();
                 bodyList.add(sensorInfo);
                 return HttpResponse.newBuilder().status(HttpCode.OK)
                         .body(objectToJson(bodyList)).build();
             } else if (path.equals("/sensorData")) {
-                List<SensorData> sensorDataList = new ArrayList<>();
-                SensorData data = new SensorData();
-                data.setTime(ZonedDateTime.of(LocalDateTime.MAX, ZoneOffset.UTC));
-                data.setTemperature(25.5F);
-                sensorDataList.add(data);
-                SensorInfo sensorInfo = new SensorInfo();
-                sensorInfo.setData(sensorDataList);
-                sensorInfo.setEui("1234");
+                Map<String, Object> dataObj = new HashMap<>();
+                dataObj.put("temperature", 25.5F);
+                dataObj.put("time", "1970-01-01T00:00:00+00:00");
+                List<Object> data = new ArrayList<>();
+                data.add(dataObj);
+                Map<String, Object> sensorDataJson = new HashMap<>();
+                sensorDataJson.put("eui", "1234");
+                sensorDataJson.put("name", "name");
+                sensorDataJson.put("data", data);
+
                 return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson(sensorInfo)).build();
+                        .body(objectToJson(sensorDataJson)).build();
             }
             return HttpResponse.newBuilder().status(SERVER_ERROR).build();
 
         });
 
-        AzureAuthenticationService authService = mock(AzureAuthenticationService.class);
+        AuthenticationService authService = mock(AuthenticationService.class);
         when(authService.getAccessToken()).thenReturn("#12345");
 
         AzureConfig config = mock(AzureConfig.class);
@@ -72,11 +75,9 @@ class AzureFetcherTest {
 
         fetcher.init();
 
-        AzureModel model = fetcher.fetch();
+        AzureModel model = fetcher.fetch(Optional.empty());
 
         assertNotNull(model.getSensors());
-        assertEquals(LocalDateTime.MIN, model.getFrom());
-        assertEquals(LocalDateTime.MAX, model.getTo());
         assertEquals(1, model.getSensors().size());
 
         SensorInfo sensorInfo = model.getSensors().get(0);
@@ -100,7 +101,7 @@ class AzureFetcherTest {
 
         });
 
-        AzureAuthenticationService authService = mock(AzureAuthenticationService.class);
+        AuthenticationService authService = mock(AuthenticationService.class);
         when(authService.getAccessToken()).thenReturn("#12345");
 
         AzureConfig config = mock(AzureConfig.class);
@@ -112,11 +113,9 @@ class AzureFetcherTest {
 
         assertThrows(Exception.class, fetcher::init);
 
-        AzureModel model = fetcher.fetch();
+        AzureModel model = fetcher.fetch(Optional.empty());
 
         assertNotNull(model.getSensors());
-        assertEquals(LocalDateTime.MIN, model.getFrom());
-        assertEquals(LocalDateTime.MIN, model.getTo());
         assertEquals(0, model.getSensors().size());
     }
 
@@ -134,7 +133,7 @@ class AzureFetcherTest {
 
         });
 
-        AzureAuthenticationService authService = mock(AzureAuthenticationService.class);
+        AuthenticationService authService = mock(AuthenticationService.class);
         when(authService.getAccessToken()).thenReturn("#12345");
 
         AzureConfig config = mock(AzureConfig.class);
@@ -146,11 +145,9 @@ class AzureFetcherTest {
 
         assertThrows(Exception.class, fetcher::init);
 
-        AzureModel model = fetcher.fetch();
+        AzureModel model = fetcher.fetch(Optional.empty());
 
         assertNotNull(model.getSensors());
-        assertEquals(LocalDateTime.MIN, model.getFrom());
-        assertEquals(LocalDateTime.MIN, model.getTo());
         assertEquals(0, model.getSensors().size());
     }
 
@@ -164,6 +161,7 @@ class AzureFetcherTest {
             if (path.equals("/sensorInfo")) {
                 SensorInfo sensorInfo = new SensorInfo();
                 sensorInfo.setEui("1234");
+                sensorInfo.setStatus(1);
                 List<SensorInfo> bodyList = new ArrayList<>();
                 bodyList.add(sensorInfo);
                 return HttpResponse.newBuilder().status(HttpCode.OK)
@@ -175,7 +173,7 @@ class AzureFetcherTest {
 
         });
 
-        AzureAuthenticationService authService = mock(AzureAuthenticationService.class);
+        AuthenticationService authService = mock(AuthenticationService.class);
         when(authService.getAccessToken()).thenReturn("#12345");
 
         AzureConfig config = mock(AzureConfig.class);
@@ -187,11 +185,9 @@ class AzureFetcherTest {
 
         fetcher.init();
 
-        AzureModel model = fetcher.fetch();
+        AzureModel model = fetcher.fetch(Optional.empty());
 
         assertNotNull(model.getSensors());
-        assertEquals(LocalDateTime.MIN, model.getFrom());
-        assertEquals(LocalDateTime.MIN, model.getTo());
         assertEquals(1, model.getSensors().size());
 
         SensorInfo sensorInfo = model.getSensors().get(0);
@@ -213,7 +209,7 @@ class AzureFetcherTest {
             return HttpResponse.newBuilder().status(SERVER_ERROR).build();
         });
 
-        AzureAuthenticationService authService = mock(AzureAuthenticationService.class);
+        AuthenticationService authService = mock(AuthenticationService.class);
         when(authService.getAccessToken()).thenReturn("#12345");
 
         AzureConfig config = mock(AzureConfig.class);
@@ -225,11 +221,9 @@ class AzureFetcherTest {
 
         assertThrows(Exception.class, fetcher::init);
 
-        AzureModel model = fetcher.fetch();
+        AzureModel model = fetcher.fetch(Optional.empty());
 
         assertNotNull(model.getSensors());
-        assertEquals(LocalDateTime.MIN, model.getFrom());
-        assertEquals(LocalDateTime.MIN, model.getTo());
         assertEquals(0, model.getSensors().size());
     }
 
@@ -243,6 +237,7 @@ class AzureFetcherTest {
             if (path.equals("/sensorInfo")) {
                 SensorInfo sensorInfo = new SensorInfo();
                 sensorInfo.setEui("1234");
+                sensorInfo.setStatus(1);
                 List<SensorInfo> bodyList = new ArrayList<>();
                 bodyList.add(sensorInfo);
                 return HttpResponse.newBuilder().status(HttpCode.OK)
@@ -254,7 +249,7 @@ class AzureFetcherTest {
             return HttpResponse.newBuilder().status(SERVER_ERROR).build();
         });
 
-        AzureAuthenticationService authService = mock(AzureAuthenticationService.class);
+        AuthenticationService authService = mock(AuthenticationService.class);
         when(authService.getAccessToken()).thenReturn("#12345");
 
         AzureConfig config = mock(AzureConfig.class);
@@ -266,14 +261,57 @@ class AzureFetcherTest {
 
         fetcher.init();
 
-        AzureModel model = fetcher.fetch();
+        AzureModel model = fetcher.fetch(Optional.empty());
 
         assertNotNull(model.getSensors());
-        assertEquals(LocalDateTime.MIN, model.getFrom());
-        assertEquals(LocalDateTime.MIN, model.getTo());
         assertEquals(1, model.getSensors().size());
 
         assertEquals(0, model.getSensors().get(0).getData().size());
     }
-}
- */
+
+    @Test
+    void fetch() throws Exception {
+
+        DefaultConfig defConfig = new DefaultConfig("", null);
+
+        defConfig.setProperty("startDate", LocalDateTime.of(2020, 1, 1, 0, 0));
+        defConfig.setProperty("limitPerSensor", 1);
+        Map<String, String> infoHost = new HashMap<>();
+        infoHost.put("domain", "https://iotlorawan.azurewebsites.net"); infoHost.put("path", "api/sensors");
+        defConfig.setProperty("sensorInfoHost", infoHost);
+        Map<String, String> dataHost = new HashMap<>();
+        dataHost.put("domain", "https://iotlorawan.azurewebsites.net"); dataHost.put("path", "api/sensordata");
+        defConfig.setProperty("sensorDataHost", dataHost);
+
+        Map<String, Object> authConfig = new HashMap<>();
+        authConfig.put("username", "netluky@ima.cz");
+        authConfig.put("password", "SensLogIMA1");
+        authConfig.put("refreshPeriodIfFail", 1000);
+        Map<String, String> host = new HashMap<>();
+        host.put("domain", "https://iotlorawan.azurewebsites.net"); host.put("path", "api/accounts/login");
+        authConfig.put("host", host);
+
+        defConfig.setProperty("authentication", authConfig);
+
+        Map<String, Object> sessionProxy = new HashMap<>();
+        sessionProxy.put("lastObservationHost", new HashMap<String, String>() {{
+            put("domain", "http://51.15.45.95:8080/senslog1");
+            put("path", "SensorService");
+        }});
+        sessionProxy.put("user", "vilcini");
+        sessionProxy.put("group", "vilcini");
+        defConfig.setProperty("sessionProxy", sessionProxy);
+
+        AzureConfig config = new AzureConfig(defConfig);
+        AuthenticationService authService = AuthenticationService.newAuthService(config.getAuthentication(), newHttpSSLClient());
+        AzureFetcher fetcher = new AzureFetcher(config, authService, newHttpClient());
+        AzureProxySession proxySession = new AzureProxySession(fetcher, config.getSessionProxy(), newHttpClient());
+
+        ExecutableFetcher<AzureModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
+        executableFetcher.getRawFetcher().init();
+
+        executableFetcher.execute();
+        executableFetcher.execute();
+
+    }
+}

+ 8 - 8
connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/auth/AzureAuthenticationServiceTest.java → connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/auth/AuthenticationServiceTest.java

@@ -21,7 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-class AzureAuthenticationServiceTest {
+class AuthenticationServiceTest {
 
     @Test
     void getAccessToken_Valid_Response_True() {
@@ -49,7 +49,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }
@@ -84,7 +84,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }
@@ -124,7 +124,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }
@@ -162,7 +162,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }
@@ -199,7 +199,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }
@@ -237,7 +237,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }
@@ -274,7 +274,7 @@ class AzureAuthenticationServiceTest {
         }});
         AzureAuthConfig authConfig = new AzureAuthConfig(defaultConfig);
 
-        AzureAuthenticationService service = AzureAuthenticationService.newAuthService(authConfig, httpClient);
+        AuthenticationService service = AuthenticationService.newAuthService(authConfig, httpClient);
 
         assertEquals("#12345", service.getAccessToken());
     }

+ 0 - 9
connector-fetch-azure/src/test/resources/schema/sensorInfoSchema.json

@@ -12,15 +12,6 @@
     "description": "Full description of each sensor",
     "required": [
       "eui",
-      "acronym",
-      "name",
-      "period",
-      "tolerance",
-      "location",
-      "description",
-      "sensorDataId",
-      "sensorDataTime",
-      "created",
       "status"
     ],
     "properties": {

+ 17 - 0
connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateConfig.java

@@ -5,6 +5,11 @@ import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.connector.fetch.fieldclimate.auth.AuthConfig;
 
 import java.time.LocalDateTime;
+import java.time.OffsetTime;
+import java.time.ZoneOffset;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
 
 public class FieldClimateConfig {
 
@@ -15,6 +20,8 @@ public class FieldClimateConfig {
     private final AuthConfig authentication;
     private final Integer period;
     private final FieldClimateSessionProxyConfig sessionProxy;
+    private final Set<String> blockedStations;
+    private final TimeZone timeZone;
 
     public FieldClimateConfig(DefaultConfig config) {
         this.startDate = config.getLocalDateTimeProperty("startDate");
@@ -23,6 +30,8 @@ public class FieldClimateConfig {
         this.stationTimeRangeHost = new HostConfig(config.getPropertyConfig("stationTimeRangeHost"));
         this.authentication = new AuthConfig(config.getPropertyConfig("authentication"));
         this.period = config.getIntegerProperty("period");
+        this.blockedStations = config.getSetProperty("blockedStations", String.class);
+        this.timeZone = TimeZone.getTimeZone(config.getStringProperty("timeZone"));
 
         this.sessionProxy = config.containsProperty("sessionProxy") ?
                 new FieldClimateSessionProxyConfig(config.getPropertyConfig("sessionProxy")) : null;
@@ -52,6 +61,14 @@ public class FieldClimateConfig {
         return period;
     }
 
+    public Set<String> getBlockedStations() {
+        return blockedStations;
+    }
+
+    public TimeZone getTimeZone() {
+        return timeZone;
+    }
+
     public FieldClimateSessionProxyConfig getSessionProxy() {
         return sessionProxy;
     }

+ 36 - 25
connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcher.java

@@ -19,18 +19,20 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.lang.reflect.Type;
-import java.time.LocalDateTime;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
-import static cz.senslog.common.http.ContentType.APPLICATION_JSON;
+import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
 import static cz.senslog.common.http.HttpHeader.*;
 import static cz.senslog.common.http.HttpMethod.GET;
 import static cz.senslog.common.json.BasicJson.jsonToObject;
 import static cz.senslog.common.util.StringUtils.isBlank;
 import static java.lang.String.format;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
 import static java.time.ZoneOffset.UTC;
-import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
-import static java.time.format.DateTimeFormatter.ofPattern;
+import static java.time.format.DateTimeFormatter.*;
 import static java.util.Collections.emptyList;
 import static org.apache.http.client.utils.DateUtils.formatDate;
 
@@ -93,7 +95,13 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
             throw logger.throwing(new Exception("Received empty list of stations."));
         }
 
-        stationInfos = stations;
+        stationInfos = new ArrayList<>(stations.size());
+        for (StationInfo station : stations) { // TODO change to whitelist
+            if (!config.getBlockedStations().contains(station.getName().getOriginal())) {
+                stationInfos.add(station);
+            }
+        }
+
         logger.info("{} stations were loaded.", stationInfos.size());
         logger.info(stationInfos.toString());
     }
@@ -103,13 +111,12 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
         assert config != null; assert  authService != null; assert  httpClient != null;
 
         FieldClimateSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
+        OffsetDateTime startDate = ZonedDateTime.of(config.getStartDate(), config.getTimeZone().toZoneId()).toOffsetDateTime();
 
+        OffsetDateTime globalFrom = MAX, globalTo = MIN;
         List<StationData> stationDataList = new ArrayList<>(stationInfos.size());
         int totalFetched = 0;
 
-        LocalDateTime globalFrom = LocalDateTime.MAX;
-        LocalDateTime globalTo = LocalDateTime.MIN;
-
         for (StationInfo station : stationInfos) {
 
             String stationId = station.getName().getOriginal();
@@ -120,19 +127,20 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
             }
 
             logger.debug("Getting a start date time for station {}.", stationId);
-            LocalDateTime localFrom = session.getLastTimeForUnit(stationId, config.getStartDate());
+            OffsetDateTime localFrom = session.getLastUnitTime(stationId, startDate);
 
             StationTimeRange timeRange = getTimeRangeForStation(stationId);
             if (timeRange == null) continue;
 
-            if (localFrom.isBefore(timeRange.getMin_date())) {
-                localFrom = timeRange.getMin_date();
+            OffsetDateTime stationMin = ZonedDateTime.of(timeRange.getMin_date(), config.getTimeZone().toZoneId()).toOffsetDateTime();
+            if (localFrom.isBefore(stationMin)) {
+                localFrom = stationMin;
             }
 
-            LocalDateTime localTo = localFrom.plusHours(config.getPeriod());
-
-            if (localTo.isAfter(timeRange.getMax_date())) {
-                localTo = timeRange.getMax_date();
+            OffsetDateTime localTo = localFrom.plusHours(config.getPeriod());
+            OffsetDateTime stationMax = ZonedDateTime.of(timeRange.getMax_date(), config.getTimeZone().toZoneId()).toOffsetDateTime();
+            if (localTo.isAfter(stationMax)) {
+                localTo = stationMax;
             }
 
             if (localFrom.equals(localTo)) {
@@ -146,8 +154,8 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
             logger.info("Creating a http request to {}.", stationDataHost);
             String path = stationDataHost.getPath()
                     .replace("{station_id}", stationId)
-                    .replace("{from}", String.valueOf(localFrom.toEpochSecond(UTC)))
-                    .replace("{to}", String.valueOf(localTo.toEpochSecond(UTC)));
+                    .replace("{from}", String.valueOf(localFrom.toLocalDateTime().toEpochSecond(UTC)))
+                    .replace("{to}", String.valueOf(localTo.toLocalDateTime().toEpochSecond(UTC)));
 
             String requestDate = formatDate(new Date());
             String authToken = authService.getAccessToken(GET.name() + path + requestDate);
@@ -177,11 +185,14 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
 
                 stationData.setId(stationId);
 
-                LocalDateTime latestTimestamp = LocalDateTime.MIN;
+                OffsetDateTime latestTimestamp = OffsetDateTime.MIN;
                 for (Map<String, String> sensorDataMap : stationData.getData()) {
                     String strDate = sensorDataMap.getOrDefault("date", "");
-                    LocalDateTime timestamp = LocalDateTime.parse(strDate, ofPattern("yyyy-MM-dd HH:mm:ss"));
-                    sensorDataMap.put("date", timestamp.format(ISO_DATE_TIME));
+                    final DateTimeFormatter formatter = ofPattern("yyyy-MM-dd HH:mm:ss");
+                    LocalDateTime date = LocalDateTime.parse(strDate, formatter);
+                    ZonedDateTime zonedDate = ZonedDateTime.of(date, config.getTimeZone().toZoneId());
+                    OffsetDateTime timestamp = zonedDate.toOffsetDateTime();
+                    sensorDataMap.put("date", timestamp.format(ISO_OFFSET_DATE_TIME));
 
                     if (latestTimestamp.isBefore(timestamp)) {
                         latestTimestamp = timestamp;
@@ -209,7 +220,7 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
             session.addLastUnitTime(stationId, localTo);
 
             if (globalFrom.isAfter(localFrom)) {
-                globalFrom = localFrom;
+                  globalFrom = localFrom;
             }
 
             if (globalTo.isBefore(localTo)) {
@@ -219,15 +230,15 @@ public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession
 
         FieldClimateModel model;
         if (totalFetched != 0) {
-            String fromStr = globalFrom.format(ISO_DATE_TIME);
-            String toStr = globalTo.format(ISO_DATE_TIME);
-            logger.info("Fetched {} records from {} to {}.", totalFetched, fromStr, toStr);
+
+            logger.info("Fetched {} records from {} to {}.",
+                    totalFetched, globalFrom.format(ISO_OFFSET_DATE_TIME), globalTo.format(ISO_OFFSET_DATE_TIME));
 
             model = new FieldClimateModel(stationDataList, globalFrom, globalTo);
 
         } else {
             logger.warn("No data was fetched.");
-            LocalDateTime now = LocalDateTime.now();
+            OffsetDateTime now = OffsetDateTime.now();
             model = new FieldClimateModel(emptyList(), now, now);
         }
 

+ 5 - 7
connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySession.java

@@ -14,8 +14,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.lang.reflect.Type;
-import java.time.LocalDateTime;
-import java.time.ZonedDateTime;
+import java.time.OffsetDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
@@ -23,14 +22,14 @@ import java.util.Optional;
 import static cz.senslog.connector.fetch.fieldclimate.FieldClimateSession.emptySession;
 import static cz.senslog.common.json.BasicJson.jsonToObject;
 import static cz.senslog.connector.model.converter.FieldClimateUnitConverter.convertIdToFieldClimateId;
-import static java.time.ZonedDateTime.parse;
+import static java.time.OffsetDateTime.parse;
 import static java.time.format.DateTimeFormatter.ofPattern;
 
 public class FieldClimateProxySession extends FetchProxySession<FieldClimateSession, FieldClimateModel> {
 
     private static Logger logger = LogManager.getLogger(FieldClimateProxySession.class);
 
-    protected static class ObservationInfo { ZonedDateTime timeStamp; Long unitId; }
+    protected static class ObservationInfo { OffsetDateTime timeStamp; Long unitId; }
 
     private final HttpClient httpClient;
     private final FieldClimateSessionProxyConfig config;
@@ -72,7 +71,7 @@ public class FieldClimateProxySession extends FetchProxySession<FieldClimateSess
         logger.debug("Parsing body of the response to the list of class {}.", ObservationInfo.class);
         Type lastObsType = new TypeToken<Collection<ObservationInfo>>() {}.getType();
         List<ObservationInfo> lastObservations = jsonToObject(response.getBody(), lastObsType,
-                Tuple.of(ZonedDateTime.class, el -> parse(el+"00",  ofPattern("yyyy-MM-dd HH:mm:ssZ")))
+                Tuple.of(OffsetDateTime.class, el -> parse(el, ofPattern("yyyy-MM-dd HH:mm:ssX")))
         );
 
         FieldClimateSession session = previousSession.filter(FieldClimateSession::isActive)
@@ -81,9 +80,8 @@ public class FieldClimateProxySession extends FetchProxySession<FieldClimateSess
 
         logger.debug("Filling the new session of last timestamps from observations per each station.");
         for (ObservationInfo info : lastObservations) {
-            LocalDateTime lastUnit = info.timeStamp.toLocalDateTime();
             String fcUnitId = convertIdToFieldClimateId(info.unitId);
-            session.addLastUnitTime(fcUnitId, lastUnit);
+            session.addLastUnitTime(fcUnitId, info.timeStamp);
         }
 
         logger.info("Loaded and successfully parsed {} last observations from stations.", lastObservations.size());

+ 7 - 7
connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateSession.java

@@ -2,19 +2,19 @@ package cz.senslog.connector.fetch.fieldclimate;
 
 import cz.senslog.connector.model.api.ProxySessionModel;
 
-import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.*;
 
-import static java.time.LocalDateTime.MIN;
+import static java.time.OffsetDateTime.MIN;
 
 public class FieldClimateSession extends ProxySessionModel {
 
     static class UnitSession {
         boolean used;
 
-        LocalDateTime lastTime;
+        OffsetDateTime lastTime;
 
-        public UnitSession(LocalDateTime lastTime) {
+        public UnitSession(OffsetDateTime lastTime) {
             this.used = false;
             this.lastTime = lastTime;
         }
@@ -31,7 +31,7 @@ public class FieldClimateSession extends ProxySessionModel {
         this.unitSessions = new HashMap<>();
     }
 
-    public void addLastUnitTime(String unitId, LocalDateTime unitTime) {
+    public void addLastUnitTime(String unitId, OffsetDateTime unitTime) {
         String id = unitId.toUpperCase();
         UnitSession unitSession = unitSessions.get(id);
         if (unitSession == null) {
@@ -42,10 +42,10 @@ public class FieldClimateSession extends ProxySessionModel {
         }
     }
 
-    public LocalDateTime getLastTimeForUnit(String unitId, LocalDateTime minUnitTime) {
+    public OffsetDateTime getLastUnitTime(String unitId, OffsetDateTime minUnitTime) {
         String id = unitId.toUpperCase();
         UnitSession unitSession = this.unitSessions.get(id);
-        LocalDateTime savedLast = MIN;
+        OffsetDateTime savedLast = MIN;
         if (unitSession != null) {
             unitSession.used = true;
             savedLast = unitSession.lastTime;

+ 2 - 0
connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/StationTimeRange.java

@@ -1,6 +1,8 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
 import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
 
 import static cz.senslog.common.json.BasicJson.objectToJson;
 

+ 71 - 50
connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcherTest.java

@@ -1,25 +1,27 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
 import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
 import cz.senslog.common.http.HttpClient;
 import cz.senslog.common.http.HttpRequest;
 import cz.senslog.common.http.HttpResponse;
+import cz.senslog.connector.model.converter.FieldClimateModelSenslogV1ModelConverter;
 import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
 import cz.senslog.connector.model.fieldclimate.SensorDataInfo;
 import cz.senslog.connector.model.fieldclimate.StationData;
 import cz.senslog.connector.model.fieldclimate.StationInfo;
+import cz.senslog.connector.model.v1.SenslogV1Model;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.mockito.stubbing.Answer;
 
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
+import static cz.senslog.common.http.HttpClient.newHttpClient;
+import static cz.senslog.common.http.HttpClient.newHttpSSLClient;
 import static cz.senslog.common.http.HttpCode.OK;
 import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
 import static java.lang.String.format;
@@ -61,6 +63,7 @@ class FieldClimateFetcherTest {
             put("privateKey", "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b");
         }});
 
+
         fcDConfig.setProperty("sessionProxy", new HashMap<String, Object>(){{
             put("user", "<username>");
             put("group", "<group>");
@@ -70,6 +73,13 @@ class FieldClimateFetcherTest {
             }});
         }});
 
+        fcDConfig.setProperty("blockedStations", new ArrayList<String>(){{
+            add("#123");
+            add("#456");
+        }});
+
+        fcDConfig.setProperty("timeZone", "Europe/Riga");
+
         fcDConfig.setProperty("startDate", startDate);
         fcDConfig.setProperty("period", 12);
 
@@ -156,16 +166,6 @@ class FieldClimateFetcherTest {
         FieldClimateModel model = fetcher.fetch(Optional.empty());
 
         assertTrue(model.getStations().isEmpty());
-        LocalDateTime now = LocalDateTime.now();
-        LocalDateTime expectationNow = LocalDateTime.of(now.getYear(), now.getMonth(), now.getDayOfMonth(), now.getHour(), now.getMinute());
-
-        LocalDateTime nowFrom = model.getFrom();
-        LocalDateTime actualFrom = LocalDateTime.of(nowFrom.getYear(), nowFrom.getMonth(), nowFrom.getDayOfMonth(), nowFrom.getHour(), nowFrom.getMinute());
-        assertEquals(expectationNow, actualFrom);
-
-        LocalDateTime nowTo = model.getTo();
-        LocalDateTime actualTo = LocalDateTime.of(nowTo.getYear(), nowTo.getMonth(), nowTo.getDayOfMonth(), nowTo.getHour(), nowTo.getMinute());
-        assertEquals(expectationNow, actualTo);
     }
 
     @Test
@@ -360,8 +360,8 @@ class FieldClimateFetcherTest {
 
         FieldClimateModel model = fetcher.fetch(Optional.empty());
 
-        assertEquals(startDate.plusHours(1), model.getFrom());
-        assertEquals(startDate.plusHours(2), model.getTo());
+        assertEquals(startDate.plusHours(1), model.getFrom().toLocalDateTime());
+        assertEquals(startDate.plusHours(2), model.getTo().toLocalDateTime());
 
         assertEquals(1, model.getStations().size());
 
@@ -422,33 +422,22 @@ class FieldClimateFetcherTest {
         fetcher.init();
 
         FieldClimateModel model1 = fetcher.fetch(Optional.empty());
-        assertEquals(startDate, model1.getFrom());
-        assertEquals(startDate.plusHours(config.getPeriod()), model1.getTo());
+        assertEquals(startDate, model1.getFrom().toLocalDateTime());
+        assertEquals(startDate.plusHours(config.getPeriod()), model1.getTo().toLocalDateTime());
 
         FieldClimateModel model2 = fetcher.fetch(Optional.empty());
-        assertEquals(startDate.plusHours(config.getPeriod()), model2.getFrom());
-        assertEquals(startDate.plusHours(2*config.getPeriod()), model2.getTo());
+        assertEquals(startDate.plusHours(config.getPeriod()), model2.getFrom().toLocalDateTime());
+        assertEquals(startDate.plusHours(2*config.getPeriod()), model2.getTo().toLocalDateTime());
 
         FieldClimateModel model3 = fetcher.fetch(Optional.empty());
-        LocalDateTime now = LocalDateTime.now();
-        LocalDateTime expectationNow = LocalDateTime.of(now.getYear(), now.getMonth(), now.getDayOfMonth(), now.getHour(), now.getMinute());
-
-        LocalDateTime nowFrom = model3.getFrom();
-        LocalDateTime actualFrom = LocalDateTime.of(nowFrom.getYear(), nowFrom.getMonth(), nowFrom.getDayOfMonth(), nowFrom.getHour(), nowFrom.getMinute());
-        assertEquals(expectationNow, actualFrom);
-
-        LocalDateTime nowTo = model3.getTo();
-        LocalDateTime actualTo = LocalDateTime.of(nowTo.getYear(), nowTo.getMonth(), nowTo.getDayOfMonth(), nowTo.getHour(), nowTo.getMinute());
-        assertEquals(expectationNow, actualTo);
     }
 
-
     /*
     @Test
     void proxyFetch() throws Exception {
 
         DefaultConfig propertyConfig = new DefaultConfig("proxySession", null);
-        propertyConfig.setProperty("lastObservationHost", new HashMap<String, String>(){{
+        propertyConfig.setProperty("lastObservationHost", new HashMap<String, String>() {{
             put("domain", "http://51.15.45.95:8080/senslog1");
             put("path", "SensorService");
         }});
@@ -466,39 +455,71 @@ class FieldClimateFetcherTest {
         executableFetcher.execute();
         executableFetcher.execute();
     }
-    */
 
-    /*
+     */
+
     @Test
     void fetch() throws Exception {
 
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, HttpClient.newHttpSSLClient());
+        DefaultConfig fcDConfig = new DefaultConfig("fcConfig", null);
+        fcDConfig.setProperty("stationsHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/user/stations");
+        }});
 
-        fetcher.init();
+        fcDConfig.setProperty("stationDataHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/data/normal/{station_id}/raw/from/{from}/to/{to}");
+        }});
 
-        for (int i = 0; i < 10; i++) {
-            FieldClimateModel model = fetcher.fetch(Optional.empty());
+        fcDConfig.setProperty("stationTimeRangeHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/data/{station_id}");
+        }});
 
-            model.getStations().removeIf(s -> !s.getId().equals("0120821E"));
-            model.getStations().forEach(s -> s.getSensors().removeIf(se -> se.getCode() != 19969));
+        fcDConfig.setProperty("authentication", new HashMap<String, String>(){{
+            put("publicKey", "3737ed4fe98fae975e54991216ed473c8d7db48662deff19");
+            put("privateKey", "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b");
+        }});
 
-            model.getStations().stream().peek(st -> System.out.println(format("%s %s-%s", st.getId(), model.getFrom(), model.getTo()))).forEach(st -> st.getSensors()
-                    .forEach(se -> System.out.println(format("\t%s\t%s\t%s", se.getName(), se.getCode(), se.getCh()))));
+        fcDConfig.setProperty("timeZone", "Europe/Riga");
 
-            SenslogV1Model sModel = new FieldClimateModelSenslogV1ModelConverter().convert(model);
+        fcDConfig.setProperty("blockedStations", new ArrayList<String>(){{
+             add("0120821D");
+             add("0120821E");
+            // add("00208048");
+        }});
 
-            System.out.println("\nObservation");
-            sModel.getObservations().stream().map(s -> (Observation)s).forEach(s -> System.out.println(format("\t%s\t%s", s.getUnitId(), s.getSensorId())));
-        }
+        Map<String, Object> sessionProxy = new HashMap<>();
+        sessionProxy.put("lastObservationHost", new HashMap<String, String>() {{
+            put("domain", "http://51.15.45.95:8080/senslog1");
+            put("path", "SensorService");
+        }});
+        sessionProxy.put("user", "vilcini");
+        sessionProxy.put("group", "vilcini");
+        fcDConfig.setProperty("sessionProxy", sessionProxy);
 
+        fcDConfig.setProperty("startDate", LocalDateTime.of(2020, 7, 1, 7, 45,0));
+        fcDConfig.setProperty("period", 1);
 
-        SenslogV1Model model = new FieldClimateModelSenslogV1ModelConverter().convert(fetcher.fetch(Optional.empty()));
-        Observation obs = (Observation)model.getObservations().get(0);
+        FieldClimateConfig config = new FieldClimateConfig(fcDConfig);
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, HttpClient.newHttpSSLClient());
+        FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, config.getSessionProxy(), newHttpClient());
+
+//        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
+        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.create(fetcher);
 
-        System.out.println(obs);
+        executableFetcher.getRawFetcher().init();
 
+
+        for (int i = 0; i < 10; i++) {
+            FieldClimateModel fieldClimateModel = executableFetcher.execute();
+            final FieldClimateModelSenslogV1ModelConverter converter = new FieldClimateModelSenslogV1ModelConverter();
+            SenslogV1Model senslogV1Model = converter.convert(fieldClimateModel);
+            System.out.println(senslogV1Model);
+        }
     }
-    */
+
 
     /*
     private void saveToCSV(FieldClimateModel model) throws IOException {

+ 10 - 18
connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySessionTest.java

@@ -1,8 +1,5 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
-import com.google.gson.Gson;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializer;
 import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.fetch.api.ExecutableFetcher;
 import cz.senslog.common.http.HttpClient;
@@ -15,15 +12,16 @@ import org.junit.jupiter.api.Test;
 import org.mockito.stubbing.Answer;
 
 import java.time.LocalDateTime;
-import java.time.ZonedDateTime;
+import java.time.OffsetDateTime;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 
 import static cz.senslog.common.http.HttpCode.OK;
 import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
+import static cz.senslog.common.json.BasicJson.objectToJson;
 import static cz.senslog.connector.model.converter.FieldClimateUnitConverter.convertIdToFieldClimateId;
 import static java.time.ZoneOffset.UTC;
-import static java.time.format.DateTimeFormatter.ofPattern;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -78,7 +76,7 @@ class FieldClimateProxySessionTest {
         propertyConfig.setProperty("user", "<user>");
         propertyConfig.setProperty("group", "<group>");
 
-        ZonedDateTime timestamp = ZonedDateTime.of(2020, 1, 1, 1, 1, 0, 0, UTC);
+        OffsetDateTime timestamp = OffsetDateTime.of(LocalDateTime.of(2020, 1, 1, 1, 1, 0, 0), UTC);
         Long unitId = 1234L;
         String stationId = convertIdToFieldClimateId(unitId);
 
@@ -87,16 +85,10 @@ class FieldClimateProxySessionTest {
             HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
             String path = request.getUrl().getPath();
             if (path.contains("SensorService")) {
-                FieldClimateProxySession.ObservationInfo lastObs = new FieldClimateProxySession.ObservationInfo();
-                lastObs.timeStamp = timestamp;
-                lastObs.unitId = unitId;
-
-                Gson gson = new Gson().newBuilder()
-                        .registerTypeAdapter(ZonedDateTime.class, (JsonSerializer<ZonedDateTime>) (zonedDateTime, type, jsonSerializationContext) -> {
-                                String dateTime = zonedDateTime.format(ofPattern("yyyy-MM-dd HH:mm:ssZ"));
-                                return new JsonPrimitive(dateTime.substring(0, dateTime.length()-2));
-                        }).create();
-                String stationsJson = gson.toJson(singletonList(lastObs));
+                Map<String, Object> mapJson = new HashMap<>();
+                mapJson.put("timeStamp", "2020-01-01 01:01:00+00");
+                mapJson.put("unitId", unitId);
+                String stationsJson = objectToJson(singletonList(mapJson));
                 return HttpResponse.newBuilder()
                         .status(OK).body(stationsJson).build();
             }
@@ -108,7 +100,7 @@ class FieldClimateProxySessionTest {
         FieldClimateFetcher fetcher = mock(FieldClimateFetcher.class);
         when(fetcher.fetch(any(Optional.class))).then(a -> {
             FieldClimateSession session = (FieldClimateSession) a.getArgument(0, Optional.class).get();
-            LocalDateTime dateTime = session.getLastTimeForUnit(stationId, LocalDateTime.MIN);
+            OffsetDateTime dateTime = session.getLastUnitTime(stationId, OffsetDateTime.MIN);
             StationData stationData = new StationData();
             stationData.setId(stationId);
             return new FieldClimateModel(singletonList(stationData), dateTime, dateTime);
@@ -122,6 +114,6 @@ class FieldClimateProxySessionTest {
 
         assertEquals(1, model.getStations().size());
         assertEquals(stationId, model.getStations().get(0).getId());
-        assertEquals(timestamp.toLocalDateTime(), model.getFrom());
+        assertEquals(timestamp, model.getFrom());
     }
 }

+ 10 - 10
connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateSessionTest.java

@@ -3,9 +3,9 @@ package cz.senslog.connector.fetch.fieldclimate;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 
-import static java.time.LocalDateTime.MIN;
+import static java.time.OffsetDateTime.MIN;
 import static org.junit.jupiter.api.Assertions.*;
 
 class FieldClimateSessionTest {
@@ -30,7 +30,7 @@ class FieldClimateSessionTest {
     void lastUnitTime_addNewUnit() {
         FieldClimateSession session = new FieldClimateSession(true);
 
-        LocalDateTime actualTime = LocalDateTime.now();
+        OffsetDateTime actualTime = OffsetDateTime.now();
 
         session.addLastUnitTime("#1", actualTime);
         session.addLastUnitTime("#1", actualTime.plusMinutes(1));
@@ -40,8 +40,8 @@ class FieldClimateSessionTest {
         session.addLastUnitTime("#2", actualTime.plusMinutes(2));
         session.addLastUnitTime("#2", actualTime.minusMinutes(2));
 
-        assertEquals(actualTime.plusMinutes(1), session.getLastTimeForUnit("#1", MIN));
-        assertEquals(actualTime.plusMinutes(2), session.getLastTimeForUnit("#2", MIN));
+        assertEquals(actualTime.plusMinutes(1), session.getLastUnitTime("#1", MIN));
+        assertEquals(actualTime.plusMinutes(2), session.getLastUnitTime("#2", MIN));
     }
 
     @Test
@@ -49,7 +49,7 @@ class FieldClimateSessionTest {
 
         FieldClimateSession session = new FieldClimateSession(true);
 
-        LocalDateTime actualTime = LocalDateTime.now();
+        OffsetDateTime actualTime = OffsetDateTime.now();
 
         session.addLastUnitTime("#1", actualTime);
         session.addLastUnitTime("#2", actualTime);
@@ -59,9 +59,9 @@ class FieldClimateSessionTest {
         String[] removedUnits = session.reduceLastUnits("#1", "#2");
         assertEquals(2, removedUnits.length);
 
-        assertEquals(actualTime, session.getLastTimeForUnit("#3", MIN));
-        assertEquals(actualTime, session.getLastTimeForUnit("#4", MIN));
-        assertEquals(MIN, session.getLastTimeForUnit("#1", MIN));
-        assertEquals(MIN, session.getLastTimeForUnit("#2", MIN));
+        assertEquals(actualTime, session.getLastUnitTime("#3", MIN));
+        assertEquals(actualTime, session.getLastUnitTime("#4", MIN));
+        assertEquals(MIN, session.getLastUnitTime("#1", MIN));
+        assertEquals(MIN, session.getLastUnitTime("#2", MIN));
     }
 }

+ 5 - 4
connector-model/src/main/java/cz/senslog/connector/model/api/AbstractModel.java

@@ -1,6 +1,7 @@
 package cz.senslog.connector.model.api;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 
 /**
  * The abstract class {@code AbstractModel} represents a base class
@@ -13,18 +14,18 @@ import java.time.LocalDateTime;
 public abstract class AbstractModel {
 
     /** Time range from until were gotten the data. */
-    private final LocalDateTime from, to;
+    private final OffsetDateTime from, to;
 
-    protected AbstractModel(LocalDateTime from, LocalDateTime to) {
+    protected AbstractModel(OffsetDateTime from, OffsetDateTime to) {
         this.from = from;
         this.to = to;
     }
 
-    public LocalDateTime getFrom() {
+    public OffsetDateTime getFrom() {
         return from;
     }
 
-    public LocalDateTime getTo() {
+    public OffsetDateTime getTo() {
         return to;
     }
 }

+ 2 - 1
connector-model/src/main/java/cz/senslog/connector/model/azure/AzureModel.java

@@ -3,6 +3,7 @@ package cz.senslog.connector.model.azure;
 import cz.senslog.connector.model.api.AbstractModel;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.List;
 
 /**
@@ -24,7 +25,7 @@ public class AzureModel extends AbstractModel {
      * @param from - start of the time range.
      * @param to - end of the time range.
      */
-    public AzureModel(List<SensorInfo> sensors, LocalDateTime from, LocalDateTime to) {
+    public AzureModel(List<SensorInfo> sensors, OffsetDateTime from, OffsetDateTime to) {
         super(from, to);
         this.sensors = sensors;
     }

+ 4 - 3
connector-model/src/main/java/cz/senslog/connector/model/azure/SensorData.java

@@ -3,6 +3,7 @@ package cz.senslog.connector.model.azure;
 import cz.senslog.common.util.NumberUtils;
 import org.apache.logging.log4j.util.BiConsumer;
 
+import java.time.OffsetDateTime;
 import java.time.ZonedDateTime;
 import java.util.HashMap;
 import java.util.Map;
@@ -20,7 +21,7 @@ import static cz.senslog.connector.model.azure.SensorType.*;
 public class SensorData {
 
     /** Timestamp when values were measured. */
-    private ZonedDateTime time;
+    private OffsetDateTime time;
 
     /** Value of temperature in celsius degree (°C). */
     private Float temperature;
@@ -40,11 +41,11 @@ public class SensorData {
     /** Value of battery level. */
     private Float batteryLevel;
 
-    public ZonedDateTime getTime() {
+    public OffsetDateTime getTime() {
         return time;
     }
 
-    public void setTime(ZonedDateTime time) {
+    public void setTime(OffsetDateTime time) {
         this.time = time;
     }
 

+ 4 - 3
connector-model/src/main/java/cz/senslog/connector/model/azure/SensorInfo.java

@@ -1,6 +1,7 @@
 package cz.senslog.connector.model.azure;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.List;
 
 import static cz.senslog.common.json.BasicJson.objectToJson;
@@ -41,7 +42,7 @@ public class SensorInfo {
     private Long sensorDataId;
 
     /**  */
-    private LocalDateTime sensorDataTime;
+    private OffsetDateTime sensorDataTime;
 
     /** Time when sensor was created and started to produce data. */
     private LocalDateTime created;
@@ -119,11 +120,11 @@ public class SensorInfo {
         this.sensorDataId = sensorDataId;
     }
 
-    public LocalDateTime getSensorDataTime() {
+    public OffsetDateTime getSensorDataTime() {
         return sensorDataTime;
     }
 
-    public void setSensorDataTime(LocalDateTime sensorDataTime) {
+    public void setSensorDataTime(OffsetDateTime sensorDataTime) {
         this.sensorDataTime = sensorDataTime;
     }
 

+ 36 - 7
connector-model/src/main/java/cz/senslog/connector/model/config/PropertyConfig.java

@@ -5,11 +5,12 @@ import cz.senslog.common.util.ClassUtils;
 import cz.senslog.common.util.LocalDateTimeUtils;
 
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
 
 import static java.lang.String.format;
+import static java.util.Collections.emptySet;
 import static java.util.Optional.ofNullable;
 
 /**
@@ -110,10 +111,16 @@ public class PropertyConfig {
      * @return localDateTime value.
      */
     public LocalDateTime getLocalDateTimeProperty(String name) {
-        LocalDateTime value = LocalDateTimeUtils.valueOf(getProperty(name));
+        Object object = getProperty(name);
+
+        if (object instanceof LocalDateTime) {
+            return (LocalDateTime) object;
+        } else if (object instanceof Date) {
+            Date date = (Date) object;
+            return date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDateTime();
+        } else if (object instanceof String) {
+            return LocalDateTime.parse((String)object, DateTimeFormatter.ISO_DATE_TIME);
 
-        if (value != null) {
-            return value;
         } else {
             throw new ClassCastException(format(
                     "Property '%s' can not be cast to %s", getNewPropertyId(name), LocalDateTime.class)
@@ -127,7 +134,29 @@ public class PropertyConfig {
      * @return optional of localDateTime value.
      */
     public Optional<LocalDateTime> getOptionalLocalDateTimeProperty(String name) {
-        return getOptionalProperty(name).map(LocalDateTimeUtils::valueOf);
+        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<Object> list = (List<Object>) value;
+            Set<T> res = new HashSet<>(list.size());
+            for (Object o : list) {
+                res.add(ClassUtils.cast(o, type));
+            }
+            return res;
+        }
+
+        return emptySet();
     }
 
     /**

+ 0 - 2
connector-model/src/main/java/cz/senslog/connector/model/converter/AzureUnitConverter.java

@@ -5,8 +5,6 @@ import java.util.Map;
 
 /**
  * Converter sensor EUI in hex value to sensor ID in dec value.
- * @param hexValue - identifier in hex.
- * @return identifier in dec.
  */
 public class AzureUnitConverter {
 

+ 5 - 6
connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV1ModelConverter.java

@@ -12,8 +12,7 @@ import cz.senslog.connector.model.v1.SenslogV1Model;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.time.LocalDateTime;
-import java.time.ZonedDateTime;
+import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -22,8 +21,8 @@ import java.util.Map;
 import static cz.senslog.connector.model.fieldclimate.SensorType.Group.POSITION;
 import static cz.senslog.connector.model.fieldclimate.SensorType.countOfGroup;
 import static java.lang.String.format;
-import static java.time.ZoneOffset.UTC;
-import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
 
 public class FieldClimateModelSenslogV1ModelConverter implements Converter<FieldClimateModel, SenslogV1Model> {
 
@@ -50,7 +49,7 @@ public class FieldClimateModelSenslogV1ModelConverter implements Converter<Field
         for (StationData station : stations) {
 
             int locationMapInitSize = station.getData().size() * countOfGroup(POSITION);
-            Map<ZonedDateTime, Position> locationData = new HashMap<>(locationMapInitSize);
+            Map<OffsetDateTime, Position> locationData = new HashMap<>(locationMapInitSize);
 
             Long unitId = FieldClimateUnitConverter.convertIdToSensLogId(station.getId());
 
@@ -104,7 +103,7 @@ public class FieldClimateModelSenslogV1ModelConverter implements Converter<Field
 
                         Float value = Float.valueOf(strValue);
                         String strDate = dataMap.getOrDefault("date", "");
-                        ZonedDateTime time = LocalDateTime.parse(strDate, ISO_DATE_TIME).atZone(UTC);
+                        OffsetDateTime time = OffsetDateTime.parse(strDate, ISO_OFFSET_DATE_TIME);
 
                         if (sensorType.isGrouped()) {
                             switch (sensorType.getGroup()) {

+ 2 - 1
connector-model/src/main/java/cz/senslog/connector/model/fieldclimate/FieldClimateModel.java

@@ -3,13 +3,14 @@ package cz.senslog.connector.model.fieldclimate;
 import cz.senslog.connector.model.api.AbstractModel;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.List;
 
 public class FieldClimateModel extends AbstractModel {
 
     private final List<StationData> stations;
 
-    public FieldClimateModel(List<StationData> stations, LocalDateTime from, LocalDateTime to) {
+    public FieldClimateModel(List<StationData> stations, OffsetDateTime from, OffsetDateTime to) {
         super(from, to);
         this.stations = stations;
     }

+ 4 - 3
connector-model/src/main/java/cz/senslog/connector/model/v1/Record.java

@@ -1,5 +1,6 @@
 package cz.senslog.connector.model.v1;
 
+import java.time.OffsetDateTime;
 import java.time.ZonedDateTime;
 
 import static java.time.format.DateTimeFormatter.ofPattern;
@@ -11,7 +12,7 @@ public abstract class Record {
     private Long unitId;
 
     /** Timestamp when the value was measured. */
-    private ZonedDateTime time;
+    private OffsetDateTime time;
 
     public Long getUnitId() {
         return unitId;
@@ -21,7 +22,7 @@ public abstract class Record {
         this.unitId = unitId;
     }
 
-    public ZonedDateTime getTime() {
+    public OffsetDateTime getTime() {
         return time;
     }
 
@@ -29,7 +30,7 @@ public abstract class Record {
         return time != null ? time.format(ofPattern("yyyy-MM-dd HH:mm:ssZ")) : "";
     }
 
-    public void setTime(ZonedDateTime time) {
+    public void setTime(OffsetDateTime time) {
         this.time = time;
     }
 

+ 2 - 1
connector-model/src/main/java/cz/senslog/connector/model/v1/SenslogV1Model.java

@@ -3,6 +3,7 @@ package cz.senslog.connector.model.v1;
 import cz.senslog.connector.model.api.AbstractModel;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.List;
 
 /**
@@ -24,7 +25,7 @@ public class SenslogV1Model extends AbstractModel {
      * @param from - start of the time range.
      * @param to - end of the time range.
      */
-    public SenslogV1Model(List<Record> observations, LocalDateTime from, LocalDateTime to) {
+    public SenslogV1Model(List<Record> observations, OffsetDateTime from, OffsetDateTime to) {
         super(from, to);
         this.observations = observations;
     }

+ 2 - 1
connector-model/src/main/java/cz/senslog/connector/model/v2/SenslogV2Model.java

@@ -3,6 +3,7 @@ package cz.senslog.connector.model.v2;
 import cz.senslog.connector.model.api.AbstractModel;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.util.List;
 
 /**
@@ -24,7 +25,7 @@ public class SenslogV2Model extends AbstractModel {
      * @param from - start of the time range.
      * @param to - end of the time range.
      */
-    public SenslogV2Model(List<Observation> observations, LocalDateTime from, LocalDateTime to) {
+    public SenslogV2Model(List<Observation> observations, OffsetDateTime from, OffsetDateTime to) {
         super(from, to);
         this.observations = observations;
     }

+ 2 - 2
connector-model/src/test/java/cz/senslog/connector/model/api/ConverterProviderTest.java

@@ -2,8 +2,8 @@ package cz.senslog.connector.model.api;
 
 import org.junit.jupiter.api.Test;
 
-import static java.time.LocalDateTime.MAX;
-import static java.time.LocalDateTime.MIN;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
 import static org.junit.jupiter.api.Assertions.*;
 
 class ConverterProviderTest {

+ 7 - 8
connector-model/src/test/java/cz/senslog/connector/model/converter/AzureModelSenslogV1ModelConverterTest.java

@@ -8,14 +8,13 @@ import cz.senslog.connector.model.v1.Record;
 import cz.senslog.connector.model.v1.SenslogV1Model;
 import org.junit.jupiter.api.Test;
 
-import java.time.LocalDateTime;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
+import java.time.*;
 import java.util.ArrayList;
 import java.util.List;
 
 import static cz.senslog.connector.model.azure.SensorType.*;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
 import static java.util.Collections.emptyList;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -26,7 +25,7 @@ class AzureModelSenslogV1ModelConverterTest {
     void convert_FullModel_True() {
 
         SensorData sensorData = new SensorData();
-        sensorData.setTime(ZonedDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0,0,0), ZoneOffset.UTC));
+        sensorData.setTime(OffsetDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0,0,0), ZoneOffset.UTC));
         sensorData.setBatteryLevel(100.0F);
         sensorData.setCo2(600.0F);
         sensorData.setHumidity(50.0F);
@@ -44,7 +43,7 @@ class AzureModelSenslogV1ModelConverterTest {
         List<SensorInfo> sensors = new ArrayList<>(1);
         sensors.add(sensorInfo);
 
-        AzureModel azureModel = new AzureModel(sensors, LocalDateTime.MIN, LocalDateTime.MAX);
+        AzureModel azureModel = new AzureModel(sensors, MIN, MAX);
 
         AzureModelSenslogV1ModelConverter converter = new AzureModelSenslogV1ModelConverter();
         SenslogV1Model senslogV1Model = converter.convert(azureModel);
@@ -99,7 +98,7 @@ class AzureModelSenslogV1ModelConverterTest {
     @Test
     void convert_EmptySensors_EmptyModel() {
 
-        AzureModel azureModel = new AzureModel(emptyList(), LocalDateTime.MIN, LocalDateTime.MAX);
+        AzureModel azureModel = new AzureModel(emptyList(), MIN, MAX);
 
         AzureModelSenslogV1ModelConverter converter = new AzureModelSenslogV1ModelConverter();
 
@@ -118,7 +117,7 @@ class AzureModelSenslogV1ModelConverterTest {
         List<SensorInfo> sensors = new ArrayList<>(1);
         sensors.add(sensorInfo);
 
-        AzureModel azureModel = new AzureModel(sensors, LocalDateTime.MIN, LocalDateTime.MAX);
+        AzureModel azureModel = new AzureModel(sensors, MIN, MAX);
 
         AzureModelSenslogV1ModelConverter converter = new AzureModelSenslogV1ModelConverter();
         SenslogV1Model senslogV1Model = converter.convert(azureModel);

+ 8 - 5
connector-model/src/test/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV1ModelConverterTest.java

@@ -9,6 +9,7 @@ import cz.senslog.connector.model.v1.SenslogV1Model;
 import org.junit.jupiter.api.Test;
 
 import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -16,8 +17,10 @@ import java.util.List;
 import java.util.Map;
 
 import static cz.senslog.connector.model.fieldclimate.SensorType.*;
-import static java.time.LocalDateTime.MAX;
-import static java.time.LocalDateTime.MIN;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
+import static java.time.ZoneOffset.UTC;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 import static java.util.Collections.emptyList;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -55,7 +58,7 @@ class FieldClimateModelSenslogV1ModelConverterTest {
 
         // set data
         data.put("1234_mac_serial_21_avg", "20");
-        data.put("date", "1970-01-01T00:00:00");
+        data.put("date", OffsetDateTime.of(LocalDateTime.of(1970, 1, 1, 0, 0), UTC).format(ISO_OFFSET_DATE_TIME));
 
         return new FieldClimateModel(stations, MIN, MAX);
     }
@@ -133,7 +136,7 @@ class FieldClimateModelSenslogV1ModelConverterTest {
         // set data
         data.put("1234_mac_serial_19969_avg", "42");
         LocalDateTime timestamp = LocalDateTime.of(1970, 1, 1, 0, 0);
-        data.put("date", timestamp.format(DateTimeFormatter.ISO_DATE_TIME));
+        data.put("date", OffsetDateTime.of(timestamp, UTC).format(ISO_OFFSET_DATE_TIME));
 
         SenslogV1Model model = new FieldClimateModelSenslogV1ModelConverter()
                 .convert(new FieldClimateModel(stations, MIN, MAX));
@@ -178,7 +181,7 @@ class FieldClimateModelSenslogV1ModelConverterTest {
         station.setData(sensorData);
 
         LocalDateTime timestamp = LocalDateTime.of(1970, 1, 1, 0, 0);
-        data.put("date", timestamp.format(DateTimeFormatter.ISO_DATE_TIME));
+        data.put("date", OffsetDateTime.of(timestamp, UTC).format(ISO_OFFSET_DATE_TIME));
 
         {
             // set LATITUDE sensor

+ 4 - 2
connector-push-rest-senslog-v1/src/main/java/cz/senslog/connector/push/rest/senslog/v1/SenslogV1Pusher.java

@@ -17,6 +17,8 @@ import java.util.UUID;
 import java.util.concurrent.LinkedTransferQueue;
 import java.util.function.Supplier;
 
+import static cz.senslog.common.http.HttpContentType.TEXT_PLAIN;
+
 /**
  * The class {@code SenslogV1Pusher} represents an implementation of {@link ConnectorPusher}.
  * The class receive a {@link SenslogV1Model} which contains data to send.
@@ -146,7 +148,7 @@ class  SenslogV1Pusher implements ConnectorPusher<SenslogV1Model> {
 
     private HttpRequest prepareRequest(Observation observation, HostConfig hostConfig) {
         return HttpRequest.newBuilder()
-                .contentType(ContentType.TEXT_PLAIN)
+                .contentType(TEXT_PLAIN)
                 .url(URLBuilder.newBuilder(hostConfig.getDomain(), hostConfig.getPath())
                         .addParam("Operation", "InsertObservation")
                         .addParam("value", observation.getValue())
@@ -167,7 +169,7 @@ class  SenslogV1Pusher implements ConnectorPusher<SenslogV1Model> {
         }
 
         return HttpRequest.newBuilder()
-                .contentType(ContentType.TEXT_PLAIN)
+                .contentType(TEXT_PLAIN)
                 .url(URLBuilder.newBuilder(hostConfig.getDomain(), hostConfig.getPath())
                         .addParam("Operation", "InsertPosition")
                         .addParam("date", position.getFormattedTime())

+ 3 - 6
connector-push-rest-senslog-v1/src/test/java/cz/senslog/connector/push/rest/senslog/v1/SenslogV1PusherTest.java

@@ -10,10 +10,7 @@ import cz.senslog.connector.model.v1.Record;
 import cz.senslog.connector.model.v1.SenslogV1Model;
 import org.junit.jupiter.api.Test;
 
-import java.time.LocalDateTime;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
+import java.time.*;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -44,7 +41,7 @@ class SenslogV1PusherTest {
         observation.setUnitId(1L);
         observation.setSensorId(1L);
         observation.setValue(42F);
-        observation.setTime(ZonedDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0,0,0), ZoneOffset.UTC));
+        observation.setTime(OffsetDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0,0,0), ZoneOffset.UTC));
 
         observations.add(observation);
         SenslogV1Model model = new SenslogV1Model(observations, null, null);
@@ -75,7 +72,7 @@ class SenslogV1PusherTest {
         observation.setUnitId(1L);
         observation.setSensorId(1L);
         observation.setValue(42F);
-        observation.setTime(ZonedDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0,0,0), ZoneOffset.UTC));
+        observation.setTime(OffsetDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0,0,0), ZoneOffset.UTC));
 
         observations.add(observation);
         SenslogV1Model model = new SenslogV1Model(observations, null, null);