Преглед изворни кода

Added DemoLogger example, preperation for GFS, Analytics, minor fixes

Lukas Cerny пре 1 година
родитељ
комит
efca257827
96 измењених фајлова са 5334 додато и 127 уклоњено
  1. 4 4
      README.md
  2. 21 0
      config/demoToLogger.yaml
  3. 17 0
      config/fetcherPusher_demo.yaml
  4. 21 0
      config/gfsSenslog.yaml
  5. 32 0
      config/senslog1Analytics.yaml
  6. 33 0
      config/senslogTelemetryTheros.yaml
  7. 22 4
      connector-fetch-demo/src/main/java/cz/senslog/connector/fetch/demo/ConnectorFetchDemoProvider.java
  8. 3 1
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/ConnectorFetchFieldClimateProvider.java
  9. 1 1
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcher.java
  10. 1 1
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySession.java
  11. 0 5
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcherTest.java
  12. 1 1
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySessionTest.java
  13. 1481 0
      connector-fetch-gfs/lib/GRIB2Tools.jar
  14. 34 0
      connector-fetch-gfs/pom.xml
  15. 256 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribFile.java
  16. 116 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection.java
  17. 84 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection0.java
  18. 60 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection1.java
  19. 26 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection2.java
  20. 54 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection3.java
  21. 51 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection4.java
  22. 148 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection5.java
  23. 40 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection6.java
  24. 70 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection7.java
  25. 53 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection8.java
  26. 415 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/RandomAccessGribFile.java
  27. 89 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/StreamedGribFile.java
  28. 22 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate50.java
  29. 36 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate52.java
  30. 19 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate53.java
  31. 12 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate5x.java
  32. 119 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataSection72.java
  33. 46 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataSection73.java
  34. 9 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataSection7x.java
  35. 136 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/griddefinition/GridDefinitionTemplate30.java
  36. 8 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/griddefinition/GridDefinitionTemplate3x.java
  37. 43 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/ProductDefinitionTemplate40.java
  38. 37 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/ProductDefinitionTemplate48.java
  39. 8 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/ProductDefinitionTemplate4x.java
  40. 26 0
      connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/TimeRangeSpecification.java
  41. 16 0
      connector-fetch-gfs/src/main/java/cz/senslog/connector/fetch/gfs/ConnectorFetchGFSProvider.java
  42. 75 0
      connector-fetch-gfs/src/main/java/cz/senslog/connector/fetch/gfs/GFSFetcher.java
  43. 10 0
      connector-fetch-gfs/src/main/java/cz/senslog/connector/fetch/gfs/GFSSession.java
  44. 1 0
      connector-fetch-gfs/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider
  45. 19 0
      connector-fetch-gfs/src/test/java/cz/senslog/connector/fetch/gfs/GFSFetcherTest.java
  46. 38 0
      connector-fetch-gfs/src/test/java/cz/senslog/connector/fetch/gfs/TestResourceUtils.java
  47. BIN
      connector-fetch-gfs/src/test/resources/gfs.t00z.goessimpgrb2.0p25.f003
  48. 4 0
      connector-fetch-gfs/src/test/resources/gfs.t00z.goessimpgrb2.0p25.f003.idx
  49. 39 0
      connector-fetch-senslog-telemetry/pom.xml
  50. 33 0
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/ConnectorFetchSenslogTelemetryProvider.java
  51. 54 0
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryConfig.java
  52. 151 0
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryFetcher.java
  53. 25 0
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryProxySession.java
  54. 56 0
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetrySession.java
  55. 1 0
      connector-fetch-senslog-telemetry/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider
  56. 34 0
      connector-fetch-senslog-telemetry/src/test/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryFetcherTest.java
  57. 65 19
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogConfig.java
  58. 114 53
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcher.java
  59. 2 2
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogProxySession.java
  60. 30 20
      connector-fetch-senslog-v1/src/test/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcherTest.java
  61. 20 0
      connector-model/src/main/java/cz/senslog/connector/model/analytics/AnalyticsModel.java
  62. 34 0
      connector-model/src/main/java/cz/senslog/connector/model/analytics/Observation.java
  63. 2 2
      connector-model/src/main/java/cz/senslog/connector/model/api/ConverterProvider.java
  64. 27 0
      connector-model/src/main/java/cz/senslog/connector/model/api/GeoJsonModel.java
  65. 6 1
      connector-model/src/main/java/cz/senslog/connector/model/config/PropertyConfig.java
  66. 13 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/DemoModelLoggerModelConverter.java
  67. 13 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/GFSModelSenslogV1ModelConverter.java
  68. 11 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/GeoJsonTransientConverter.java
  69. 4 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/ModelConverterProvider.java
  70. 36 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/Senslog1ModelAnalyticsModelConverter.java
  71. 8 1
      connector-model/src/main/java/cz/senslog/connector/model/demo/DemoModel.java
  72. 12 0
      connector-model/src/main/java/cz/senslog/connector/model/gfs/GFSModel.java
  73. 25 0
      connector-model/src/main/java/cz/senslog/connector/model/logger/LoggerModel.java
  74. 33 0
      connector-push-analytics/pom.xml
  75. 16 0
      connector-push-analytics/src/main/java/cz/senslog/connector/push/analytics/AnalyticsConfig.java
  76. 51 0
      connector-push-analytics/src/main/java/cz/senslog/connector/push/analytics/AnalyticsPusher.java
  77. 30 0
      connector-push-analytics/src/main/java/cz/senslog/connector/push/analytics/ConnectorPushAnalyticsProvider.java
  78. 1 0
      connector-push-analytics/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider
  79. 33 0
      connector-push-logger/pom.xml
  80. 26 0
      connector-push-logger/src/main/java/cz/senslog/connector/push/logger/ConnectorPushPushProvider.java
  81. 1 0
      connector-push-logger/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider
  82. 2 1
      connector-push-senslog-v1/src/main/java/cz/senslog/connector/push/rest/senslog/v1/SenslogV1ConnectorPushProvider.java
  83. 40 0
      connector-push-theros/pom.xml
  84. 41 0
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/AuthConfig.java
  85. 81 0
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/AuthService.java
  86. 65 0
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/AuthorizationToken.java
  87. 18 0
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/ConnectorPushTherosProvider.java
  88. 22 0
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/TherosConfig.java
  89. 75 0
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/TherosPusher.java
  90. 1 0
      connector-push-theros/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider
  91. 35 0
      connector-push-theros/src/test/java/cz/senslog/connector/push/theros/TherosPusherTest.java
  92. 1 0
      connector-tools/src/main/java/cz/senslog/connector/tools/http/HttpContentType.java
  93. 4 6
      connector-tools/src/main/java/cz/senslog/connector/tools/json/BasicJson.java
  94. 26 0
      connector-tools/src/main/java/cz/senslog/connector/tools/json/JsonUtils.java
  95. 58 5
      docker-compose.yaml
  96. 42 0
      pom.xml

+ 4 - 4
README.md

@@ -45,13 +45,13 @@ services:
 ## Build & run
 To build all or a specific one service.
 ```sh
-$ docker-compose build
-$ docker-compose build <service_name>  // e.g. fcs2
+$ docker compose build
+$ docker compose build <service_name>  // e.g. demoLogger
 ```
 To start all or a specific one service.
 ```sh
-$ docker-compose up
-$ docker-compose up <service>          // e.g. fcs2
+$ docker compose up
+$ docker compose up <service>          // e.g. demoLogger
 ```
 
 

+ 21 - 0
config/demoToLogger.yaml

@@ -0,0 +1,21 @@
+settings:
+  - Demo: # name of the fetcher module, e.g., Demo
+      name: "Demo fetcher"
+      provider: "cz.senslog.connector.fetch.demo.ConnectorFetchDemoProvider"
+      # the same line as in the file META-INF/services/cz.senslog...ConnectorFetchProvider
+
+      # other attributes based on the module configuration
+
+  - Logger: # name of the pusher module, e.g., Logger
+      name: "Logger pusher"
+      provider: "cz.senslog.connector.push.logger.ConnectorPushPushProvider"
+
+      # other attributes based on the module configuration
+
+connectors:
+  - DemoSenslogV1:        # name of the connector, e.g., DemoSenslogV1
+      fetcher: "Demo"     # name of the fetcher from 'settings'
+      pusher: "Logger"    # name of the pusher from 'settings'
+      period: 10          # 86_400 = 24h
+#      startAt: "02:30:00" # hh:mm:ss  # non-mandatory attribute
+      initDelay: 5        # non-mandatory attribute

+ 17 - 0
config/fetcherPusher_demo.yaml

@@ -0,0 +1,17 @@
+
+settings:
+    - Demo:
+        name: "Demo"
+        provider: "cz.senslog.connector.fetch.demo.ConnectorFetchDemoProvider"
+
+    - Pusher:
+        name: "Pusher"
+        provider: "<pusher_class>"
+
+connectors:
+    - DemoPusher:
+        fetcher: "Demo"
+        pusher: "Pusher"
+        period: 86_400 # 24h
+        startAt: "02:30:00" # hh:mm:ss
+#        initDelay: 5

+ 21 - 0
config/gfsSenslog.yaml

@@ -0,0 +1,21 @@
+
+settings:
+    - GFS:
+        name: "GSF"
+        provider: "cz.senslog.connector.fetch.gfs.ConnectorFetchDemoProvider"
+
+    - SensLog:
+        name: "SensLog"
+        provider: "cz.senslog.connector.push.rest.senslog.v1.SenslogV1ConnectorPushProvider"
+        baseUrl: "https://sensor.lesprojekt.cz/senslog15"
+        auth:
+            username: "watchdog"
+            password: "HAFhaf"
+
+connectors:
+    - DemoPusher:
+        fetcher: "GSF"
+        pusher: "SensLog"
+        period: 86_400 # 24h
+        startAt: "02:30:00" # hh:mm:ss
+#        initDelay: 5

+ 32 - 0
config/senslog1Analytics.yaml

@@ -0,0 +1,32 @@
+
+settings:
+    - Senslog:
+        name: "SensLog v1"
+        provider: "cz.senslog.connector.fetch.senslog.v1.SenslogFetchProvider"
+
+        timeZone: "Europe/Prague"
+        startDate: now # 2024-04-17T11:00:00+02:00
+        delayStartDate: 60 # minutes
+        interval: 10 # minutes
+        user: "osek"
+
+        baseUrl: "http://sensor.lesprojekt.cz/senslog15"
+        auth:
+            username: "osek"
+            password: "oSek"
+
+        allowedStation:
+             "1305167549174391": [340070001]
+
+    - Analytics:
+        name: "SensLog Analytics"
+        provider: "cz.senslog.connector.push.analytics.ConnectorPushAnalyticsProvider"
+
+        url: "http://172.17.0.1:9092/observations"
+
+connectors:
+    - SenslogToAnalytics:
+        fetcher: "Senslog"
+        pusher: "Analytics"
+        period: 600 # 10 minutes
+        initDelay: 5

+ 33 - 0
config/senslogTelemetryTheros.yaml

@@ -0,0 +1,33 @@
+
+settings:
+    - Telemetry:
+        name: "SensLog Telemetry"
+        provider: "cz.senslog.fetch.senslog.telemetry.ConnectorFetchSenslogTelemetryProvider"
+
+        baseUrl: "https://theros.wirelessinfo.cz/"
+        bearerToken: "#123"
+
+        startAt: "2023-08-25T00:00:00+00:00"
+        interval: 4    # hours
+        campaignId: 2
+        limit: 10
+
+    - Theros:
+        name: "Theros"
+        provider: "cz.senslog.connector.push.theros.ConnectorPushTherosProvider"
+
+        baseUrl: "https://theros.iccs.gr/iot/api/v1"
+        auth:
+            url: "https://auth-theros.iccs.gr/realms/theros/protocol/openid-connect/token"
+            clientId: "iot-client"
+            clientSecret: "cKIhqWuk6foyn2taKwEzgWBJdqu5HkMv"
+            username: "lcerny@lesprojekt.cz"
+            password: "PF7n4Nc*b2AKTzr9L_"
+
+connectors:
+    - TelemetryTheros:
+        fetcher: "Telemetry"
+        pusher: "Theros"
+        period: 5 # 86_400 # 24h
+#        startAt: "02:30:00" # hh:mm:ss
+        initDelay: 5

+ 22 - 4
connector-fetch-demo/src/main/java/cz/senslog/connector/fetch/demo/ConnectorFetchDemoProvider.java

@@ -1,16 +1,34 @@
 package cz.senslog.connector.fetch.demo;
 
 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.model.api.AbstractModel;
+import cz.senslog.connector.model.api.ProxySessionModel;
 import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.demo.DemoModel;
+
+import java.time.OffsetDateTime;
+import java.util.Optional;
 
 public class ConnectorFetchDemoProvider implements ConnectorFetchProvider  {
 
     @Override
-    public ExecutableFetcher<? extends AbstractModel> createExecutableFetcher(DefaultConfig defaultConfig) {
-        throw new RuntimeException(
-                "This is a demo module used as an inspiration for creating other modules."
-        );
+    public ExecutableFetcher<DemoModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+        return ExecutableFetcher.create(new ConnectorFetcher<ProxySessionModel, DemoModel>() {
+
+            private int executionRun;
+
+            @Override
+            public void init() throws Exception {
+                this.executionRun = 0;
+            }
+
+            @Override
+            public DemoModel fetch(Optional<ProxySessionModel> session) {
+                OffsetDateTime now = OffsetDateTime.now();
+                return new DemoModel("Execution run: " + (++executionRun), now, now);
+            }
+        });
     }
 }

+ 3 - 1
connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/ConnectorFetchFieldClimateProvider.java

@@ -1,7 +1,9 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
+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.model.config.DefaultConfig;
-import cz.senslog.connector.fetch.api.*;
 import cz.senslog.connector.fetch.fieldclimate.auth.AuthConfig;
 import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
 import cz.senslog.connector.model.fieldclimate.FieldClimateModel;

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

@@ -1,10 +1,10 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
 import com.google.gson.reflect.TypeToken;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.connector.tools.exception.ParseException;
 import cz.senslog.connector.tools.exception.SyntaxException;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;

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

@@ -1,9 +1,9 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
 import com.google.gson.reflect.TypeToken;
-import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.fetch.api.FetchProxySession;
+import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;

+ 0 - 5
connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcherTest.java

@@ -1,14 +1,11 @@
 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.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;
-import cz.senslog.connector.model.converter.FieldClimateModelSenslogV1ModelConverter;
 import cz.senslog.connector.model.fieldclimate.*;
-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;
@@ -17,8 +14,6 @@ import org.mockito.stubbing.Answer;
 import java.time.LocalDateTime;
 import java.util.*;
 
-import static cz.senslog.connector.tools.http.HttpClient.newHttpClient;
-import static cz.senslog.connector.tools.http.HttpClient.newHttpSSLClient;
 import static cz.senslog.connector.tools.http.HttpCode.OK;
 import static cz.senslog.connector.tools.http.HttpCode.SERVER_ERROR;
 import static java.lang.String.format;

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

@@ -1,7 +1,7 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
 import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.fetch.gfs.ExecutableFetcher;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;

Разлика између датотеке није приказан због своје велике величине
+ 1481 - 0
connector-fetch-gfs/lib/GRIB2Tools.jar


+ 34 - 0
connector-fetch-gfs/pom.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cz.senslog</groupId>
+        <artifactId>connector-period</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>connector-fetch-gfs</artifactId>
+    <name>fetch-gfs</name>
+    <packaging>jar</packaging>
+
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-fetch-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 256 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribFile.java

@@ -0,0 +1,256 @@
+package com.ph.grib2tools.grib2file;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.logging.Logger;
+
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate50;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate5x;
+import com.ph.grib2tools.grib2file.griddefinition.GridDefinitionTemplate30;
+import com.ph.grib2tools.grib2file.griddefinition.GridDefinitionTemplate3x;
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate40;
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate48;
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate4x;
+
+public abstract class GribFile implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+
+	// Maximum number of Sections 5, 6, 7 supported
+	protected final int MAXNUMSECTIONS567 = 10;
+
+	// Conversation factor for converting GRIB file units to degree (for coordinates)
+	public static int GRIB2DEGUNIT = 1000000;
+
+	// A string for identifying the data set. For information only, so can
+	// be "" or null if not needed.
+	protected String typeid;
+	
+	// A string pointing to the source of the GRIB2 data. For information only,
+	// so can be "" or null if not needed
+	protected String source;
+	
+	// Sections of the GRIB file
+	protected GribSection0 section0;
+	protected GribSection1 section1;
+	protected GribSection2 section2;
+	protected GribSection3 section3;
+	protected GribSection4 section4;
+	protected GribSection5 section5[];
+	protected GribSection6 section6[];
+	protected GribSection7 section7[];
+	protected GribSection8 section8;
+
+	// Counter for multiple Sections 5, 6, 7
+	protected int gridcnt;
+
+	private static final Logger log = Logger.getLogger(GribFile.class.getName());
+
+	
+	public GribFile(String typeid, String source) {
+
+		this.typeid = typeid;
+		this.source = source;
+		
+		this.gridcnt = 0;
+		
+		section5 = new GribSection5[MAXNUMSECTIONS567];
+		section6 = new GribSection6[MAXNUMSECTIONS567];
+		section7 = new GribSection7[MAXNUMSECTIONS567];
+	}
+
+	// Reads all data describing structure and type of the product, the data etc. The
+	// data itself is not read, also the end section (Section 8) is not considered.
+	// This offers the opportunity to process the data separately and independently.
+	//public void importMetadatFromStream(InputStream gribfile) {
+	public void importMetadatFromStream(InputStream gribfile) throws IOException {
+					
+		if (gribfile == null) return;
+
+		// Read Section 0 and verify if the data is valid GRIB/GRIB2 data
+		section0 = new GribSection0(gribfile);
+		if (!section0.verifyMagicNumber()) {
+			System.out.println("This is not a GRIB file");
+		}
+		if (!section0.verifyGribVersion()) {
+			System.out.println("This is not a GRIB2 file");
+		}
+
+		// Process the GRIB file
+		while (true) {
+
+			// Read the next Section of the GRIB file
+			GribSection nextsection = new GribSection(gribfile).initSection();			
+//if (nextsection == null) continue;
+if (nextsection == null) break;
+			if (nextsection.sectionnumber == 1) {
+				section1 = (GribSection1)nextsection;
+				section1.readData(gribfile);
+			}
+			else if (nextsection.sectionnumber == 2) {
+				section2 = (GribSection2)nextsection;
+				section2.readData(gribfile);
+			}
+			else if (nextsection.sectionnumber == 3) {
+				section3 = (GribSection3)nextsection;
+				section3.readData(gribfile);
+			}
+			else if (nextsection.sectionnumber == 4) {
+				section4 = (GribSection4)nextsection;
+				section4.readData(gribfile);
+			}
+			else if (nextsection.sectionnumber == 5) {
+				section5[gridcnt] = (GribSection5)nextsection;
+				section5[gridcnt].readData(gribfile);
+			}
+			else if (nextsection.sectionnumber == 6) {
+				section6[gridcnt] = (GribSection6)nextsection;
+				section6[gridcnt].readData(gribfile);
+				break;
+			}
+			
+			// Stop processing the GRIB file if no known section is found 
+			else break;
+		}
+	}
+
+	// Handle Section 8 of a GRIB file
+	public void finalizeImport(InputStream gribfile) {
+
+		// Read Section 8 and verify if the GRIB data is terminated correctly
+		section8 = new GribSection8(gribfile);
+		if (!section8.verifyEndIdentifier()) {
+			System.out.println("End of GRIB file not reached.");
+		}		
+	}
+
+	// Write the complete GRIB file data to an OutputStream
+	//public void writeToStream(OutputStream gribFile) {
+	public void writeToStream(OutputStream gribFile) throws IOException {
+		
+		section0.writeToStream(gribFile);
+		section1.writeToStream(gribFile);
+		section2.writeToStream(gribFile);
+		section3.writeToStream(gribFile);
+		section4.writeToStream(gribFile);
+		
+		for (int cnt=0; cnt<gridcnt; cnt++) {
+			section5[cnt].writeToStream(gribFile);
+			section6[cnt].writeToStream(gribFile);
+			section7[cnt].writeToStream(gribFile);
+		}
+		
+		section8.writeToStream(gribFile);
+	}
+
+	public String getType() { return typeid; }
+	public String getSource() {return source; }
+	public int getGridCount() { return gridcnt; }
+	
+	// Returns the GridDefinitionTemplate of the GRIB file according to the Template Number
+	public GridDefinitionTemplate3x getGridDefinitionTemplate() {
+		
+		GridDefinitionTemplate3x gridDefinition = null;
+		
+		if (section3.gridDefinitionTemplateNumber == 0)
+	        gridDefinition = (GridDefinitionTemplate30)section3.gridDefinitionTemplate;
+	    else {
+	        log.severe("Grid Definition Template Number 3." + section3.gridDefinitionTemplateNumber + " not implemented.");
+	    }	
+		
+		return gridDefinition;
+	}
+
+	// Returns the ProductDefinitionTemplate of the GRIB file according to the Template Number
+	public ProductDefinitionTemplate4x getProductDefinitionTemplate() {
+		
+		ProductDefinitionTemplate4x productDefinition = null;
+
+	    if (section4.productDefinitionTemplateNumber == 0)
+	        productDefinition = (ProductDefinitionTemplate40)section4.productDefinitionTemplate;
+	    else if (section4.productDefinitionTemplateNumber == 8)
+	        productDefinition = (ProductDefinitionTemplate48)section4.productDefinitionTemplate;
+	    else {
+	        log.severe("Product Definition Template Number 4." + section4.productDefinitionTemplateNumber + " not implemented.");
+	    }
+	    
+	    return productDefinition;
+	}
+	
+	// Returns the DataRepresentationTemplate of grid gridid of the GRIB file according to the Template Number
+	public DataRepresentationTemplate5x getDataRepresentationTemplate(int gridid) {
+		
+		DataRepresentationTemplate5x dataRepresentation = null;
+
+	    if (section5[gridid].dataRepresentationTemplateNumber == 0)
+	        dataRepresentation = (DataRepresentationTemplate50)section5[gridid].dataRepresentationTemplate;
+	    else {
+	        log.severe("Data Representation Template Number 5." + section5[gridid].dataRepresentationTemplateNumber + " not implemented.");
+	    }
+	    
+	    return dataRepresentation;
+	}
+	
+	public GribSection0 getSection0() { return section0; }
+	public GribSection1 getSection1() { return section1; }
+	public GribSection2 getSection2() { return section2; }
+	public GribSection3 getSection3() { return section3; }
+	public GribSection4 getSection4() { return section4; }
+	public GribSection5 getSection5(int gridid) { return section5[gridid]; }
+	public GribSection6 getSection6(int gridid) { return section6[gridid]; }
+	public GribSection7 getSection7(int gridid) { return section7[gridid]; }
+	public GribSection8 getSection8() { return section8; }
+
+	public void setSection0(GribSection0 section0) { this.section0 = section0; }
+	public void setSection1(GribSection1 section1) { this.section1 = section1; }
+	public void setSection2(GribSection2 section2) { this.section2 = section2; }
+	public void setSection3(GribSection3 section3) { this.section3 = section3; }
+	public void setSection4(GribSection4 section4) { this.section4 = section4; }
+	public void setSection5(int idx, GribSection5 section5) { this.section5[idx] = section5; }
+	public void setSection6(int idx, GribSection6 section6) { this.section6[idx] = section6; }
+	public void setSection7(int idx, GribSection7 section7) { this.section7[idx] = section7; }
+	public void setSection8(GribSection8 section8) { this.section8 = section8; }
+
+	public static int getLatIndex(GridDefinitionTemplate30 gridDefinition, double latitude) {
+		
+		int idx;
+		
+		int latTicks = GribFile.degToUnits(latitude);
+
+		if (gridDefinition.getDirectionJ() == 1) {
+			idx = (latTicks - gridDefinition.firstPointLat) / gridDefinition.jDirectionIncrement;
+		}
+		else {
+			idx = (gridDefinition.firstPointLat - latTicks) / gridDefinition.jDirectionIncrement;
+		}
+		
+		return idx;
+	}
+
+	public static int getLonIndex(GridDefinitionTemplate30 gridDefinition, double longitude) {
+		
+		int idx;
+
+		int lonTicks = GribFile.degToUnits(longitude);
+		
+		if (gridDefinition.getDirectionI() == 1) {
+			while (lonTicks < gridDefinition.firstPointLon) lonTicks += GribFile.degToUnits(360);
+			while (lonTicks > gridDefinition.lastPointLon) lonTicks -= GribFile.degToUnits(360);
+			idx = (lonTicks - gridDefinition.firstPointLon) / gridDefinition.iDirectionIncrement;
+		}
+		else {
+			while (lonTicks > gridDefinition.firstPointLon) lonTicks -= GribFile.degToUnits(360);
+			while (lonTicks < gridDefinition.lastPointLon) lonTicks += GribFile.degToUnits(360);
+			idx = (gridDefinition.firstPointLon - lonTicks) / gridDefinition.iDirectionIncrement;
+		}
+
+		return idx;
+	}
+
+	public static long getVersion() { return serialVersionUID; }
+	public static String getVersionString() { return Long.toString(serialVersionUID); }
+
+	public static double unitsToDeg(int units) { return (double)units/GRIB2DEGUNIT; }
+	public static int degToUnits(double deg) { return (int)(deg*GRIB2DEGUNIT); }
+}

+ 116 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection.java

@@ -0,0 +1,116 @@
+package com.ph.grib2tools.grib2file;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+// Template of a GRIB Section, valid for Section types from 1 to 7. Not valid for Sections of type 0 and 8
+public class GribSection implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+
+	// Length of the section
+	public int sectionlength;
+
+	// Section number
+	public byte sectionnumber;
+
+	// Data of the section
+	public byte[] sectiondata;
+	
+	
+	public GribSection(int len, byte num, byte[] data) {
+		sectionlength = len;
+		sectionnumber = num;
+		sectiondata = data;
+	}
+	
+	public GribSection(InputStream gribfile) throws IOException {
+
+
+		// All Sections of type 1 to 7 begin with a five byte long header. This header consists of a four byte
+		// long length of the section, followed by a one byte section number (type)
+		byte[] sectionheader = new byte[5];
+		gribfile.read(sectionheader);		
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectionheader);
+
+		// Extract section length and section number (type) from the header
+		sectionlength = byteBuffer.getInt();
+		sectionnumber = byteBuffer.get();
+	}
+	
+	public GribSection initSection() {
+		
+		if (sectionnumber == 1) return new GribSection1(this);
+		else if (sectionnumber == 2) return new GribSection2(this);
+		else if (sectionnumber == 3) return new GribSection3(this);
+		else if (sectionnumber == 4) return new GribSection4(this);
+		else if (sectionnumber == 5) return new GribSection5(this);
+		else if (sectionnumber == 6) return new GribSection6(this);
+		else if (sectionnumber == 7) return new GribSection7(this);
+		else System.out.println("Section Number " + sectionnumber + " not implemented");
+		
+		return null;
+	}
+		
+	public void readData(InputStream gribfile) throws IOException {
+		
+		// Read complete section
+		sectiondata = new byte[sectionlength-5];
+		gribfile.read(sectiondata);
+	}
+	
+	public void writeToStream(OutputStream gribFile) throws IOException {
+
+		DataOutputStream dataout = new DataOutputStream(gribFile);
+
+		dataout.writeInt(sectionlength);
+		gribFile.write(sectionnumber);
+		gribFile.write(sectiondata);
+	}
+   
+   // Adjust a one byte value extracted from a GRIB file according to the GRIB specification to obtain
+   // a correct unsigned byte
+   public static short adjustUnsignedByte(int unsignedbyte) {
+      return (short) ((unsignedbyte & 0x7F) + (unsignedbyte & 0x80));
+   }
+	
+	// Adjust a two byte value extracted from a GRIB file according to the GRIB specification to obtain
+	// a correct unsigned short
+	public static int adjustUnsignedShort(int unsignedshort) {
+	   return (unsignedshort & 0x7FFF) + (unsignedshort & 0x8000);
+	}
+
+   // Adjust a four byte value extracted from a GRIB file according to the GRIB specification to obtain
+   // a correct unsigned int
+   public static long adjustUnsignedInt(int unsignedint) {
+      return (unsignedint & 0x7FFFFFFF) + (unsignedint & 0x80000000L);
+   }
+   
+   // Convert a one byte value extracted from a GRIB file according to the GRIB specification to recover
+   // the sign of a signed one
+   public static byte correctNegativeByte(byte uncorrectedvalue) {
+      byte correctedvalue = (byte)(uncorrectedvalue & 0x7f);
+      if ((uncorrectedvalue & 0x80) == 0x80) correctedvalue = (byte) -correctedvalue;
+      assert uncorrectedvalue<0x80 || uncorrectedvalue != correctedvalue;
+      return correctedvalue;
+   }
+	
+	// Convert a two byte value extracted from a GRIB file according to the GRIB specification to recover
+	// the sign of a signed short
+	public static short correctNegativeShort(short uncorrectedvalue) {
+		short correctedvalue = (short)(uncorrectedvalue & 0x7fff);
+		if ((uncorrectedvalue & 0x8000) == 0x8000) correctedvalue = (short) -correctedvalue;
+		return correctedvalue;
+	}
+
+	// Convert a four byte value extracted from a GRIB file according to the GRIB specification to recover
+	// the sign of a signed int
+	public static int correctNegativeInt(int uncorrectedvalue) {
+		int correctedvalue = uncorrectedvalue & 0x7fffffff;
+		if ((uncorrectedvalue & 0x80000000L) == 0x80000000L) correctedvalue = -correctedvalue;
+		return correctedvalue;
+	}
+}

+ 84 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection0.java

@@ -0,0 +1,84 @@
+package com.ph.grib2tools.grib2file;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+public class GribSection0 implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+
+	// Start identifier of a GRIB file 
+	protected final static byte[] GRIBMAGICNUMBER = {71, 82, 73, 66};
+
+	// Content and structure of a Section 0
+	protected byte[] magicnumberbytes = new byte[4];
+	public short reserved;
+	public byte discipline;
+	protected byte number;
+	protected long totalLength;
+	
+	
+	public GribSection0(InputStream gribfile) {
+		
+		try {
+
+			// Read complete section
+			byte[] section0 = new byte[16];
+/*
+			gribfile.read(section0);		
+*/
+			byte[] b = new byte[1];
+
+			gribfile.read(b);
+			while(b[0] != 71) gribfile.read(b);
+			
+			section0[0] = b[0];
+			for (int i=1; i<16; i++) {
+				gribfile.read(b);
+				section0[i] = b[0];
+			}
+
+			ByteBuffer byteBuffer = ByteBuffer.wrap(section0);
+
+			// Parse section and extract data
+			byteBuffer.get(magicnumberbytes);
+			reserved = byteBuffer.getShort();
+			discipline = byteBuffer.get();
+			number = byteBuffer.get();
+			totalLength = byteBuffer.getLong();
+
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}			
+	}
+
+	public void writeToStream(OutputStream gribFile) {
+		
+		DataOutputStream dataout = new DataOutputStream(gribFile);
+		
+		try {
+			
+			gribFile.write(magicnumberbytes);
+			dataout.writeShort(reserved);
+			gribFile.write(discipline);
+			gribFile.write(number);
+			dataout.writeLong(totalLength);
+			
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+	
+	public boolean verifyMagicNumber() {		
+		return (ByteBuffer.wrap(magicnumberbytes).compareTo(ByteBuffer.wrap(GRIBMAGICNUMBER)) == 0);			
+	}	
+
+	public boolean verifyGribVersion() {		
+		return (number == 2);			
+	}	
+}

+ 60 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection1.java

@@ -0,0 +1,60 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class GribSection1 extends GribSection {	
+	
+	private static final long serialVersionUID = 100L;
+
+	// Content and structure of a Section 1
+	public short generatingCentre;
+	public short generatingSubcentre;
+	public byte masterTablesVersion;
+	public byte localTablesVersion;
+	public byte significanceOfReferenceTime;
+	public short year;
+	public byte month;
+	public byte day;
+	public byte hour;
+	public byte minute;
+	public byte second;
+	public byte productionStatus;
+	public byte type;
+	
+
+	public GribSection1(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection1(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+	
+	public void readSection() {
+		
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectiondata);
+		
+		// Parse section and extract data
+		generatingCentre = byteBuffer.getShort();
+		generatingSubcentre = byteBuffer.getShort();
+		masterTablesVersion = byteBuffer.get();
+		localTablesVersion = byteBuffer.get();
+		significanceOfReferenceTime = byteBuffer.get();
+		year = byteBuffer.getShort();
+		month = byteBuffer.get();
+		day = byteBuffer.get();
+		hour = byteBuffer.get();
+		minute = byteBuffer.get();
+		second = byteBuffer.get();
+		productionStatus = byteBuffer.get();
+		type = byteBuffer.get();		
+	}
+}

+ 26 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection2.java

@@ -0,0 +1,26 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class GribSection2 extends GribSection {		
+
+	private static final long serialVersionUID = 100L;
+
+	public GribSection2(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection2(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+
+	public void readSection() {
+	}
+}

+ 54 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection3.java

@@ -0,0 +1,54 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.griddefinition.GridDefinitionTemplate30;
+import com.ph.grib2tools.grib2file.griddefinition.GridDefinitionTemplate3x;
+
+public class GribSection3 extends GribSection {	
+	
+	private static final long serialVersionUID = 100L;
+
+	// Content and structure of a Section 3
+	public byte sourceOfGridDefinition;
+	public int numDataPoints;
+	public byte numOfOctetsForOptionalList;
+	public byte interpretationOfList;
+	public short gridDefinitionTemplateNumber;
+	public GridDefinitionTemplate3x gridDefinitionTemplate;
+	public byte[] optionalListOfPoints;
+	
+	public GribSection3(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection3(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+
+	public void readSection() {
+		
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectiondata);
+		
+		// Parse section and extract data
+		sourceOfGridDefinition = byteBuffer.get();
+		numDataPoints = byteBuffer.getInt();
+		numOfOctetsForOptionalList = byteBuffer.get();
+		interpretationOfList = byteBuffer.get();
+		gridDefinitionTemplateNumber = byteBuffer.getShort();
+		
+		if (gridDefinitionTemplateNumber == 0) gridDefinitionTemplate = new GridDefinitionTemplate30(byteBuffer);
+		else System.out.println("Grid Definition Template Number 3." + gridDefinitionTemplateNumber + " not implemented.");
+		
+		optionalListOfPoints = new byte[numOfOctetsForOptionalList];
+		byteBuffer.get(optionalListOfPoints);
+	}
+}

+ 51 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection4.java

@@ -0,0 +1,51 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate40;
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate48;
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate4x;
+
+public class GribSection4 extends GribSection {	
+	
+	private static final long serialVersionUID = 100L;
+
+	// Content and structure of a Section 4
+	public short numberCoordinateValues;
+	public short productDefinitionTemplateNumber;
+	public ProductDefinitionTemplate4x productDefinitionTemplate;
+	public byte[] optionalListOfCoordinates;
+	
+	
+	public GribSection4(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection4(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+
+	public void readSection() {
+		
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectiondata);
+
+		// Parse section and extract data
+		numberCoordinateValues = byteBuffer.getShort();
+		productDefinitionTemplateNumber = byteBuffer.getShort();
+		
+		if (productDefinitionTemplateNumber == 0) productDefinitionTemplate = new ProductDefinitionTemplate40(byteBuffer);
+		else if (productDefinitionTemplateNumber == 8) productDefinitionTemplate = new ProductDefinitionTemplate48(byteBuffer);
+		else System.out.println("Product Definition Template Number 4." + productDefinitionTemplateNumber + " not implemented.");
+		
+		optionalListOfCoordinates = new byte[numberCoordinateValues];
+		byteBuffer.get(optionalListOfCoordinates);
+	}
+}

+ 148 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection5.java

@@ -0,0 +1,148 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate50;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate52;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate53;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate5x;
+
+public class GribSection5 extends GribSection {	
+	
+	private static final long serialVersionUID = 100L;
+
+	private static final Logger log = Logger.getLogger(GribSection5.class.getName());
+
+	// Content and structure of a Section 5
+	@Deprecated
+	protected int numberDataPoints;
+	protected int numberValues;		// replaces numberDataPoints
+	public short dataRepresentationTemplateNumber;
+	public DataRepresentationTemplate5x dataRepresentationTemplate;
+	
+	
+	public GribSection5(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection5(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+
+	public void readSection() {
+		
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectiondata);
+
+		// Parse section and extract data
+		numberValues = byteBuffer.getInt();
+		numberDataPoints = numberValues;	// For downward compatibility
+		dataRepresentationTemplateNumber = byteBuffer.getShort();
+		
+		if (dataRepresentationTemplateNumber == 0) dataRepresentationTemplate = new DataRepresentationTemplate50(byteBuffer);
+		else if (dataRepresentationTemplateNumber == 2) dataRepresentationTemplate = new DataRepresentationTemplate52(byteBuffer);
+		else if (dataRepresentationTemplateNumber == 3) dataRepresentationTemplate = new DataRepresentationTemplate53(byteBuffer);
+		else System.out.println("Data Representation Template Number 5." + dataRepresentationTemplateNumber + " not implemented.");
+	}
+	
+	@Deprecated
+	public int getNumDataPoints(){ return numberDataPoints; }
+
+	public int getNumValues(){ return numberValues; }
+
+	@Deprecated
+	public float calcValue(short unsignedraw) {
+	
+		// Calculate value according to the GRIB specification 
+		int raw = adjustUnsignedShort(unsignedraw);
+		float val = (dataRepresentationTemplate.referenceValueR + (float)(0+raw) * (float)Math.pow(2, dataRepresentationTemplate.binaryScaleFactorE) / (float)Math.pow(10, dataRepresentationTemplate.decimalScaleFactorD));
+		
+		return val;
+	}
+	
+	public float calcValue(byte valuesArray[], int sourcevaluecnt) {
+
+		float val = 0;
+		
+		if (dataRepresentationTemplateNumber == 0) {
+
+			int numbits = ((DataRepresentationTemplate50)dataRepresentationTemplate).numberBits;
+			int baseMask = (int)(Math.pow(2, numbits) - 1); 
+
+			// Position of the value in the array of values of Section 7
+			int sourcebitpos = sourcevaluecnt * numbits;
+	
+			// Position of byte and bit of the value array 
+			int valstartbyte = (int)Math.floor(sourcebitpos/8);
+			int valstartbit = sourcebitpos % 8;
+	
+			// Retrieve value from value array
+			int raw;
+			if (numbits <= 8) {
+
+				// Adapt value array access to align to array end
+				int readstartbyte;
+				int rawValueBitLength;
+				if (valstartbyte < valuesArray.length-2) {
+					readstartbyte = valstartbyte;
+					rawValueBitLength = 16;
+				}
+				else {
+					readstartbyte = valuesArray.length-2;
+					rawValueBitLength = 16 + ((valuesArray.length-2) - valstartbyte) * 8;
+				}
+
+				// Get data from values array
+				ByteBuffer byteBuffer = ByteBuffer.wrap(valuesArray, readstartbyte, 2);
+				short rawValue = byteBuffer.getShort();
+	
+				// Adjust for correct position in bit stream
+				rawValue = (short) (rawValue >> (rawValueBitLength-numbits-valstartbit));
+				rawValue = (short) (rawValue & baseMask);
+
+				raw = adjustUnsignedByte(rawValue);
+			}
+			else {
+
+				// Adapt value array access to align to array end
+				int readstartbyte;
+				int rawValueBitLength;
+				if (valstartbyte < valuesArray.length-4) {
+					readstartbyte = valstartbyte;
+					rawValueBitLength = 32;
+				}
+				else {
+					readstartbyte = valuesArray.length-4;
+					rawValueBitLength = 32 + ((valuesArray.length-4) - valstartbyte) * 8;
+				}
+
+				// Get data from values array				
+				ByteBuffer byteBuffer = ByteBuffer.wrap(valuesArray, readstartbyte, 4);
+				int rawValue = byteBuffer.getInt();
+	
+				// Adjust for correct position in bit stream
+				rawValue = (int) (rawValue >> (rawValueBitLength-numbits-valstartbit));
+				rawValue = (int) (rawValue & baseMask);
+
+				raw = adjustUnsignedShort(rawValue);
+			}
+			
+			// Calculate value according to the GRIB specification 
+			val = (dataRepresentationTemplate.referenceValueR + (float)(0+raw) * (float)Math.pow(2, dataRepresentationTemplate.binaryScaleFactorE) / (float)Math.pow(10, dataRepresentationTemplate.decimalScaleFactorD));
+		}
+		
+		else {
+			log.warning("Data Representation Template Number 5." + dataRepresentationTemplateNumber + " not implemented.");
+		}
+
+		return val;
+	}
+}

+ 40 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection6.java

@@ -0,0 +1,40 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public class GribSection6 extends GribSection {	
+	
+	private static final long serialVersionUID = 100L;
+
+	// Content and structure of a Section 6
+	public byte bitMapIndicator;
+	public byte[] bitMap;
+	
+	
+	public GribSection6(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection6(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+
+	public void readSection() {
+		
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectiondata);
+
+		// Parse section and extract data
+		bitMapIndicator = byteBuffer.get();
+		
+		bitMap = new byte[sectionlength-6];
+		byteBuffer.get(bitMap);
+	}
+}

+ 70 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection7.java

@@ -0,0 +1,70 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate50;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate52;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate53;
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate5x;
+import com.ph.grib2tools.grib2file.datarepresentation.DataSection72;
+import com.ph.grib2tools.grib2file.datarepresentation.DataSection73;
+import com.ph.grib2tools.grib2file.datarepresentation.DataSection7x;
+
+public class GribSection7 extends GribSection {	
+	
+	private static final long serialVersionUID = 200L;
+	
+	private int numberDataPoints;
+	private DataRepresentationTemplate5x dataRepresentation;
+	public DataSection7x data;
+	
+	// Contains the full data grid. If a bit map is used, this field contains the
+	// full data grid, including the fields without a value, which are set to 0.
+	// If no bitmap is used, this field contains the section 7 data
+	public byte bitmapDecodedData[];
+
+	public GribSection7(int len, byte num, byte[] data) {
+		super(len, num, data);
+		bitmapDecodedData = data;
+	}
+	
+	public GribSection7(InputStream gribfile) throws IOException {
+		super(gribfile);		
+	}
+
+	public GribSection7(GribSection gribSection) {
+		super(gribSection.sectionlength, gribSection.sectionnumber, gribSection.sectiondata);
+	}
+
+	@Override
+	public void readData(InputStream gribfile) throws IOException {
+		super.readData(gribfile);
+		readSection();
+	}
+
+	public void readSection() {
+		ByteBuffer byteBuffer = ByteBuffer.wrap(sectiondata);
+		if (dataRepresentation.getClass().equals(DataRepresentationTemplate50.class)) { 
+			// No action required
+		}
+		else if (dataRepresentation.getClass().equals(DataRepresentationTemplate52.class)) {
+			data = new DataSection72(numberDataPoints, (DataRepresentationTemplate52) dataRepresentation, byteBuffer);
+		} else if (dataRepresentation.getClass().equals(DataRepresentationTemplate53.class)) {
+			data = new DataSection73(numberDataPoints, (DataRepresentationTemplate53) dataRepresentation, byteBuffer);
+		} else
+			System.out.println("Data Representation Template Number 7." + dataRepresentation + " not implemented.");
+	}	
+	
+	public void setDataRepresentation(int numberDataPoints,
+                                      DataRepresentationTemplate5x dataRepresentation)
+	{
+		this.numberDataPoints = numberDataPoints;
+		this.dataRepresentation = dataRepresentation;
+	}
+	
+	public void setBitmapDecodedData(byte decodedData[]) {
+		bitmapDecodedData = decodedData;
+	}
+}

+ 53 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/GribSection8.java

@@ -0,0 +1,53 @@
+package com.ph.grib2tools.grib2file;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+public class GribSection8 implements Serializable {	
+	
+	private static final long serialVersionUID = 100L;
+
+	// End identifier of a GRIB file 
+	protected final static byte[] GRIBENDIDENTIFIER = {55, 55, 55, 55};
+
+	// Content and structure of a Section 8
+	byte[] endidentifierbytes = new byte[4];
+
+	
+	public GribSection8(InputStream gribfile) {
+
+		try {
+			
+			// Read complete section
+			byte[] section8 = new byte[4];
+			gribfile.read(section8);		
+			ByteBuffer byteBuffer = ByteBuffer.wrap(section8);
+
+			// Parse section and extract data
+			byteBuffer.get(endidentifierbytes);
+
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	public void writeToStream(OutputStream gribFile) {
+				
+		try {
+			
+			gribFile.write(endidentifierbytes);
+			
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	public boolean verifyEndIdentifier() {
+		return (ByteBuffer.wrap(endidentifierbytes).compareTo(ByteBuffer.wrap(GRIBENDIDENTIFIER)) == 0);			
+	}
+}

+ 415 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/RandomAccessGribFile.java

@@ -0,0 +1,415 @@
+package com.ph.grib2tools.grib2file;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate50;
+import com.ph.grib2tools.grib2file.griddefinition.GridDefinitionTemplate30;
+
+// A representation of a GRIB file containing all meta data and allowing a random
+// access to the data of the GRIB file. While the random access allows great flexibility
+// it requires that the complete data is held in memory 
+public class RandomAccessGribFile extends GribFile {
+
+	private static final long serialVersionUID = 100L;
+
+	private static final Logger log = Logger.getLogger(RandomAccessGribFile.class.getName());
+
+	
+	public RandomAccessGribFile(String typeid, String source) {
+		super(typeid, source);
+	}
+
+	public void importFromStream(InputStream gribfile, int numskip) throws IOException {
+					
+		if (gribfile == null) return;
+		
+		// By overwriting the section variables, the first numskip GRIB file data structures
+		// within a stream or a file can be skipped
+		for (int t=0; t<numskip+1; t++) {		
+
+			gridcnt = 0;
+	
+			//while (true) {
+			while (gridcnt < 1) {
+
+				// Read all meta data but not the data itself in Section 7
+				importMetadatFromStream(gribfile);
+				
+				// Read the data of Section 7 into memory
+				GribSection nextsection = new GribSection(gribfile).initSection();
+				if (nextsection.sectionnumber == 7) {
+					section7[gridcnt] = (GribSection7)nextsection;
+					//section7[gridcnt].setDataRepresentation(section5[gridcnt].numberDataPoints, section5[gridcnt].dataRepresentationTemplate);
+					section7[gridcnt].setDataRepresentation(section5[gridcnt].numberValues, section5[gridcnt].dataRepresentationTemplate);
+					section7[gridcnt].readData(gribfile);
+					
+					// Decode bit map to expand sparse data grid to full data grid
+					section7[gridcnt].setBitmapDecodedData(decodeDataBitmap(gridcnt));
+					
+					gridcnt++;
+				}
+				else {
+					log.warning("Section " + nextsection.sectionnumber + " found while Section 7 expected. aborting.");
+					return;
+				}
+			}
+	
+			finalizeImport(gribfile);
+		}		
+	}
+	
+	private byte[] decodeDataBitmap(int gridcnt) {
+		
+		//GribSection6 section6 = gribFile.getSection6(0);
+		if (section6[gridcnt].bitMapIndicator == 0) {
+			
+			GridDefinitionTemplate30 gridDefinition = (GridDefinitionTemplate30) this.getGridDefinitionTemplate();
+			DataRepresentationTemplate50 dataRepresentation = (DataRepresentationTemplate50) this.getDataRepresentationTemplate(gridcnt);		
+			int numbits = dataRepresentation.numberBits;
+			int baseMask = (int)(Math.pow(2, numbits) - 1); 
+
+			byte valuesArray[] = section7[gridcnt].sectiondata;
+			byte decodedGridData[] = new byte[gridDefinition.numberPointsLat*gridDefinition.numberPointsLon*2];
+			
+			if ((gridDefinition.scanningMode & 0x04) == 0x00) {
+				
+				// Holds the index of the next value in the value array of Section 7
+				int sourcevaluecnt = 0;
+				
+				for (int j=0; j<gridDefinition.numberPointsLat; j++) {
+					for (int i=0; i<gridDefinition.numberPointsLon; i++) {
+
+						// Index of the bit map
+						int bitmapIdx;
+						bitmapIdx = j*gridDefinition.numberPointsLon + i;
+
+						// Position of byte and bit of the bit map 
+						int bitmapBytePos = (int)Math.floor(bitmapIdx/8);
+						int bitmapBitPos = bitmapIdx % 8;
+						byte bitmapByte = section6[gridcnt].bitMap[bitmapBytePos];
+
+						int rawValue;
+						
+						// Grid point without value, here we set this to zero. Depending on the data, this is
+						// not the best value. Maybe value for empty grid pos should be made configurable
+						if ((bitmapByte & (1 << (7-bitmapBitPos))) == 0) rawValue = 0;
+						
+						// Grid point with value
+						else {
+							
+							// Position of the value in the array of values of Section 7
+							int sourcebitpos = sourcevaluecnt * numbits;
+
+							// Position of byte and bit of the value array 
+							int valstartbyte = (int)Math.floor(sourcebitpos/8);
+							int valstartbit = sourcebitpos % 8;
+
+							// Retrieve value from value array
+							if (numbits <= 8) {
+
+								// Adapt value array access to align to array end
+								int readstartbyte;
+								int rawValueBitLength;
+								if (valstartbyte < valuesArray.length-2) {
+									readstartbyte = valstartbyte;
+									rawValueBitLength = 16;
+								}
+								else {
+									readstartbyte = valuesArray.length-2;
+									rawValueBitLength = 16 + ((valuesArray.length-2) - valstartbyte) * 8;
+								}
+
+								// Get data from values array
+								ByteBuffer byteBuffer = ByteBuffer.wrap(valuesArray, readstartbyte, 2);
+								rawValue = byteBuffer.getShort();
+
+								// Adjust for correct position in bit stream
+								rawValue = (byte) (rawValue >> (rawValueBitLength-numbits-valstartbit));
+								rawValue = (byte) (rawValue & baseMask);
+							}
+							else {
+
+								// Adapt value array access to align to array end
+								int readstartbyte;
+								int rawValueBitLength;
+								if (valstartbyte < valuesArray.length-4) {
+									readstartbyte = valstartbyte;
+									rawValueBitLength = 32;
+								}
+								else {
+									readstartbyte = valuesArray.length-4;
+									rawValueBitLength = 32 + ((valuesArray.length-4) - valstartbyte) * 8;
+								}
+
+								// Get data from values array
+								ByteBuffer byteBuffer = ByteBuffer.wrap(valuesArray, readstartbyte, 4);
+								rawValue = byteBuffer.getInt();
+
+								// Adjust for correct position in bit stream
+								rawValue = (int) (rawValue >> (rawValueBitLength-numbits-valstartbit));
+								rawValue = (int) (rawValue & baseMask);
+							}
+
+							// Increase the index of the next value in the value array of Section 7
+							sourcevaluecnt++;
+						}
+
+						
+						// Position of the value in the array of values of Section 7
+						int targetbitpos = bitmapIdx * numbits;
+
+						// Position of byte and bit of the value array 
+						int targetstartbyte = (int)Math.floor(targetbitpos/8);
+						int targetstartbit = targetbitpos % 8;
+
+						// Write value in the full grid data array
+						if (numbits <= 8) {
+
+							// Adapt target data array access to align to array end
+							int readstartbyte;
+							int rawValueBitLength;
+							if (targetstartbyte < decodedGridData.length-2) {
+								readstartbyte = targetstartbyte;
+								rawValueBitLength = 16;
+							}
+							else {
+								readstartbyte = decodedGridData.length-2;
+								rawValueBitLength = 16 + ((decodedGridData.length-2) - targetstartbyte) * 8;
+							}
+
+							// Create bit mask of length numbits shifted to have the alignment of
+							// the data in the bit stream
+							short mask = (short)(baseMask << (rawValueBitLength-numbits-targetstartbit));
+
+							// Blend data into right position of the existing data stream 
+							ByteBuffer byteBuffer = ByteBuffer.wrap(decodedGridData, readstartbyte, 2);
+							short curData = byteBuffer.getShort();
+							short data = (short)(curData & (~mask));
+							data = (short)(data | (rawValue  << (rawValueBitLength-numbits-targetstartbit)));
+
+							decodedGridData[readstartbyte] = (byte) ((data >> 8) & 0xFF);														
+							decodedGridData[readstartbyte+1] = (byte) (data & 0xFF);														
+						}
+						else {
+
+							// Adapt target data array access to align to array end
+							int readstartbyte;
+							int rawValueBitLength;
+							if (targetstartbyte < decodedGridData.length-4) {
+								readstartbyte = targetstartbyte;
+								rawValueBitLength = 32;
+							}
+							else {
+								readstartbyte = decodedGridData.length-4;
+								rawValueBitLength = 32 + ((decodedGridData.length-4) - targetstartbyte) * 8;
+							}
+
+							// Create bit mask of length numbits shifted to have the alignment of
+							// the data in the bit stream
+							int mask = (int)(baseMask << (rawValueBitLength-numbits-targetstartbit));
+							
+							// Blend data into right position of the existing data stream 
+							ByteBuffer byteBuffer = ByteBuffer.wrap(decodedGridData, readstartbyte, 4);
+							int curData = byteBuffer.getInt();
+							int data = (int)(curData & (~mask));
+							data = (int)(data | (rawValue  << (rawValueBitLength-numbits-targetstartbit)));
+
+							decodedGridData[readstartbyte] = (byte) (data >> 24);
+							decodedGridData[readstartbyte+1] = (byte) ((data >> 16) & 0xFF);														
+							decodedGridData[readstartbyte+2] = (byte) ((data >> 8) & 0xFF);														
+							decodedGridData[readstartbyte+3] = (byte) (data & 0xFF);														
+						}
+					}
+				}
+			}	
+				
+			return decodedGridData;
+		}
+		
+		else {
+			return section7[gridcnt].sectiondata;
+		}
+	}
+	
+	// Extracts the data belonging to the passed coordinate (lat, lon) in degrees and returns the
+	// value represented by this data. If the passed coordinate is not a grid point of the
+	// data grid, the data belonging to the grid point closest to the passed position
+	// is considered. 
+	public float getValueAtLocation(int grididx, double lat, double lon) {
+		return getValueAt(grididx, GribFile.degToUnits(lat), GribFile.degToUnits(lon));
+	}
+	
+	// Extracts the data belonging to the passed coordinate (lat, lon) in units and returns the
+	// value represented by this data. If the passed coordinate is not a grid point of the
+	// data grid, the data belonging to the grid point closest to the passed position
+	// is considered. 
+	public float getValueAt(int grididx, int lat, int lon) {
+	
+		GribSection5 sec5 = getSection5(grididx);
+		GribSection7 sec7 = getSection7(grididx);
+				
+		float val = 0;
+		
+//		if (sec5.dataRepresentationTemplateNumber == 0) {
+
+			if (section3.gridDefinitionTemplateNumber == 0) {
+	
+				GridDefinitionTemplate30 gridDefinition = (GridDefinitionTemplate30)section3.gridDefinitionTemplate;
+	
+				int deltaj = 0;
+/*
+				// Implementation of Scanning Modes
+//				if ((gridDefinition.scanningMode & 0x01) == 0x01) log.warning("Scanning mode " + 0x01 + " not supported");
+//				if ((gridDefinition.scanningMode & 0x02) == 0x02) log.warning("Scanning mode " + 0x02 + " not supported");
+				if ((gridDefinition.scanningMode & 0x04) == 0x04) log.warning("Scanning mode " + 0x04 + " not supported");
+				if ((gridDefinition.scanningMode & 0x08) == 0x08) log.warning("Scanning mode " + 0x08 + " not supported");
+				if ((gridDefinition.scanningMode & 0x10) == 0x10) log.warning("Scanning mode " + 0x10 + " not supported");
+				if ((gridDefinition.scanningMode & 0x20) == 0x20) log.warning("Scanning mode " + 0x20 + " not supported");
+				if ((gridDefinition.scanningMode & 0x40) == 0x40) deltaj = gridDefinition.jDirectionIncrement / 2;
+				if ((gridDefinition.scanningMode & 0x80) == 0x80) log.warning("Scanning mode " + 0x80 + " not supported");
+*/
+				deltaj = gridDefinition.getDeltaJ();
+	
+				// Calculate j index of the matrix containing the data the contains the data of the 
+				// passed latitude lat
+				int deltalat = lat - (gridDefinition.firstPointLat + deltaj);
+				//int jidx = Math.round((float)deltalat / (float)gridDefinition.jDirectionIncrement);
+//				int jidx = Math.round((float)deltalat / (float)gridDefinition.getStepJ());
+				int jidx = (int)Math.floor((float)deltalat / (float)gridDefinition.getStepJ());
+
+				// Calculate i index of the matrix containing the data the contains the data of the 
+				// passed longitude lon
+				int firstPointLon = gridDefinition.firstPointLon + 0;
+				if (firstPointLon >= GribFile.degToUnits(180)) firstPointLon -= GribFile.degToUnits(360);
+				int deltalon = lon - firstPointLon;
+				//int iidx = Math.round((float)deltalon / (float)gridDefinition.iDirectionIncrement);
+				int iidx = Math.round((float)deltalon / (float)gridDefinition.getStepI());
+
+				// Extract data belonging to the referred location and calculate the value represented by the data
+				byte data[] = sec7.bitmapDecodedData;
+				val = sec5.calcValue(data, jidx*gridDefinition.numberPointsLon+iidx);
+			}
+			
+			else {
+				log.warning("Grid Definition Template Number 3." + section3.gridDefinitionTemplateNumber + " not implemented.");
+			}
+/*
+		}
+		
+		else {
+			log.warning("Data Representation Template Number 5." + sec5.dataRepresentationTemplateNumber + " not implemented.");
+		}
+*/		
+		return val;
+	}
+
+	// Extracts the data belonging to the passed coordinate (lat, lon) in degrees and returns the
+	// value represented by this data. If the passed coordinate is not a grid point of the
+	// data grid, the data belonging to the neighbored grid points is interpolated and used
+	// to calculate the value belonging to the passed position.
+	public float interpolateValueAtLocation(int grididx, double lat, double lon) {
+		return interpolateValueAt(grididx, GribFile.degToUnits(lat), GribFile.degToUnits(lon));
+	}
+
+	// Extracts the data belonging to the passed coordinate (lat, lon) in units and returns the
+	// value represented by this data. If the passed coordinate is not a grid point of the
+	// data grid, the data belonging to the neighbored grid points is interpolated and used
+	// to calculate the value belonging to the passed position.
+	public float interpolateValueAt(int grididx, int lat, int lon) {
+		
+		GribSection5 sec5 = getSection5(grididx);
+		GribSection7 sec7 = getSection7(grididx);
+		
+		float val = 0;
+		
+//		if (sec5.dataRepresentationTemplateNumber == 0) {
+
+			if (section3.gridDefinitionTemplateNumber == 0) {
+	
+				GridDefinitionTemplate30 gridDefinition = (GridDefinitionTemplate30)section3.gridDefinitionTemplate;
+
+				int deltaj = 0;
+/*
+				// Implementation of Scanning Modes
+//				if ((gridDefinition.scanningMode & 0x01) == 0x01) log.warning("Scanning mode " + 0x01 + " not supported");
+//				if ((gridDefinition.scanningMode & 0x02) == 0x02) log.warning("Scanning mode " + 0x02 + " not supported");
+				if ((gridDefinition.scanningMode & 0x04) == 0x04) log.warning("Scanning mode " + 0x04 + " not supported");
+				if ((gridDefinition.scanningMode & 0x08) == 0x08) log.warning("Scanning mode " + 0x08 + " not supported");
+				if ((gridDefinition.scanningMode & 0x10) == 0x10) log.warning("Scanning mode " + 0x10 + " not supported");
+				if ((gridDefinition.scanningMode & 0x20) == 0x20) log.warning("Scanning mode " + 0x20 + " not supported");
+				if ((gridDefinition.scanningMode & 0x40) == 0x40) deltaj = gridDefinition.jDirectionIncrement / 2;
+				if ((gridDefinition.scanningMode & 0x80) == 0x80) log.warning("Scanning mode " + 0x80 + " not supported");					
+*/
+				deltaj = gridDefinition.getDeltaJ();
+deltaj = 0;				
+				// Find j indices of the grid points of the matrix containing the data that surround the
+				// passed latitude lat
+				int deltalat = lat - (gridDefinition.firstPointLat + deltaj);
+				//int jidx1 = Math.round((float)deltalat / (float)gridDefinition.jDirectionIncrement);
+				//int jidx1 = (int)Math.floor((float)deltalat / (float)gridDefinition.jDirectionIncrement);
+				int jidx1 = (int)Math.floor((float)deltalat / (float)gridDefinition.getStepJ());
+				
+				// Correct indices when end of the array dimension is reached
+				if (jidx1 >= gridDefinition.numberPointsLat-1) jidx1--; 
+				int jidx2 = jidx1 + 1;
+
+				// Find i indices of the grid points of the matrix containing the data that surround the
+				// passed longitude lon
+				int firstPointLon = gridDefinition.firstPointLon + 0;
+// 17.10.21
+//				if (firstPointLon >= GribFile.degToUnits(180)) firstPointLon -= GribFile.degToUnits(360);
+				int deltalon = lon - firstPointLon;
+				//int iidx1 = Math.round((float)deltalon / (float)gridDefinition.iDirectionIncrement);
+				//int iidx1 = (int)Math.floor((float)deltalon / (float)gridDefinition.iDirectionIncrement);
+				int iidx1 = (int)Math.floor((float)deltalon / (float)gridDefinition.getStepI());
+
+				// Correct indices when end of the array dimension is reached
+				if (iidx1 >= gridDefinition.numberPointsLon-1) iidx1--; 
+				int iidx2 = iidx1 + 1;
+
+				
+				//byte data[] = sec7.sectiondata;				
+				byte data[] = sec7.bitmapDecodedData;				
+
+				// Extract data of the four grid points surrounding the passed coordinate and calculate the
+				// values represented by the data
+				float val11 = sec5.calcValue(data, jidx1*gridDefinition.numberPointsLon+iidx1);
+				float val12 = sec5.calcValue(data, jidx1*gridDefinition.numberPointsLon+iidx2); 
+				
+				float val21 = sec5.calcValue(data, jidx2*gridDefinition.numberPointsLon+iidx1);
+				float val22 = sec5.calcValue(data, jidx2*gridDefinition.numberPointsLon+iidx2); 
+
+				
+				// Find latitudes of the grid points of the matrix containing the data that surround the
+				// passed latitude lat
+				//int lat1 = (gridDefinition.firstPointLat + deltaj) + jidx1 * gridDefinition.jDirectionIncrement;
+				//int lat2 = lat1 + gridDefinition.jDirectionIncrement;
+				int lat1 = (gridDefinition.firstPointLat + deltaj) + jidx1 * gridDefinition.getStepJ();
+				int lat2 = lat1 + gridDefinition.getStepJ();
+
+				// Find longitudes of the grid points of the matrix containing the data that surround the
+				// passed longitude lon
+				int lon1 = firstPointLon + iidx1 * gridDefinition.getStepI();
+				int lon2 = lon1 + gridDefinition.getStepI();
+
+				// Interpolate value belonging to the passed location
+				float val1x = val11 + (val12-val11) * (lon-lon1) / (lon2-lon1);				
+				float val2x = val21 + (val22-val21) * (lon-lon1) / (lon2-lon1);
+				val = val1x + (val2x-val1x) * (lat-lat1) / (lat2-lat1);
+			}
+			
+			else {
+				log.warning("Grid Definition Template Number 3." + section3.gridDefinitionTemplateNumber + " not implemented.");
+			}
+/*			
+		}
+
+		else {
+			log.warning("Data Representation Template Number 5." + sec5.dataRepresentationTemplateNumber + " not implemented.");
+		}
+*/
+		return val;
+	}
+}

+ 89 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/StreamedGribFile.java

@@ -0,0 +1,89 @@
+package com.ph.grib2tools.grib2file;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+import com.ph.grib2tools.grib2file.datarepresentation.DataRepresentationTemplate50;
+
+// A representation of a GRIB file containing all meta data and allowing access to a
+// GRIB file that is streamed. As a consequence of the streaming, it is not possible
+// to access the data randomly.
+public class StreamedGribFile extends GribFile {
+
+	private static final long serialVersionUID = 100L;
+
+	protected InputStream gribfile;
+	
+	private static final Logger log = Logger.getLogger(StreamedGribFile.class.getName());
+
+	
+	public StreamedGribFile(String typeid, String source) {
+		super(typeid, source);
+	}
+
+	public boolean prepareImportFromStream(InputStream gribfile1, int numskip) throws IOException {
+					
+		this.gribfile = gribfile1;
+		
+		if (gribfile == null) return false;
+		
+		// By overwriting the section variables, the first numskip GRIB file data structures
+		// within a stream or a file can be skipped
+		for (int t=0; t<numskip+1; t++) {		
+	
+			gridcnt = 0;
+
+			// Read all meta data but not the data itself in Section 7
+			importMetadatFromStream(gribfile);
+
+			// Consider Section 7 but do not read its data into memory
+			GribSection nextsection = new GribSection(gribfile).initSection();
+			if (nextsection.sectionnumber == 7) {
+				section7[gridcnt] = (GribSection7)nextsection;
+				gridcnt++;
+			}
+			else {
+				log.warning("Section " + nextsection.sectionnumber + " found while Section 7 expected. aborting.");
+				return false;
+			}
+
+			// End import of data if a Section 7 does not contain any data
+			if (section7[gridcnt-1].sectionlength == 5) finalizeImport(gribfile1);
+		}
+
+		return true;
+	}
+
+	// Extracts the next data field from the streamed GRIB file and returns the value represented
+	// by the data
+	public float nextValue() throws IOException {
+	
+		int grididx = 0;
+		
+		GribSection5 sec5 = getSection5(grididx);
+		
+		float val = 0;
+		
+		if (sec5.dataRepresentationTemplateNumber == 0) {
+
+			// Get the resolution of the data (number of bytes per value)
+			DataRepresentationTemplate50 dataRepresentation = (DataRepresentationTemplate50)sec5.dataRepresentationTemplate;	
+			int bytesperval = dataRepresentation.numberBits / 8;
+			
+			// Read data from data stream
+			byte data[] = new byte[bytesperval];
+			gribfile.read(data);
+
+			// Determine the value represented by the data
+			short unsignedraw = ByteBuffer.wrap(data).getShort();
+			val = sec5.calcValue(unsignedraw);
+		}
+		
+		else {
+			log.warning("Data Representation Template Number 5." + sec5.dataRepresentationTemplateNumber + " not implemented.");
+		}
+		
+		return val;
+	}
+}

+ 22 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate50.java

@@ -0,0 +1,22 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.GribSection;
+
+public class DataRepresentationTemplate50 extends DataRepresentationTemplate5x {
+	
+	private static final long serialVersionUID = 100L;
+
+	public byte numberBits;
+	public byte typeOfField;
+		
+
+	public DataRepresentationTemplate50(ByteBuffer byteBuffer) {
+
+		referenceValueR = byteBuffer.getFloat();
+		binaryScaleFactorE = GribSection.correctNegativeShort(byteBuffer.getShort());
+		decimalScaleFactorD = GribSection.correctNegativeShort(byteBuffer.getShort());
+		numberBits = byteBuffer.get();
+		typeOfField = byteBuffer.get();
+	}	
+}

+ 36 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate52.java

@@ -0,0 +1,36 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.GribSection;
+
+public class DataRepresentationTemplate52 extends DataRepresentationTemplate50 {
+	
+	private static final long serialVersionUID = 100L;
+	byte groupSplittingMethodUsed;
+	byte missingValueManagementUsed;
+	long primaryMissingValueSubstitute;
+	long secondaryMissingValueSubstitute;
+	int numberOfGroupsOfDataValues;
+	short referenceForGroupWidths;
+	short numberOfBitsUsedForTheGroupWidths;
+	int referenceForGroupLengths;
+	short lengthIncrementForTheGroupLengths;
+	int trueLengthOfLastGroup;
+	short numberOfBitsForScaledGroupLengths;
+
+	public DataRepresentationTemplate52(ByteBuffer byteBuffer) {
+	   super(byteBuffer);
+	   assert byteBuffer.position() == 21 - 5;
+	   groupSplittingMethodUsed = byteBuffer.get();
+	   missingValueManagementUsed = byteBuffer.get();
+	   primaryMissingValueSubstitute = GribSection.adjustUnsignedInt( byteBuffer.getInt() );
+	   secondaryMissingValueSubstitute = GribSection.adjustUnsignedInt( byteBuffer.getInt() );
+	   numberOfGroupsOfDataValues = byteBuffer.getInt() ;      // If negative this will go wrong during data extraction anyway
+	   referenceForGroupWidths = GribSection.adjustUnsignedByte(byteBuffer.get());
+	   numberOfBitsUsedForTheGroupWidths = GribSection.adjustUnsignedByte(byteBuffer.get());
+	   referenceForGroupLengths = byteBuffer.getInt() ;        // If negative this will go wrong during data extraction anyway
+	   lengthIncrementForTheGroupLengths = GribSection.adjustUnsignedByte(byteBuffer.get());
+	   trueLengthOfLastGroup = byteBuffer.getInt();            // If negative this will go wrong during data extraction anyway
+	   numberOfBitsForScaledGroupLengths = GribSection.adjustUnsignedByte(byteBuffer.get());
+	}	
+}

+ 19 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate53.java

@@ -0,0 +1,19 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.GribSection;
+
+public class DataRepresentationTemplate53 extends DataRepresentationTemplate52 {
+	
+	private static final long serialVersionUID = 100L;
+	byte orderOfSpatialDifferencing;
+	short numberOfOctetsExtraDescriptors;
+		
+
+	public DataRepresentationTemplate53(ByteBuffer byteBuffer) {
+	   super(byteBuffer);
+	   assert byteBuffer.position()==47 - 5;
+		orderOfSpatialDifferencing = byteBuffer.get();
+		numberOfOctetsExtraDescriptors = GribSection.adjustUnsignedByte(byteBuffer.get());
+	}	
+}

+ 12 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataRepresentationTemplate5x.java

@@ -0,0 +1,12 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+
+import java.io.Serializable;
+
+public class DataRepresentationTemplate5x implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+
+	public float referenceValueR;
+	public short binaryScaleFactorE;
+	public short decimalScaleFactorD;
+}

+ 119 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataSection72.java

@@ -0,0 +1,119 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.GribSection;
+
+public class DataSection72 extends DataSection7x {
+	
+	private static final long serialVersionUID = 100L;
+	
+   // 6-xx              NG group reference values (XI in the decoding formula), each of which is encoded using the number of bits specified in octet 20 of Data Representation Template 5.0. Bits set to zero shall be appended as necessary to ensure this sequence of numbers ends on an octet boundary.
+	public int[] groupRefValues;
+   // [xx+1]-yy         NG group widths, each of which is encoded using the number of bits specified in octet 37 of Data Representation Template 5.2. Bits set to zero shall be appended as necessary to ensure this sequence of numbers ends on an octet boundary.
+	public int[] groupWidths;
+   // [yy+1]-zz         NG scaled group lengths, each of which is encoded using the number of bits specified in octet 47 of Data Representation Template 5.2. Bits set to zero shall be appended as necessary to ensure this sequence of numbers ends on an octet boundary. (see Note 14 of Data Representation Template 5.2)
+	public int[] scaledGroupLength;
+   
+   
+   // It's sucks but data are inverted.
+   protected DataSection72() {}
+	
+	public DataSection72(int numberDataPoints, DataRepresentationTemplate52 representation, ByteBuffer byteBuffer)
+	{
+	   readData72(numberDataPoints, representation, byteBuffer);
+   }
+	
+	protected final void readData72(int numberDataPoints, DataRepresentationTemplate52 representation, ByteBuffer byteBuffer)
+   {
+      groupRefValues    = new int[(int) representation.numberOfGroupsOfDataValues];
+      groupWidths       = new int[(int) representation.numberOfGroupsOfDataValues];
+      scaledGroupLength = new int[(int) representation.numberOfGroupsOfDataValues];
+      variablePart      = new int[numberDataPoints];
+      BitReader bR = new BitReader(byteBuffer);
+      for (int i=0; i<groupRefValues.length; ++i)
+      {
+         groupRefValues[i] = bR.readBits( representation.numberBits );
+      }
+      bR.flushBit();
+      for (int i=0; i<groupWidths.length; ++i)
+      {
+         groupWidths[i] = bR.readBits( representation.numberOfBitsUsedForTheGroupWidths );
+      }
+      bR.flushBit();
+      for (int i=0; i<scaledGroupLength.length; ++i)
+      {
+         scaledGroupLength[i] = bR.readBits(representation.numberOfBitsForScaledGroupLengths);
+      }
+      bR.flushBit();
+      
+      int groupWidth, idxGrp=0, idxX=0;
+      // For each group except last
+      for ( ; idxGrp<groupRefValues.length-1; ++idxGrp)
+      {
+         int groupLength = scaledGroupLength[idxGrp] * representation.lengthIncrementForTheGroupLengths + representation.referenceForGroupLengths;
+         groupWidth = groupWidths[idxGrp];
+         for (int idxInGrp=0; idxInGrp < groupLength; ++idxInGrp, ++idxX)
+         {
+            variablePart[idxX] = bR.readBits(groupWidth) + groupRefValues[idxGrp];
+         }
+      }
+      // Read last group
+      groupWidth = groupWidths[idxGrp];
+      for (int idxInGrp=0; idxInGrp < representation.trueLengthOfLastGroup; ++idxInGrp, ++idxX)
+      {
+         variablePart[idxX] = bR.readBits(groupWidth) + groupRefValues[idxGrp];
+      }
+   }
+      
+   public static class BitReader
+   {
+      ByteBuffer bb;
+      int byteInProgress = 0;
+      int nbBitRead = 8;
+      public BitReader(ByteBuffer byteBuffer)
+      {
+         this.bb = byteBuffer;
+      }
+      public int readBits(int nbBitRequested)
+      {
+         int nbBitRequestedRemaining = nbBitRequested;
+         int result = 0;
+         // If remaining bit in buffer
+         if (8-nbBitRead>0)
+         {
+            // Extract all available but not more than needed
+            int nbAvailable = Math.min(8-nbBitRead, nbBitRequestedRemaining);
+            // First a shift to the right to eliminate the "not yet to read" bits (if any)
+            // Then a mask to eliminate the "already read" bits (if any)
+            result = (byteInProgress >> (8-nbBitRead-nbAvailable)) & ((1<<nbAvailable)-1);
+            // We have read data in the buffer, they are no more available
+            nbBitRead += nbAvailable;
+            nbBitRequestedRemaining -= nbAvailable;
+         }
+         // There is a need to read full byte (that's easy)
+         while (nbBitRequestedRemaining>=8)
+         {
+            byteInProgress = GribSection.adjustUnsignedByte(bb.get());
+            result = (result << 8) + byteInProgress;
+            nbBitRequestedRemaining -= 8;
+         }
+         // Read the last bit needed (less than 8)
+         if (nbBitRequestedRemaining>0)
+         {
+            // Get the next byte
+            byteInProgress = GribSection.adjustUnsignedByte(bb.get());
+            // First a shift to the left for the "already recovered" bits
+            // Second a shift to the right to eliminate the "not yet to read" bits (they will always be some)
+            result = (result << nbBitRequestedRemaining) + (byteInProgress >> (8-nbBitRequestedRemaining));
+            // The buffered byte is not empty
+            nbBitRead = nbBitRequestedRemaining;
+         }
+         return result;
+      }
+      public void flushBit()
+      {
+         nbBitRead = 8;
+      }
+   }
+}

+ 46 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataSection73.java

@@ -0,0 +1,46 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+
+import java.nio.ByteBuffer;
+
+import com.ph.grib2tools.grib2file.GribSection;
+
+public class DataSection73 extends DataSection72 {
+	
+	private static final long serialVersionUID = 100L;
+	
+	public int[] fstValuesOfOriginal;
+   public DataSection73(int numberDataPoints, DataRepresentationTemplate53 representation, ByteBuffer byteBuffer) {
+      // We cant read "parent" data here
+      super();
+	   fstValuesOfOriginal = new int[representation.orderOfSpatialDifferencing+1];
+	   for (int i=0; i<fstValuesOfOriginal.length; ++i)
+	   {
+	      switch (representation.numberOfOctetsExtraDescriptors)
+	      {
+	         case 1:
+	            fstValuesOfOriginal[i] = GribSection.correctNegativeByte(byteBuffer.get());
+	            break;
+	         case 2:
+	            fstValuesOfOriginal[i] = GribSection.correctNegativeShort(byteBuffer.getShort());
+	            break;
+	         case 4:
+	            fstValuesOfOriginal[i] = GribSection.correctNegativeInt(byteBuffer.getInt());
+	            break;
+            default:
+	            throw new IllegalArgumentException(String.format("Unsupported nb of octets : %d", representation.numberOfOctetsExtraDescriptors));
+	      }
+	   }
+	   readData72(numberDataPoints, representation, byteBuffer);
+	   assert !byteBuffer.hasRemaining();
+	   
+	   // Compute real values
+	   // ****************************************
+	   // WARNING : That's the part i am not sure : we lose the first 2 variablePart why ?
+      // ****************************************
+	   for (int i=0; i<fstValuesOfOriginal.length-1; ++i) { variablePart[i] = fstValuesOfOriginal[i]; }
+	   int on = fstValuesOfOriginal[fstValuesOfOriginal.length-1];
+	   for (int i=fstValuesOfOriginal.length-1; i<variablePart.length; ++i) {
+	      variablePart[i] = variablePart[i]+on+2*variablePart[i-1]-variablePart[i-2];
+	   }
+   }
+}

+ 9 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/datarepresentation/DataSection7x.java

@@ -0,0 +1,9 @@
+package com.ph.grib2tools.grib2file.datarepresentation;
+
+public class DataSection7x
+{
+   /** The sum x1+x2 so that real value = (ref + (x1+x2)*2^n)/10^n.
+    * See "Guide to the WMO Table Driven Code Form Used for the Representation and Exchange of Regularly
+    * Spaced Data In Binary Form" */
+   public int[] variablePart;
+}

+ 136 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/griddefinition/GridDefinitionTemplate30.java

@@ -0,0 +1,136 @@
+package com.ph.grib2tools.grib2file.griddefinition;
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+import com.ph.grib2tools.grib2file.GribSection;
+import com.ph.grib2tools.grib2file.RandomAccessGribFile;
+
+public class GridDefinitionTemplate30 extends GridDefinitionTemplate3x {
+	
+	private static final long serialVersionUID = 100L;
+
+	private static final Logger log = Logger.getLogger(GridDefinitionTemplate30.class.getName());
+
+	public byte shapeOfEarth;
+	public byte scaleFactorRadiusSphericalEarth;
+	public int scaledValueRadiusSphericalEarth;
+	public byte scaleFactorMajorAxisOblateSpheroidEarth;
+	public int scaledValueMajorAxisOblateSpheroidEarth;
+	public byte scaleFactorMinorAxisOblateSpheroidEarth;
+	public int scaledValueMinorAxisOblateSpheroidEarth;
+	public int numberPointsLon;
+	public int numberPointsLat;
+	public int basicAngle;
+	public int subdivisionsBasicAngle;
+	public int firstPointLat;
+	public int firstPointLon;
+	public byte resolutionAndComponentFlags;
+	public int lastPointLat;
+	public int lastPointLon;
+	
+	// The following are protected, should bed accessed using getStepI() and getStepJ()
+	public int iDirectionIncrement;
+	public int jDirectionIncrement;
+	public byte scanningMode;
+	
+	
+	public GridDefinitionTemplate30(ByteBuffer byteBuffer) {
+
+		shapeOfEarth = byteBuffer.get();
+		scaleFactorRadiusSphericalEarth = byteBuffer.get();
+		scaledValueRadiusSphericalEarth = byteBuffer.getInt();
+		scaleFactorMajorAxisOblateSpheroidEarth = byteBuffer.get();
+		scaledValueMajorAxisOblateSpheroidEarth = byteBuffer.getInt();
+		scaleFactorMinorAxisOblateSpheroidEarth = byteBuffer.get();
+		scaledValueMinorAxisOblateSpheroidEarth = byteBuffer.getInt();
+		numberPointsLon = byteBuffer.getInt();
+		numberPointsLat = byteBuffer.getInt();
+		basicAngle = byteBuffer.getInt();
+		subdivisionsBasicAngle = byteBuffer.getInt();
+		firstPointLat = GribSection.correctNegativeInt(byteBuffer.getInt());
+		firstPointLon = GribSection.correctNegativeInt(byteBuffer.getInt());
+		resolutionAndComponentFlags = byteBuffer.get();
+		lastPointLat = GribSection.correctNegativeInt(byteBuffer.getInt());
+		lastPointLon = GribSection.correctNegativeInt(byteBuffer.getInt());
+		iDirectionIncrement = GribSection.correctNegativeInt(byteBuffer.getInt());
+		jDirectionIncrement = GribSection.correctNegativeInt(byteBuffer.getInt());
+		scanningMode = byteBuffer.get();
+		
+		// Supporte Scanning Modes: 0x01, 0x02, 0x40
+		if ((scanningMode & 0xBC) != 0x00) log.warning("Scanning mode " + scanningMode + " not supported");
+	}
+
+	// Compares this GridDefinitionTemplate30 with the passed GridDefinitionTemplate30 other
+	public boolean equals(GridDefinitionTemplate30 other) {
+
+		if (other == null) return false;
+		
+		if ((this.firstPointLat == other.firstPointLat) &&
+			(this.firstPointLon == other.firstPointLon) &&
+			(this.lastPointLat == other.lastPointLat) &&
+			(this.lastPointLon == other.lastPointLon) &&
+			(this.iDirectionIncrement == other.iDirectionIncrement) &&
+			(this.jDirectionIncrement == other.jDirectionIncrement) &&
+			(this.numberPointsLat == other.numberPointsLat) &&
+			(this.numberPointsLon == other.numberPointsLon)
+			) {
+			return true;
+		}
+		
+		return false;
+	}
+	
+	public int getStepI() {
+		
+		int sign = getDirectionI();
+		return iDirectionIncrement * sign;
+	}
+	
+	public int getStepJ() {
+
+		int sign = getDirectionJ();
+		return jDirectionIncrement * sign;
+	}
+
+	public int getDirectionI() {
+		
+		int sign;
+/*
+		if ((scanningMode & 1) != 0) sign = -1;
+		else sign = 1;
+*/
+		if (lastPointLon > firstPointLon) sign = 1;
+		else sign = -1;
+
+		return sign;
+	}
+	
+	public int getDirectionJ() {
+
+		int sign;
+/*
+		if ((scanningMode & 2) != 0) sign = 1;
+		else sign = -1;
+*/
+		if (lastPointLat > firstPointLat) sign = 1;
+		else sign = -1;
+
+		return sign;
+	}
+
+	public int getDeltaJ() {
+		
+		int delta = 0;
+		
+//		if ((scanningMode & 0x01) == 0x01) log.warning("Scanning mode " + 0x01 + " not supported");
+//		if ((scanningMode & 0x02) == 0x02) log.warning("Scanning mode " + 0x02 + " not supported");
+//		if ((scanningMode & 0x04) == 0x04) log.warning("Scanning mode " + 0x04 + " not supported");
+//		if ((scanningMode & 0x08) == 0x08) log.warning("Scanning mode " + 0x08 + " not supported");
+//		if ((scanningMode & 0x10) == 0x10) log.warning("Scanning mode " + 0x10 + " not supported");
+//		if ((scanningMode & 0x20) == 0x20) log.warning("Scanning mode " + 0x20 + " not supported");
+		if ((scanningMode & 0x40) == 0x40) delta = jDirectionIncrement / 2;
+//		if ((scanningMode & 0x80) == 0x80) log.warning("Scanning mode " + 0x80 + " not supported");					
+
+		return delta;
+	}
+}

+ 8 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/griddefinition/GridDefinitionTemplate3x.java

@@ -0,0 +1,8 @@
+package com.ph.grib2tools.grib2file.griddefinition;
+
+import java.io.Serializable;
+
+public class GridDefinitionTemplate3x implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+}

+ 43 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/ProductDefinitionTemplate40.java

@@ -0,0 +1,43 @@
+package com.ph.grib2tools.grib2file.productdefinition;
+import java.nio.ByteBuffer;
+
+public class ProductDefinitionTemplate40 extends ProductDefinitionTemplate4x {
+	
+	private static final long serialVersionUID = 100L;
+
+	public byte parameterCategory;
+	public byte parameterNumber;
+	public byte typeOfGeneratingProcess;
+	public byte backgroundGeneratingProcessId;
+	public byte analysisOrForecastGeneratingProcessId;
+	public short hoursObservationalDataCutOff;
+	public byte minutesObservationalDataCutOff;
+	public byte unitTimeRange;
+	public int forecastTime;
+	public byte typeFirstFixedSurface;
+	public byte scaleFactorFirstFixedSurface;
+	public int scaledValueFirstFixedSurface;
+	public byte typeSecondFixedSurface;
+	public byte scaleFactorSecondFixedSurface;
+	public int scaledValueSecondFixedSurface;	
+	
+
+	public ProductDefinitionTemplate40(ByteBuffer byteBuffer) {
+
+		parameterCategory = byteBuffer.get();
+		parameterNumber = byteBuffer.get();
+		typeOfGeneratingProcess = byteBuffer.get();
+		backgroundGeneratingProcessId = byteBuffer.get();
+		analysisOrForecastGeneratingProcessId = byteBuffer.get();
+		hoursObservationalDataCutOff = byteBuffer.getShort();
+		minutesObservationalDataCutOff = byteBuffer.get();
+		unitTimeRange = byteBuffer.get();
+		forecastTime = byteBuffer.getInt();
+		typeFirstFixedSurface = byteBuffer.get();
+		scaleFactorFirstFixedSurface = byteBuffer.get();
+		scaledValueFirstFixedSurface = byteBuffer.getInt();
+		typeSecondFixedSurface = byteBuffer.get();
+		scaleFactorSecondFixedSurface = byteBuffer.get();
+		scaledValueSecondFixedSurface = byteBuffer.getInt();	
+	}
+}

+ 37 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/ProductDefinitionTemplate48.java

@@ -0,0 +1,37 @@
+package com.ph.grib2tools.grib2file.productdefinition;
+import java.nio.ByteBuffer;
+
+public class ProductDefinitionTemplate48 extends ProductDefinitionTemplate4x {	
+	
+	private static final long serialVersionUID = 100L;
+
+	public ProductDefinitionTemplate40 productDefinitionTemplate40;
+
+	public short yearEnd;
+	public byte monthEnd;
+	public byte dayEnd;
+	public byte hourEnd;
+	public byte minuteEnd;
+	public byte secondEnd;
+	public byte numberTimeRangeSpecifications;
+	public int totalNumberDataValuesMissing;
+	public TimeRangeSpecification[] timeRangeSpecification; 
+
+
+	public ProductDefinitionTemplate48(ByteBuffer byteBuffer) {
+
+		productDefinitionTemplate40 = new ProductDefinitionTemplate40(byteBuffer);
+		
+		yearEnd = byteBuffer.getShort();
+		monthEnd = byteBuffer.get();
+		dayEnd = byteBuffer.get();
+		hourEnd = byteBuffer.get();
+		minuteEnd = byteBuffer.get();
+		secondEnd = byteBuffer.get();
+		numberTimeRangeSpecifications = byteBuffer.get();
+		totalNumberDataValuesMissing = byteBuffer.getInt();
+		
+		timeRangeSpecification = new TimeRangeSpecification[numberTimeRangeSpecifications];
+		for (int i=0; i<numberTimeRangeSpecifications; i++) timeRangeSpecification[i] = new TimeRangeSpecification(byteBuffer);
+	}
+}

+ 8 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/ProductDefinitionTemplate4x.java

@@ -0,0 +1,8 @@
+package com.ph.grib2tools.grib2file.productdefinition;
+
+import java.io.Serializable;
+
+public class ProductDefinitionTemplate4x implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+}

+ 26 - 0
connector-fetch-gfs/src/main/java/com/ph/grib2tools/grib2file/productdefinition/TimeRangeSpecification.java

@@ -0,0 +1,26 @@
+package com.ph.grib2tools.grib2file.productdefinition;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+
+public class TimeRangeSpecification implements Serializable {
+
+	private static final long serialVersionUID = 100L;
+
+	public byte statisticalProcess;
+	public byte typeOfTimeIncrement;
+	public byte unitTimeRange;
+	public int lengthTimeRange;
+	public byte unitTimeIncrement;
+	public int lengthTimeIncrement;
+	
+	
+	public TimeRangeSpecification(ByteBuffer byteBuffer) {
+
+		statisticalProcess = byteBuffer.get();
+		typeOfTimeIncrement = byteBuffer.get();
+		unitTimeRange = byteBuffer.get();
+		lengthTimeRange = byteBuffer.getInt();
+		unitTimeIncrement = byteBuffer.get();
+		lengthTimeIncrement = byteBuffer.getInt();
+	}
+}

+ 16 - 0
connector-fetch-gfs/src/main/java/cz/senslog/connector/fetch/gfs/ConnectorFetchGFSProvider.java

@@ -0,0 +1,16 @@
+package cz.senslog.connector.fetch.gfs;
+
+import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+
+public class ConnectorFetchGFSProvider implements ConnectorFetchProvider {
+
+    @Override
+    public ExecutableFetcher<? extends AbstractModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+
+        return ExecutableFetcher.create(new GFSFetcher());
+
+    }
+}

+ 75 - 0
connector-fetch-gfs/src/main/java/cz/senslog/connector/fetch/gfs/GFSFetcher.java

@@ -0,0 +1,75 @@
+package cz.senslog.connector.fetch.gfs;
+
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.model.gfs.GFSModel;
+
+
+import com.ph.grib2tools.grib2file.GribFile;
+import com.ph.grib2tools.grib2file.GribSection1;
+import com.ph.grib2tools.grib2file.RandomAccessGribFile;
+import com.ph.grib2tools.grib2file.griddefinition.GridDefinitionTemplate30;
+import com.ph.grib2tools.grib2file.productdefinition.ProductDefinitionTemplate40;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+public class GFSFetcher implements ConnectorFetcher<GFSSession, GFSModel> {
+
+    @Override
+    public void init() throws Exception {
+
+    }
+
+    @Override
+    public GFSModel fetch(Optional<GFSSession> session) {
+
+        String filename = "/home/vrenclouff/Development/Senslog/connector-period/connector-fetch-gfs/src/test/resources/gfs.t00z.goessimpgrb2.0p25.f003";
+
+        // Defines how many GRIB file structures shall be skipped when reading the GRIB2 file. This
+        // is useful since some organizations put several GRIB2 file structures in one file.
+        int numskip = 0;
+
+        // Id of the grid within the GRIBF2 file, since GRIB2 files can contain several grids (Sections 5-7)
+        int gridid = 0;
+
+        System.out.println("Reading file " + filename + ", file structure " + numskip + ":");
+        try {
+
+            // Open GRIB2 file and read it
+            InputStream inputstream;
+            inputstream = Files.newInputStream(Paths.get(filename));
+            RandomAccessGribFile gribFile = new RandomAccessGribFile("testdata", filename);
+            gribFile.importFromStream(inputstream, numskip);
+
+            // Get identification information
+            GribSection1 section1 = gribFile.getSection1();
+            System.out.println("Date: " + String.format("%02d", section1.day) + "." + String.format("%02d", section1.month) + "." + section1.year);
+            System.out.println("Time: " + String.format("%02d", section1.hour) + ":" + String.format("%02d", section1.minute) + "." + String.format("%02d", section1.second));
+            System.out.println("Generating centre: " + section1.generatingCentre);
+
+            // Get product information
+            ProductDefinitionTemplate40 productDefinition = (ProductDefinitionTemplate40)gribFile.getProductDefinitionTemplate();
+            System.out.println("Forecast time: " + productDefinition.forecastTime);
+            System.out.println("Parameter category: " + productDefinition.parameterCategory);
+            System.out.println("Parameter number: " + productDefinition.parameterNumber);
+
+            // Get grid information
+            GridDefinitionTemplate30 gridDefinition = (GridDefinitionTemplate30)gribFile.getGridDefinitionTemplate();
+            System.out.println("Covered area:");
+            System.out.println("   from (latitude, longitude): " + GribFile.unitsToDeg(gridDefinition.firstPointLat) + ", " + GribFile.unitsToDeg(gridDefinition.firstPointLon));
+            System.out.println("   to: (latitude, longitude): " + GribFile.unitsToDeg(gridDefinition.lastPointLat) + ", " + GribFile.unitsToDeg(gridDefinition.lastPointLon));
+
+            // Get grid data
+            double latitude = 52.52;
+            double longitude = 13.38;
+            System.out.println("Value at (" + latitude + ", " + longitude + "): " + gribFile.interpolateValueAtLocation(gridid, latitude, longitude));
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 10 - 0
connector-fetch-gfs/src/main/java/cz/senslog/connector/fetch/gfs/GFSSession.java

@@ -0,0 +1,10 @@
+package cz.senslog.connector.fetch.gfs;
+
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+public class GFSSession extends ProxySessionModel {
+
+    public GFSSession(boolean isActive) {
+        super(isActive);
+    }
+}

+ 1 - 0
connector-fetch-gfs/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider

@@ -0,0 +1 @@
+cz.senslog.connector.fetch.gfs.ConnectorFetchGFSProvider

+ 19 - 0
connector-fetch-gfs/src/test/java/cz/senslog/connector/fetch/gfs/GFSFetcherTest.java

@@ -0,0 +1,19 @@
+package cz.senslog.connector.fetch.gfs;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class GFSFetcherTest {
+
+    @Test
+    void fetch() throws IOException {
+        new GFSFetcher().fetch(null);
+    }
+}

+ 38 - 0
connector-fetch-gfs/src/test/java/cz/senslog/connector/fetch/gfs/TestResourceUtils.java

@@ -0,0 +1,38 @@
+package cz.senslog.connector.fetch.gfs;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Properties;
+
+public final class TestResourceUtils {
+
+    public static Path getResourcePath(String filePath) throws URISyntaxException {
+        URL url = TestResourceUtils.class.getClassLoader().getResource(filePath);
+        return Paths.get(Objects.requireNonNull(url).toURI());
+    }
+
+    public static byte[] loadResourceFile(String filePath) {
+        try {
+            return Files.readAllBytes(getResourcePath(filePath));
+        } catch (URISyntaxException | IOException e) {
+            System.err.println(Arrays.toString(e.getStackTrace()));
+            return new byte[0];
+        }
+    }
+
+    public static Properties loadProperties(String filename) {
+        Properties props = new Properties();
+        try {
+            props.load(Files.newInputStream(getResourcePath((filename))));
+        } catch (URISyntaxException | IOException e) {
+            System.err.println(e.getMessage());
+        }
+        return props;
+    }
+}

BIN
connector-fetch-gfs/src/test/resources/gfs.t00z.goessimpgrb2.0p25.f003


+ 4 - 0
connector-fetch-gfs/src/test/resources/gfs.t00z.goessimpgrb2.0p25.f003.idx

@@ -0,0 +1,4 @@
+1:0:d=2024041000:var discipline=3 center=7 local_table=1 parmcat=192 parm=53:top of atmosphere:3 hour fcst:
+2:946352:d=2024041000:var discipline=3 center=7 local_table=1 parmcat=192 parm=54:top of atmosphere:3 hour fcst:
+3:1952057:d=2024041000:var discipline=3 center=7 local_table=1 parmcat=192 parm=55:top of atmosphere:3 hour fcst:
+4:3038592:d=2024041000:var discipline=3 center=7 local_table=1 parmcat=192 parm=58:top of atmosphere:3 hour fcst:

+ 39 - 0
connector-fetch-senslog-telemetry/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cz.senslog</groupId>
+        <artifactId>connector-period</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <artifactId>connector-fetch-senslog-telemetry</artifactId>
+    <name>fetch-senslog-telemetry</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-fetch-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 33 - 0
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/ConnectorFetchSenslogTelemetryProvider.java

@@ -0,0 +1,33 @@
+package cz.senslog.fetch.senslog.telemetry;
+
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.tools.http.HttpClient;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class ConnectorFetchSenslogTelemetryProvider implements cz.senslog.connector.fetch.api.ConnectorFetchProvider {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorFetchSenslogTelemetryProvider.class);
+
+    @Override
+    public ExecutableFetcher<GeoJsonModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new fetch provider {}.", ConnectorFetchSenslogTelemetryProvider.class.getSimpleName());
+
+        logger.debug("Creating a new configuration.");
+        SensLogTelemetryConfig config = new SensLogTelemetryConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", SensLogTelemetryFetcher.class.getSimpleName());
+
+
+        logger.debug("Creating a new instance of {}.", SensLogTelemetryFetcher.class);
+        SensLogTelemetryFetcher fetcher = new SensLogTelemetryFetcher(config, HttpClient.newHttpSSLClient());
+        logger.info("Fetcher for {} was created successfully.", SensLogTelemetryFetcher.class.getSimpleName());
+
+        logger.debug("Creating a new instance of {}.", SensLogTelemetryProxySession.class);
+        ExecutableFetcher<GeoJsonModel> executor = ExecutableFetcher.createWithProxySession(new SensLogTelemetryProxySession(fetcher));
+        logger.info("Fetcher executor for {} was created successfully.", SensLogTelemetryFetcher.class.getSimpleName());
+
+        return executor;
+    }
+}

+ 54 - 0
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryConfig.java

@@ -0,0 +1,54 @@
+package cz.senslog.fetch.senslog.telemetry;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+
+import java.time.LocalDateTime;
+
+public class SensLogTelemetryConfig {
+
+    private final String baseUrl;
+    private final String bearerToken;
+    private final LocalDateTime startAt;
+    private final int campaignId;
+    private final int limit;
+    private final int interval;
+
+    SensLogTelemetryConfig(DefaultConfig defaultConfig) {
+        this.baseUrl = defaultConfig.getStringProperty("baseUrl");
+        this.bearerToken = defaultConfig.getStringProperty("bearerToken");
+
+        if (defaultConfig.containsProperty("startAt")) {
+            String startAtString = defaultConfig.getStringProperty("startAt");
+            this.startAt = startAtString.equalsIgnoreCase("now") ? LocalDateTime.now() : defaultConfig.getLocalDateTimeProperty("startAt");
+        } else {
+            this.startAt = null;
+        }
+        this.campaignId = defaultConfig.getIntegerProperty("campaignId");
+        this.limit = defaultConfig.getIntegerProperty("limit");
+        this.interval = defaultConfig.getIntegerProperty("interval");
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public LocalDateTime getStartAt() {
+        return startAt;
+    }
+
+    public String getBearerToken() {
+        return bearerToken;
+    }
+
+    public int getLimit() {
+        return limit;
+    }
+
+    public int getCampaignId() {
+        return campaignId;
+    }
+
+    public int getInterval() {
+        return interval;
+    }
+}

+ 151 - 0
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryFetcher.java

@@ -0,0 +1,151 @@
+package cz.senslog.fetch.senslog.telemetry;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.json.BasicJson;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+
+import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_GEO_JSON;
+import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_JSON;
+import static cz.senslog.connector.tools.http.HttpHeader.*;
+
+public class SensLogTelemetryFetcher implements ConnectorFetcher<SensLogTelemetrySession, GeoJsonModel> {
+
+    private static final Logger logger = LogManager.getLogger(SensLogTelemetryFetcher.class);
+
+    private static final class CampaignInfo {
+        int id;
+        String name, description;
+        OffsetDateTime fromTime, toTime;
+    }
+
+    private final SensLogTelemetryConfig config;
+
+    private final HttpClient httpClient;
+
+    private CampaignInfo campaignInfo;
+
+    public SensLogTelemetryFetcher() {
+        this(null, null);
+    }
+
+    public SensLogTelemetryFetcher(SensLogTelemetryConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    public void init() throws Exception {
+        assert config != null; assert httpClient != null;
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("campaigns/%d", config.getCampaignId()))
+                        .addParam("zone", "UTC")
+                        .addParam("navigationLinks", "false")
+                        .build())
+                .header(ACCEPT, APPLICATION_JSON)
+                .header(AUTHORIZATION, "Bearer " + config.getBearerToken())
+                .build();
+
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isError()) {
+            throw logger.throwing(new RuntimeException(response.getBody()));
+        }
+
+        campaignInfo = BasicJson.jsonToObject(response.getBody(), CampaignInfo.class);
+        if (campaignInfo.id != config.getCampaignId()) {
+            throw logger.throwing(new RuntimeException("Invalid campaign id: " + config.getCampaignId()));
+        }
+
+        logger.info("Initialized fetching telemetries from the campaign '{}'", campaignInfo.name);
+    }
+
+    @Override
+    public GeoJsonModel fetch(Optional<SensLogTelemetrySession> sessionOpt) {
+
+        final OffsetDateTime now = OffsetDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
+
+        SensLogTelemetrySession session = sessionOpt.orElse(null);
+        assert session != null;
+
+        if (session.hasNotNext()) {
+            if (session.getFromTime() == null) {
+                if (config.getStartAt() != null) {
+                    session.setFromTime(OffsetDateTime.of(config.getStartAt(), ZoneOffset.UTC));
+                } else {
+                    session.setFromTime(campaignInfo.fromTime);
+                }
+                session.setToTime(session.getFromTime().plusHours(config.getInterval()));
+            }
+
+            if (session.getToTime().isAfter(now)) {
+                logger.info("To early for the new data, the next interval is in {} minutes.", Duration.between(now, session.getToTime()).toMinutes());
+                return GeoJsonModel.empty();
+            }
+        }
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("campaigns/%d/units/observations", config.getCampaignId()))
+                        .addParam("limit", config.getLimit())
+                        .addParam("format", "geojson")
+                        .addParam("from", session.getFromTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
+                        .addParam("to", session.getToTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
+                        .addParam("zone", "UTC")
+                        .addParam("offset", session.getOffset())
+                        .addParam("navigationLinks", "false")
+                        .build())
+                .header(ACCEPT, APPLICATION_GEO_JSON)
+                .header(AUTHORIZATION, "Bearer " + config.getBearerToken())
+                .build();
+
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isError()) {
+            logger.error(response.getBody());
+            return GeoJsonModel.empty();
+        }
+
+        JsonObject geoJson = BasicJson.jsonToObject(response.getBody(), JsonObject.class);
+
+        JsonObject metadata = geoJson.remove("metadata").getAsJsonObject();
+        int geoSize = metadata.get("size").getAsInt();
+        session.setOffset(geoSize);
+        session.hasNext(metadata.get("hasNext").getAsBoolean());
+
+        if (session.hasNotNext()) {
+            session.setFromTime(session.getToTime());
+            session.setToTime(session.getToTime().plusHours(config.getInterval()));
+            session.setOffset(0);
+        }
+
+        JsonArray features = geoJson.getAsJsonArray("features");
+        if (features.size() != geoSize) {
+            logger.warn("Retrieved metadata size of features differs to " + geoSize + " from " + features.size());
+            geoSize = features.size();
+        }
+
+        if (geoSize <= 0) {
+            return GeoJsonModel.empty();
+        }
+
+        OffsetDateTime fTimestampFirst = OffsetDateTime.parse(features.get(0).getAsJsonObject().getAsJsonObject("properties").get("timestamp").getAsString());
+        OffsetDateTime fTimestampLast = OffsetDateTime.parse(features.get(geoSize-1).getAsJsonObject().getAsJsonObject("properties").get("timestamp").getAsString());
+
+        return new GeoJsonModel(geoJson, fTimestampFirst, fTimestampLast);
+    }
+}

+ 25 - 0
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryProxySession.java

@@ -0,0 +1,25 @@
+package cz.senslog.fetch.senslog.telemetry;
+
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.api.FetchProxySession;
+import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.util.Optional;
+
+public class SensLogTelemetryProxySession extends FetchProxySession<SensLogTelemetrySession, GeoJsonModel> {
+
+    protected SensLogTelemetryProxySession(ConnectorFetcher<SensLogTelemetrySession, GeoJsonModel> instance) {
+        super(instance);
+    }
+
+    @Override
+    protected SensLogTelemetrySession preProcessing(Optional<SensLogTelemetrySession> previousSession) {
+        return previousSession.filter(ProxySessionModel::isActive).orElse(new SensLogTelemetrySession(true));
+    }
+
+    @Override
+    protected void postProcessing(Optional<GeoJsonModel> model, Optional<SensLogTelemetrySession> session) {
+
+    }
+}

+ 56 - 0
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetrySession.java

@@ -0,0 +1,56 @@
+package cz.senslog.fetch.senslog.telemetry;
+
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.time.OffsetDateTime;
+
+public class SensLogTelemetrySession extends ProxySessionModel {
+
+    private OffsetDateTime fromTime, toTime;
+    private int offset;
+    private boolean hasNext;
+
+    public SensLogTelemetrySession(boolean isActive) {
+        super(isActive);
+        this.offset = 0;
+        this.hasNext = false;
+        this.fromTime = null;
+        this.toTime = null;
+    }
+
+    public int getOffset() {
+        return offset;
+    }
+
+    public void setOffset(int offset) {
+        this.offset = offset;
+    }
+
+    public boolean hasNext() {
+        return hasNext;
+    }
+
+    public boolean hasNotNext() {
+        return !hasNext;
+    }
+
+    public void hasNext(boolean hasNext) {
+        this.hasNext = hasNext;
+    }
+
+    public OffsetDateTime getFromTime() {
+        return fromTime;
+    }
+
+    public void setFromTime(OffsetDateTime fromTime) {
+        this.fromTime = fromTime;
+    }
+
+    public OffsetDateTime getToTime() {
+        return toTime;
+    }
+
+    public void setToTime(OffsetDateTime toTime) {
+        this.toTime = toTime;
+    }
+}

+ 1 - 0
connector-fetch-senslog-telemetry/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider

@@ -0,0 +1 @@
+cz.senslog.fetch.senslog.telemetry.ConnectorFetchSenslogTelemetryProvider

+ 34 - 0
connector-fetch-senslog-telemetry/src/test/java/cz/senslog/fetch/senslog/telemetry/SensLogTelemetryFetcherTest.java

@@ -0,0 +1,34 @@
+package cz.senslog.fetch.senslog.telemetry;
+
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.tools.http.HttpClient;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SensLogTelemetryFetcherTest {
+
+    @Test
+    void onlineTest() throws Exception {
+
+        DefaultConfig defaultConfig = new DefaultConfig("", null);
+        defaultConfig.setProperty("baseUrl", "https://theros.wirelessinfo.cz/");
+        defaultConfig.setProperty("startAt", "2023-08-25T00:00:00+00:00");
+        defaultConfig.setProperty("bearerToken", "#123");
+        defaultConfig.setProperty("campaignId", 2);
+        defaultConfig.setProperty("limit", 10);
+        defaultConfig.setProperty("interval", 12);
+
+        SensLogTelemetryConfig config = new SensLogTelemetryConfig(defaultConfig);
+        SensLogTelemetryFetcher fetcher = new SensLogTelemetryFetcher(config, HttpClient.newHttpSSLClient());
+        SensLogTelemetryProxySession proxySession = new SensLogTelemetryProxySession(fetcher);
+        ExecutableFetcher<GeoJsonModel> executor = ExecutableFetcher.createWithProxySession(proxySession);
+
+        fetcher.init();
+        for (int i = 0; i < 10; i++) {
+            executor.execute();
+        }
+    }
+}

+ 65 - 19
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogConfig.java

@@ -5,29 +5,71 @@ package cz.senslog.connector.fetch.senslog.v1;
 
 import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.connector.model.config.PropertyConfig;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
 import java.time.LocalDateTime;
+import java.util.TimeZone;
 
 public class SenslogConfig {
 
+    public static class AuthConfig {
+
+        private final String username;
+        private final String password;
+
+        private AuthConfig(PropertyConfig config) {
+            this.username = config.getStringProperty("username");
+            this.password = config.getStringProperty("password");
+        }
+
+        public String getUsername() {
+            return username;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+    }
+
+    private final TimeZone timeZone;
     private final LocalDateTime startDate;
-    private final HostConfig sensorServiceHost;
-    private final HostConfig dataServiceHost;
     private final String user;
-    private final String group;
     private final Integer interval;
     private final AllowedStation allowedStation;
     private final SenslogSessionProxyConfig sessionProxy;
-
+    private final AuthConfig auth;
+    private final String baseUrl;
+    private final URI baseUri;
 
     SenslogConfig(DefaultConfig defaultConfig) {
-        this.startDate = defaultConfig.getLocalDateTimeProperty("startDate");
-        this.sensorServiceHost = new HostConfig(defaultConfig.getPropertyConfig("sensorServiceHost"));
-        this.dataServiceHost = new HostConfig(defaultConfig.getPropertyConfig("dataServiceHost"));
+        this.timeZone = TimeZone.getTimeZone(defaultConfig.getStringProperty("timeZone"));
+        String startDate = defaultConfig.getStringProperty("startDate");
+
+        LocalDateTime tempStartDate = null;
+        if (startDate.equalsIgnoreCase("now")) {
+            tempStartDate = LocalDateTime.ofInstant(Instant.now(), timeZone.toZoneId());
+        } else {
+            tempStartDate = defaultConfig.getLocalDateTimeProperty("startDate");
+        }
+
+        if (defaultConfig.containsProperty("delayStartDate")) {
+            tempStartDate = tempStartDate.minusMinutes(defaultConfig.getIntegerProperty("delayStartDate"));
+        }
+
+        this.startDate = tempStartDate;
         this.user = defaultConfig.getStringProperty("user");
-        this.group = defaultConfig.getStringProperty("group");
         this.interval = defaultConfig.getIntegerProperty("interval");
         this.allowedStation = new AllowedStation(defaultConfig.getPropertyConfig("allowedStation"));
+        this.auth = defaultConfig.containsProperty("auth") ? new AuthConfig(defaultConfig.getPropertyConfig("auth")) : null;
+        this.baseUrl = defaultConfig.getStringProperty("baseUrl");
+        try {
+            this.baseUri = new URI(baseUrl);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("The config is in invalid format. " + e.getMessage());
+        }
 
         this.sessionProxy = defaultConfig.containsProperty("sessionProxy") ?
                 new SenslogSessionProxyConfig(defaultConfig.getPropertyConfig("sessionProxy")) : null;
@@ -37,31 +79,35 @@ public class SenslogConfig {
         return startDate;
     }
 
-    public HostConfig getSensorServiceHost() {
-        return sensorServiceHost;
-    }
-
-    public HostConfig getDataServiceHost() {
-        return dataServiceHost;
+    public TimeZone getTimeZone() {
+        return timeZone;
     }
 
     public String getUser() {
         return user;
     }
 
-    public String getGroup() {
-        return group;
-    }
-
     public Integer getInterval() {
         return interval;
     }
 
-    public AllowedStation getAllowedStation() {
+    AllowedStation getAllowedStation() {
         return allowedStation;
     }
 
     public SenslogSessionProxyConfig getSessionProxy() {
         return sessionProxy;
     }
+
+    public AuthConfig getAuth() {
+        return auth;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public URI getBaseURI() {
+        return baseUri;
+    }
 }

+ 114 - 53
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcher.java

@@ -4,15 +4,11 @@
 package cz.senslog.connector.fetch.senslog.v1;
 
 import com.google.gson.reflect.TypeToken;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.model.v1.Record;
-import cz.senslog.connector.tools.http.HttpClient;
-import cz.senslog.connector.tools.http.HttpRequest;
-import cz.senslog.connector.tools.http.HttpResponse;
-import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.http.*;
 import cz.senslog.connector.tools.util.Tuple;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.model.api.ProxySessionModel;
-import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.connector.model.v1.*;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -21,9 +17,10 @@ import java.lang.reflect.Type;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
 
 import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
-import static java.time.OffsetDateTime.now;
 import static java.time.OffsetDateTime.parse;
 import static java.time.format.DateTimeFormatter.ofPattern;
 
@@ -31,17 +28,24 @@ public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV
 
     private static final Logger logger = LogManager.getLogger(SenslogFetcher.class);
 
+    private static final int MAX_AUTH_ERRORS = 3;
+
     private final SenslogConfig config;
     private final HttpClient httpClient;
 
     private final SensLogSession localSession;
     private final Map<Long, UnitInfo> units;
+    private Tuple<Boolean, HttpCookie> authCookie;
+    private final AtomicInteger authErrors;
+
 
     SenslogFetcher(SenslogConfig config, HttpClient httpClient) {
         this.config = config;
         this.httpClient = httpClient;
         this.localSession = SensLogSession.emptySession();
         this.units = new HashMap<>();
+        this.authCookie = Tuple.of(false, null);
+        this.authErrors = new AtomicInteger(0);
     }
 
     SenslogFetcher() { this(null, null); }
@@ -49,54 +53,117 @@ public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV
 
     protected static class ObservationInfo { Float value; OffsetDateTime time; }
 
-    @Override
-    public void init() {
+    private synchronized HttpCookie getAuthCookie() {
+        /*
+        if (authCookie.getItem1()) {
+            return authCookie.getItem2();
+        }
+
+        if (config.getAuth() == null) {
+            authCookie = Tuple.of(true, null);
+            return authCookie.getItem2();
+        }
+
+         */
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), "/ControllerServlet")
+                        .addParam("username", config.getAuth().getUsername())
+                        .addParam("password", config.getAuth().getPassword())
+                        .build()
+                ).build();
+        logger.info("Getting new auth cookie from the server: {}.", config.getBaseUrl());
+
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received new auth token with the status '{}' from the server {}.", response.getStatus(), config.getBaseUrl());
+
+        if (response.isError()) {
+            logger.warn("Authorization failed. Error code {} with the reason '{}'.", response.getStatus(), response.getBody());
+            HttpCookie cookie = HttpCookie.empty();
+            authCookie = Tuple.of(false, cookie);
+            return cookie;
+        }
+
+        final Type lastObsType = new TypeToken<Map<String, Object>>() {}.getType();
+        Map<String, Object> jsonResponse = jsonToObject(response.getBody(), lastObsType);
+
+        if (!jsonResponse.containsKey("sessionid")) {
+            logger.error("Authorization failed. JSON does not contain session id. {}", response.getBody());
+            HttpCookie cookie = HttpCookie.empty();
+            authCookie = Tuple.of(false, cookie);
+            return cookie;
+        }
+
+        String sessionId = (String) jsonResponse.get("sessionid");
+        String domain = config.getBaseURI().getHost();
+        String path = config.getBaseURI().getPath();
+        HttpCookie cookie = new HttpCookie("JSESSIONID", sessionId, domain, path);
+        authCookie = Tuple.of(true, cookie);
+        return cookie;
+    }
 
-        HostConfig dataServiceHost = config.getDataServiceHost();
-        logger.info("Getting last observations from {}.", dataServiceHost.getDomain());
+    private Optional<String> callRequest(HttpRequest.Builder reqBuilder) {
+        HttpCookie authCookie = getAuthCookie();
+        if (!authCookie.isSecure()) {
+            logger.warn("Auth cookie is not valid to be used.");
+            return Optional.empty();
+        }
+
+        HttpRequest request = reqBuilder.addCookie(authCookie).build();
+        logger.info("Getting new data from the server: {}.", request.getUrl());
+
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received new data with the status '{}' from the server {}.", response.getStatus(), request.getUrl());
+
+        if (response.getStatus() == HttpCode.UNAUTHORIZED && authErrors.get() <= MAX_AUTH_ERRORS) {
+            this.authCookie = Tuple.of(false, null);
+            authErrors.incrementAndGet();
+            return callRequest(reqBuilder);
+        }
+
+        if (response.isError()) {
+            logger.error("code: {}, message: {}", response.getStatus(), response.getBody());
+            throw new IllegalStateException(response.getBody());
+        }
+
+        authErrors.set(0);
+        return Optional.of(response.getBody());
+    }
 
-        HttpRequest unitsRequest = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(dataServiceHost.getDomain(), dataServiceHost.getPath())
+    @Override
+    public void init() throws Exception {
+
+        HttpRequest.Builder reqUnitsBuilder = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), "/DataService")
                         .addParam("Operation", "GetUnitsList")
                         .addParam("user", config.getUser())
-                        .build())
-                .build();
-        logger.info("Creating a http request to {}.", unitsRequest);
+                        .build());
 
-        HttpResponse unitsResponse = httpClient.send(unitsRequest);
-        logger.info("Received a response with a status: {} for the domain {}.", unitsResponse.getStatus(), dataServiceHost.getDomain());
+        Optional<String> resUnitsBody = callRequest(reqUnitsBuilder);
 
-        if (unitsResponse.isOk()) {
-            logger.debug("Parsing body of the response to the list of class {}.", UnitInfo.class);
+        if (resUnitsBody.isPresent()) {
             Type unitInfoType = new TypeToken<Collection<UnitInfo>>() {}.getType();
-            List<UnitInfo> unitInfos = jsonToObject(unitsResponse.getBody(), unitInfoType);
+            List<UnitInfo> unitInfos = jsonToObject(resUnitsBody.get(), unitInfoType);
 
             for (UnitInfo unit : unitInfos) {
                 if (!config.getAllowedStation().isAllowed(unit.getUnitId())) {
-                    logger.info("Unit {} is not allowd in configuration.", unit.getUnitId());continue;
+                    logger.info("Unit {} is not allowed in configuration.", unit.getUnitId());continue;
                 }
 
                 units.put(unit.getUnitId(), unit);
 
-                HostConfig sensorServiceHost = config.getSensorServiceHost();
-                logger.info("Getting last observations from {}.", sensorServiceHost.getDomain());
-
-                HttpRequest sensorRequest = HttpRequest.newBuilder().GET()
-                        .url(URLBuilder.newBuilder(sensorServiceHost.getDomain(), sensorServiceHost.getPath())
+                HttpRequest.Builder reqSensorsBuilder = HttpRequest.newBuilder().GET()
+                        .url(URLBuilder.newBuilder(config.getBaseUrl(), "/SensorService")
                                 .addParam("Operation", "GetSensors")
                                 .addParam("user", config.getUser())
                                 .addParam("unit_id", unit.getUnitId())
-                                .build())
-                        .build();
-                logger.info("Creating a http request to {}.", sensorRequest);
+                                .build());
 
-                HttpResponse sensorResponse = httpClient.send(sensorRequest);
-                logger.info("Received a response with a status: {} for the domain {}.", sensorResponse.getStatus(), sensorServiceHost.getDomain());
+                Optional<String> resSensorsBody = callRequest(reqSensorsBuilder);
 
-                if (sensorResponse.isOk()) {
-                    logger.debug("Parsing body of the response to the list of class {}.", SensorInfo.class);
+                if (resSensorsBody.isPresent()) {
                     Type sensorInfoType = new TypeToken<Collection<SensorInfo>>() {}.getType();
-                    List<SensorInfo> sensors = jsonToObject(sensorResponse.getBody(), sensorInfoType,
+                    List<SensorInfo> sensors = jsonToObject(resSensorsBody.get(), sensorInfoType,
                             Tuple.of(OffsetDateTime.class, el -> parse(el + "00", ofPattern("yyyy-MM-dd HH:mm:ssZ"))));
 
                     logger.info("For the unit {} was added {} sensors.", unit.getUnitId(), sensors.size());
@@ -107,19 +174,17 @@ public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV
                         }
                     }
                     unit.setSensors(allowedSensors);
-                } else {
-                    logger.error("Can not get data from the server {}. Error {} {}",
-                            sensorServiceHost.getDomain(), sensorResponse.getStatus(), sensorResponse.getBody());
                 }
             }
-        } else {
-            logger.error("Can not get data from the server {}. Error {} {}",
-                    dataServiceHost.getDomain(), unitsResponse.getStatus(), unitsResponse.getBody());
         }
     }
 
     @Override
     public SenslogV1Model fetch(Optional<SensLogSession> persistenceSession) {
+        HttpCookie authCookie = getAuthCookie();
+
+        OffsetDateTime now = ZonedDateTime.ofInstant(Instant.now(), config.getTimeZone().toZoneId()).toOffsetDateTime();
+        OffsetDateTime startDate = ZonedDateTime.of(config.getStartDate(), config.getTimeZone().toZoneId()).toOffsetDateTime();
 
         SensLogSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
 
@@ -128,26 +193,22 @@ public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV
         for (UnitInfo unit : units.values()) {
             for (SensorInfo sensor : unit.getSensors()) {
 
-                ZoneOffset offset = sensor.getFirstObservationTime().getOffset();
-
                 OffsetDateTime firstValueDate = sensor.getFirstObservationTime();
-                OffsetDateTime startDate = config.getStartDate().atOffset(offset);
                 OffsetDateTime lastFetch = session.getLiveInfo(unit.getUnitId(), sensor.getSensorId()).lastFetch;
 
-                long fromDateEpoch = Math.max(startDate.toEpochSecond(), Math.max(firstValueDate.toEpochSecond(), lastFetch.toEpochSecond()));
-                OffsetDateTime fromDate = OffsetDateTime.ofInstant(Instant.ofEpochSecond(fromDateEpoch), offset);
-                OffsetDateTime toDate = fromDate.plusHours(config.getInterval());
+                OffsetDateTime fromDate = Stream.of(startDate, firstValueDate, lastFetch).max(Comparator.comparing(OffsetDateTime::toEpochSecond)).get();
+                OffsetDateTime toDate = fromDate.plusMinutes(config.getInterval());
 
-                if (toDate.isAfter(now())) {
-                    continue;
+                if (fromDate.isBefore(now) && toDate.isAfter(now)) {
+                    toDate = now;
                 }
 
                 logger.info("Getting new observations for the unit {} and the sensor {} from {} to {}.", unit.getUnitId(), sensor.getSensorId(), fromDate, toDate);
 
-                final HostConfig sensorServiceHost = config.getSensorServiceHost();
                 final DateTimeFormatter pattern = ofPattern("yyyy-MM-dd HH:mm:ssZ");
                 HttpRequest observationRequest = HttpRequest.newBuilder().GET()
-                        .url(URLBuilder.newBuilder(sensorServiceHost.getDomain(), sensorServiceHost.getPath())
+                        .addCookie(authCookie)
+                        .url(URLBuilder.newBuilder(config.getBaseUrl(), "/SensorService")
                                 .addParam("Operation", "GetObservations")
                                 .addParam("user", config.getUser())
                                 .addParam("unit_id", unit.getUnitId())
@@ -156,10 +217,8 @@ public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV
                                 .addParam("to", toDate.format(pattern))
                                 .build())
                         .build();
-                logger.info("Creating a http request to {}.", observationRequest);
 
                 HttpResponse observationResponse = httpClient.send(observationRequest);
-                logger.info("Received a response with a status: {} for the domain {}.", observationResponse.getStatus(), sensorServiceHost.getDomain());
 
                 if (observationResponse.isOk()) {
                     Type observationType = new TypeToken<Collection<ObservationInfo>>() {}.getType();
@@ -183,6 +242,8 @@ public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV
                     }
 
                     session.updateLastFetch(unit.getUnitId(), sensor.getSensorId(), lastTimeStamp);
+                } else {
+                    logger.error("Request railed: {}", observationResponse.getBody());
                 }
             }
         }

+ 2 - 2
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogProxySession.java

@@ -3,13 +3,13 @@
 
 package cz.senslog.connector.fetch.senslog.v1;
 
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.api.FetchProxySession;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;
 import cz.senslog.connector.tools.http.URLBuilder;
 import cz.senslog.connector.tools.util.Tuple;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.api.FetchProxySession;
 import cz.senslog.connector.model.config.HostConfig;
 import cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter;
 import cz.senslog.connector.model.v1.SenslogV1Model;

+ 30 - 20
connector-fetch-senslog-v1/src/test/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcherTest.java

@@ -1,45 +1,55 @@
 package cz.senslog.connector.fetch.senslog.v1;
 
+import cz.senslog.connector.model.analytics.AnalyticsModel;
+import cz.senslog.connector.model.converter.Senslog1ModelAnalyticsModelConverter;
+import cz.senslog.connector.model.converter.SenslogV1ModelAFarCloudModelConverter;
+import cz.senslog.connector.model.v1.SenslogV1Model;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.tools.json.BasicJson;
 import org.junit.jupiter.api.Test;
 
-import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
+import java.time.*;
+import java.util.*;
 
 import static org.junit.jupiter.api.Assertions.*;
 
 class SenslogFetcherTest {
 
-    /*
+
     @Test
-    void fetch() {
+    void fetch() throws Exception {
 
         DefaultConfig defaultConfig = new DefaultConfig("", null);
 
-        defaultConfig.setProperty("startDate", LocalDateTime.of(2020, 3, 11, 0, 0,0));
-        defaultConfig.setProperty("user", "afarcloud");
-        defaultConfig.setProperty("group", "afc");
-
-        Map<String, String> sensorServiceHost = new HashMap<>();
-        sensorServiceHost.put("domain", "http://foodie.lesprojekt.cz:8080/MapLogOT");
-        sensorServiceHost.put("path", "SensorService");
-        defaultConfig.setProperty("sensorServiceHost", sensorServiceHost);
+        defaultConfig.setProperty("startDate", "now");
+        defaultConfig.setProperty("timeZone", "Europe/Prague");
+        defaultConfig.setProperty("baseUrl", "http://sensor.lesprojekt.cz/senslog15");
+        defaultConfig.setProperty("user", "osek");
+        defaultConfig.setProperty("interval", 20);
 
-        Map<String, String> dataServiceHost = new HashMap<>();
-        dataServiceHost.put("domain", "http://foodie.lesprojekt.cz:8080/MapLogOT");
-        dataServiceHost.put("path", "DataService");
-        defaultConfig.setProperty("dataServiceHost", dataServiceHost);
+        Map<String, String> auth = new HashMap<>();
+        auth.put("username", "osek");
+        auth.put("password", "oSek");
+        defaultConfig.setProperty("auth", auth);
 
+        Map<String, List<Long>> allowedStation = new HashMap<>();
+        allowedStation.put("1305167549174391", Collections.singletonList(340070001L));
+        defaultConfig.setProperty("allowedStation", allowedStation);
 
         SenslogConfig config = new SenslogConfig(defaultConfig);
         SenslogFetcher fetcher = new SenslogFetcher(config, HttpClient.newHttpClient());
+        Senslog1ModelAnalyticsModelConverter converter = new Senslog1ModelAnalyticsModelConverter();
 
         fetcher.init();
-        fetcher.fetch(Optional.empty());
+
+        SensLogSession session = new SensLogSession(true);
+
+        AnalyticsModel a1 = converter.convert(fetcher.fetch(Optional.of(session)));
+        System.out.println(BasicJson.objectToJson(a1.getObservations()));
+
+        AnalyticsModel a2 = converter.convert(fetcher.fetch(Optional.of(session)));
+        System.out.println(BasicJson.objectToJson(a2.getObservations()));
     }
 
-     */
 }

+ 20 - 0
connector-model/src/main/java/cz/senslog/connector/model/analytics/AnalyticsModel.java

@@ -0,0 +1,20 @@
+package cz.senslog.connector.model.analytics;
+
+import cz.senslog.connector.model.api.AbstractModel;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+
+public class AnalyticsModel extends AbstractModel {
+
+    private final List<Observation> observations;
+
+    public AnalyticsModel(List<Observation> observations, OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+        this.observations = observations;
+    }
+
+    public List<Observation> getObservations() {
+        return observations;
+    }
+}

+ 34 - 0
connector-model/src/main/java/cz/senslog/connector/model/analytics/Observation.java

@@ -0,0 +1,34 @@
+package cz.senslog.connector.model.analytics;
+
+import java.time.OffsetDateTime;
+
+public class Observation {
+
+    private final long unitId;
+    private final long sensorId;
+    private final OffsetDateTime timestamp;
+    private final double observedValue;
+
+    public Observation(long unitId, long sensorId, OffsetDateTime timestamp, double observedValue) {
+        this.unitId = unitId;
+        this.sensorId = sensorId;
+        this.timestamp = timestamp;
+        this.observedValue = observedValue;
+    }
+
+    public long getUnitId() {
+        return unitId;
+    }
+
+    public long getSensorId() {
+        return sensorId;
+    }
+
+    public OffsetDateTime getTimestamp() {
+        return timestamp;
+    }
+
+    public double getObservedValue() {
+        return observedValue;
+    }
+}

+ 2 - 2
connector-model/src/main/java/cz/senslog/connector/model/api/ConverterProvider.java

@@ -45,7 +45,7 @@ public abstract class ConverterProvider {
             Converter<?,?> converter = converterClass.newInstance();
 
             CONVERTERS.add(new Wrapper(fetchModel, pushModel, converter));
-            logger.info("Registered a new converter {} for {} -> {}.", converterClass, fetchModel, pushModel);
+            logger.info("Registered a new converter {} for {} -> {}.", converterClass.getSimpleName(), fetchModel.getSimpleName(), pushModel.getSimpleName());
         } catch (InstantiationException | IllegalAccessException e) {
             logger.error("Can not create an instance for {}.", converterClass);
             logger.catching(e);
@@ -54,7 +54,7 @@ public abstract class ConverterProvider {
     }
 
     public Converter getConverter(Class<? extends AbstractModel> fetchModel, Class<? extends AbstractModel> pushModel) {
-        logger.info("Getting a converter for {} -> {}. ", fetchModel, pushModel);
+        logger.info("Getting a converter for {} -> {}. ", fetchModel.getSimpleName(), pushModel.getSimpleName());
         for (Wrapper item : CONVERTERS) {
             if (item.input.equals(fetchModel) && item.output.equals(pushModel)) {
                 return item.converter;

+ 27 - 0
connector-model/src/main/java/cz/senslog/connector/model/api/GeoJsonModel.java

@@ -0,0 +1,27 @@
+package cz.senslog.connector.model.api;
+
+import com.google.gson.JsonObject;
+
+import java.time.OffsetDateTime;
+
+public class GeoJsonModel extends AbstractModel{
+
+    private final JsonObject geoJson;
+
+    public static GeoJsonModel empty() {
+        return new GeoJsonModel(new JsonObject(), null, null);
+    }
+
+    public GeoJsonModel(JsonObject geoJson, OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+        this.geoJson = geoJson;
+    }
+
+    public JsonObject getGeoJson() {
+        return geoJson;
+    }
+
+    public boolean isEmpty() {
+        return geoJson.isEmpty();
+    }
+}

+ 6 - 1
connector-model/src/main/java/cz/senslog/connector/model/config/PropertyConfig.java

@@ -98,7 +98,12 @@ public class PropertyConfig {
      * @return string value.
      */
     public String getStringProperty(String name) {
-        return ClassUtils.cast(getProperty(name), String.class);
+        Object object = getProperty(name);
+        if (object instanceof String) {
+            return (String) object;
+        } else {
+            return object.toString();
+        }
     }
 
     /**

+ 13 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/DemoModelLoggerModelConverter.java

@@ -0,0 +1,13 @@
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.demo.DemoModel;
+import cz.senslog.connector.model.logger.LoggerModel;
+
+public class DemoModelLoggerModelConverter implements Converter<DemoModel, LoggerModel> {
+
+    @Override
+    public LoggerModel convert(DemoModel model) {
+        return new LoggerModel(model.getMessage(), model.getFrom(), model.getTo());
+    }
+}

+ 13 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/GFSModelSenslogV1ModelConverter.java

@@ -0,0 +1,13 @@
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.gfs.GFSModel;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+
+public class GFSModelSenslogV1ModelConverter implements Converter<GFSModel, SenslogV1Model> {
+
+    @Override
+    public SenslogV1Model convert(GFSModel model) {
+        return null;
+    }
+}

+ 11 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/GeoJsonTransientConverter.java

@@ -0,0 +1,11 @@
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.api.GeoJsonModel;
+
+public class GeoJsonTransientConverter implements Converter<GeoJsonModel, GeoJsonModel> {
+    @Override
+    public GeoJsonModel convert(GeoJsonModel model) {
+        return model;
+    }
+}

+ 4 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/ModelConverterProvider.java

@@ -25,5 +25,9 @@ public class ModelConverterProvider extends ConverterProvider {
         register(SenslogV1ModelAFarCloudModelConverter.class);
         register(DrutesModelSenslogV1ModelConverter.class);
         register(AlapansModelSenslogV1ModelConverter.class);
+        register(GFSModelSenslogV1ModelConverter.class);
+        register(Senslog1ModelAnalyticsModelConverter.class);
+        register(GeoJsonTransientConverter.class);
+        register(DemoModelLoggerModelConverter.class);
     }
 }

+ 36 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/Senslog1ModelAnalyticsModelConverter.java

@@ -0,0 +1,36 @@
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.analytics.AnalyticsModel;
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.v1.Observation;
+import cz.senslog.connector.model.v1.Record;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class Senslog1ModelAnalyticsModelConverter implements Converter<SenslogV1Model, AnalyticsModel> {
+
+    @Override
+    public AnalyticsModel convert(SenslogV1Model model) {
+        if (model.getObservations() == null || model.getObservations().isEmpty()) {
+            return new AnalyticsModel(Collections.emptyList(), model.getFrom(), model.getTo());
+        }
+
+        List<cz.senslog.connector.model.analytics.Observation> analyticObservations = new ArrayList<>();
+        for (Record obs : model.getObservations()) {
+            if (!(obs instanceof Observation)) { continue; }
+
+            Observation observation = (Observation) obs;
+            analyticObservations.add(new cz.senslog.connector.model.analytics.Observation(
+                    observation.getUnitId(),
+                    observation.getSensorId(),
+                    observation.getTime(),
+                    observation.getValue()
+            ));
+        }
+
+        return new AnalyticsModel(analyticObservations, model.getFrom(), model.getTo());
+    }
+}

+ 8 - 1
connector-model/src/main/java/cz/senslog/connector/model/demo/DemoModel.java

@@ -6,7 +6,14 @@ import java.time.OffsetDateTime;
 
 public class DemoModel extends AbstractModel {
 
-    protected DemoModel(OffsetDateTime from, OffsetDateTime to) {
+    private final String message;
+
+    public DemoModel(String message, OffsetDateTime from, OffsetDateTime to) {
         super(from, to);
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
     }
 }

+ 12 - 0
connector-model/src/main/java/cz/senslog/connector/model/gfs/GFSModel.java

@@ -0,0 +1,12 @@
+package cz.senslog.connector.model.gfs;
+
+import cz.senslog.connector.model.api.AbstractModel;
+
+import java.time.OffsetDateTime;
+
+public class GFSModel extends AbstractModel
+{
+    protected GFSModel(OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+    }
+}

+ 25 - 0
connector-model/src/main/java/cz/senslog/connector/model/logger/LoggerModel.java

@@ -0,0 +1,25 @@
+package cz.senslog.connector.model.logger;
+
+import cz.senslog.connector.model.api.AbstractModel;
+
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class LoggerModel extends AbstractModel {
+
+    private final String message;
+
+    public LoggerModel(String message, OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public String toString() {
+        return getMessage() + "\t|\t" + getFrom().format(DateTimeFormatter.ISO_DATE_TIME) + " - " + getTo().format(DateTimeFormatter.ISO_DATE_TIME);
+    }
+}

+ 33 - 0
connector-push-analytics/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cz.senslog</groupId>
+        <artifactId>connector-period</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>connector-push-analytics</artifactId>
+    <name>push-analytics</name>
+    <packaging>jar</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-push-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 16 - 0
connector-push-analytics/src/main/java/cz/senslog/connector/push/analytics/AnalyticsConfig.java

@@ -0,0 +1,16 @@
+package cz.senslog.connector.push.analytics;
+
+import cz.senslog.connector.model.config.PropertyConfig;
+
+public class AnalyticsConfig {
+
+    private final String url;
+
+    AnalyticsConfig(PropertyConfig config) {
+        this.url = config.getStringProperty("url");
+    }
+
+    public String getUrl() {
+        return url;
+    }
+}

+ 51 - 0
connector-push-analytics/src/main/java/cz/senslog/connector/push/analytics/AnalyticsPusher.java

@@ -0,0 +1,51 @@
+package cz.senslog.connector.push.analytics;
+
+import cz.senslog.connector.model.analytics.AnalyticsModel;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.json.BasicJson;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class AnalyticsPusher implements ConnectorPusher<AnalyticsModel> {
+
+    private static final Logger logger = LogManager.getLogger(AnalyticsPusher.class);
+
+    private final AnalyticsConfig config;
+    private final HttpClient httpClient;
+
+
+    public AnalyticsPusher(AnalyticsConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    public void init() throws Exception {
+
+    }
+
+    @Override
+    public void push(AnalyticsModel model) {
+        if (model == null || model.getObservations() == null || model.getObservations().isEmpty()) {
+            logger.warn("Nothing to push. The model is empty."); return;
+        }
+
+        HttpRequest request = HttpRequest.newBuilder().POST()
+                .url(URLBuilder.newBuilder(config.getUrl()).build())
+                .header("Content-Type", "application/json")
+                .body(BasicJson.objectToJson(model.getObservations()))
+                .build();
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isOk()) {
+            logger.info("Successfully uploaded to the SensLog Analytics. {}", response.getBody());
+        } else {
+            logger.error("SensLog Analytics Failed. The reason {}.", response.getBody());
+        }
+
+    }
+}

+ 30 - 0
connector-push-analytics/src/main/java/cz/senslog/connector/push/analytics/ConnectorPushAnalyticsProvider.java

@@ -0,0 +1,30 @@
+package cz.senslog.connector.push.analytics;
+
+import cz.senslog.connector.model.analytics.AnalyticsModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.push.api.ConnectorPushProvider;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static cz.senslog.connector.tools.http.HttpClient.newHttpSSLClient;
+
+public class ConnectorPushAnalyticsProvider implements ConnectorPushProvider {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorPushAnalyticsProvider.class);
+
+    @Override
+    public ConnectorPusher<AnalyticsModel> createPusher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new push provider {}.", AnalyticsPusher.class);
+
+        logger.debug("Creating a new configuration.");
+        AnalyticsConfig config = new AnalyticsConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", AnalyticsPusher.class);
+
+        logger.debug("Creating a new instance of {}.", AnalyticsPusher.class);
+        AnalyticsPusher pusher = new AnalyticsPusher(config, newHttpSSLClient());
+        logger.info("Pusher for {} was created successfully.", AnalyticsPusher.class);
+
+        return pusher;
+    }
+}

+ 1 - 0
connector-push-analytics/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider

@@ -0,0 +1 @@
+cz.senslog.connector.push.analytics.ConnectorPushAnalyticsProvider

+ 33 - 0
connector-push-logger/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cz.senslog</groupId>
+        <artifactId>connector-period</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>connector-push-logger</artifactId>
+    <name>push-logger</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-push-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 26 - 0
connector-push-logger/src/main/java/cz/senslog/connector/push/logger/ConnectorPushPushProvider.java

@@ -0,0 +1,26 @@
+package cz.senslog.connector.push.logger;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.logger.LoggerModel;
+import cz.senslog.connector.push.api.ConnectorPushProvider;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class ConnectorPushPushProvider implements ConnectorPushProvider {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorPushPushProvider.class);
+
+    @Override
+    public ConnectorPusher<LoggerModel> createPusher(DefaultConfig config) {
+        return new ConnectorPusher<LoggerModel>() {
+            @Override
+            public void init() {}
+
+            @Override
+            public void push(LoggerModel model) {
+                logger.info("Pushing model: {}", model);
+            }
+        };
+    }
+}

+ 1 - 0
connector-push-logger/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider

@@ -0,0 +1 @@
+cz.senslog.connector.push.logger.ConnectorPushPushProvider

+ 2 - 1
connector-push-senslog-v1/src/main/java/cz/senslog/connector/push/rest/senslog/v1/SenslogV1ConnectorPushProvider.java

@@ -17,7 +17,8 @@ import static cz.senslog.connector.tools.http.HttpClient.newHttpClient;
  * @version 1.0
  * @since 1.0
  */
-public final class SenslogV1ConnectorPushProvider implements ConnectorPushProvider {
+public final class
+SenslogV1ConnectorPushProvider implements ConnectorPushProvider {
 
     private static final Logger logger = LogManager.getLogger(SenslogV1ConnectorPushProvider.class);
 

+ 40 - 0
connector-push-theros/pom.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cz.senslog</groupId>
+        <artifactId>connector-period</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <artifactId>connector-push-theros</artifactId>
+    <name>push-theros</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-push-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 41 - 0
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/AuthConfig.java

@@ -0,0 +1,41 @@
+package cz.senslog.connector.push.theros;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.config.PropertyConfig;
+
+public class AuthConfig {
+
+    private final String url;
+    private final String clientId;
+    private final String clientSecret;
+    private final String username;
+    private final String password;
+
+    AuthConfig(PropertyConfig defaultConfig) {
+        this.url = defaultConfig.getStringProperty("url");
+        this.clientId = defaultConfig.getStringProperty("clientId");
+        this.clientSecret = defaultConfig.getStringProperty("clientSecret");
+        this.username = defaultConfig.getStringProperty("username");
+        this.password = defaultConfig.getStringProperty("password");
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+}

+ 81 - 0
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/AuthService.java

@@ -0,0 +1,81 @@
+package cz.senslog.connector.push.theros;
+
+import com.google.gson.JsonObject;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.json.BasicJson;
+import cz.senslog.connector.tools.json.JsonUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_FORM_URLENCODED;
+import static cz.senslog.connector.tools.http.HttpHeader.CONTENT_TYPE;
+
+public class AuthService {
+
+    private static final Logger logger = LogManager.getLogger(AuthService.class);
+
+    private final AuthConfig config;
+
+    private final HttpClient httpClient;
+
+    private final String credentials;
+
+    private AuthorizationToken authorizationToken;
+
+    AuthService(AuthConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+
+        JsonObject credentialsJson = new JsonObject();
+        credentialsJson.addProperty("client_id", config.getClientId());
+        credentialsJson.addProperty("client_secret", config.getClientSecret());
+        credentialsJson.addProperty("grant_type", "client_credentials");
+        credentialsJson.addProperty("username", config.getUsername());
+        credentialsJson.addProperty("password", config.getPassword());
+
+        this.credentials = JsonUtils.jsonObjectToFormURLEncodedString(credentialsJson);
+        this.authorizationToken = null;
+    }
+
+    public synchronized boolean authenticate() {
+        if (AuthorizationToken.isValid(authorizationToken)) {
+            return true;
+        }
+
+        HttpRequest request = HttpRequest.newBuilder().POST()
+                .url(URLBuilder.newBuilder(config.getUrl()).build())
+                .header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED)
+                .body(credentials)
+                .build();
+
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isError()) {
+            logger.error(response.getBody());
+            authorizationToken = null;
+        } else {
+            JsonObject tokenInfoJson = BasicJson.jsonToObject(response.getBody(), JsonObject.class);
+            authorizationToken = AuthorizationToken.of(
+                    tokenInfoJson.get("access_token").getAsString(),
+                    tokenInfoJson.get("token_type").getAsString(),
+                    tokenInfoJson.get("scope").getAsString(),
+                    tokenInfoJson.get("expires_in").getAsInt()
+            );
+        }
+
+        return AuthorizationToken.isValid(authorizationToken);
+    }
+
+    public AuthorizationToken getAuthorizationToken() {
+        if (AuthorizationToken.isValid(authorizationToken)) {
+            return authorizationToken;
+        }
+        if (authenticate()) {
+            return authorizationToken;
+        }
+        return null;
+    }
+}

+ 65 - 0
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/AuthorizationToken.java

@@ -0,0 +1,65 @@
+package cz.senslog.connector.push.theros;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+
+public class AuthorizationToken {
+
+    private final String accessToken;
+    private final String tokenType;
+    private final String[] scopes;
+    private final LocalDateTime expirationTime;
+
+    public static AuthorizationToken of(String accessToken, String tokenType, String scopes, int expiresInSeconds) {
+        return new AuthorizationToken(accessToken, tokenType, scopes.split(" "), LocalDateTime.now().plusSeconds(expiresInSeconds));
+    }
+
+    public AuthorizationToken(String accessToken, String tokenType, String[] scopes, LocalDateTime expirationTime) {
+        this.accessToken = accessToken;
+        this.tokenType = tokenType;
+        this.scopes = scopes;
+        this.expirationTime = expirationTime;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public String getTokenType() {
+        return tokenType;
+    }
+
+    public String[] getScopes() {
+        return scopes;
+    }
+
+    public LocalDateTime getExpirationTime() {
+        return expirationTime;
+    }
+
+    public boolean isExpired() {
+        return expirationTime.isBefore(LocalDateTime.now());
+    }
+
+    public boolean isNotExpired() {
+        return !isExpired();
+    }
+
+    public boolean isValid() {
+        return isNotExpired() && accessToken != null;
+    }
+
+    public static boolean isValid(AuthorizationToken token) {
+        return token != null && token.isValid();
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                "accessToken:'" + accessToken + '\'' +
+                ", tokenType:'" + tokenType + '\'' +
+                ", scopes:" + Arrays.toString(scopes) +
+                ", expirationTime:" + expirationTime +
+                '}';
+    }
+}

+ 18 - 0
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/ConnectorPushTherosProvider.java

@@ -0,0 +1,18 @@
+package cz.senslog.connector.push.theros;
+
+import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import cz.senslog.connector.tools.http.HttpClient;
+
+public class ConnectorPushTherosProvider implements cz.senslog.connector.push.api.ConnectorPushProvider {
+
+    @Override
+    public ConnectorPusher<GeoJsonModel> createPusher(DefaultConfig defaultConfig) {
+        TherosConfig config = new TherosConfig(defaultConfig);
+
+        AuthService authService = new AuthService(config.getAuthConfig(), HttpClient.newHttpSSLClient());
+
+        return new TherosPusher(config, authService, HttpClient.newHttpSSLClient());
+    }
+}

+ 22 - 0
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/TherosConfig.java

@@ -0,0 +1,22 @@
+package cz.senslog.connector.push.theros;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+
+public class TherosConfig {
+
+    private final String baseUrl;
+    private final AuthConfig authConfig;
+
+    TherosConfig(DefaultConfig defaultConfig) {
+        this.baseUrl = defaultConfig.getStringProperty("baseUrl");
+        this.authConfig = new AuthConfig(defaultConfig.getPropertyConfig("auth"));
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public AuthConfig getAuthConfig() {
+        return authConfig;
+    }
+}

+ 75 - 0
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/TherosPusher.java

@@ -0,0 +1,75 @@
+package cz.senslog.connector.push.theros;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.json.BasicJson;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.List;
+
+import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_JSON;
+import static cz.senslog.connector.tools.http.HttpHeader.*;
+
+public class TherosPusher implements ConnectorPusher<GeoJsonModel> {
+
+    private static final Logger logger = LogManager.getLogger(TherosPusher.class);
+
+    private final TherosConfig config;
+    private final HttpClient httpClient;
+    private final AuthService authService;
+
+    public TherosPusher(TherosConfig config, AuthService authService, HttpClient httpClient) {
+        this.config = config;
+        this.authService = authService;
+        this.httpClient = httpClient;
+
+    }
+
+    @Override
+    public void init() {
+        if (authService.authenticate()) {
+            logger.info("Authentication was successful.");
+        } else {
+            throw logger.throwing(new RuntimeException("Authentication failed."));
+        }
+    }
+
+    @Override
+    public void push(GeoJsonModel model) {
+
+        if (model == null || model.isEmpty()) {
+            logger.warn("Nothing to push."); return;
+        }
+
+        List<JsonElement> geoFeatures = model.getGeoJson().getAsJsonArray("features").asList();
+        geoFeatures.forEach(feature -> feature.getAsJsonObject().get("properties").getAsJsonObject()
+                        .addProperty("lotNumber", "X1234345")
+                );
+
+        AuthorizationToken token = authService.getAuthorizationToken();
+        HttpRequest request = HttpRequest.newBuilder().POST()
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), "observations").build())
+                .header(CONTENT_TYPE, APPLICATION_JSON)
+                .header(AUTHORIZATION, String.format("%s %s", token.getTokenType(), token.getAccessToken()))
+                .body(model.getGeoJson().toString())
+                .build();
+
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isOk()) {
+            JsonObject geoJson = BasicJson.jsonToObject(response.getBody(), JsonObject.class);
+            int uploadedFeatures = geoJson.get("features").getAsJsonArray().size();
+            logger.info("Send telemetries {}/{} from {} to {}.", geoFeatures.size(), uploadedFeatures, model.getFrom(), model.getTo());
+        } else {
+            logger.error("Can not send telemetries from {} to {}.", model.getFrom(), model.getTo());
+            logger.error(response.getBody());
+        }
+    }
+}

+ 1 - 0
connector-push-theros/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider

@@ -0,0 +1 @@
+cz.senslog.connector.push.theros.ConnectorPushTherosProvider

+ 35 - 0
connector-push-theros/src/test/java/cz/senslog/connector/push/theros/TherosPusherTest.java

@@ -0,0 +1,35 @@
+package cz.senslog.connector.push.theros;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.tools.http.HttpClient;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TherosPusherTest {
+
+    @Test
+    void onlineTest() throws Exception {
+
+        DefaultConfig defaultConfig = new DefaultConfig(null, null);
+        defaultConfig.setProperty("baseUrl", "https://theros.iccs.gr/iot/api/v1");
+        defaultConfig.setProperty("auth", new HashMap<String, String>() {{
+            put("url", "https://auth-theros.iccs.gr/realms/theros/protocol/openid-connect/token");
+            put("clientId", "iot-client");
+            put("clientSecret", "cKIhqWuk6foyn2taKwEzgWBJdqu5HkMv");
+            put("username", "lcerny@lesprojekt.cz");
+            put("password", "PF7n4Nc*b2AKTzr9L_");
+        }});
+
+        TherosConfig config = new TherosConfig(defaultConfig);
+        AuthService authService = new AuthService(config.getAuthConfig(), HttpClient.newHttpSSLClient());
+
+        TherosPusher pusher = new TherosPusher(config, authService, HttpClient.newHttpSSLClient());
+
+        pusher.init();
+//        pusher.push(null);
+
+    }
+}

+ 1 - 0
connector-tools/src/main/java/cz/senslog/connector/tools/http/HttpContentType.java

@@ -4,6 +4,7 @@ public final class HttpContentType {
     public final static String APPLICATION_ATOM_XML         = "application/atom+xml";
     public final static String APPLICATION_FORM_URLENCODED  = "application/x-www-form-urlencoded";
     public final static String APPLICATION_JSON             = "application/json";
+    public final static String APPLICATION_GEO_JSON         = "application/geo+json";
     public final static String APPLICATION_OCTET_STREAM     = "application/octet-stream";
     public final static String APPLICATION_SVG_XML          = "application/svg+xml";
     public final static String APPLICATION_XHTML_XML        = "application/xhtml+xml";

+ 4 - 6
connector-tools/src/main/java/cz/senslog/connector/tools/json/BasicJson.java

@@ -23,13 +23,13 @@ import static java.time.format.DateTimeFormatter.*;
 /**
  * The class {@code BasicJson} represents a basic wrapper for {@link Gson} library.
  * Provides basic converter from object to string and string to object.
- *
+
  * Configuration contains basic formatters for {@see LocalDateTime}, {@see ZonedDateTime} and {@see Class}.
- *
- *
+
+
  * Both time classes are formatter to ISO format e.q. '2011-12-03T10:15:30',
  * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'.
- *
+
  * Class is formatted as the full name of the class.
  *
  * @author Lukas Cerny
@@ -238,8 +238,6 @@ public class BasicJson {
                     case NULL:
                         jsonReader.skipValue();
                         break;
-                    case END_DOCUMENT:
-                        break loop;
                     default:
                         throw new AssertionError(token);
                 }

+ 26 - 0
connector-tools/src/main/java/cz/senslog/connector/tools/json/JsonUtils.java

@@ -0,0 +1,26 @@
+package cz.senslog.connector.tools.json;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class JsonUtils {
+
+    private static String encodeToUTF8(String string) {
+        try {
+            return URLEncoder.encode(string, "UTF-8");
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public static String jsonObjectToFormURLEncodedString(final JsonObject jsonObject) {
+        return jsonObject.asMap().entrySet().stream()
+                .map(e -> e.getKey() + "=" + encodeToUTF8(e.getValue().getAsString()))
+                .collect(Collectors.joining("&"));
+    }
+}

+ 58 - 5
docker-compose.yaml

@@ -1,8 +1,8 @@
-version: '3.7'
+version: '3.8'
 
 services:
 
-  dts1:
+  drutes2senslog:
     container_name: drutesSenslog1
     build:
       dockerfile: docker/Dockerfile
@@ -17,7 +17,7 @@ services:
       APP_PARAMS: -cf config/drutesSenslog1.yaml
       TZ: Europe/Prague
 
-  lws1:
+  lora2senslog:
     container_name: loraWanSenslog1
     build:
       dockerfile: docker/Dockerfile
@@ -28,7 +28,7 @@ services:
     environment:
       APP_PARAMS: -cf config/lorawanSenslog1.yaml
 
-  fcs1:
+  fieldclimate2senslog:
     container_name: fieldclimateSenslog1
     build:
       dockerfile: docker/Dockerfile
@@ -43,7 +43,7 @@ services:
         DEBUG: "false"
         LOG_MONITOR: "false"
 
-  alps1:
+  alapan2senslog:
     container_name: alapansSenslog1
     build:
       dockerfile: docker/Dockerfile
@@ -52,3 +52,56 @@ services:
         MAVEN_PROFILE: AlapansSenslog1
     environment:
       APP_PARAMS: -cf config/alapansSenslog.yaml
+
+  gfs2senslog:
+    container_name: gfsSenslog1
+    build:
+      dockerfile: docker/Dockerfile
+      context: .
+      args:
+        MAVEN_PROFILE: GfsSenslog1
+    ports:
+      - "5005:5005"
+    restart: always
+    environment:
+      APP_PARAMS: -cf config/gfsSenslog.yaml
+      DEBUG: "true"
+
+  senslog2analytics:
+    container_name: senslog1Analytics
+    build:
+      dockerfile: docker/Dockerfile
+      context: .
+      args:
+        MAVEN_PROFILE: Senslog1Analytics
+#    ports:
+#      - "5005:5005"
+    restart: always
+    environment:
+      APP_PARAMS: -cf config/senslog1Analytics.yaml
+      DEBUG: "false"
+
+  senslogtelemetry2theros:
+    container_name: senslogTelemetryTheros
+    build:
+      dockerfile: docker/Dockerfile
+      context: .
+      args:
+        MAVEN_PROFILE: SenslogTelemetryTheros
+    ports:
+      - "5005:5005"
+    restart: always
+    environment:
+      APP_PARAMS: -cf config/senslogTelemetryTheros.yaml
+      DEBUG: "true"
+
+  demo2logger:
+    container_name: demoLogger
+    build:
+      dockerfile: docker/Dockerfile
+      context: .
+      args:
+        MAVEN_PROFILE: DemoLogger
+    environment:
+      APP_PARAMS: -cf config/demoToLogger.yaml
+      DEBUG: "false"

+ 42 - 0
pom.xml

@@ -53,6 +53,38 @@
         </profile>
 
         <profile>
+            <id>GfsSenslog1</id>
+            <modules>
+                <module>connector-fetch-gfs</module>
+                <module>connector-push-senslog-v1</module>
+            </modules>
+        </profile>
+
+        <profile>
+            <id>Senslog1Analytics</id>
+            <modules>
+                <module>connector-fetch-senslog-v1</module>
+                <module>connector-push-analytics</module>
+            </modules>
+        </profile>
+
+        <profile>
+            <id>SenslogTelemetryTheros</id>
+            <modules>
+                <module>connector-fetch-senslog-telemetry</module>
+                <module>connector-push-theros</module>
+            </modules>
+        </profile>
+
+        <profile>
+            <id>DemoLogger</id>
+            <modules>
+                <module>connector-fetch-demo</module>
+                <module>connector-push-logger</module>
+            </modules>
+        </profile>
+
+        <profile>
             <id>all</id>
             <activation>
                 <activeByDefault>true</activeByDefault>
@@ -62,9 +94,14 @@
                 <module>connector-push-senslog-v1</module>
                 <module>connector-fetch-fieldclimate</module>
                 <module>connector-fetch-senslog-v1</module>
+                <module>connector-fetch-senslog-telemetry</module>
                 <module>connector-push-afarcloud</module>
                 <module>connector-fetch-drutes</module>
                 <module>connector-fetch-alapans</module>
+                <module>connector-fetch-gfs</module>
+                <module>connector-push-analytics</module>
+                <module>connector-push-theros</module>
+                <module>connector-push-logger</module>
             </modules>
         </profile>
     </profiles>
@@ -78,6 +115,11 @@
         <module>connector-tools</module>
         <module>connector-fetch-demo</module>
         <module>connector-fetch-alapans</module>
+        <module>connector-fetch-gfs</module>
+        <module>connector-push-analytics</module>
+        <module>connector-fetch-senslog-telemetry</module>
+        <module>connector-push-theros</module>
+        <module>connector-push-logger</module>
     </modules>
 
     <properties>

Неке датотеке нису приказане због велике количине промена