Kaynağa Gözat

Added license

Lukas Cerny 4 yıl önce
ebeveyn
işleme
e3c5e38fa1
100 değiştirilmiş dosya ile 7454 ekleme ve 6495 silme
  1. BIN
      config/.lorawanSenslog1.yaml.un~
  2. 69 69
      config/fieldclimateSenslog1.yaml
  3. 70 0
      config/fieldclimateSenslogM.yaml
  4. 59 50
      config/lorawanSenslog1.yaml
  5. 56 53
      config/senslog1AFarCloud.yaml
  6. 21 0
      connector-app/license.txt
  7. 136 133
      connector-app/src/main/java/cz/senslog/connector/app/Application.java
  8. 18 15
      connector-app/src/main/java/cz/senslog/connector/app/Main.java
  9. 98 95
      connector-app/src/main/java/cz/senslog/connector/app/config/AppConfig.java
  10. 52 49
      connector-app/src/main/java/cz/senslog/connector/app/config/ConfigurationServiceImpl.java
  11. 158 156
      connector-app/src/main/java/cz/senslog/connector/app/config/Connector.java
  12. 233 230
      connector-app/src/main/java/cz/senslog/connector/app/config/ConnectorBuilder.java
  13. 59 56
      connector-app/src/main/java/cz/senslog/connector/app/config/DatabaseBuilderImpl.java
  14. 69 66
      connector-app/src/main/java/cz/senslog/connector/app/config/DatabaseConfigurationServiceImpl.java
  15. 45 42
      connector-app/src/main/java/cz/senslog/connector/app/config/FileBuilderImpl.java
  16. 260 257
      connector-app/src/main/java/cz/senslog/connector/app/config/FileConfigurationServiceImpl.java
  17. 83 80
      connector-app/src/main/java/cz/senslog/connector/app/config/Parameters.java
  18. 56 53
      connector-app/src/main/java/cz/senslog/connector/app/config/ServiceProvider.java
  19. 54 51
      connector-app/src/main/java/cz/senslog/connector/app/config/api/ConfigurationService.java
  20. 49 46
      connector-app/src/main/java/cz/senslog/connector/app/config/api/DatabaseBuilder.java
  21. 22 19
      connector-app/src/main/java/cz/senslog/connector/app/config/api/DatabaseConfigurationService.java
  22. 27 24
      connector-app/src/main/java/cz/senslog/connector/app/config/api/FileBuilder.java
  23. 24 21
      connector-app/src/main/java/cz/senslog/connector/app/config/api/FileConfigurationService.java
  24. 21 0
      connector-fetch-api/license.txt
  25. 64 61
      connector-fetch-api/src/main/java/cz/senslog/connector/fetch/ConnectorFetch.java
  26. 25 22
      connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/ConnectorFetchProvider.java
  27. 33 30
      connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/ConnectorFetcher.java
  28. 51 48
      connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/ExecutableFetcher.java
  29. 50 47
      connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/FetchProxySession.java
  30. 18 0
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AllowedStation.java
  31. 6 0
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureConfig.java
  32. 299 284
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureFetcher.java
  33. 157 101
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureProxySession.java
  34. 62 62
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/ConnectorFetchAzureProvider.java
  35. 167 167
      connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AuthenticationService.java
  36. 3 4
      connector-fetch-azure/src/main/resources/schema/sensorInfoSchema.json
  37. 109 100
      connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/AzureConfigTest.java
  38. 334 312
      connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/AzureFetcherTest.java
  39. 50 46
      connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/ConnectorFetchAzureProviderTest.java
  40. 53 53
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/ConnectorFetchFieldClimateProvider.java
  41. 311 311
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcher.java
  42. 97 97
      connector-fetch-fieldclimate/src/main/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySession.java
  43. 614 611
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcherTest.java
  44. 21 0
      connector-fetch-senslog-v1/license.txt
  45. 41 38
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/AllowedStation.java
  46. 71 68
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SensLogSession.java
  47. 67 64
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogConfig.java
  48. 48 45
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetchProvider.java
  49. 191 188
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcher.java
  50. 128 126
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogProxySession.java
  51. 28 25
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogSessionProxyConfig.java
  52. 21 0
      connector-model/license.txt
  53. 23 20
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/AFarCloudModel.java
  54. 33 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/CollarAcceleration.java
  55. 49 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/CollarAnomalies.java
  56. 71 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/CollarMeasurement.java
  57. 35 32
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/Location.java
  58. 8 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MeasurementType.java
  59. 39 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiCollarMeasurements.java
  60. 18 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiMeasurement.java
  61. 48 0
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiSensorMeasurements.java
  62. 0 41
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiSimpleObservation.java
  63. 48 45
      connector-model/src/main/java/cz/senslog/connector/model/afarcloud/SensorMeasurement.java
  64. 33 30
      connector-model/src/main/java/cz/senslog/connector/model/api/AbstractModel.java
  65. 27 24
      connector-model/src/main/java/cz/senslog/connector/model/api/Converter.java
  66. 66 63
      connector-model/src/main/java/cz/senslog/connector/model/api/ConverterProvider.java
  67. 8 0
      connector-model/src/main/java/cz/senslog/connector/model/api/JsonSerializable.java
  68. 22 19
      connector-model/src/main/java/cz/senslog/connector/model/api/ProxySessionModel.java
  69. 66 13
      connector-model/src/main/java/cz/senslog/connector/model/azure/SensorData.java
  70. 188 171
      connector-model/src/main/java/cz/senslog/connector/model/azure/SensorInfo.java
  71. 0 34
      connector-model/src/main/java/cz/senslog/connector/model/azure/SensorType.java
  72. 73 70
      connector-model/src/main/java/cz/senslog/connector/model/config/ConnectorDescriptor.java
  73. 45 42
      connector-model/src/main/java/cz/senslog/connector/model/config/DefaultConfig.java
  74. 55 52
      connector-model/src/main/java/cz/senslog/connector/model/config/HostConfig.java
  75. 195 192
      connector-model/src/main/java/cz/senslog/connector/model/config/PropertyConfig.java
  76. 10 7
      connector-model/src/main/java/cz/senslog/connector/model/config/UnitSensorChecker.java
  77. 19 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/AFarCloudUnitProperties.java
  78. 162 161
      connector-model/src/main/java/cz/senslog/connector/model/converter/AFarCloudUnitSensorConverter.java
  79. 95 84
      connector-model/src/main/java/cz/senslog/connector/model/converter/AzureModelSenslogV1ModelConverter.java
  80. 82 85
      connector-model/src/main/java/cz/senslog/connector/model/converter/AzureModelSenslogV2ModelConverter.java
  81. 124 42
      connector-model/src/main/java/cz/senslog/connector/model/converter/AzureUnitConverter.java
  82. 156 153
      connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV1ModelConverter.java
  83. 18 15
      connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV2ModelConverter.java
  84. 23 20
      connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateUnitConverter.java
  85. 28 25
      connector-model/src/main/java/cz/senslog/connector/model/converter/ModelConverterProvider.java
  86. 25 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/ReverseMap.java
  87. 103 78
      connector-model/src/main/java/cz/senslog/connector/model/converter/SenslogV1ModelAFarCloudModelConverter.java
  88. 59 35
      connector-model/src/main/java/cz/senslog/connector/model/fieldclimate/StationInfo.java
  89. 42 39
      connector-model/src/main/java/cz/senslog/connector/model/v1/Observation.java
  90. 76 73
      connector-model/src/main/java/cz/senslog/connector/model/v1/Position.java
  91. 40 37
      connector-model/src/main/java/cz/senslog/connector/model/v1/Record.java
  92. 54 51
      connector-model/src/main/java/cz/senslog/connector/model/v1/SenslogV1Model.java
  93. 86 83
      connector-model/src/main/java/cz/senslog/connector/model/v1/SensorInfo.java
  94. 68 65
      connector-model/src/main/java/cz/senslog/connector/model/v1/UnitInfo.java
  95. 7 7
      connector-model/src/test/java/cz/senslog/connector/model/converter/AzureModelSenslogV1ModelConverterTest.java
  96. 21 0
      connector-push-afarcloud/license.txt
  97. 20 17
      connector-push-afarcloud/src/main/java/cz/senslog/connector/push/afarcloud/AFarCloudConfig.java
  98. 32 29
      connector-push-afarcloud/src/main/java/cz/senslog/connector/push/afarcloud/AFarCloudPushProvider.java
  99. 75 66
      connector-push-afarcloud/src/main/java/cz/senslog/connector/push/afarcloud/AFarCloudPusher.java
  100. 21 0
      connector-push-api/license.txt

BIN
config/.lorawanSenslog1.yaml.un~


+ 69 - 69
config/fieldclimateSenslog1.yaml

@@ -1,70 +1,70 @@
-api1: &fieldClimateApiDomain
-  domain: "https://api.fieldclimate.com/v1"
-
-api2: &senslogApiDomain
-  domain: "http://51.15.45.95:8080/senslog1"
-
-settings:
-  - SenslogV1:
-      name: "Senslog V1"
-      provider: "cz.senslog.connector.push.rest.senslog.v1.SenslogV1ConnectorPushProvider"
-      host:
-        <<: *senslogApiDomain
-        path: "FeederServlet"
-
-  - Fieldclimate:
-      name: "FieldClimate: Pessl Instruments"
-      provider: "cz.senslog.connector.fetch.fieldclimate.ConnectorFetchFieldClimateProvider"
-      startDate: "2020-01-01T00:00:00.000" # yyyy-MM-DD hh:mm:ss.sss
-      timeZone: "Europe/Riga"
-      period: 1 # in hours (integer)
-
-      authentication:
-        publicKey: "3737ed4fe98fae975e54991216ed473c8d7db48662deff19"
-        privateKey: "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b"
-
-      stationsHost:
-        <<: *fieldClimateApiDomain
-        path: "/user/stations"
-
-      stationDataHost:
-        <<: *fieldClimateApiDomain
-        path: "/data/normal/{station_id}/raw/from/{from}/to/{to}"
-
-      stationTimeRangeHost:
-        <<: *fieldClimateApiDomain
-        path: "/data/{station_id}"
-
-      allowedStation:
-        # station_id:
-        "0120821E":
-            # sensor_id: [channel]
-            - 17153: [4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073]
-            - 19969: [4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049]
-            - 20228: [4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061]
-
-        # station_id: [sensor_id]
-        "00208048": [4, 5, 6, 49, 143, 506, 507, 600]
-
-        # station_id:
-        "0120821D":
-            #sensor_id: [channel]
-            - 17153: [4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073]
-            - 19969: [4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049]
-            - 20228: [4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061]
-
-
-      sessionProxy:
-        user: "vilcini"
-        group: "vilcini"
-        lastObservationHost:
-          <<: *senslogApiDomain
-          path: "SensorService"
-
-
-connectors:
-    - FieldclimateToV1:
-        fetcher: "Fieldclimate"
-        pusher: "SenslogV1"
-        period: 600 # 10 minutes
+api1: &fieldClimateApiDomain
+  domain: "https://api.fieldclimate.com/v1"
+
+api2: &senslogApiDomain
+  domain: "http://51.15.45.95:8080/senslog1"
+
+settings:
+  - SenslogV1:
+      name: "Senslog V1"
+      provider: "cz.senslog.connector.push.rest.senslog.v1.SenslogV1ConnectorPushProvider"
+      host:
+        <<: *senslogApiDomain
+        path: "FeederServlet"
+
+  - Fieldclimate:
+      name: "FieldClimate: Pessl Instruments"
+      provider: "cz.senslog.connector.fetch.fieldclimate.ConnectorFetchFieldClimateProvider"
+      startDate: "1970-01-01T00:00:00.000" # yyyy-MM-DD hh:mm:ss.sss
+      timeZone: "Europe/Riga"
+      period: 12 # in hours (integer)
+
+      authentication:
+        publicKey: "3737ed4fe98fae975e54991216ed473c8d7db48662deff19"
+        privateKey: "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b"
+
+      stationsHost:
+        <<: *fieldClimateApiDomain
+        path: "/user/stations"
+
+      stationDataHost:
+        <<: *fieldClimateApiDomain
+        path: "/data/normal/{station_id}/raw/from/{from}/to/{to}"
+
+      stationTimeRangeHost:
+        <<: *fieldClimateApiDomain
+        path: "/data/{station_id}"
+
+      allowedStation:
+        # station_id:
+        "0120821E":
+            # sensor_id: [channel]
+            - 17153: [4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073]
+            - 19969: [4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049]
+            - 20228: [4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061]
+
+        # station_id: [sensor_id]
+        "00208048": [4, 5, 6, 49, 143, 506, 507, 600]
+
+        # station_id:
+        "0120821D":
+            #sensor_id: [channel]
+            - 17153: [4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073]
+            - 19969: [4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049]
+            - 20228: [4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061]
+
+
+#      sessionProxy:
+#        user: "vilcini"
+#        group: "vilcini"
+#        lastObservationHost:
+#          <<: *senslogApiDomain
+#          path: "SensorService"
+
+
+connectors:
+    - FieldclimateToV1:
+        fetcher: "Fieldclimate"
+        pusher: "SenslogV1"
+        period: 20 # 10 minutes
         initDelay: 5

+ 70 - 0
config/fieldclimateSenslogM.yaml

@@ -0,0 +1,70 @@
+api1: &fieldClimateApiDomain
+  domain: "https://api.fieldclimate.com/v1"
+
+api2: &senslogApiDomain
+  domain: "http://51.15.45.95:8080/senslogm"
+
+settings:
+  - SenslogV1:
+      name: "Senslog V1"
+      provider: "cz.senslog.connector.push.rest.senslog.v1.SenslogV1ConnectorPushProvider"
+      host:
+        <<: *senslogApiDomain
+        path: "FeederServlet"
+
+  - Fieldclimate:
+      name: "FieldClimate: Pessl Instruments"
+      provider: "cz.senslog.connector.fetch.fieldclimate.ConnectorFetchFieldClimateProvider"
+      startDate: "2020-01-01T00:00:00.000" # yyyy-MM-DD hh:mm:ss.sss
+      timeZone: "Europe/Riga"
+      period: 1 # in hours (integer)
+
+      authentication:
+        publicKey: "3737ed4fe98fae975e54991216ed473c8d7db48662deff19"
+        privateKey: "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b"
+
+      stationsHost:
+        <<: *fieldClimateApiDomain
+        path: "/user/stations"
+
+      stationDataHost:
+        <<: *fieldClimateApiDomain
+        path: "/data/normal/{station_id}/raw/from/{from}/to/{to}"
+
+      stationTimeRangeHost:
+        <<: *fieldClimateApiDomain
+        path: "/data/{station_id}"
+
+      allowedStation:
+        # station_id:
+        "0120821E":
+            # sensor_id: [channel]
+            - 17153: [4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073]
+            - 19969: [4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049]
+            - 20228: [4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061]
+
+        # station_id: [sensor_id]
+        "00208048": [4, 5, 6, 49, 143, 506, 507, 600]
+
+        # station_id:
+        "0120821D":
+            #sensor_id: [channel]
+            - 17153: [4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073]
+            - 19969: [4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049]
+            - 20228: [4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061]
+
+
+      sessionProxy:
+        user: "pessl"
+        group: "pessl"
+        lastObservationHost:
+          <<: *senslogApiDomain
+          path: "SensorService"
+
+
+connectors:
+    - FieldclimateToV1:
+        fetcher: "Fieldclimate"
+        pusher: "SenslogV1"
+        period: 60
+        initDelay: 5

+ 59 - 50
config/lorawanSenslog1.yaml

@@ -1,50 +1,59 @@
-api1: &azureApiDomain
-    domain: "https://iotlorawan.azurewebsites.net"
-
-api2: &senslogApiDomain
-    domain: "http://foodie.lesprojekt.cz:8080/MapLogOT"
-
-settings:
-    - AzureLoraWan:
-        name: "IoT LoraWan"
-        provider: "cz.senslog.connector.fetch.azure.ConnectorFetchAzureProvider"
-        startDate: 2020-03-13T19:30:00.000
-        # endDate: 2020-03-11T09:00:00.000
-        limitPerSensor: 100
-
-        sensorInfoHost:
-            <<: *azureApiDomain
-            path: "api/sensors"
-        
-        sensorDataHost:
-            <<: *azureApiDomain
-            path: "api/sensordata"
-        
-        authentication:
-            host:
-                <<: *azureApiDomain
-                path: "api/accounts/login"
-            username: "netluky@ima.cz"
-            password: "SensLogIMA1"
-            refreshPeriodIfFail: 10000
-
-        sessionProxy:
-            user: "afarcloud"
-            group: "afc"
-            lastObservationHost:
-              <<: *senslogApiDomain
-              path: "SensorService"
-        
-    - SenslogV1:
-        name: "Senslog V1"
-        provider: "cz.senslog.connector.push.rest.senslog.v1.SenslogV1ConnectorPushProvider"
-        host:
-            <<: *senslogApiDomain
-            path: "FeederServlet"
-
-connectors:
-    - AzureSenslogV1:
-        fetcher: "AzureLoraWan"
-        pusher: "SenslogV1"
-        period: 600
-        initDelay: 10
+api1: &azureApiDomain
+    domain: "https://iotlorawan.azurewebsites.net"
+
+api2: &senslogApiDomain
+    domain: "https://sensor.lesprojekt.cz/DBServiceST"
+
+settings:
+    - AzureLoraWan:
+        name: "IoT LoraWan"
+        provider: "cz.senslog.connector.fetch.azure.ConnectorFetchAzureProvider"
+        # startDate: 2020-09-10T00:00:00.000
+        startDate: 2021-04-08T00:00:00.000
+       # endDate: 2021-04-27T00:00:00.000
+        limitPerSensor: 300
+
+        sensorInfoHost:
+            <<: *azureApiDomain
+            path: "api/sensors"
+        
+        sensorDataHost:
+            <<: *azureApiDomain
+            path: "api/sensordata"
+        
+        authentication:
+            host:
+                <<: *azureApiDomain
+                path: "api/accounts/login"
+            username: "netluky@ima.cz"
+            password: "SensLogIMA1"
+            refreshPeriodIfFail: 10000
+
+        sessionProxy:
+            user: "afarcloud"
+            group: "afc"
+            lastObservationHost:
+              <<: *senslogApiDomain
+              path: "SensorService"
+
+        allowedStation:
+            # eui:
+            "8CF9574000000948":
+            "8CF95740000008AE":
+#            "086BD7D4B501":    # not new data at Azure (up to date at SensLog)
+#            "90FD9FA5F420":    # not registered yet
+
+        
+    - SenslogV1:
+        name: "Senslog V1"
+        provider: "cz.senslog.connector.push.rest.senslog.v1.SenslogV1ConnectorPushProvider"
+        host:
+            <<: *senslogApiDomain
+            path: "FeederServlet"
+
+connectors:
+    - AzureSenslogV1:
+        fetcher: "AzureLoraWan"
+        pusher: "SenslogV1"
+        period: 20
+        initDelay: 1

+ 56 - 53
config/senslog1AFarCloud.yaml

@@ -1,53 +1,56 @@
-
-settings:
-    - Senslog:
-        name: "SensLog v1"
-        provider: "cz.senslog.connector.fetch.senslog.v1.SenslogFetchProvider"
-
-        api: &apiDomain
-            domain: "http://foodie.lesprojekt.cz:8080/MapLogOT"
-
-        startDate: 2020-01-01T00:00:00.000
-        interval: 2 # hours
-
-        user: "afarcloud"
-        group: "afc"
-
-        sensorServiceHost:
-            <<: *apiDomain
-            path: "SensorService"
-
-        dataServiceHost:
-            <<: *apiDomain
-            path: "DataService"
-
-        allowedStation:
-            # station_id: [sensor_id]
-            "10002222": [340020000, 410010000, 560030000]
-            "10002376": [340020000, 410010000, 560030000]
-            "1305167562287832": [340420000, 410180000, 360200000, 460090000, 470160000, 470180000, 480080000, 620030000]
-            "1305167561991327": [340380097, 410150097, 360200000, 780010097]
-            "1305167562028072": [340380097, 410150097, 360200000, 780010097]
-            "1305167562017824": [340380097, 410150097, 360200000, 780010097]
-
-        sessionProxy:
-            timeZone: "Europe/Prague"
-            lastObservationHost:
-                domain: "http://torcos.etsist.upm.es:9219"
-                path: "getObservationsBySensor/latest"
-
-
-    - AFC:
-        name: "AFarCloud"
-        provider: "cz.senslog.connector.push.afarcloud.AFarCloudPushProvider"
-
-        telemetryHost:
-            domain: "https://torcos.etsist.upm.es:9207"
-            path: "/telemetry"
-
-connectors:
-    - SenslogToAFC:
-        fetcher: "Senslog"
-        pusher: "AFC"
-        period: 600
-        initDelay: 1
+
+settings:
+    - Senslog:
+        name: "SensLog v1"
+        provider: "cz.senslog.connector.fetch.senslog.v1.SenslogFetchProvider"
+
+        api: &apiDomain
+            domain: "https://sensor.lesprojekt.cz/DBServiceST"
+
+        startDate: 2021-09-04T00:00:00+02:00
+       # startDate: 2020-09-10T00:00:00.000
+       # endDate: 2021-04-27T00:00:00.000
+        
+        interval: 12 # hours
+
+        user: "afarcloud"
+        group: "afc"
+
+        sensorServiceHost:
+            <<: *apiDomain
+            path: "SensorService"
+
+        dataServiceHost:
+            <<: *apiDomain
+            path: "DataService"
+
+        allowedStation:
+            # station_id: [sensor_id]
+             "10002222": [340020000, 410010000, 560030000]
+             "10002376": [340020000, 410010000, 560030000]
+            # "1305167562287832": [340420000, 410180000, 360200000, 460090000, 470160000, 470180000, 480080000, 620030000]
+            # "1305167561991327": [340380097, 410150097, 360200000, 780010097]
+            # "1305167562028072": [340380097, 410150097, 360200000, 780010097]
+            # "1305167562017824": [340380097, 410150097, 360200000, 780010097]
+
+        sessionProxy:
+            timeZone: "Europe/Prague"
+            lastObservationHost:
+                domain: "http://torcos.etsist.upm.es:9219"
+                path: "getObservationsBySensor/latest"
+
+
+    - AFC:
+        name: "AFarCloud"
+        provider: "cz.senslog.connector.push.afarcloud.AFarCloudPushProvider"
+
+        telemetryHost:
+            domain: "https://torcos.etsist.upm.es:9207"
+            path: "/telemetry"
+
+connectors:
+    - SenslogToAFC:
+        fetcher: "Senslog"
+        pusher: "AFC"
+        period: 10
+        initDelay: 5

+ 21 - 0
connector-app/license.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 University of West Bohemia (UWB) https://www.zcu.cz/en, LESPROJEKT-SLUŽBY s.r.o. (LESP) https://www.lesprojekt.cz/en
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 136 - 133
connector-app/src/main/java/cz/senslog/connector/app/Application.java

@@ -1,133 +1,136 @@
-package cz.senslog.connector.app;
-
-import cz.senslog.connector.app.config.*;
-import cz.senslog.connector.app.config.api.ConfigurationService;
-import cz.senslog.connector.app.config.api.FileConfigurationService;
-import cz.senslog.connector.fetch.ConnectorFetch;
-import cz.senslog.connector.model.converter.ModelConverterProvider;
-import cz.senslog.connector.push.ConnectorPush;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.io.IOException;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * The class {@code Application} represents a trigger for entire application.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-class Application extends Thread {
-
-    private static Logger logger = LogManager.getLogger(Application.class);
-
-    /** Attribute of basic configuration values of the application. */
-    private final AppConfig appConfig;
-
-    /** Attribute of input parameters of the application. */
-    private final Parameters params;
-
-    private CountDownLatch latch;
-
-    private ScheduledExecutorService scheduler;
-
-    /**
-     * Initialization method to trigger the application.
-     * @param args - array of parameters.
-     * @return new thread of {@code Runnable}.
-     * @throws IOException throws if input parameters or application configuration file can not be parsed.
-     */
-    static Thread init(String... args) throws IOException {
-        AppConfig appConfig = AppConfig.load();
-        Parameters parameters = Parameters.parse(appConfig, args);
-
-        if (parameters.isHelp()) {
-            return new Thread(parameters::printHelp);
-        }
-
-        Application app = new Application(appConfig, parameters);
-        Runtime.getRuntime().addShutdownHook(new Thread(app::interrupt, "clean-app"));
-
-        return app;
-    }
-
-    /**
-     * Private constructor of the class. Accessible via static init method {@link Application#init(String...)}.
-     * @param appConfig basic configuration of the application. More info of the class {@see AppConfig}.
-     * @param parameters parsed input parameters of the application. More info of the class  {@see Parameters}.
-     */
-    private Application(AppConfig appConfig, Parameters parameters) {
-        super("app");
-
-        this.appConfig = appConfig;
-        this.params = parameters;
-    }
-
-    @Override
-    public void interrupt() {
-        logger.info("Stopping the application {} version {}", appConfig.getName(), appConfig.getVersion());
-
-        if (latch != null) {
-            for (int i = 0; i < latch.getCount(); i++) {
-                latch.countDown();
-            }
-        }
-
-        logger.info("Cleaning all connector's threads.");
-        if (scheduler != null) {
-            scheduler.shutdownNow();
-        }
-
-        logger.info("The application was stopped.");
-        super.interrupt();
-    }
-
-    @Override
-    public void run() {
-        logger.info("Starting the application {} version {}", appConfig.getName(), appConfig.getVersion());
-
-
-        ConfigurationService configService;
-        try {
-            FileConfigurationService service = ConfigurationService.newFileBuilder()
-                    .fileName(params.getConfigFileName()).build();
-
-            service.load();
-
-            configService = service;
-        } catch (IOException e) {
-            logger.catching(e); return;
-        }
-
-
-        ServiceProvider serviceProvider = new ServiceProvider(ConnectorFetch::getProvider, ConnectorPush::getProvider);
-        ModelConverterProvider converterProvider = new ModelConverterProvider();
-        ConnectorBuilder connectorBuilder = ConnectorBuilder.init(serviceProvider, converterProvider, configService);
-        Set<Connector> connectors = connectorBuilder.createConnectors();
-
-        if (!connectors.isEmpty()) {
-            scheduler = Executors.newScheduledThreadPool(connectors.size());
-            latch = new CountDownLatch(connectors.size());
-
-            logger.info("Starting a scheduler for {} connector(s).", connectors.size());
-            connectors.forEach(c -> c.schedule(scheduler, latch));
-
-            try {
-                logger.info("Waiting for the working threads.");
-                latch.await();
-                logger.info("All scheduled connector finished their job.");
-            } catch (InterruptedException e) {
-                logger.catching(e);
-            }
-        } else {
-            logger.warn("No connectors were loaded.");
-        }
-
-        interrupt();
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app;
+
+import cz.senslog.connector.app.config.*;
+import cz.senslog.connector.app.config.api.ConfigurationService;
+import cz.senslog.connector.app.config.api.FileConfigurationService;
+import cz.senslog.connector.fetch.ConnectorFetch;
+import cz.senslog.connector.model.converter.ModelConverterProvider;
+import cz.senslog.connector.push.ConnectorPush;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * The class {@code Application} represents a trigger for entire application.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+class Application extends Thread {
+
+    private static final Logger logger = LogManager.getLogger(Application.class);
+
+    /** Attribute of basic configuration values of the application. */
+    private final AppConfig appConfig;
+
+    /** Attribute of input parameters of the application. */
+    private final Parameters params;
+
+    private CountDownLatch latch;
+
+    private ScheduledExecutorService scheduler;
+
+    /**
+     * Initialization method to trigger the application.
+     * @param args - array of parameters.
+     * @return new thread of {@code Runnable}.
+     * @throws IOException throws if input parameters or application configuration file can not be parsed.
+     */
+    static Thread init(String... args) throws IOException {
+        AppConfig appConfig = AppConfig.load();
+        Parameters parameters = Parameters.parse(appConfig, args);
+
+        if (parameters.isHelp()) {
+            return new Thread(parameters::printHelp);
+        }
+
+        Application app = new Application(appConfig, parameters);
+        Runtime.getRuntime().addShutdownHook(new Thread(app::interrupt, "clean-app"));
+
+        return app;
+    }
+
+    /**
+     * Private constructor of the class. Accessible via static init method {@link Application#init(String...)}.
+     * @param appConfig basic configuration of the application. More info of the class {@see AppConfig}.
+     * @param parameters parsed input parameters of the application. More info of the class  {@see Parameters}.
+     */
+    private Application(AppConfig appConfig, Parameters parameters) {
+        super("app");
+
+        this.appConfig = appConfig;
+        this.params = parameters;
+    }
+
+    @Override
+    public void interrupt() {
+        logger.info("Stopping the application {} version {}", appConfig.getName(), appConfig.getVersion());
+
+        if (latch != null) {
+            for (int i = 0; i < latch.getCount(); i++) {
+                latch.countDown();
+            }
+        }
+
+        logger.info("Cleaning all connector's threads.");
+        if (scheduler != null) {
+            scheduler.shutdownNow();
+        }
+
+        logger.info("The application was stopped.");
+        super.interrupt();
+    }
+
+    @Override
+    public void run() {
+        logger.info("Starting the application {} version {}", appConfig.getName(), appConfig.getVersion());
+
+
+        ConfigurationService configService;
+        try {
+            FileConfigurationService service = ConfigurationService.newFileBuilder()
+                    .fileName(params.getConfigFileName()).build();
+
+            service.load();
+
+            configService = service;
+        } catch (IOException e) {
+            logger.catching(e); return;
+        }
+
+
+        ServiceProvider serviceProvider = new ServiceProvider(ConnectorFetch::getProvider, ConnectorPush::getProvider);
+        ModelConverterProvider converterProvider = new ModelConverterProvider();
+        ConnectorBuilder connectorBuilder = ConnectorBuilder.init(serviceProvider, converterProvider, configService);
+        Set<Connector> connectors = connectorBuilder.createConnectors();
+
+        if (!connectors.isEmpty()) {
+            scheduler = Executors.newScheduledThreadPool(connectors.size());
+            latch = new CountDownLatch(connectors.size());
+
+            logger.info("Starting a scheduler for {} connector(s).", connectors.size());
+            connectors.forEach(c -> c.schedule(scheduler, latch));
+
+            try {
+                logger.info("Waiting for the working threads.");
+                latch.await();
+                logger.info("All scheduled connector finished their job.");
+            } catch (InterruptedException e) {
+                logger.catching(e);
+            }
+        } else {
+            logger.warn("No connectors were loaded.");
+        }
+
+        interrupt();
+    }
+}

+ 18 - 15
connector-app/src/main/java/cz/senslog/connector/app/Main.java

@@ -1,15 +1,18 @@
-package cz.senslog.connector.app;
-
-/**
- * Main of the application.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Main {
-
-    public static void main(String[] args) throws Exception {
-        Application.init(args).start();
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app;
+
+/**
+ * Main of the application.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Main {
+
+    public static void main(String[] args) throws Exception {
+        Application.init(args).start();
+    }
+}

+ 98 - 95
connector-app/src/main/java/cz/senslog/connector/app/config/AppConfig.java

@@ -1,95 +1,98 @@
-package cz.senslog.connector.app.config;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-/**
- * The class {@code AppConfig} represents basic configuration of
- * the application. The configuration file is located in resources
- * and the values are connected to the properties in parent pom.xml.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class AppConfig {
-
-    private static Logger logger = LogManager.getLogger(AppConfig.class);
-
-
-    /** Name of the properties configuration file. */
-    private static final String PROPERTIES_FILE_NAME = "project.properties";
-
-    /** Attribute of loaded properties. */
-    private final Properties properties;
-
-    /**
-     * Static method to load the configuration file.
-     * @return new instance of {@code AppConfig}.
-     * @throws IOException throws if the file is not loaded successfully.
-     */
-    public static AppConfig load() throws IOException {
-        logger.debug("Loading application configuration file '{}'", PROPERTIES_FILE_NAME);
-
-        Properties properties = new Properties();
-
-        logger.debug("Getting the class loader from the class {}.", AppConfig.class.getName());
-        ClassLoader loader = AppConfig.class.getClassLoader();
-
-        logger.debug("Opening the file '{}'.", PROPERTIES_FILE_NAME);
-        InputStream stream = loader.getResourceAsStream(PROPERTIES_FILE_NAME);
-
-        logger.debug("Parsing application configuration file '{}'", PROPERTIES_FILE_NAME);
-        properties.load(stream);
-
-        logger.debug("Application configuration file was loaded successfully.");
-        return new AppConfig(properties);
-    }
-
-    /**
-     * Private constructor of the class. Accessible via static init method {@link AppConfig#load()}.
-     * @param properties - loaded properties from the file.
-     */
-    private AppConfig(Properties properties) {
-        this.properties = properties;
-    }
-
-    /**
-     * Returns name of the application defined in pom.xml
-     * @return name of the application or 'unknown'.
-     */
-    public String getName() {
-        return getProperty("app.name", "unknown");
-    }
-
-    /**
-     * Returns version of the application defined in pom.xml
-     * @return version of the application or empty string.
-     */
-    public String getVersion() {
-        return getProperty("app.version", "");
-    }
-
-    /**
-     * General method the get a property .
-     * @param propertyName - name of the property.
-     * @param defaultValue - default value if property does not exists.
-     * @return value of the property or default value.
-     */
-    private String getProperty(String propertyName, String defaultValue) {
-        logger.debug("Getting property with the name '{}'", propertyName);
-        String value = properties.getProperty(propertyName);
-
-        if (value == null) {
-            logger.debug("Property '{}' was not found. Used default property value '{}'.", propertyName, defaultValue);
-            return defaultValue;
-        } else {
-            logger.debug("Property '{}' was loaded successfully.", propertyName);
-            return value;
-        }
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * The class {@code AppConfig} represents basic configuration of
+ * the application. The configuration file is located in resources
+ * and the values are connected to the properties in parent pom.xml.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class AppConfig {
+
+    private static final Logger logger = LogManager.getLogger(AppConfig.class);
+
+
+    /** Name of the properties configuration file. */
+    private static final String PROPERTIES_FILE_NAME = "project.properties";
+
+    /** Attribute of loaded properties. */
+    private final Properties properties;
+
+    /**
+     * Static method to load the configuration file.
+     * @return new instance of {@code AppConfig}.
+     * @throws IOException throws if the file is not loaded successfully.
+     */
+    public static AppConfig load() throws IOException {
+        logger.debug("Loading application configuration file '{}'", PROPERTIES_FILE_NAME);
+
+        Properties properties = new Properties();
+
+        logger.debug("Getting the class loader from the class {}.", AppConfig.class.getName());
+        ClassLoader loader = AppConfig.class.getClassLoader();
+
+        logger.debug("Opening the file '{}'.", PROPERTIES_FILE_NAME);
+        InputStream stream = loader.getResourceAsStream(PROPERTIES_FILE_NAME);
+
+        logger.debug("Parsing application configuration file '{}'", PROPERTIES_FILE_NAME);
+        properties.load(stream);
+
+        logger.debug("Application configuration file was loaded successfully.");
+        return new AppConfig(properties);
+    }
+
+    /**
+     * Private constructor of the class. Accessible via static init method {@link AppConfig#load()}.
+     * @param properties - loaded properties from the file.
+     */
+    private AppConfig(Properties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * Returns name of the application defined in pom.xml
+     * @return name of the application or 'unknown'.
+     */
+    public String getName() {
+        return getProperty("app.name", "unknown");
+    }
+
+    /**
+     * Returns version of the application defined in pom.xml
+     * @return version of the application or empty string.
+     */
+    public String getVersion() {
+        return getProperty("app.version", "");
+    }
+
+    /**
+     * General method the get a property .
+     * @param propertyName - name of the property.
+     * @param defaultValue - default value if property does not exists.
+     * @return value of the property or default value.
+     */
+    private String getProperty(String propertyName, String defaultValue) {
+        logger.debug("Getting property with the name '{}'", propertyName);
+        String value = properties.getProperty(propertyName);
+
+        if (value == null) {
+            logger.debug("Property '{}' was not found. Used default property value '{}'.", propertyName, defaultValue);
+            return defaultValue;
+        } else {
+            logger.debug("Property '{}' was loaded successfully.", propertyName);
+            return value;
+        }
+    }
+}

+ 52 - 49
connector-app/src/main/java/cz/senslog/connector/app/config/ConfigurationServiceImpl.java

@@ -1,49 +1,52 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.connector.app.config.api.ConfigurationService;
-import cz.senslog.connector.model.config.ConnectorDescriptor;
-import cz.senslog.connector.model.config.DefaultConfig;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The class {@code ConfigurationServiceImpl} represents an implementation of {@link ConfigurationService}.
- * The class is used to provide a registration for some new connectors and class configurations.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public abstract class ConfigurationServiceImpl implements ConfigurationService {
-
-    private Set<ConnectorDescriptor> connectorDescriptors;
-    private Map<String, DefaultConfig> configurations;
-
-    ConfigurationServiceImpl() {
-        this.connectorDescriptors = new HashSet<>();
-        this.configurations = new HashMap<>();
-    }
-
-    protected void addConnectorDescriptor(ConnectorDescriptor descriptor) {
-        connectorDescriptors.add(descriptor);
-    }
-
-    protected void addProviderConfiguration(String providerId, DefaultConfig config) {
-        if (!configurations.containsKey(providerId)) {
-            configurations.put(providerId, config);
-        }
-    }
-
-    @Override
-    public Set<ConnectorDescriptor> getConnectorDescriptors() {
-        return connectorDescriptors;
-    }
-
-    @Override
-    public DefaultConfig getConfigForProviderId(String providerId) {
-        return configurations.get(providerId);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import cz.senslog.connector.app.config.api.ConfigurationService;
+import cz.senslog.connector.model.config.ConnectorDescriptor;
+import cz.senslog.connector.model.config.DefaultConfig;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The class {@code ConfigurationServiceImpl} represents an implementation of {@link ConfigurationService}.
+ * The class is used to provide a registration for some new connectors and class configurations.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public abstract class ConfigurationServiceImpl implements ConfigurationService {
+
+    private Set<ConnectorDescriptor> connectorDescriptors;
+    private Map<String, DefaultConfig> configurations;
+
+    ConfigurationServiceImpl() {
+        this.connectorDescriptors = new HashSet<>();
+        this.configurations = new HashMap<>();
+    }
+
+    protected void addConnectorDescriptor(ConnectorDescriptor descriptor) {
+        connectorDescriptors.add(descriptor);
+    }
+
+    protected void addProviderConfiguration(String providerId, DefaultConfig config) {
+        if (!configurations.containsKey(providerId)) {
+            configurations.put(providerId, config);
+        }
+    }
+
+    @Override
+    public Set<ConnectorDescriptor> getConnectorDescriptors() {
+        return connectorDescriptors;
+    }
+
+    @Override
+    public DefaultConfig getConfigForProviderId(String providerId) {
+        return configurations.get(providerId);
+    }
+}

+ 158 - 156
connector-app/src/main/java/cz/senslog/connector/app/config/Connector.java

@@ -1,157 +1,159 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.common.exception.ModuleInterruptedException;
-import cz.senslog.connector.fetch.api.ExecutableFetcher;
-import cz.senslog.connector.model.api.AbstractModel;
-import cz.senslog.connector.model.api.Converter;
-import cz.senslog.connector.push.api.ConnectorPusher;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-
-import static cz.senslog.common.util.Pipeline.of;
-import static java.util.Optional.ofNullable;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-/**
- * The class {@code Connector} represents a created connector
- * which allows to be scheduled by defined period.
- *
- * The idea is to wrap functionality of a connector. The flow is
- * defined as 'fetcher' -> 'converter' -> 'pusher'.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class Connector {
-
-    private static Logger logger = LogManager.getLogger(Connector.class);
-
-    /** Default initialization delay value when the scheduler starts to schedule tasks (in seconds). */
-    private static int DEFAULT_TASK_DELAY = 2;
-
-    /** Default value for scheduling tasks when the value missing in the configuration file (in seconds). */
-    private static int DEFAULT_SCHEDULE_PERIOD = 3600;  // every hour
-
-    /** Name of the connector */
-    private final String name;
-
-    /** Instance of a fetcher that provides data. */
-    private final ExecutableFetcher<? super AbstractModel> fetcherExecutor;
-
-    /** Instance of a pusher that receives data. */
-    private final ConnectorPusher<? super AbstractModel> pusher; // TODO refactor to a ExecutableFetcher
-
-    /** Converter between fetch and push. */
-    private final Converter<? super AbstractModel, ? super AbstractModel> converter;
-
-    /** Period for scheduler. */
-    private final Optional<Integer> period;
-
-    /** Initialization delay for scheduler. */
-    private final Optional<Integer> initDelay;
-
-    /**
-     * Constructor allows to set all attributes.
-     * @param name - name of the connector.
-     * @param fetcherExecutor - instance of fetcher.
-     * @param pusher - instance of pusher.
-     * @param converter - instance of converter.
-     * @param period - period for scheduling.
-     */
-    public Connector(
-            String name,
-            ExecutableFetcher<? super AbstractModel> fetcherExecutor,
-            ConnectorPusher<? super AbstractModel> pusher,
-            Converter<? super AbstractModel, ? super AbstractModel> converter,
-            Integer period,
-            Integer initDelay
-    ) {
-        this.name = name;
-        this.fetcherExecutor = fetcherExecutor;
-        this.pusher = pusher;
-        this.converter = converter;
-        this.period = ofNullable(period);
-        this.initDelay = ofNullable(initDelay);
-    }
-
-    /**
-     * Returns name of the connector.
-     * @return name of the connector.
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns period for scheduler.
-     * @return period for scheduler.
-     */
-    public Optional<Integer> getPeriod() {
-        return period;
-    }
-
-    /**
-     * Returns initialization delay for scheduler.
-     * @return delay for scheduler.
-     */
-    public Optional<Integer> getInitDelay() {
-        return initDelay;
-    }
-
-    /**
-     * Returns scheduling runnable task of the connector flow.
-     * @return runnable task
-     */
-    public Runnable getTask() {
-        return () -> of(fetcherExecutor::execute).pipe(converter::convert).end(pusher::push);
-    }
-
-    /**
-     * Schedules connector according by settings. Input parameters are scheduled service
-     * {@link ScheduledExecutorService} and {@link CountDownLatch} uses as a thread barrier.
-     * @param scheduledService - scheduled service
-     * @param latch - thread counter
-     */
-    public void schedule(ScheduledExecutorService scheduledService, CountDownLatch latch) {
-
-        int schedulePeriod = getPeriod().orElse(DEFAULT_SCHEDULE_PERIOD);
-        int delay = getInitDelay().orElse(DEFAULT_TASK_DELAY);
-        Runnable task = getTask();
-
-        logger.info("Scheduling the {} starts in {} with the period {} seconds.", getName(), delay, schedulePeriod);
-        ScheduledFuture<?> future = scheduledService.scheduleAtFixedRate(task, delay, schedulePeriod, SECONDS);
-
-        new Thread(() -> {
-            try {
-                future.get();
-            } catch (ModuleInterruptedException e) {
-                logger.warn(e.getMessage());
-            } catch (Exception e) {
-                logger.catching(e);
-            } finally {
-                future.cancel(true);
-                latch.countDown();
-            }
-        }, "thread-"+getName()).start();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Connector connector = (Connector) o;
-        return Objects.equals(name, connector.name);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name);
-    }
+package cz.senslog.connector.app.config;
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+import cz.senslog.common.exception.ModuleInterruptedException;
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+
+import static cz.senslog.common.util.Pipeline.of;
+import static java.util.Optional.ofNullable;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * The class {@code Connector} represents a created connector
+ * which allows to be scheduled by defined period.
+ *
+ * The idea is to wrap functionality of a connector. The flow is
+ * defined as 'fetcher' -> 'converter' -> 'pusher'.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class Connector {
+
+    private static final Logger logger = LogManager.getLogger(Connector.class);
+
+    /** Default initialization delay value when the scheduler starts to schedule tasks (in seconds). */
+    private static final int DEFAULT_TASK_DELAY = 2;
+
+    /** Default value for scheduling tasks when the value missing in the configuration file (in seconds). */
+    private static final int DEFAULT_SCHEDULE_PERIOD = 3600;  // every hour
+
+    /** Name of the connector */
+    private final String name;
+
+    /** Instance of a fetcher that provides data. */
+    private final ExecutableFetcher<? super AbstractModel> fetcherExecutor;
+
+    /** Instance of a pusher that receives data. */
+    private final ConnectorPusher<? super AbstractModel> pusher; // TODO refactor to a ExecutableFetcher
+
+    /** Converter between fetch and push. */
+    private final Converter<? super AbstractModel, ? super AbstractModel> converter;
+
+    /** Period for scheduler. */
+    private final Optional<Integer> period;
+
+    /** Initialization delay for scheduler. */
+    private final Optional<Integer> initDelay;
+
+    /**
+     * Constructor allows to set all attributes.
+     * @param name - name of the connector.
+     * @param fetcherExecutor - instance of fetcher.
+     * @param pusher - instance of pusher.
+     * @param converter - instance of converter.
+     * @param period - period for scheduling.
+     */
+    public Connector(
+            String name,
+            ExecutableFetcher<? super AbstractModel> fetcherExecutor,
+            ConnectorPusher<? super AbstractModel> pusher,
+            Converter<? super AbstractModel, ? super AbstractModel> converter,
+            Integer period,
+            Integer initDelay
+    ) {
+        this.name = name;
+        this.fetcherExecutor = fetcherExecutor;
+        this.pusher = pusher;
+        this.converter = converter;
+        this.period = ofNullable(period);
+        this.initDelay = ofNullable(initDelay);
+    }
+
+    /**
+     * Returns name of the connector.
+     * @return name of the connector.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns period for scheduler.
+     * @return period for scheduler.
+     */
+    public Optional<Integer> getPeriod() {
+        return period;
+    }
+
+    /**
+     * Returns initialization delay for scheduler.
+     * @return delay for scheduler.
+     */
+    public Optional<Integer> getInitDelay() {
+        return initDelay;
+    }
+
+    /**
+     * Returns scheduling runnable task of the connector flow.
+     * @return runnable task
+     */
+    public Runnable getTask() {
+        return () -> of(fetcherExecutor::execute).pipe(converter::convert).end(pusher::push);
+    }
+
+    /**
+     * Schedules connector according by settings. Input parameters are scheduled service
+     * {@link ScheduledExecutorService} and {@link CountDownLatch} uses as a thread barrier.
+     * @param scheduledService - scheduled service
+     * @param latch - thread counter
+     */
+    public void schedule(ScheduledExecutorService scheduledService, CountDownLatch latch) {
+
+        int schedulePeriod = getPeriod().orElse(DEFAULT_SCHEDULE_PERIOD);
+        int delay = getInitDelay().orElse(DEFAULT_TASK_DELAY);
+        Runnable task = getTask();
+
+        logger.info("Scheduling the {} starts in {} with the period {} seconds.", getName(), delay, schedulePeriod);
+        ScheduledFuture<?> future = scheduledService.scheduleAtFixedRate(task, delay, schedulePeriod, SECONDS);
+
+        new Thread(() -> {
+            try {
+                future.get();
+            } catch (ModuleInterruptedException e) {
+                logger.warn(e.getMessage());
+            } catch (Exception e) {
+                logger.catching(e);
+            } finally {
+                future.cancel(true);
+                latch.countDown();
+            }
+        }, "thread-"+getName()).start();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Connector connector = (Connector) o;
+        return Objects.equals(name, connector.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
 }

+ 233 - 230
connector-app/src/main/java/cz/senslog/connector/app/config/ConnectorBuilder.java

@@ -1,230 +1,233 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.connector.app.config.api.ConfigurationService;
-import cz.senslog.connector.model.config.ConnectorDescriptor;
-import cz.senslog.connector.model.config.DefaultConfig;
-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.Converter;
-import cz.senslog.connector.model.api.ConverterProvider;
-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 java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.HashSet;
-import java.util.Set;
-
-import static java.lang.String.format;
-
-/**
- * The class {@code ConnectorBuilder} provides a builder for the class {@link Connector}.
- * The class creates new connectors according to configuration.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class ConnectorBuilder {
-
-    private static Logger logger = LogManager.getLogger(ConnectorBuilder.class);
-
-    /** Attribute provides fetch/push providers ({@link ServiceProvider}). */
-    private final ServiceProvider serviceProvider;
-
-    /** Attribute provides converters ({@link ConverterProvider}). */
-    private final ConverterProvider converterProvider;
-
-    /** Attribute provides configuration ({@link ConfigurationService}). */
-    private final ConfigurationService configService;
-
-    /**
-     * Static method for initialization. Sets all attributes.
-     * @param serviceProvider - service for fetch/push providers.
-     * @param converterProvider - service for converter.
-     * @param configService - service for configuration.
-     * @return new instance of {@code ConnectorBuilder}.
-     */
-    public static ConnectorBuilder init(ServiceProvider serviceProvider, ConverterProvider converterProvider, ConfigurationService configService) {
-        return new ConnectorBuilder(serviceProvider, converterProvider, configService);
-    }
-
-    /**
-     * Private constructor of the class. Accessible via static init method {@link ConnectorBuilder#init(ServiceProvider, ConverterProvider, ConfigurationService)}.
-     * @param serviceProvider - service for fetch/push providers.
-     * @param converterProvider - service for converter.
-     * @param configService - service for configuration.
-     */
-    private ConnectorBuilder(ServiceProvider serviceProvider, ConverterProvider converterProvider, ConfigurationService configService) {
-        this.serviceProvider = serviceProvider;
-        this.converterProvider = converterProvider;
-        this.configService = configService;
-    }
-
-    /**
-     * Creates and returns new instance of fetcher.
-     * @param fetchProviderId - id of fetcher provider.
-     * @return new instance of fetcher {@code ExecutableFetcher}.
-     * @throws Exception throws if the fetch provider does not exist or any configuration does not exists.
-     */
-    private ExecutableFetcher createFetcherExecutor(String fetchProviderId) throws Exception {
-        logger.debug("Creating a new instance of fetcher for {}.", fetchProviderId);
-
-        DefaultConfig config = configService.getConfigForProviderId(fetchProviderId);
-        if (config == null) {
-            throw logger.throwing(new Exception(format(
-                    "Can not find a default settings for the provider %s.", fetchProviderId
-            )));
-        }
-
-        ConnectorFetchProvider provider = serviceProvider.getFetchProvider(config.getProvider());
-        if (provider == null) {
-            throw logger.throwing(new Exception(format(
-                    "Can not find a fetch provider instance for the %s.", config.getProvider()
-            )));
-        }
-
-        return provider.createExecutableFetcher(config);
-    }
-
-    /**
-     * Creates and returns new instance of pusher.
-     * @param pushProviderId - class of push provider.
-     * @return new instance of pusher {@code ConnectorPusher}.
-     * @throws Exception throws if the push provider does not exist or any configuration does not exists.
-     */
-    private ConnectorPusher getPusherInstance(String pushProviderId) throws Exception {
-        logger.debug("Creating a new instance of pusher for {}.", pushProviderId);
-
-        DefaultConfig config = configService.getConfigForProviderId(pushProviderId);
-        if (config == null) {
-            throw logger.throwing(new Exception(format(
-                    "Can not find a default settings for the provider %s.", pushProviderId
-            )));
-        }
-
-        ConnectorPushProvider provider = serviceProvider.getPushProvider(config.getProvider());
-        if (provider == null) {
-            throw logger.throwing(new Exception(format(
-                    "Can not find a push provider instance for the %s.", config.getProvider()
-            )));
-        }
-
-        return provider.createPusher(config);
-    }
-
-    /**
-     * Creates connectors depends on configuration.
-     * For each connector descriptor is loaded fetch and push provider and created fetcher and pusher.
-     * From these instances is get their input model which is child of {@link AbstractModel}.
-     * If everything is successful then is called #init() method and created a new connector {@link Connector}.
-     * If anything throws an exception, creating of the connector will be skipped.
-     * @return set of created and valid connectors.
-     */
-    public Set<Connector> createConnectors() {
-        logger.info("Starting to create new connectors.");
-
-        logger.debug("Getting all connector descriptors from the configuration service.");
-        Set<ConnectorDescriptor> connectorDescriptors = configService.getConnectorDescriptors();
-        logger.debug("Creating an empty set of connectors with init size {}.", connectorDescriptors.size());
-        Set<Connector> connectors = new HashSet<>(connectorDescriptors.size());
-
-        for (ConnectorDescriptor connDesc : connectorDescriptors) {
-            try {
-                logger.debug("Getting descriptors for a new '{}' connector connection.", connDesc.getName());
-                logger.debug("Connector: {}", connDesc);
-
-                ExecutableFetcher fetcherExecutor = createFetcherExecutor(connDesc.getFetcherId());
-                ConnectorFetcher fetcher = fetcherExecutor.getRawFetcher();
-
-                ConnectorPusher pusher = getPusherInstance(connDesc.getPusherId());
-
-                Class<? extends AbstractModel> inputModel = getAbstractModelFromGeneric(fetcher.getClass());
-                Class<? extends AbstractModel> outputModel = getAbstractModelFromGeneric(pusher.getClass());
-
-                Converter converter = converterProvider.getConverter(inputModel, outputModel);
-                if (converter == null) {
-                    throw logger.throwing(new Exception(format(
-                            "Can not find converter for connector: %s -> %s.", fetcher.getClass(), pusher.getClass()
-                    )));
-                }
-
-                logger.info("Invocation of initialization method for the {}.", fetcher.getClass());
-                fetcher.init();
-
-                logger.info("Invocation of initialization method for the {}.", pusher.getClass());
-                pusher.init();
-
-                logger.debug("Creating a new {} connector.", connDesc.getName());
-                Connector connector = new Connector(connDesc.getName(), fetcherExecutor, pusher, converter, connDesc.getPeriod(), connDesc.getDelay());
-
-                logger.debug("Saving the {} connector.", connDesc.getName());
-                connectors.add(connector);
-
-                logger.info("New connector connection {} was created successfully.", connDesc.getName());
-            } catch (Exception e) {
-                logger.error("Creating of the connector {} was skipped.", connDesc.getName());
-                logger.catching(e);
-            }
-        }
-        return connectors;
-    }
-
-    /**
-     * Gets a generic parameters from the input class.
-     * Input class could be type of {@code ConnectorFetcher} or {@code ConnectorPusher}.
-     * @param aClass - class contain generic parameters.
-     * @return generic parameter extended from {@link AbstractModel}.
-     * @throws Exception throws if can not be get generic parameter from the input class.
-     */
-    @SuppressWarnings("unchecked")
-    private Class<? extends AbstractModel> getAbstractModelFromGeneric(Class aClass) throws Exception {
-        Type[] classTypes = aClass.getGenericInterfaces();
-        if (classTypes.length == 0) {
-            throw logger.throwing(new Exception(format(
-                "%s does not implements any interface.", aClass
-            )));
-        }
-
-        Type interfaceType = classTypes[0];
-        if (!(interfaceType instanceof ParameterizedType)) {
-            throw logger.throwing(new Exception(format(
-                    "%s implemented interface does not contain generic parameters.", aClass
-            )));
-        }
-
-        ParameterizedType parameterizedInterfaceType = (ParameterizedType) interfaceType;
-        Type[] classArgumentTypes = parameterizedInterfaceType.getActualTypeArguments();
-        if (classArgumentTypes.length == 0) {
-            throw logger.throwing(new Exception(format(
-                    "%s implements empty generic parameters.", aClass
-            )));
-        }
-
-        Type classModelType; // TODO refactor
-        if (classArgumentTypes.length == 2) {
-            classModelType = classArgumentTypes[1];
-        } else {
-            classModelType = classArgumentTypes[0];
-        }
-
-        if (!(classModelType instanceof Class)) {
-            throw logger.throwing(new Exception(format(
-                    "%s contains generic parameters which are not instance of %s.", aClass, Class.class
-            )));
-        }
-
-        Class<?> classModel = (Class<?>) classModelType;
-        if (!AbstractModel.class.isAssignableFrom(classModel)) {
-            throw logger.throwing(new Exception(format(
-                    "%s does not contain generic parameters extended from %s", aClass, AbstractModel.class
-            )));
-        }
-
-       return (Class<? extends AbstractModel>) classModel;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import cz.senslog.connector.app.config.api.ConfigurationService;
+import cz.senslog.connector.model.config.ConnectorDescriptor;
+import cz.senslog.connector.model.config.DefaultConfig;
+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.Converter;
+import cz.senslog.connector.model.api.ConverterProvider;
+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 java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import static java.lang.String.format;
+
+/**
+ * The class {@code ConnectorBuilder} provides a builder for the class {@link Connector}.
+ * The class creates new connectors according to configuration.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class ConnectorBuilder {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorBuilder.class);
+
+    /** Attribute provides fetch/push providers ({@link ServiceProvider}). */
+    private final ServiceProvider serviceProvider;
+
+    /** Attribute provides converters ({@link ConverterProvider}). */
+    private final ConverterProvider converterProvider;
+
+    /** Attribute provides configuration ({@link ConfigurationService}). */
+    private final ConfigurationService configService;
+
+    /**
+     * Static method for initialization. Sets all attributes.
+     * @param serviceProvider - service for fetch/push providers.
+     * @param converterProvider - service for converter.
+     * @param configService - service for configuration.
+     * @return new instance of {@code ConnectorBuilder}.
+     */
+    public static ConnectorBuilder init(ServiceProvider serviceProvider, ConverterProvider converterProvider, ConfigurationService configService) {
+        return new ConnectorBuilder(serviceProvider, converterProvider, configService);
+    }
+
+    /**
+     * Private constructor of the class. Accessible via static init method {@link ConnectorBuilder#init(ServiceProvider, ConverterProvider, ConfigurationService)}.
+     * @param serviceProvider - service for fetch/push providers.
+     * @param converterProvider - service for converter.
+     * @param configService - service for configuration.
+     */
+    private ConnectorBuilder(ServiceProvider serviceProvider, ConverterProvider converterProvider, ConfigurationService configService) {
+        this.serviceProvider = serviceProvider;
+        this.converterProvider = converterProvider;
+        this.configService = configService;
+    }
+
+    /**
+     * Creates and returns new instance of fetcher.
+     * @param fetchProviderId - id of fetcher provider.
+     * @return new instance of fetcher {@code ExecutableFetcher}.
+     * @throws Exception throws if the fetch provider does not exist or any configuration does not exists.
+     */
+    private ExecutableFetcher createFetcherExecutor(String fetchProviderId) throws Exception {
+        logger.debug("Creating a new instance of fetcher for {}.", fetchProviderId);
+
+        DefaultConfig config = configService.getConfigForProviderId(fetchProviderId);
+        if (config == null) {
+            throw logger.throwing(new Exception(format(
+                    "Can not find a default settings for the provider %s.", fetchProviderId
+            )));
+        }
+
+        ConnectorFetchProvider provider = serviceProvider.getFetchProvider(config.getProvider());
+        if (provider == null) {
+            throw logger.throwing(new Exception(format(
+                    "Can not find a fetch provider instance for the %s.", config.getProvider()
+            )));
+        }
+
+        return provider.createExecutableFetcher(config);
+    }
+
+    /**
+     * Creates and returns new instance of pusher.
+     * @param pushProviderId - class of push provider.
+     * @return new instance of pusher {@code ConnectorPusher}.
+     * @throws Exception throws if the push provider does not exist or any configuration does not exists.
+     */
+    private ConnectorPusher getPusherInstance(String pushProviderId) throws Exception {
+        logger.debug("Creating a new instance of pusher for {}.", pushProviderId);
+
+        DefaultConfig config = configService.getConfigForProviderId(pushProviderId);
+        if (config == null) {
+            throw logger.throwing(new Exception(format(
+                    "Can not find a default settings for the provider %s.", pushProviderId
+            )));
+        }
+
+        ConnectorPushProvider provider = serviceProvider.getPushProvider(config.getProvider());
+        if (provider == null) {
+            throw logger.throwing(new Exception(format(
+                    "Can not find a push provider instance for the %s.", config.getProvider()
+            )));
+        }
+
+        return provider.createPusher(config);
+    }
+
+    /**
+     * Creates connectors depends on configuration.
+     * For each connector descriptor is loaded fetch and push provider and created fetcher and pusher.
+     * From these instances is get their input model which is child of {@link AbstractModel}.
+     * If everything is successful then is called #init() method and created a new connector {@link Connector}.
+     * If anything throws an exception, creating of the connector will be skipped.
+     * @return set of created and valid connectors.
+     */
+    public Set<Connector> createConnectors() {
+        logger.info("Starting to create new connectors.");
+
+        logger.debug("Getting all connector descriptors from the configuration service.");
+        Set<ConnectorDescriptor> connectorDescriptors = configService.getConnectorDescriptors();
+        logger.debug("Creating an empty set of connectors with init size {}.", connectorDescriptors.size());
+        Set<Connector> connectors = new HashSet<>(connectorDescriptors.size());
+
+        for (ConnectorDescriptor connDesc : connectorDescriptors) {
+            try {
+                logger.debug("Getting descriptors for a new '{}' connector connection.", connDesc.getName());
+                logger.debug("Connector: {}", connDesc);
+
+                ExecutableFetcher fetcherExecutor = createFetcherExecutor(connDesc.getFetcherId());
+                ConnectorFetcher fetcher = fetcherExecutor.getRawFetcher();
+
+                ConnectorPusher pusher = getPusherInstance(connDesc.getPusherId());
+
+                Class<? extends AbstractModel> inputModel = getAbstractModelFromGeneric(fetcher.getClass());
+                Class<? extends AbstractModel> outputModel = getAbstractModelFromGeneric(pusher.getClass());
+
+                Converter converter = converterProvider.getConverter(inputModel, outputModel);
+                if (converter == null) {
+                    throw logger.throwing(new Exception(format(
+                            "Can not find converter for connector: %s -> %s.", fetcher.getClass(), pusher.getClass()
+                    )));
+                }
+
+                logger.info("Invocation of initialization method for the {}.", fetcher.getClass());
+                fetcher.init();
+
+                logger.info("Invocation of initialization method for the {}.", pusher.getClass());
+                pusher.init();
+
+                logger.debug("Creating a new {} connector.", connDesc.getName());
+                Connector connector = new Connector(connDesc.getName(), fetcherExecutor, pusher, converter, connDesc.getPeriod(), connDesc.getDelay());
+
+                logger.debug("Saving the {} connector.", connDesc.getName());
+                connectors.add(connector);
+
+                logger.info("New connector connection {} was created successfully.", connDesc.getName());
+            } catch (Exception e) {
+                logger.error("Creating of the connector {} was skipped.", connDesc.getName());
+                logger.catching(e);
+            }
+        }
+        return connectors;
+    }
+
+    /**
+     * Gets a generic parameters from the input class.
+     * Input class could be type of {@code ConnectorFetcher} or {@code ConnectorPusher}.
+     * @param aClass - class contain generic parameters.
+     * @return generic parameter extended from {@link AbstractModel}.
+     * @throws Exception throws if can not be get generic parameter from the input class.
+     */
+    @SuppressWarnings("unchecked")
+    private Class<? extends AbstractModel> getAbstractModelFromGeneric(Class aClass) throws Exception {
+        Type[] classTypes = aClass.getGenericInterfaces();
+        if (classTypes.length == 0) {
+            throw logger.throwing(new Exception(format(
+                "%s does not implements any interface.", aClass
+            )));
+        }
+
+        Type interfaceType = classTypes[0];
+        if (!(interfaceType instanceof ParameterizedType)) {
+            throw logger.throwing(new Exception(format(
+                    "%s implemented interface does not contain generic parameters.", aClass
+            )));
+        }
+
+        ParameterizedType parameterizedInterfaceType = (ParameterizedType) interfaceType;
+        Type[] classArgumentTypes = parameterizedInterfaceType.getActualTypeArguments();
+        if (classArgumentTypes.length == 0) {
+            throw logger.throwing(new Exception(format(
+                    "%s implements empty generic parameters.", aClass
+            )));
+        }
+
+        Type classModelType; // TODO refactor
+        if (classArgumentTypes.length == 2) {
+            classModelType = classArgumentTypes[1];
+        } else {
+            classModelType = classArgumentTypes[0];
+        }
+
+        if (!(classModelType instanceof Class)) {
+            throw logger.throwing(new Exception(format(
+                    "%s contains generic parameters which are not instance of %s.", aClass, Class.class
+            )));
+        }
+
+        Class<?> classModel = (Class<?>) classModelType;
+        if (!AbstractModel.class.isAssignableFrom(classModel)) {
+            throw logger.throwing(new Exception(format(
+                    "%s does not contain generic parameters extended from %s", aClass, AbstractModel.class
+            )));
+        }
+
+       return (Class<? extends AbstractModel>) classModel;
+    }
+}

+ 59 - 56
connector-app/src/main/java/cz/senslog/connector/app/config/DatabaseBuilderImpl.java

@@ -1,56 +1,59 @@
-package cz.senslog.connector.app.config;
-
-
-import cz.senslog.connector.app.config.api.DatabaseBuilder;
-import cz.senslog.connector.app.config.api.DatabaseConfigurationService;
-
-/**
- * The class {@code DatabaseBuilderImpl} represents an implementation of {@link DatabaseBuilder}.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class DatabaseBuilderImpl implements DatabaseBuilder {
-
-    /** Connection url to a database. */
-    private String connectionUrl;
-
-    /** Username to a database. */
-    private String username;
-
-    /** Password to a database. */
-    private String password;
-
-    /** Name of connector in database. */
-    private String connectorName;
-
-    @Override
-    public DatabaseBuilder connectionUrl(String connectionUrl) {
-        this.connectionUrl = connectionUrl;
-        return this;
-    }
-
-    @Override
-    public DatabaseBuilder username(String username) {
-        this.username = username;
-        return this;
-    }
-
-    @Override
-    public DatabaseBuilder password(String password) {
-        this.password = password;
-        return this;
-    }
-
-    @Override
-    public DatabaseBuilder connectorName(String connectorName) {
-        this.connectorName = connectorName;
-        return this;
-    }
-
-    @Override
-    public DatabaseConfigurationService build() {
-        return new DatabaseConfigurationServiceImpl(connectionUrl, username, password, connectorName);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+
+import cz.senslog.connector.app.config.api.DatabaseBuilder;
+import cz.senslog.connector.app.config.api.DatabaseConfigurationService;
+
+/**
+ * The class {@code DatabaseBuilderImpl} represents an implementation of {@link DatabaseBuilder}.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class DatabaseBuilderImpl implements DatabaseBuilder {
+
+    /** Connection url to a database. */
+    private String connectionUrl;
+
+    /** Username to a database. */
+    private String username;
+
+    /** Password to a database. */
+    private String password;
+
+    /** Name of connector in database. */
+    private String connectorName;
+
+    @Override
+    public DatabaseBuilder connectionUrl(String connectionUrl) {
+        this.connectionUrl = connectionUrl;
+        return this;
+    }
+
+    @Override
+    public DatabaseBuilder username(String username) {
+        this.username = username;
+        return this;
+    }
+
+    @Override
+    public DatabaseBuilder password(String password) {
+        this.password = password;
+        return this;
+    }
+
+    @Override
+    public DatabaseBuilder connectorName(String connectorName) {
+        this.connectorName = connectorName;
+        return this;
+    }
+
+    @Override
+    public DatabaseConfigurationService build() {
+        return new DatabaseConfigurationServiceImpl(connectionUrl, username, password, connectorName);
+    }
+}

+ 69 - 66
connector-app/src/main/java/cz/senslog/connector/app/config/DatabaseConfigurationServiceImpl.java

@@ -1,66 +1,69 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.connector.app.config.api.ConfigurationService;
-import cz.senslog.connector.app.config.api.DatabaseConfigurationService;
-import cz.senslog.connector.model.config.ConnectorDescriptor;
-import cz.senslog.connector.model.config.DefaultConfig;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.Set;
-
-import static java.lang.String.format;
-
-/**
- * The class {@code DatabaseConfigurationServiceImpl} represents an implementation of {@link DatabaseConfigurationService}.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-class DatabaseConfigurationServiceImpl implements ConfigurationService, DatabaseConfigurationService {
-
-    private static Logger logger = LogManager.getLogger(DatabaseConfigurationServiceImpl.class);
-
-    /** Connection url to a database. */
-    private final String connectionUrl;
-
-    /** Username to a database. */
-    private final String username;
-
-    /** Password to a database. */
-    private final String password;
-
-    /** Name of connector in a database. */
-    private final String connectorName;
-
-    /**
-     * Constructor sets all attributes.
-     * @param connectionUrl - connection url.
-     * @param username - username.
-     * @param password - password.
-     */
-    DatabaseConfigurationServiceImpl(String connectionUrl, String username, String password, String connectorName) {
-        this.connectionUrl = connectionUrl;
-        this.username = username;
-        this.password = password;
-        this.connectorName = connectorName;
-    }
-
-    @Override
-    public void connect() {
-        throw logger.throwing(new UnsupportedOperationException(format(
-                "%s#connect() is not implemented.", DatabaseConfigurationServiceImpl.class
-        )));
-    }
-
-    @Override
-    public Set<ConnectorDescriptor> getConnectorDescriptors() {
-        return null;
-    }
-
-    @Override
-    public DefaultConfig getConfigForProviderId(String providerId) {
-        return null;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import cz.senslog.connector.app.config.api.ConfigurationService;
+import cz.senslog.connector.app.config.api.DatabaseConfigurationService;
+import cz.senslog.connector.model.config.ConnectorDescriptor;
+import cz.senslog.connector.model.config.DefaultConfig;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Set;
+
+import static java.lang.String.format;
+
+/**
+ * The class {@code DatabaseConfigurationServiceImpl} represents an implementation of {@link DatabaseConfigurationService}.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+class DatabaseConfigurationServiceImpl implements ConfigurationService, DatabaseConfigurationService {
+
+    private static final Logger logger = LogManager.getLogger(DatabaseConfigurationServiceImpl.class);
+
+    /** Connection url to a database. */
+    private final String connectionUrl;
+
+    /** Username to a database. */
+    private final String username;
+
+    /** Password to a database. */
+    private final String password;
+
+    /** Name of connector in a database. */
+    private final String connectorName;
+
+    /**
+     * Constructor sets all attributes.
+     * @param connectionUrl - connection url.
+     * @param username - username.
+     * @param password - password.
+     */
+    DatabaseConfigurationServiceImpl(String connectionUrl, String username, String password, String connectorName) {
+        this.connectionUrl = connectionUrl;
+        this.username = username;
+        this.password = password;
+        this.connectorName = connectorName;
+    }
+
+    @Override
+    public void connect() {
+        throw logger.throwing(new UnsupportedOperationException(format(
+                "%s#connect() is not implemented.", DatabaseConfigurationServiceImpl.class
+        )));
+    }
+
+    @Override
+    public Set<ConnectorDescriptor> getConnectorDescriptors() {
+        return null;
+    }
+
+    @Override
+    public DefaultConfig getConfigForProviderId(String providerId) {
+        return null;
+    }
+}

+ 45 - 42
connector-app/src/main/java/cz/senslog/connector/app/config/FileBuilderImpl.java

@@ -1,42 +1,45 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.connector.app.config.api.FileBuilder;
-import cz.senslog.connector.app.config.api.FileConfigurationService;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * The class {@code FileBuilderImpl} represents an implementation of {@link FileBuilder}.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class FileBuilderImpl implements FileBuilder {
-
-    private static Logger logger = LogManager.getLogger(FileBuilderImpl.class);
-
-    /** Name of the file configuration. */
-    private String fileName;
-
-    /**
-     * Constructor.
-     */
-    public FileBuilderImpl() {
-        logger.debug("Creating a builder for the configuration service.");
-    }
-
-    @Override
-    public FileBuilder fileName(String fileName) {
-        this.fileName = fileName;
-        return this;
-    }
-
-    @Override
-    public FileConfigurationService build() {
-        logger.debug("Building a new FileConfigurationService");
-        FileConfigurationService service = new FileConfigurationServiceImpl(fileName);
-        logger.debug("FileConfigurationService was build successfully.");
-        return service;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import cz.senslog.connector.app.config.api.FileBuilder;
+import cz.senslog.connector.app.config.api.FileConfigurationService;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The class {@code FileBuilderImpl} represents an implementation of {@link FileBuilder}.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class FileBuilderImpl implements FileBuilder {
+
+    private static final Logger logger = LogManager.getLogger(FileBuilderImpl.class);
+
+    /** Name of the file configuration. */
+    private String fileName;
+
+    /**
+     * Constructor.
+     */
+    public FileBuilderImpl() {
+        logger.debug("Creating a builder for the configuration service.");
+    }
+
+    @Override
+    public FileBuilder fileName(String fileName) {
+        this.fileName = fileName;
+        return this;
+    }
+
+    @Override
+    public FileConfigurationService build() {
+        logger.debug("Building a new FileConfigurationService");
+        FileConfigurationService service = new FileConfigurationServiceImpl(fileName);
+        logger.debug("FileConfigurationService was build successfully.");
+        return service;
+    }
+}

+ 260 - 257
connector-app/src/main/java/cz/senslog/connector/app/config/FileConfigurationServiceImpl.java

@@ -1,257 +1,260 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.connector.app.config.api.FileConfigurationService;
-import cz.senslog.connector.model.config.ConnectorDescriptor;
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.common.exception.UnsupportedFileException;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.yaml.snakeyaml.Yaml;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.*;
-
-/**
- * The class {@code FileConfigurationServiceImpl} represents an implementation of {@link FileConfigurationService}.
- *
- * Configuration file is in YAML format and contains two major groups: 'settings' and 'connectors'.
- *
- * <h2>connectors</h2>
- * Each connector must contain following attributes:
- *  - name of connector
- *  - ID of fetch provider (ID is mentioned in 'settings' group)
- *  - ID of push provider (ID is mentioned in 'settings' group)
- *  - period in second when the connector will be scheduled
- *
- *  Example:
- *
- *      connectors:
- *          - ConnectorName:
- *              fetcher: "<id>"
- *              pusher: "<id>"
- *              period: 60
- *
- *
- * <h2>settings</h2>
- * Each provider must contain basic attributes:
- *  - identifier of provider
- *  - name of provider
- *  - provider class
- *
- *  Other attributes are dynamically loaded when they are needed
- *  but must keep the key and value syntax.
- *
- *  Example:
- *
- *      settings:
- *          - ProviderID:
- *              name: "<name>"
- *              provider: "cz.senslog.connector.ClassProvider"
- *
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-class FileConfigurationServiceImpl extends ConfigurationServiceImpl implements FileConfigurationService {
-
-    private static Logger logger = LogManager.getLogger(FileConfigurationServiceImpl.class);
-
-    /** Name of the configuration file. */
-    private final String fileName;
-
-    /**
-     * Constructors sets all attributes.
-     * @param fileName - name of the configuration file.
-     */
-    FileConfigurationServiceImpl(String fileName) {
-        logger.debug("Creating a new FileConfigurationService.");
-        this.fileName = fileName;
-    }
-
-    @Override
-    public void load() throws IOException {
-        logger.info("Loading '{}' configuration file.", fileName);
-
-        if (!fileName.toLowerCase().endsWith(".yaml")) {
-            throw new UnsupportedFileException(fileName + "does not contain .yaml extension.");
-        }
-
-        Path filePath = Paths.get(fileName);
-        if (Files.notExists(filePath)) {
-            throw new FileNotFoundException(fileName + " does not exist");
-        }
-
-        Map<Object, Object> properties;
-
-        logger.debug("Opening the file '{}'.", fileName);
-        try (InputStream fileStream = Files.newInputStream(filePath)) {
-            logger.debug("Parsing the yaml file '{}'.", fileName);
-            properties = new Yaml().load(fileStream);
-            logger.debug("The configuration yaml file '{}' was parsed successfully.", fileName);
-        }
-
-        if (properties == null || properties.isEmpty()) {
-            throw new IOException(String.format(
-                    "The configuration yaml file %s is empty or was not loaded successfully. ", fileName
-            ));
-        }
-
-        logger.debug("Getting 'settings' property from the configuration file.");
-        List settingsList = (List)properties.get("settings");
-
-        logger.debug("Getting 'connectors' property from the configuration file.");
-        List connectorsList = (List) properties.get("connectors");
-
-        logger.debug("Starting to parse all connector descriptors from the config file.");
-        settings(settingsList);
-
-        logger.debug("Starting to create all connector connection from the configuration file.");
-        createConnectorDescriptors(connectorsList);
-
-        logger.info("The configuration file '{}' was parsed successfully.", fileName);
-    }
-
-    private void settings(List settingsList) throws InvalidPropertiesFormatException {
-
-        logger.debug("Parsing 'settings' from the configuration file.");
-        for (Object settings : settingsList) {
-            if (!(settings instanceof Map)) {
-                throw logger.throwing(new InvalidPropertiesFormatException(
-                        "Property 'settings' is not in the valid format."
-                ));
-            }
-
-            Map settingsMap = (Map) settings;
-            for (Object settingsEntryObject : settingsMap.entrySet()) {
-                if (!(settingsEntryObject instanceof Map.Entry)) {
-                    throw logger.throwing(new InvalidPropertiesFormatException(
-                            "Values in property 'settings' are not accessible as a dictionary."
-                    ));
-                }
-
-                Map.Entry settingsEntry = (Map.Entry) settingsEntryObject;
-
-                String descriptorId = (String) settingsEntry.getKey();
-
-                logger.debug("Getting descriptor for the settings ID '{}'.", descriptorId);
-                Object settingsValuesObject = settingsEntry.getValue();
-                if (!(settingsValuesObject instanceof Map)) {
-                    throw logger.throwing(new InvalidPropertiesFormatException(
-                            "Values for the descriptor '"+descriptorId+"' are not accessible as a dictionary."
-                    ));
-                }
-
-                Map settingsValuesMap = (Map) settingsValuesObject;
-
-                logger.debug("Getting property 'provider' from the settings descriptor '{}'.", descriptorId);
-                String providerClassStr = (String) settingsValuesMap.get("provider");
-                if (providerClassStr == null) {
-                    throw logger.throwing(new NoSuchElementException(
-                            "Property 'provider' was not found."
-                    ));
-                }
-                settingsValuesMap.remove("provider");
-
-                Class<?> providerClass;
-                try {
-                    logger.debug("Creating a class from the provider class name {}.", providerClassStr);
-                    providerClass = Class.forName(providerClassStr);
-                } catch (ClassNotFoundException e) {
-                    logger.catching(e);
-                    continue;
-                }
-
-                logger.debug("Creating a new DefaultConfig class for the settings descriptor '{}'.", descriptorId);
-                DefaultConfig defaultConfig = new DefaultConfig(descriptorId, providerClass);
-
-                logger.debug("Starting to set all properties from the settings descriptor '{}'.", descriptorId);
-                for (Object propertyEntryObject : settingsValuesMap.entrySet()) {
-                    if (!(propertyEntryObject instanceof Map.Entry)) {
-                        throw logger.throwing(new InvalidPropertiesFormatException(
-                                        "Property values in the descriptor '"+descriptorId+"' are not accessible as a dictionary."
-                        ));
-                    }
-
-                    Map.Entry propertyEntry = (Map.Entry) propertyEntryObject;
-
-                    logger.trace("Setting property '{}' from the settings descriptor '{}'.", propertyEntry.getKey(), descriptorId);
-                    defaultConfig.setProperty((String) propertyEntry.getKey(), propertyEntry.getValue());
-                }
-
-                logger.debug("Saving the settings descriptor '{}'.", descriptorId);
-                addProviderConfiguration(descriptorId, defaultConfig);
-
-            }
-        }
-    }
-
-    private void createConnectorDescriptors(List connectorsList) throws InvalidPropertiesFormatException {
-
-        logger.debug("Parsing 'connectors' from the configuration file.");
-        for (Object connector : connectorsList) {
-            if (!(connector instanceof Map)) {
-                throw logger.throwing(new InvalidPropertiesFormatException(
-                        "Property 'connectors' is not in the valid format."
-                ));
-            }
-
-            Map connectorMap = (Map) connector;
-
-            for (Object connectorEntryObject : connectorMap.entrySet()) {
-                if (!(connectorEntryObject instanceof Map.Entry)) {
-                    throw logger.throwing(new InvalidPropertiesFormatException(
-                            "Values in property 'connectors' are not accessible as a dictionary."
-                    ));
-                }
-
-                Map.Entry connectorEntry = (Map.Entry) connectorEntryObject;
-
-                String descriptorId = (String) connectorEntry.getKey();
-
-                logger.debug("Getting descriptor for the connector ID '{}'.", descriptorId);
-                Object connectorValuesObject = connectorEntry.getValue();
-                if (!(connectorValuesObject instanceof Map)) {
-                    throw logger.throwing(new InvalidPropertiesFormatException(
-                            "Values for the descriptor '"+descriptorId+"' are not accessible as a dictionary."
-                    ));
-                }
-
-                Map connectorValuesMap = (Map) connectorValuesObject;
-
-                logger.debug("Getting the fetch class provider for the connector ID '{}'.", descriptorId);
-                String fetchProviderId = (String) connectorValuesMap.get("fetcher");
-                if (fetchProviderId == null) {
-                    throw logger.throwing(new NoSuchElementException(
-                            "Property 'fetcher' does not exist in connector descriptor '"+descriptorId+"'."
-                    ));
-                }
-
-                logger.debug("Getting the push class provider for the connector ID '{}'.", descriptorId);
-                String pushProviderId = (String) connectorValuesMap.get("pusher");
-                if (pushProviderId == null) {
-                    throw logger.throwing(new NoSuchElementException(
-                            "Property 'pusher' does not exist in connector descriptor '"+descriptorId+"'."
-                    ));
-                }
-
-                logger.debug("Getting property 'period' from the connector descriptor '{}'.", descriptorId);
-                Integer period = (Integer) connectorValuesMap.get("period");
-
-                logger.debug("Getting property 'initDelay' from the connector descriptor '{}'.", descriptorId);
-                Integer delay = (Integer) connectorValuesMap.get("initDelay");
-
-                logger.debug("Creating a new ConnectorDescriptor class for the connector descriptor '{}'.", descriptorId);
-                ConnectorDescriptor connDesc = new ConnectorDescriptor(descriptorId, fetchProviderId, pushProviderId, period, delay);
-
-                logger.debug("Saving the connector descriptor '{}'.", descriptorId);
-                addConnectorDescriptor(connDesc);
-            }
-        }
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import cz.senslog.connector.app.config.api.FileConfigurationService;
+import cz.senslog.connector.model.config.ConnectorDescriptor;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.common.exception.UnsupportedFileException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+/**
+ * The class {@code FileConfigurationServiceImpl} represents an implementation of {@link FileConfigurationService}.
+ *
+ * Configuration file is in YAML format and contains two major groups: 'settings' and 'connectors'.
+ *
+ * <h2>connectors</h2>
+ * Each connector must contain following attributes:
+ *  - name of connector
+ *  - ID of fetch provider (ID is mentioned in 'settings' group)
+ *  - ID of push provider (ID is mentioned in 'settings' group)
+ *  - period in second when the connector will be scheduled
+ *
+ *  Example:
+ *
+ *      connectors:
+ *          - ConnectorName:
+ *              fetcher: "<id>"
+ *              pusher: "<id>"
+ *              period: 60
+ *
+ *
+ * <h2>settings</h2>
+ * Each provider must contain basic attributes:
+ *  - identifier of provider
+ *  - name of provider
+ *  - provider class
+ *
+ *  Other attributes are dynamically loaded when they are needed
+ *  but must keep the key and value syntax.
+ *
+ *  Example:
+ *
+ *      settings:
+ *          - ProviderID:
+ *              name: "<name>"
+ *              provider: "cz.senslog.connector.ClassProvider"
+ *
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+class FileConfigurationServiceImpl extends ConfigurationServiceImpl implements FileConfigurationService {
+
+    private static final Logger logger = LogManager.getLogger(FileConfigurationServiceImpl.class);
+
+    /** Name of the configuration file. */
+    private final String fileName;
+
+    /**
+     * Constructors sets all attributes.
+     * @param fileName - name of the configuration file.
+     */
+    FileConfigurationServiceImpl(String fileName) {
+        logger.debug("Creating a new FileConfigurationService.");
+        this.fileName = fileName;
+    }
+
+    @Override
+    public void load() throws IOException {
+        logger.info("Loading '{}' configuration file.", fileName);
+
+        if (!fileName.toLowerCase().endsWith(".yaml")) {
+            throw new UnsupportedFileException(fileName + "does not contain .yaml extension.");
+        }
+
+        Path filePath = Paths.get(fileName);
+        if (Files.notExists(filePath)) {
+            throw new FileNotFoundException(fileName + " does not exist");
+        }
+
+        Map<Object, Object> properties;
+
+        logger.debug("Opening the file '{}'.", fileName);
+        try (InputStream fileStream = Files.newInputStream(filePath)) {
+            logger.debug("Parsing the yaml file '{}'.", fileName);
+            properties = new Yaml().load(fileStream);
+            logger.debug("The configuration yaml file '{}' was parsed successfully.", fileName);
+        }
+
+        if (properties == null || properties.isEmpty()) {
+            throw new IOException(String.format(
+                    "The configuration yaml file %s is empty or was not loaded successfully. ", fileName
+            ));
+        }
+
+        logger.debug("Getting 'settings' property from the configuration file.");
+        List<?> settingsList = (List<?>)properties.get("settings");
+
+        logger.debug("Getting 'connectors' property from the configuration file.");
+        List<?> connectorsList = (List<?>) properties.get("connectors");
+
+        logger.debug("Starting to parse all connector descriptors from the config file.");
+        settings(settingsList);
+
+        logger.debug("Starting to create all connector connection from the configuration file.");
+        createConnectorDescriptors(connectorsList);
+
+        logger.info("The configuration file '{}' was parsed successfully.", fileName);
+    }
+
+    private void settings(List<?> settingsList) throws InvalidPropertiesFormatException {
+
+        logger.debug("Parsing 'settings' from the configuration file.");
+        for (Object settings : settingsList) {
+            if (!(settings instanceof Map)) {
+                throw logger.throwing(new InvalidPropertiesFormatException(
+                        "Property 'settings' is not in the valid format."
+                ));
+            }
+
+            Map<?, ?> settingsMap = (Map<?, ?>) settings;
+            for (Object settingsEntryObject : settingsMap.entrySet()) {
+                if (!(settingsEntryObject instanceof Map.Entry)) {
+                    throw logger.throwing(new InvalidPropertiesFormatException(
+                            "Values in property 'settings' are not accessible as a dictionary."
+                    ));
+                }
+
+                Map.Entry<?, ?> settingsEntry = (Map.Entry<?, ?>) settingsEntryObject;
+
+                String descriptorId = (String) settingsEntry.getKey();
+
+                logger.debug("Getting descriptor for the settings ID '{}'.", descriptorId);
+                Object settingsValuesObject = settingsEntry.getValue();
+                if (!(settingsValuesObject instanceof Map)) {
+                    throw logger.throwing(new InvalidPropertiesFormatException(
+                            "Values for the descriptor '"+descriptorId+"' are not accessible as a dictionary."
+                    ));
+                }
+
+                Map<?, ?> settingsValuesMap = (Map<?, ?>) settingsValuesObject;
+
+                logger.debug("Getting property 'provider' from the settings descriptor '{}'.", descriptorId);
+                String providerClassStr = (String) settingsValuesMap.get("provider");
+                if (providerClassStr == null) {
+                    throw logger.throwing(new NoSuchElementException(
+                            "Property 'provider' was not found."
+                    ));
+                }
+                settingsValuesMap.remove("provider");
+
+                Class<?> providerClass;
+                try {
+                    logger.debug("Creating a class from the provider class name {}.", providerClassStr);
+                    providerClass = Class.forName(providerClassStr);
+                } catch (ClassNotFoundException e) {
+                    logger.catching(e);
+                    continue;
+                }
+
+                logger.debug("Creating a new DefaultConfig class for the settings descriptor '{}'.", descriptorId);
+                DefaultConfig defaultConfig = new DefaultConfig(descriptorId, providerClass);
+
+                logger.debug("Starting to set all properties from the settings descriptor '{}'.", descriptorId);
+                for (Object propertyEntryObject : settingsValuesMap.entrySet()) {
+                    if (!(propertyEntryObject instanceof Map.Entry)) {
+                        throw logger.throwing(new InvalidPropertiesFormatException(
+                                        "Property values in the descriptor '"+descriptorId+"' are not accessible as a dictionary."
+                        ));
+                    }
+
+                    Map.Entry<?, ?> propertyEntry = (Map.Entry<?, ?>) propertyEntryObject;
+
+                    logger.trace("Setting property '{}' from the settings descriptor '{}'.", propertyEntry.getKey(), descriptorId);
+                    defaultConfig.setProperty((String) propertyEntry.getKey(), propertyEntry.getValue());
+                }
+
+                logger.debug("Saving the settings descriptor '{}'.", descriptorId);
+                addProviderConfiguration(descriptorId, defaultConfig);
+
+            }
+        }
+    }
+
+    private void createConnectorDescriptors(List<?> connectorsList) throws InvalidPropertiesFormatException {
+
+        logger.debug("Parsing 'connectors' from the configuration file.");
+        for (Object connector : connectorsList) {
+            if (!(connector instanceof Map)) {
+                throw logger.throwing(new InvalidPropertiesFormatException(
+                        "Property 'connectors' is not in the valid format."
+                ));
+            }
+
+            Map<?, ?> connectorMap = (Map<?, ?>) connector;
+
+            for (Object connectorEntryObject : connectorMap.entrySet()) {
+                if (!(connectorEntryObject instanceof Map.Entry)) {
+                    throw logger.throwing(new InvalidPropertiesFormatException(
+                            "Values in property 'connectors' are not accessible as a dictionary."
+                    ));
+                }
+
+                Map.Entry<?,?> connectorEntry = (Map.Entry<?,?>) connectorEntryObject;
+
+                String descriptorId = (String) connectorEntry.getKey();
+
+                logger.debug("Getting descriptor for the connector ID '{}'.", descriptorId);
+                Object connectorValuesObject = connectorEntry.getValue();
+                if (!(connectorValuesObject instanceof Map)) {
+                    throw logger.throwing(new InvalidPropertiesFormatException(
+                            "Values for the descriptor '"+descriptorId+"' are not accessible as a dictionary."
+                    ));
+                }
+
+                Map<?,?> connectorValuesMap = (Map<?,?>) connectorValuesObject;
+
+                logger.debug("Getting the fetch class provider for the connector ID '{}'.", descriptorId);
+                String fetchProviderId = (String) connectorValuesMap.get("fetcher");
+                if (fetchProviderId == null) {
+                    throw logger.throwing(new NoSuchElementException(
+                            "Property 'fetcher' does not exist in connector descriptor '"+descriptorId+"'."
+                    ));
+                }
+
+                logger.debug("Getting the push class provider for the connector ID '{}'.", descriptorId);
+                String pushProviderId = (String) connectorValuesMap.get("pusher");
+                if (pushProviderId == null) {
+                    throw logger.throwing(new NoSuchElementException(
+                            "Property 'pusher' does not exist in connector descriptor '"+descriptorId+"'."
+                    ));
+                }
+
+                logger.debug("Getting property 'period' from the connector descriptor '{}'.", descriptorId);
+                Integer period = (Integer) connectorValuesMap.get("period");
+
+                logger.debug("Getting property 'initDelay' from the connector descriptor '{}'.", descriptorId);
+                Integer delay = (Integer) connectorValuesMap.get("initDelay");
+
+                logger.debug("Creating a new ConnectorDescriptor class for the connector descriptor '{}'.", descriptorId);
+                ConnectorDescriptor connDesc = new ConnectorDescriptor(descriptorId, fetchProviderId, pushProviderId, period, delay);
+
+                logger.debug("Saving the connector descriptor '{}'.", descriptorId);
+                addConnectorDescriptor(connDesc);
+            }
+        }
+    }
+}

+ 83 - 80
connector-app/src/main/java/cz/senslog/connector/app/config/Parameters.java

@@ -1,80 +1,83 @@
-package cz.senslog.connector.app.config;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-
-import static cz.senslog.common.util.StringUtils.isNotBlank;
-import static java.lang.String.format;
-import static java.nio.file.Files.notExists;
-import static java.nio.file.Paths.get;
-
-/**
- * The class {@code Parameters} represents input parameters from
- * the applications. For parsing is used {@see JCommander} library.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class Parameters {
-
-    private static Logger logger = LogManager.getLogger(Parameters.class);
-
-    private JCommander jCommander;
-
-    /**
-     * Static method to parse input parameters.
-     * @param appConfig - main configuration of the application.
-     * @param args - array of parameters in format e.g. ["-cf", "fileName"].
-     * @return instance of {@code Parameters}.
-     * @throws IOException throws if is chosen "-cf" or "-config-file" parameter and the file does not exist.
-     */
-    public static Parameters parse(AppConfig appConfig, String... args) throws IOException {
-        logger.debug("Parsing input parameters {}", Arrays.toString(args));
-
-        Parameters parameters = new Parameters();
-        JCommander jCommander = JCommander.newBuilder()
-                .programName(appConfig.getName())
-                .addObject(parameters).build();
-        parameters.jCommander = jCommander;
-
-        jCommander.parse(args);
-
-        String configFileName = parameters.getConfigFileName();
-        logger.debug("Checking existence of configuration file {}", configFileName);
-        if (isNotBlank(configFileName) && notExists(get(configFileName))) {
-            throw new FileNotFoundException(format("Config file %s does not exist.", configFileName));
-        }
-
-        logger.info("Parsing input parameters {} were parsed successfully.", Arrays.toString(args));
-        return parameters;
-    }
-
-    @Parameter(names = {"-h", "-help"}, help = true)
-    private boolean help = false;
-
-    @Parameter(names = {"-cf", "-config-file"}, description = "Configuration file in .yaml format.")
-    private String configFileName;
-
-    /**
-     * Returns name of the configuration file.
-     * @return string name.
-     */
-    public String getConfigFileName() {
-        return configFileName;
-    }
-
-    public boolean isHelp() {
-        return help;
-    }
-
-    public void printHelp() {
-        jCommander.usage();
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+
+import static cz.senslog.common.util.StringUtils.isNotBlank;
+import static java.lang.String.format;
+import static java.nio.file.Files.notExists;
+import static java.nio.file.Paths.get;
+
+/**
+ * The class {@code Parameters} represents input parameters from
+ * the applications. For parsing is used {@see JCommander} library.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class Parameters {
+
+    private static final Logger logger = LogManager.getLogger(Parameters.class);
+
+    private JCommander jCommander;
+
+    /**
+     * Static method to parse input parameters.
+     * @param appConfig - main configuration of the application.
+     * @param args - array of parameters in format e.g. ["-cf", "fileName"].
+     * @return instance of {@code Parameters}.
+     * @throws IOException throws if is chosen "-cf" or "-config-file" parameter and the file does not exist.
+     */
+    public static Parameters parse(AppConfig appConfig, String... args) throws IOException {
+        logger.debug("Parsing input parameters {}", Arrays.toString(args));
+
+        Parameters parameters = new Parameters();
+        JCommander jCommander = JCommander.newBuilder()
+                .programName(appConfig.getName())
+                .addObject(parameters).build();
+        parameters.jCommander = jCommander;
+
+        jCommander.parse(args);
+
+        String configFileName = parameters.getConfigFileName();
+        logger.debug("Checking existence of configuration file {}", configFileName);
+        if (isNotBlank(configFileName) && notExists(get(configFileName))) {
+            throw new FileNotFoundException(format("Config file %s does not exist.", configFileName));
+        }
+
+        logger.info("Parsing input parameters {} were parsed successfully.", Arrays.toString(args));
+        return parameters;
+    }
+
+    @Parameter(names = {"-h", "-help"}, help = true)
+    private boolean help = false;
+
+    @Parameter(names = {"-cf", "-config-file"}, description = "Configuration file in .yaml format.")
+    private String configFileName;
+
+    /**
+     * Returns name of the configuration file.
+     * @return string name.
+     */
+    public String getConfigFileName() {
+        return configFileName;
+    }
+
+    public boolean isHelp() {
+        return help;
+    }
+
+    public void printHelp() {
+        jCommander.usage();
+    }
+}

+ 56 - 53
connector-app/src/main/java/cz/senslog/connector/app/config/ServiceProvider.java

@@ -1,53 +1,56 @@
-package cz.senslog.connector.app.config;
-
-import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
-import cz.senslog.connector.push.api.ConnectorPushProvider;
-
-import java.util.function.Function;
-
-/**
- * The class {@code ServiceProvider} represents a wrapper for
- * fetch and push providers.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class ServiceProvider {
-
-    /** Function provides fetcher instance of input class. */
-    private final Function<Class<?>, ConnectorFetchProvider> fetchProviderFnc;
-
-    /** Function provides pusher instance of input class. */
-    private final Function<Class<?>, ConnectorPushProvider> pushProviderFnc;
-
-    /**
-     * Constructor allows to set all attributes.
-     * @param fetchProviderFnc - function to provider fetch instance.
-     * @param pushProviderFnc - function to provide push instance.
-     */
-    public ServiceProvider(Function<Class<?>, ConnectorFetchProvider> fetchProviderFnc,
-                           Function<Class<?>, ConnectorPushProvider> pushProviderFnc
-    ) {
-        this.fetchProviderFnc = fetchProviderFnc;
-        this.pushProviderFnc = pushProviderFnc;
-    }
-
-    /**
-     * Returns fetch instance depends on input class.
-     * @param providerClass - class of fetch.
-     * @return instance of fetch.
-     */
-    public ConnectorFetchProvider getFetchProvider(Class<?> providerClass) {
-        return fetchProviderFnc.apply(providerClass);
-    }
-
-    /**
-     * Returns push instance depends on input class.
-     * @param providerClass - class of push.
-     * @return instance of push.
-     */
-    public ConnectorPushProvider getPushProvider(Class<?> providerClass) {
-        return pushProviderFnc.apply(providerClass);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config;
+
+import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
+import cz.senslog.connector.push.api.ConnectorPushProvider;
+
+import java.util.function.Function;
+
+/**
+ * The class {@code ServiceProvider} represents a wrapper for
+ * fetch and push providers.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class ServiceProvider {
+
+    /** Function provides fetcher instance of input class. */
+    private final Function<Class<?>, ConnectorFetchProvider> fetchProviderFnc;
+
+    /** Function provides pusher instance of input class. */
+    private final Function<Class<?>, ConnectorPushProvider> pushProviderFnc;
+
+    /**
+     * Constructor allows to set all attributes.
+     * @param fetchProviderFnc - function to provider fetch instance.
+     * @param pushProviderFnc - function to provide push instance.
+     */
+    public ServiceProvider(Function<Class<?>, ConnectorFetchProvider> fetchProviderFnc,
+                           Function<Class<?>, ConnectorPushProvider> pushProviderFnc
+    ) {
+        this.fetchProviderFnc = fetchProviderFnc;
+        this.pushProviderFnc = pushProviderFnc;
+    }
+
+    /**
+     * Returns fetch instance depends on input class.
+     * @param providerClass - class of fetch.
+     * @return instance of fetch.
+     */
+    public ConnectorFetchProvider getFetchProvider(Class<?> providerClass) {
+        return fetchProviderFnc.apply(providerClass);
+    }
+
+    /**
+     * Returns push instance depends on input class.
+     * @param providerClass - class of push.
+     * @return instance of push.
+     */
+    public ConnectorPushProvider getPushProvider(Class<?> providerClass) {
+        return pushProviderFnc.apply(providerClass);
+    }
+}

+ 54 - 51
connector-app/src/main/java/cz/senslog/connector/app/config/api/ConfigurationService.java

@@ -1,51 +1,54 @@
-package cz.senslog.connector.app.config.api;
-
-import cz.senslog.connector.app.config.DatabaseBuilderImpl;
-import cz.senslog.connector.app.config.FileBuilderImpl;
-import cz.senslog.connector.model.config.ConnectorDescriptor;
-import cz.senslog.connector.model.config.DefaultConfig;
-
-import java.util.Set;
-
-/**
- * The interface {@code ConfigurationService} provides a generic service for configuration.
- * Configuration can be gotten from a file, database or anything else.
- *
- * Provides two crucial functionalities:
- *  - returns set of connector descriptors
- *  - returns configuration for a class
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface ConfigurationService {
-
-    /**
-     * Creates a builder for a configuration from a file.
-     * @return new instance of {@link FileBuilder}.
-     */
-    static FileBuilder newFileBuilder() {
-        return new FileBuilderImpl();
-    }
-
-    /**
-     * Creates a builder for a configuration from a database.
-     * @return new instance of {@link DatabaseBuilder}.
-     */
-    static DatabaseBuilder newDatabaseBuilder() {
-        return new DatabaseBuilderImpl();
-    }
-
-    /**
-     * @return set of connector descriptors.
-     */
-    Set<ConnectorDescriptor> getConnectorDescriptors();
-
-    /**
-     * Returns a configuration depends on a provider id.
-     * @param providerId - identifier of provider.
-     * @return default configuration for an input provider id.
-     */
-    DefaultConfig getConfigForProviderId(String providerId);
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config.api;
+
+import cz.senslog.connector.app.config.DatabaseBuilderImpl;
+import cz.senslog.connector.app.config.FileBuilderImpl;
+import cz.senslog.connector.model.config.ConnectorDescriptor;
+import cz.senslog.connector.model.config.DefaultConfig;
+
+import java.util.Set;
+
+/**
+ * The interface {@code ConfigurationService} provides a generic service for configuration.
+ * Configuration can be gotten from a file, database or anything else.
+ *
+ * Provides two crucial functionalities:
+ *  - returns set of connector descriptors
+ *  - returns configuration for a class
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface ConfigurationService {
+
+    /**
+     * Creates a builder for a configuration from a file.
+     * @return new instance of {@link FileBuilder}.
+     */
+    static FileBuilder newFileBuilder() {
+        return new FileBuilderImpl();
+    }
+
+    /**
+     * Creates a builder for a configuration from a database.
+     * @return new instance of {@link DatabaseBuilder}.
+     */
+    static DatabaseBuilder newDatabaseBuilder() {
+        return new DatabaseBuilderImpl();
+    }
+
+    /**
+     * @return set of connector descriptors.
+     */
+    Set<ConnectorDescriptor> getConnectorDescriptors();
+
+    /**
+     * Returns a configuration depends on a provider id.
+     * @param providerId - identifier of provider.
+     * @return default configuration for an input provider id.
+     */
+    DefaultConfig getConfigForProviderId(String providerId);
+}

+ 49 - 46
connector-app/src/main/java/cz/senslog/connector/app/config/api/DatabaseBuilder.java

@@ -1,47 +1,50 @@
-package cz.senslog.connector.app.config.api;
-
-
-/**
- * The interface {@code DatabaseBuilder} provides a configuration
- * to create new connection to a database.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface DatabaseBuilder {
-
-    /**
-     * Sets connection url to the configuration.
-     * @param connectionUrl - connection url to the database.
-     * @return instance of builder {@code DatabaseBuilder}.
-     */
-    DatabaseBuilder connectionUrl(String connectionUrl);
-
-    /**
-     * Sets username to the configuration.
-     * @param username - username to the database.
-     * @return instance of builder {@code DatabaseBuilder}.
-     */
-    DatabaseBuilder username(String username);
-
-    /**
-     * Sets password to the configuration.
-     * @param password - password to the database.
-     * @return instance of builder {@code DatabaseBuilder}.
-     */
-    DatabaseBuilder password(String password);
-
-    /**
-     * Identifier of connector name in a configuration database.
-     * @param connectorName connector name.
-     * @return instance of builder {@code DatabaseBuilder}.
-     */
-    DatabaseBuilder connectorName(String connectorName);
-
-    /**
-     * Creates a new instance with the configuration.
-     * @return new instance of {@link DatabaseConfigurationService}.
-     */
-    DatabaseConfigurationService build();
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config.api;
+
+
+/**
+ * The interface {@code DatabaseBuilder} provides a configuration
+ * to create new connection to a database.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface DatabaseBuilder {
+
+    /**
+     * Sets connection url to the configuration.
+     * @param connectionUrl - connection url to the database.
+     * @return instance of builder {@code DatabaseBuilder}.
+     */
+    DatabaseBuilder connectionUrl(String connectionUrl);
+
+    /**
+     * Sets username to the configuration.
+     * @param username - username to the database.
+     * @return instance of builder {@code DatabaseBuilder}.
+     */
+    DatabaseBuilder username(String username);
+
+    /**
+     * Sets password to the configuration.
+     * @param password - password to the database.
+     * @return instance of builder {@code DatabaseBuilder}.
+     */
+    DatabaseBuilder password(String password);
+
+    /**
+     * Identifier of connector name in a configuration database.
+     * @param connectorName connector name.
+     * @return instance of builder {@code DatabaseBuilder}.
+     */
+    DatabaseBuilder connectorName(String connectorName);
+
+    /**
+     * Creates a new instance with the configuration.
+     * @return new instance of {@link DatabaseConfigurationService}.
+     */
+    DatabaseConfigurationService build();
 }

+ 22 - 19
connector-app/src/main/java/cz/senslog/connector/app/config/api/DatabaseConfigurationService.java

@@ -1,20 +1,23 @@
-package cz.senslog.connector.app.config.api;
-
-import java.io.IOException;
-
-/**
- * The interface {@code DatabaseConfigurationService} provides functionality
- * which is mandatory only for a database and is important to use it correctly.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface DatabaseConfigurationService extends ConfigurationService {
-
-    /**
-     * Creates a new connection to the database.
-     * @throws IOException throws if the connection is not created successfully.
-     */
-    void connect() throws IOException;
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config.api;
+
+import java.io.IOException;
+
+/**
+ * The interface {@code DatabaseConfigurationService} provides functionality
+ * which is mandatory only for a database and is important to use it correctly.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface DatabaseConfigurationService extends ConfigurationService {
+
+    /**
+     * Creates a new connection to the database.
+     * @throws IOException throws if the connection is not created successfully.
+     */
+    void connect() throws IOException;
 }

+ 27 - 24
connector-app/src/main/java/cz/senslog/connector/app/config/api/FileBuilder.java

@@ -1,25 +1,28 @@
-package cz.senslog.connector.app.config.api;
-
-/**
- * The interface {@code FileBuilder} provides a configuration
- * to load a configuration file.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface FileBuilder {
-
-    /**
-     * Sets name of file to the configuration.
-     * @param fileName - name of configuration file.
-     * @return instance of builder {@code FileBuilder}.
-     */
-    FileBuilder fileName(String fileName);
-
-    /**
-     * Creates a new instance with the configuration.
-     * @return new instance of {@link FileConfigurationService}.
-     */
-    FileConfigurationService build();
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config.api;
+
+/**
+ * The interface {@code FileBuilder} provides a configuration
+ * to load a configuration file.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface FileBuilder {
+
+    /**
+     * Sets name of file to the configuration.
+     * @param fileName - name of configuration file.
+     * @return instance of builder {@code FileBuilder}.
+     */
+    FileBuilder fileName(String fileName);
+
+    /**
+     * Creates a new instance with the configuration.
+     * @return new instance of {@link FileConfigurationService}.
+     */
+    FileConfigurationService build();
 }

+ 24 - 21
connector-app/src/main/java/cz/senslog/connector/app/config/api/FileConfigurationService.java

@@ -1,22 +1,25 @@
-package cz.senslog.connector.app.config.api;
-
-import java.io.IOException;
-
-/**
- * The interface {@code FileConfigurationService} provides functionality
- * which is mandatory only for a file configuration.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface FileConfigurationService extends ConfigurationService {
-
-    /**
-     * Loads and parses the configuration file.
-     * From the configuration file is loaded configuration for each class
-     * and also connector description which is used to create a new one.
-     * @throws IOException throws if the configuration file is not loaded correctly.
-     */
-    void load() throws IOException;
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.app.config.api;
+
+import java.io.IOException;
+
+/**
+ * The interface {@code FileConfigurationService} provides functionality
+ * which is mandatory only for a file configuration.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface FileConfigurationService extends ConfigurationService {
+
+    /**
+     * Loads and parses the configuration file.
+     * From the configuration file is loaded configuration for each class
+     * and also connector description which is used to create a new one.
+     * @throws IOException throws if the configuration file is not loaded correctly.
+     */
+    void load() throws IOException;
 }

+ 21 - 0
connector-fetch-api/license.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 University of West Bohemia (UWB) https://www.zcu.cz/en, LESPROJEKT-SLUŽBY s.r.o. (LESP) https://www.lesprojekt.cz/en
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 64 - 61
connector-fetch-api/src/main/java/cz/senslog/connector/fetch/ConnectorFetch.java

@@ -1,61 +1,64 @@
-package cz.senslog.connector.fetch;
-
-import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.*;
-
-/**
- * The class {@code ConnectorFetch} represents a loader for classes implement {@link ConnectorFetchProvider}.
- * For this is used technology Java Service Provider Interface (SPI).
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class ConnectorFetch {
-
-    private static Logger logger = LogManager.getLogger(ConnectorFetch.class);
-
-    /** Map of implementations. */
-    private static Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> services;
-
-    static {
-        services = loadAll();
-    }
-
-    /**
-     * Loads and saves all available implementations of {@link ConnectorFetchProvider}.
-     * @return Map of all implementations.
-     */
-    private static Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> loadAll() {
-        logger.debug("Getting all implementation of the class {}.", ConnectorFetchProvider.class);
-        Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> services = new HashMap<>();
-        ServiceLoader<ConnectorFetchProvider> loader = ServiceLoader.load(ConnectorFetchProvider.class);
-        for (ConnectorFetchProvider connectorProvider : loader) {
-            logger.debug("Loaded the class {}.", connectorProvider.getClass());
-            services.put(connectorProvider.getClass(), connectorProvider);
-        }
-        logger.info("Successfully loaded {} class of the {}.",  services.size(), ConnectorFetchProvider.class);
-        return services;
-    }
-
-    /**
-     * Returns all implementations of {@link ConnectorFetchProvider} where
-     * the key is the class and the value is concrete implementation.
-     * @return Map of all implementations.
-     */
-    public static Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> provideAll() {
-        return services;
-    }
-
-    /**
-     * Returns an implementation of provider according to input class.
-     * @param providerClass - class which is find an implementation.
-     * @return implementation type to {@link ConnectorFetchProvider}.
-     */
-    public static ConnectorFetchProvider getProvider(Class<?> providerClass) {
-        return services.get(providerClass);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch;
+
+import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+
+/**
+ * The class {@code ConnectorFetch} represents a loader for classes implement {@link ConnectorFetchProvider}.
+ * For this is used technology Java Service Provider Interface (SPI).
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class ConnectorFetch {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorFetch.class);
+
+    /** Map of implementations. */
+    private static final Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> services;
+
+    static {
+        services = loadAll();
+    }
+
+    /**
+     * Loads and saves all available implementations of {@link ConnectorFetchProvider}.
+     * @return Map of all implementations.
+     */
+    private static Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> loadAll() {
+        logger.debug("Getting all implementation of the class {}.", ConnectorFetchProvider.class);
+        Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> services = new HashMap<>();
+        ServiceLoader<ConnectorFetchProvider> loader = ServiceLoader.load(ConnectorFetchProvider.class);
+        for (ConnectorFetchProvider connectorProvider : loader) {
+            logger.debug("Loaded the class {}.", connectorProvider.getClass());
+            services.put(connectorProvider.getClass(), connectorProvider);
+        }
+        logger.info("Successfully loaded {} class of the {}.",  services.size(), ConnectorFetchProvider.class);
+        return services;
+    }
+
+    /**
+     * Returns all implementations of {@link ConnectorFetchProvider} where
+     * the key is the class and the value is concrete implementation.
+     * @return Map of all implementations.
+     */
+    public static Map<Class<? extends ConnectorFetchProvider>, ConnectorFetchProvider> provideAll() {
+        return services;
+    }
+
+    /**
+     * Returns an implementation of provider according to input class.
+     * @param providerClass - class which is find an implementation.
+     * @return implementation type to {@link ConnectorFetchProvider}.
+     */
+    public static ConnectorFetchProvider getProvider(Class<?> providerClass) {
+        return services.get(providerClass);
+    }
+}

+ 25 - 22
connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/ConnectorFetchProvider.java

@@ -1,22 +1,25 @@
-package cz.senslog.connector.fetch.api;
-
-import cz.senslog.connector.model.api.AbstractModel;
-import cz.senslog.connector.model.config.DefaultConfig;
-
-/**
- * The interface {@code ConnectorFetchProvider} provides a generic communication interface to create a new fetcher.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface ConnectorFetchProvider {
-
-    /**
-     * Creates a new instance of {@link ExecutableFetcher}. This method receive default
-     * configuration {@link DefaultConfig} which is used to configure the new instance of a fetcher executor.
-     * @param defaultConfig - default configuration.
-     * @return new instance of {@link ConnectorFetcher}.
-     */
-    ExecutableFetcher<? extends AbstractModel> createExecutableFetcher(DefaultConfig defaultConfig);
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.api;
+
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+
+/**
+ * The interface {@code ConnectorFetchProvider} provides a generic communication interface to create a new fetcher.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface ConnectorFetchProvider {
+
+    /**
+     * Creates a new instance of {@link ExecutableFetcher}. This method receive default
+     * configuration {@link DefaultConfig} which is used to configure the new instance of a fetcher executor.
+     * @param defaultConfig - default configuration.
+     * @return new instance of {@link ConnectorFetcher}.
+     */
+    ExecutableFetcher<? extends AbstractModel> createExecutableFetcher(DefaultConfig defaultConfig);
+}

+ 33 - 30
connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/ConnectorFetcher.java

@@ -1,30 +1,33 @@
-package cz.senslog.connector.fetch.api;
-
-import cz.senslog.connector.model.api.AbstractModel;
-import cz.senslog.connector.model.api.ProxySessionModel;
-
-import java.util.Optional;
-
-/**
- * The interface {@code ConnectorFetcher} provides a generic communication interface for fetchers.
- *
- * @param <T> generic parameter of model which the method 'fetch' sens as an output.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface ConnectorFetcher<S extends ProxySessionModel, T extends AbstractModel> {
-
-    /**
-     * Initialization of fetcher. Method is called only once when is created a new connector.
-     * @throws Exception throws when initialization is not successful.
-     */
-    void init() throws Exception;
-
-    /**
-     * Method is periodically scheduled and contains logic of fetcher.
-     * @return model of data which was fetched.
-     */
-    T fetch(Optional<S> session);
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.api;
+
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.util.Optional;
+
+/**
+ * The interface {@code ConnectorFetcher} provides a generic communication interface for fetchers.
+ *
+ * @param <T> generic parameter of model which the method 'fetch' sens as an output.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface ConnectorFetcher<S extends ProxySessionModel, T extends AbstractModel> {
+
+    /**
+     * Initialization of fetcher. Method is called only once when is created a new connector.
+     * @throws Exception throws when initialization is not successful.
+     */
+    void init() throws Exception;
+
+    /**
+     * Method is periodically scheduled and contains logic of fetcher.
+     * @return model of data which was fetched.
+     */
+    T fetch(Optional<S> session);
+}

+ 51 - 48
connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/ExecutableFetcher.java

@@ -1,48 +1,51 @@
-package cz.senslog.connector.fetch.api;
-
-import cz.senslog.connector.model.api.AbstractModel;
-import cz.senslog.connector.model.api.ProxySessionModel;
-
-import java.util.Optional;
-
-import static cz.senslog.common.interceptor.ProxyUtils.createProxy;
-
-public class ExecutableFetcher<T extends AbstractModel> {
-
-    private final ConnectorFetcher<? extends ProxySessionModel, T> rawFetcher;
-
-    private final ConnectorFetcher<? extends ProxySessionModel, T> fetcher;
-
-    public static <S extends ProxySessionModel, M extends AbstractModel> ExecutableFetcher<M> create(
-            ConnectorFetcher<S, M> rawFetcher
-    ) {
-        return new ExecutableFetcher<M>(rawFetcher, rawFetcher);
-    }
-
-    public static <S extends ProxySessionModel, M extends AbstractModel> ExecutableFetcher<M> createWithProxySession(
-            FetchProxySession<S, M> proxySession
-    ) {
-        ConnectorFetcher<S, M> object = proxySession.getObject();
-        return new ExecutableFetcher<M>(object, createProxy(object.getClass(), proxySession));
-    }
-
-    private ExecutableFetcher(
-            ConnectorFetcher<? extends ProxySessionModel, T> rawFetcher,
-            ConnectorFetcher<? extends ProxySessionModel, T> fetcher
-    ) {
-        this.rawFetcher = rawFetcher;
-        this.fetcher = fetcher;
-    }
-
-    public T execute() {
-        return fetcher.fetch(Optional.empty());
-    }
-
-    public ConnectorFetcher<? extends ProxySessionModel, T> getRawFetcher() {
-        return rawFetcher;
-    }
-
-    public ConnectorFetcher<? extends ProxySessionModel, T> getFetcher() {
-        return fetcher;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.api;
+
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.util.Optional;
+
+import static cz.senslog.common.interceptor.ProxyUtils.createProxy;
+
+public class ExecutableFetcher<T extends AbstractModel> {
+
+    private final ConnectorFetcher<? extends ProxySessionModel, T> rawFetcher;
+
+    private final ConnectorFetcher<? extends ProxySessionModel, T> fetcher;
+
+    public static <S extends ProxySessionModel, M extends AbstractModel> ExecutableFetcher<M> create(
+            ConnectorFetcher<S, M> rawFetcher
+    ) {
+        return new ExecutableFetcher<M>(rawFetcher, rawFetcher);
+    }
+
+    public static <S extends ProxySessionModel, M extends AbstractModel> ExecutableFetcher<M> createWithProxySession(
+            FetchProxySession<S, M> proxySession
+    ) {
+        ConnectorFetcher<S, M> object = proxySession.getObject();
+        return new ExecutableFetcher<M>(object, createProxy(object.getClass(), proxySession));
+    }
+
+    private ExecutableFetcher(
+            ConnectorFetcher<? extends ProxySessionModel, T> rawFetcher,
+            ConnectorFetcher<? extends ProxySessionModel, T> fetcher
+    ) {
+        this.rawFetcher = rawFetcher;
+        this.fetcher = fetcher;
+    }
+
+    public T execute() {
+        return fetcher.fetch(Optional.empty());
+    }
+
+    public ConnectorFetcher<? extends ProxySessionModel, T> getRawFetcher() {
+        return rawFetcher;
+    }
+
+    public ConnectorFetcher<? extends ProxySessionModel, T> getFetcher() {
+        return fetcher;
+    }
+}

+ 50 - 47
connector-fetch-api/src/main/java/cz/senslog/connector/fetch/api/FetchProxySession.java

@@ -1,47 +1,50 @@
-package cz.senslog.connector.fetch.api;
-
-import cz.senslog.common.interceptor.AbstractMethodInterceptor;
-import cz.senslog.connector.model.api.AbstractModel;
-import cz.senslog.connector.model.api.ProxySessionModel;
-
-import java.util.Optional;
-
-import static cz.senslog.common.util.MethodExplorer.loadMethod;
-import static java.util.Optional.*;
-
-public abstract class FetchProxySession<S extends ProxySessionModel, T extends AbstractModel> extends AbstractMethodInterceptor {
-
-    private Optional<S> optionalSession;
-
-    protected FetchProxySession(ConnectorFetcher<S, T> fetcher) {
-        super(fetcher, loadMethod(ConnectorFetcher.class, "fetch", Optional.class));
-        this.optionalSession = Optional.empty();
-    }
-
-    protected abstract S preProcessing(Optional<S> previousSession);
-
-    protected abstract void postProcessing(Optional<T> model, Optional<S> session);
-
-    protected ConnectorFetcher<S, T> getObject() {
-        return (ConnectorFetcher<S, T>) super.getObject();
-    }
-
-    @Override
-    public Object[] preProcessing(Object[] methodParams) {
-        S s = preProcessing(optionalSession);
-        if (s.isActive()) {
-            optionalSession = of(s);
-            return new Object[]{optionalSession};
-        } else {
-            optionalSession = empty();
-            return null;
-        }
-    }
-
-    @Override
-    public Object postProcessing(Object returnObject) {
-        Optional<T> returnOpt = ofNullable((T)returnObject);
-        postProcessing(returnOpt, optionalSession);
-        return returnObject;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.api;
+
+import cz.senslog.common.interceptor.AbstractMethodInterceptor;
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.util.Optional;
+
+import static cz.senslog.common.util.MethodExplorer.loadMethod;
+import static java.util.Optional.*;
+
+public abstract class FetchProxySession<S extends ProxySessionModel, T extends AbstractModel> extends AbstractMethodInterceptor {
+
+    private Optional<S> optionalSession;
+
+    protected FetchProxySession(ConnectorFetcher<S, T> fetcher) {
+        super(fetcher, loadMethod(ConnectorFetcher.class, "fetch", Optional.class));
+        this.optionalSession = Optional.empty();
+    }
+
+    protected abstract S preProcessing(Optional<S> previousSession);
+
+    protected abstract void postProcessing(Optional<T> model, Optional<S> session);
+
+    protected ConnectorFetcher<S, T> getObject() {
+        return (ConnectorFetcher<S, T>) super.getObject();
+    }
+
+    @Override
+    public Object[] preProcessing(Object[] methodParams) {
+        S s = preProcessing(optionalSession);
+        if (s.isActive()) {
+            optionalSession = of(s);
+            return new Object[]{optionalSession};
+        } else {
+            optionalSession = empty();
+            return null;
+        }
+    }
+
+    @Override
+    public Object postProcessing(Object returnObject) {
+        Optional<T> returnOpt = ofNullable((T)returnObject);
+        postProcessing(returnOpt, optionalSession);
+        return returnObject;
+    }
+}

+ 18 - 0
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AllowedStation.java

@@ -0,0 +1,18 @@
+package cz.senslog.connector.fetch.azure;
+
+import cz.senslog.connector.model.config.PropertyConfig;
+
+import java.util.*;
+
+class AllowedStation {
+
+    private final Set<String> stations;
+
+    AllowedStation(PropertyConfig config) {
+        stations = config.getAttributes();
+    }
+
+    public boolean isAllowed(String eui) {
+        return stations.contains(eui);
+    }
+}

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

@@ -22,6 +22,7 @@ public class AzureConfig {
     private final HostConfig sensorDataHost;
     private final AzureAuthConfig authentication;
     private final AzureSessionProxyConfig sessionProxy;
+    private final AllowedStation allowedStation;
 
     private LocalDateTime startDate;
     private LocalDateTime endDate;
@@ -36,6 +37,7 @@ public class AzureConfig {
         this.sensorInfoHost = new HostConfig(config.getPropertyConfig("sensorInfoHost"));
         this.sensorDataHost = new HostConfig(config.getPropertyConfig("sensorDataHost"));
         this.authentication = new AzureAuthConfig(config.getPropertyConfig("authentication"));
+        this.allowedStation = new AllowedStation(config.getPropertyConfig("allowedStation"));
 
         this.startDate = config.getLocalDateTimeProperty("startDate");
         this.endDate = config.getOptionalLocalDateTimeProperty("endDate").orElse(null);
@@ -72,6 +74,10 @@ public class AzureConfig {
         return sessionProxy;
     }
 
+    public AllowedStation getAllowedStation() {
+        return allowedStation;
+    }
+
     @Override
     public String toString() {
         return objectToJson(this);

+ 299 - 284
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureFetcher.java

@@ -1,284 +1,299 @@
-package cz.senslog.connector.fetch.azure;
-
-import com.google.gson.reflect.TypeToken;
-import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.common.exception.ModuleInterruptedException;
-import cz.senslog.common.exception.SyntaxException;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import cz.senslog.common.json.JsonSchema;
-import cz.senslog.connector.model.api.ProxySessionModel;
-import cz.senslog.connector.model.azure.AzureModel;
-import cz.senslog.connector.model.azure.SensorData;
-import cz.senslog.connector.model.azure.SensorInfo;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.lang.reflect.Type;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.util.*;
-
-import static cz.senslog.common.http.HttpHeader.AUTHORIZATION;
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static cz.senslog.common.util.StringUtils.isBlank;
-import static java.lang.String.format;
-import static java.time.OffsetDateTime.MAX;
-import static java.time.OffsetDateTime.MIN;
-import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
-import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
-import static java.util.Collections.emptyList;
-
-/**
- * The class {@code AzureFetcher} represents an implementation of {@link ConnectorFetcher}.
- * The class contains {@link AzureModel} which contains fetched data.
- *
- * <h2>Initialization</h2>
- * At first are loaded both schemas for validation received JSONs.
- * When schemas were loaded successfully, is triggered the authentication process to get access token.
- * The token is used to get information of sensors. If loading fails or are not get any sensors,
- * will be throw an exception. Otherwise the sensors are saved.
- *
- * <h2>Fetch</h2>
- * If list of sensors is empty, is returned empty model.
- * For each sensor are get data which is valid by schema and parsed from json format to object.
- * If the process is successful, data is saved to the sensor and is get the time of last update.
- * All data of sensors is send in model and the time of last fetch is changed according to time of the last update.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class AzureFetcher implements ConnectorFetcher<AzureSession, AzureModel> {
-
-    private static Logger logger = LogManager.getLogger(AzureFetcher.class);
-
-
-    /** Name of the sensor info JSON schema. */
-    private static final String SENSOR_INFO_JSON_SCHEMA_NAME = "schema/sensorInfoSchema.json";
-
-    /** Name of the sensor data JSON schema. */
-    private static final String SENSOR_DATA_JSON_SCHEMA_NAME = "schema/sensorDataSchema.json";
-
-
-    /** Configuration. */
-    private final AzureConfig config;
-
-    /** Service for authentication. */
-    private final AuthenticationService authService;
-
-    /** Http client. */
-    private final HttpClient httpClient;
-
-    /** List of sensor information. */
-    private List<SensorInfo> sensorInfos;
-
-    /** Schema for sensor data. */
-    private JsonSchema sensorDataSchema;
-
-    private AzureSession localSession;
-
-    public AzureFetcher() { this(null, null, null); }
-
-    /**
-     * Constructor of the class sets all attributes.
-     * @param config - configuration for fetcher.
-     * @param authService - authentication service.
-     * @param httpClient - http client.
-     */
-    AzureFetcher(AzureConfig config, AuthenticationService authService, HttpClient httpClient) {
-        this.config = config;
-        this.authService = authService;
-        this.httpClient = httpClient;
-        this.sensorInfos = new ArrayList<>();
-        this.localSession = AzureSession.emptySession();
-    }
-
-    @Override
-    public void init() throws Exception {
-
-        logger.debug("Creating a new schema {}.", SENSOR_DATA_JSON_SCHEMA_NAME);
-        sensorDataSchema = JsonSchema.loadAsResource(SENSOR_DATA_JSON_SCHEMA_NAME);
-        logger.info("Schema {} was created successfully.", SENSOR_DATA_JSON_SCHEMA_NAME);
-
-        logger.debug("Creating a new schema {}.", SENSOR_INFO_JSON_SCHEMA_NAME);
-        JsonSchema sensorInfoSchema = JsonSchema.loadAsResource(SENSOR_INFO_JSON_SCHEMA_NAME);
-        logger.info("Schema {} was created successfully.", SENSOR_INFO_JSON_SCHEMA_NAME);
-
-        String accessToken = authService.getAccessToken();
-
-        HostConfig sensorInfoHost = config.getSensorInfoHost();
-        logger.info("Creating a http request to {}.", sensorInfoHost);
-        HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(sensorInfoHost.getDomain(), sensorInfoHost.getPath()).build())
-                .header(AUTHORIZATION, accessToken)
-                .build();
-
-        logger.info("Sending the http request to get information about sensors.");
-        HttpResponse response = httpClient.send(request);
-        logger.info("Received a response with a status: {}.", response.getStatus());
-
-        if (response.isError()) {
-            Type mapType = new TypeToken<HashMap<String, String>>() {}.getType();
-            Map<String, String> responseBody = jsonToObject(response.getBody(), mapType);
-            logger.error(responseBody.getOrDefault("Message", "unknown error"));
-            throw logger.throwing(new Exception(format(
-                    "Can not get information about the sensors. %s", response.getBody()
-            )));
-        }
-
-        String bodyJson = response.getBody();
-        List<String> errors = new ArrayList<>();
-        if (!sensorInfoSchema.validateJsonArray(bodyJson, errors)) {
-            logger.error("{} received sensor info json is not valid according to the schema.", bodyJson);
-            logger.error(Arrays.toString(errors.toArray()));
-            throw logger.throwing(new Exception(format(
-                    "Received json data is not valid according to the schema. %s", bodyJson
-            )));
-        }
-
-        logger.debug("Parsing body of the response from JSON format.");
-        Type sensorInfoListType = new TypeToken<Collection<SensorInfo>>() {}.getType();
-        List<SensorInfo> sensors = jsonToObject(response.getBody(), sensorInfoListType);
-
-        if (sensors.isEmpty()) {
-            throw logger.throwing(new Exception("Received empty list of sensors."));
-        }
-
-        logger.debug("Creating an iterator over all received sensors.");
-        for (SensorInfo sensor : sensors) {
-            logger.debug("Loading a sensor {}.", sensor.getEui());
-            if (sensor.isActive()) {
-                logger.info("Saving an active sensor {}.", sensor.getEui());
-                sensorInfos.add(sensor);
-            } else {
-                logger.info("Sensor {} is not activated.", sensor.getEui());
-            }
-        }
-
-        logger.info("{} sensors were loaded.", sensorInfos.size());
-        logger.info(sensorInfos.toString());
-    }
-
-    @Override
-    public AzureModel fetch(Optional<AzureSession> persistenceSession) {
-
-        if (sensorInfos.isEmpty()) {
-            logger.error("Sensors information were not loaded. Can not get detailed information.");
-            OffsetDateTime now = OffsetDateTime.now();
-            return new AzureModel(emptyList(), now, now);
-        }
-
-        AzureSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
-
-        String accessToken = authService.getAccessToken();
-
-        int totalFetched = 0;
-        OffsetDateTime globalFrom = MAX, globalTo = MIN;
-
-        for (SensorInfo sensorInfo : sensorInfos) {
-            logger.info("Fetching data for the sensor {}.", sensorInfo.getEui());
-            String stationId = sensorInfo.getEui();
-
-            if (isBlank(stationId)) continue;
-
-            OffsetDateTime localFrom = session.getLastTimeForUnit(stationId, MIN);
-            if (localFrom.toLocalDateTime().isBefore(config.getStartDate())) {
-                localFrom = OffsetDateTime.of(config.getStartDate(), localFrom.getOffset());
-            }
-
-            LocalDateTime endTime = config.getEndDate() != null ? config.getEndDate() : LocalDateTime.now();
-            OffsetDateTime localTo = OffsetDateTime.of(endTime, localFrom.getOffset());
-
-            HostConfig sensorDataHost = config.getSensorDataHost();
-            logger.info("Creating a http request to {}.", sensorDataHost);
-            HttpRequest request = HttpRequest.newBuilder()
-                    .url(URLBuilder.newBuilder(sensorDataHost.getDomain(), sensorDataHost.getPath())
-                            .addParam("eui", stationId)
-                            .addParam("from", localFrom.format(ISO_DATE_TIME))
-                            .addParam("to", localTo.format(ISO_DATE_TIME))
-                            .addParam("limit", config.getLimitPerSensor())
-                            .build())
-                    .header(AUTHORIZATION, accessToken)
-                    .GET().build();
-
-            logger.info("Sending the http request.");
-            logger.info(request);
-            HttpResponse response = httpClient.send(request);
-            logger.info("Received a response with a status: {}.", response.getStatus());
-
-            if (response.isError()) {
-                logger.error("Can not get data of the sensor {}. Error {} {}", stationId, response.getStatus(), response.getBody());
-                continue;
-            }
-
-            String bodyJson = response.getBody();
-            List<String> errors = new ArrayList<>();
-            if (!sensorDataSchema.validateJsonObject(bodyJson, errors)) {
-                logger.error("{} received sensor data json is not valid according to the schema.", bodyJson);
-                logger.error(Arrays.toString(errors.toArray()));
-                continue;
-            }
-
-            SensorInfo sensorInfoData;
-            try {
-                logger.debug("Parsing body of the response.");
-                sensorInfoData = jsonToObject(bodyJson, SensorInfo.class);
-                logger.info("Received {} records for the sensor {}.", sensorInfoData.getData().size(), sensorInfo.getEui());
-            } catch (SyntaxException e) {
-                logger.catching(e);
-                continue;
-            }
-
-            if (sensorInfoData.getEui().equals(sensorInfo.getEui())) {
-                logger.debug("Setting sensor data to the sensor {}.", sensorInfo.getEui());
-                sensorInfo.setData(sensorInfoData.getData());
-                totalFetched += sensorInfoData.getData().size();
-
-                logger.debug("Getting last record of the sensor data.");
-                OffsetDateTime latestTimestamp = MIN;
-                List<SensorData> sensorData = sensorInfoData.getData();
-                if (!sensorData.isEmpty()) {
-                    SensorData lastRecord = sensorData.get(sensorData.size() - 1);
-                    OffsetDateTime lastRecordTime = lastRecord.getTime();
-                    if (lastRecordTime.isAfter(latestTimestamp)) {
-                        latestTimestamp = lastRecordTime;
-                    }
-                }
-                localTo = latestTimestamp;
-
-                session.addLastUnitTime(stationId, localTo);
-
-                if (globalFrom.isAfter(localFrom)) {
-                    globalFrom = localFrom;
-                }
-
-                if (globalTo.isBefore(localTo)) {
-                    globalTo = localTo;
-                }
-            }
-        }
-
-
-        logger.info("Total fetched {} records.", totalFetched);
-        logger.info("Fetched data from {} to {}.",
-                globalFrom.format(ISO_OFFSET_DATE_TIME), globalTo.format(ISO_OFFSET_DATE_TIME));
-
-        if (totalFetched == 0 && config.getEndDate() != null) {
-            throw new ModuleInterruptedException(format(
-                    "Fetched all records from %s to %s. The connector is going to end.",
-                    config.getStartDate().format(ISO_DATE_TIME), config.getEndDate().format(ISO_DATE_TIME)
-            ));
-        }
-
-        logger.debug("Creating a new instance of Azure model.");
-        AzureModel model = new AzureModel(sensorInfos, globalFrom, globalTo);
-        logger.debug("Model was created successfully and sending it forward.");
-
-        return model;
-    }
-}
+package cz.senslog.connector.fetch.azure;
+
+import com.google.gson.reflect.TypeToken;
+import cz.senslog.common.exception.ModuleInterruptedException;
+import cz.senslog.common.exception.SyntaxException;
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import cz.senslog.common.json.JsonSchema;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
+import cz.senslog.connector.model.api.ProxySessionModel;
+import cz.senslog.connector.model.azure.AzureModel;
+import cz.senslog.connector.model.azure.SensorData;
+import cz.senslog.connector.model.azure.SensorInfo;
+import cz.senslog.connector.model.config.HostConfig;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.*;
+
+import static cz.senslog.common.http.HttpHeader.AUTHORIZATION;
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static cz.senslog.common.util.StringUtils.isBlank;
+import static cz.senslog.connector.model.converter.AzureUnitConverter.convertEuiToUnitId;
+import static java.lang.String.format;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
+import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+import static java.util.Collections.emptyList;
+
+/**
+ * The class {@code AzureFetcher} represents an implementation of {@link ConnectorFetcher}.
+ * The class contains {@link AzureModel} which contains fetched data.
+ *
+ * <h2>Initialization</h2>
+ * At first are loaded both schemas for validation received JSONs.
+ * When schemas were loaded successfully, is triggered the authentication process to get access token.
+ * The token is used to get information of sensors. If loading fails or are not get any sensors,
+ * will be throw an exception. Otherwise the sensors are saved.
+ *
+ * <h2>Fetch</h2>
+ * If list of sensors is empty, is returned empty model.
+ * For each sensor are get data which is valid by schema and parsed from json format to object.
+ * If the process is successful, data is saved to the sensor and is get the time of last update.
+ * All data of sensors is send in model and the time of last fetch is changed according to time of the last update.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class AzureFetcher implements ConnectorFetcher<AzureSession, AzureModel> {
+
+    private static final Logger logger = LogManager.getLogger(AzureFetcher.class);
+
+
+    /** Name of the sensor info JSON schema. */
+    private static final String SENSOR_INFO_JSON_SCHEMA_NAME = "schema/sensorInfoSchema.json";
+
+//    /** Name of the sensor data JSON schema. */
+//    private static final String SENSOR_DATA_JSON_SCHEMA_NAME = "schema/sensorDataSchema.json";
+
+
+    /** Configuration. */
+    private final AzureConfig config;
+
+    /** Service for authentication. */
+    private final AuthenticationService authService;
+
+    /** Http client. */
+    private final HttpClient httpClient;
+
+    /** List of sensor information. */
+    private final List<SensorInfo> sensorInfos;
+
+//    /** Schema for sensor data. */
+//    private JsonSchema sensorDataSchema;
+
+    private final AzureSession localSession;
+
+    public AzureFetcher() { this(null, null, null); }
+
+    /**
+     * Constructor of the class sets all attributes.
+     * @param config - configuration for fetcher.
+     * @param authService - authentication service.
+     * @param httpClient - http client.
+     */
+    AzureFetcher(AzureConfig config, AuthenticationService authService, HttpClient httpClient) {
+        this.config = config;
+        this.authService = authService;
+        this.httpClient = httpClient;
+        this.sensorInfos = new ArrayList<>();
+        this.localSession = AzureSession.emptySession();
+    }
+
+    @Override
+    public void init() throws Exception {
+
+//        logger.debug("Creating a new schema {}.", SENSOR_DATA_JSON_SCHEMA_NAME);
+//        sensorDataSchema = JsonSchema.loadAsResource(SENSOR_DATA_JSON_SCHEMA_NAME);
+//        logger.info("Schema {} was created successfully.", SENSOR_DATA_JSON_SCHEMA_NAME);
+
+        logger.debug("Creating a new schema {}.", SENSOR_INFO_JSON_SCHEMA_NAME);
+        JsonSchema sensorInfoSchema = JsonSchema.loadAsResource(SENSOR_INFO_JSON_SCHEMA_NAME);
+        logger.info("Schema {} was created successfully.", SENSOR_INFO_JSON_SCHEMA_NAME);
+
+        String accessToken = authService.getAccessToken();
+
+        HostConfig sensorInfoHost = config.getSensorInfoHost();
+        logger.info("Creating a http request to {}.", sensorInfoHost);
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(sensorInfoHost.getDomain(), sensorInfoHost.getPath()).build())
+                .header(AUTHORIZATION, accessToken)
+                .build();
+
+        logger.info("Sending the http request to get information about sensors.");
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received a response with a status: {}.", response.getStatus());
+
+        if (response.isError()) {
+            Type mapType = new TypeToken<HashMap<String, String>>() {}.getType();
+            Map<String, String> responseBody = jsonToObject(response.getBody(), mapType);
+            logger.error(responseBody.getOrDefault("Message", "unknown error"));
+            throw logger.throwing(new Exception(format(
+                    "Can not get information about the sensors. %s", response.getBody()
+            )));
+        }
+
+        String bodyJson = response.getBody();
+        List<String> errors = new ArrayList<>();
+        if (!sensorInfoSchema.validateJsonArray(bodyJson, errors)) {
+            logger.error("{} received sensor info json is not valid according to the schema.", bodyJson);
+            logger.error(Arrays.toString(errors.toArray()));
+            throw logger.throwing(new Exception(format(
+                    "Received json data is not valid according to the schema. %s", bodyJson
+            )));
+        }
+
+        logger.debug("Parsing body of the response from JSON format.");
+        Type sensorInfoListType = new TypeToken<Collection<SensorInfo>>() {}.getType();
+        List<SensorInfo> sensors = jsonToObject(bodyJson, sensorInfoListType);
+
+        if (sensors.isEmpty()) {
+            throw logger.throwing(new Exception("Received empty list of sensors."));
+        }
+
+        AllowedStation allowedStation = config.getAllowedStation();
+        logger.debug("Creating an iterator over all received sensors.");
+        for (SensorInfo sensor : sensors) {
+            logger.debug("Loading a sensor {}.", sensor.getEui());
+            if (sensor.isActive() && allowedStation.isAllowed(sensor.getEui())) {
+                Long unitId = convertEuiToUnitId(sensor.getEui());
+                if (unitId != null) {
+                    logger.info("Saving an active sensor {}.", sensor.getEui());
+                    sensorInfos.add(sensor);
+                } else {
+                    logger.warn("Sensor {} does not have an EUI converter.", sensor.getEui());
+                }
+            } else {
+                logger.info("Sensor {} is not activated.", sensor.getEui());
+            }
+        }
+
+        logger.info("{} sensors were loaded.", sensorInfos.size());
+        logger.info(sensorInfos.toString());
+    }
+
+    @Override
+    public AzureModel fetch(Optional<AzureSession> persistenceSession) {
+
+        if (sensorInfos.isEmpty()) {
+            logger.error("Sensors information were not loaded. Can not get detailed information.");
+            OffsetDateTime now = OffsetDateTime.now();
+            return new AzureModel(emptyList(), now, now);
+        }
+
+        AzureSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
+
+        String accessToken = authService.getAccessToken();
+
+        int totalFetched = 0;
+        OffsetDateTime globalFrom = MAX, globalTo = MIN;
+        List<SensorInfo> usedSensors = new ArrayList<>(sensorInfos.size());
+        for (SensorInfo sensorInfo : sensorInfos) {
+            logger.info("Fetching data for the sensor {}.", sensorInfo.getEui());
+            String stationId = sensorInfo.getEui();
+
+            if (isBlank(stationId)) continue;
+
+            ZoneOffset offset = sensorInfo.getSensorDataTime().getOffset();
+            OffsetDateTime firstValueDate = OffsetDateTime.of(sensorInfo.getCreated(), offset);
+            OffsetDateTime startDate = config.getStartDate().atOffset(offset);
+            OffsetDateTime lastFetch = session.getLastTimeForUnit(stationId, MIN);
+
+            long fromDateEpoch = Math.max(startDate.toEpochSecond(), Math.max(firstValueDate.toEpochSecond(), lastFetch.toEpochSecond()));
+            OffsetDateTime localFrom = OffsetDateTime.ofInstant(Instant.ofEpochSecond(fromDateEpoch), offset);
+
+            LocalDateTime endTime = config.getEndDate() != null ? config.getEndDate() : LocalDateTime.now();
+            OffsetDateTime localTo = OffsetDateTime.of(endTime, localFrom.getOffset());
+
+            HostConfig sensorDataHost = config.getSensorDataHost();
+            logger.info("Creating a http request to {}.", sensorDataHost);
+            HttpRequest request = HttpRequest.newBuilder()
+                    .url(URLBuilder.newBuilder(sensorDataHost.getDomain(), sensorDataHost.getPath())
+                            .addParam("eui", stationId)
+                            .addParam("from", localFrom.format(ISO_DATE_TIME))
+                            .addParam("to", localTo.format(ISO_DATE_TIME))
+                            .addParam("limit", config.getLimitPerSensor())
+                            .build())
+                    .header(AUTHORIZATION, accessToken)
+                    .GET().build();
+
+            logger.info("Sending the http request.");
+            logger.info(request);
+            HttpResponse response = httpClient.send(request);
+            logger.info("Received a response with a status: {}.", response.getStatus());
+
+            if (response.isError()) {
+                logger.error("Can not get data of the sensor {}. Error {} {}", stationId, response.getStatus(), response.getBody());
+                continue;
+            }
+
+            String bodyJson = response.getBody();
+//            List<String> errors = new ArrayList<>();
+//            if (!sensorDataSchema.validateJsonObject(bodyJson, errors)) {
+//                logger.error("{} received sensor data json is not valid according to the schema.", bodyJson);
+//                logger.error(Arrays.toString(errors.toArray()));
+//                continue;
+//            }
+
+            SensorInfo sensorInfoData;
+            int receivedRecords;
+            try {
+                logger.debug("Parsing body of the response.");
+                sensorInfoData = jsonToObject(bodyJson, SensorInfo.class);
+                receivedRecords = sensorInfoData.getData() == null ? 0 : sensorInfoData.getData().size();
+                logger.info("Received {} records for the sensor {}.", receivedRecords, sensorInfo.getEui());
+            } catch (SyntaxException e) {
+                logger.catching(e);
+                continue;
+            }
+
+            if (receivedRecords > 0 && sensorInfoData.getEui().equals(sensorInfo.getEui())) {
+                logger.debug("Setting sensor data to the sensor {}.", sensorInfo.getEui());
+                totalFetched += sensorInfoData.getData().size();
+
+                logger.debug("Getting last record of the sensor data.");
+                OffsetDateTime latestTimestamp = MIN;
+                List<SensorData> sensorData = sensorInfoData.getData();
+                if (!sensorData.isEmpty()) {
+                    SensorData lastRecord = sensorData.get(sensorData.size() - 1);
+                    OffsetDateTime lastRecordTime = lastRecord.getTime();
+                    if (lastRecordTime.isAfter(latestTimestamp)) {
+                        latestTimestamp = lastRecordTime;
+                    }
+
+                    usedSensors.add(new SensorInfo(sensorInfo, sensorData));
+                }
+                localTo = latestTimestamp;
+
+                session.addLastUnitTime(stationId, localTo);
+
+                if (globalFrom.isAfter(localFrom)) {
+                    globalFrom = localFrom;
+                }
+
+                if (globalTo.isBefore(localTo)) {
+                    globalTo = localTo;
+                }
+            }
+        }
+
+
+        logger.info("Total fetched {} records.", totalFetched);
+        logger.info("Fetched data from {} to {}.",
+                globalFrom.format(ISO_OFFSET_DATE_TIME), globalTo.format(ISO_OFFSET_DATE_TIME));
+
+        if (totalFetched == 0 && config.getEndDate() != null) {
+            throw new ModuleInterruptedException(format(
+                    "Fetched all records from %s to %s. The connector is going to end.",
+                    config.getStartDate().format(ISO_DATE_TIME), config.getEndDate().format(ISO_DATE_TIME)
+            ));
+        }
+
+        logger.debug("Creating a new instance of Azure model.");
+        AzureModel model = new AzureModel(usedSensors, globalFrom, globalTo);
+        logger.debug("Model was created successfully and sending it forward.");
+
+        return model;
+    }
+}

+ 157 - 101
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/AzureProxySession.java

@@ -1,102 +1,158 @@
-package cz.senslog.connector.fetch.azure;
-
-import com.google.gson.reflect.TypeToken;
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import cz.senslog.common.util.Tuple;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.api.FetchProxySession;
-import cz.senslog.connector.model.azure.AzureModel;
-import cz.senslog.connector.model.config.HostConfig;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.lang.reflect.Type;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZonedDateTime;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static cz.senslog.connector.fetch.azure.AzureSession.emptySession;
-import static cz.senslog.connector.model.converter.AzureUnitConverter.convertIdToAzureId;
-import static java.time.ZonedDateTime.parse;
-import static java.time.format.DateTimeFormatter.ofPattern;
-
-class AzureProxySession extends FetchProxySession<AzureSession, AzureModel> {
-
-    private static Logger logger = LogManager.getLogger(AzureProxySession.class);
-
-    protected static class ObservationInfo { OffsetDateTime timeStamp; Long unitId; }
-
-    private final HttpClient httpClient;
-    private final AzureSessionProxyConfig config;
-
-    public AzureProxySession(
-            ConnectorFetcher<AzureSession, AzureModel> instance,
-            AzureSessionProxyConfig config,
-            HttpClient httpClient
-    ) {
-        super(instance);
-        this.config = config;
-        this.httpClient = httpClient;
-    }
-
-    @Override
-    protected AzureSession preProcessing(Optional<AzureSession> previousSession) {
-
-        HostConfig host = config.getLastObservationHost();
-        logger.info("Getting last observations from {}.", host.getDomain());
-
-        HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
-                        .addParam("Operation", "GetLastObservations")
-                        .addParam("group", config.getGroup())
-                        .addParam("user", config.getUser())
-                        .build())
-                .build();
-        logger.info("Creating a http request to {}.", request);
-
-        HttpResponse response = httpClient.send(request);
-        logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
-
-        if (response.isError()) {
-            logger.error("Can not get data from the server {}. Error {} {}",
-                    host.getDomain(), response.getStatus(), response.getBody());
-            return emptySession();
-        }
-
-        logger.debug("Parsing body of the response to the list of class {}.", ObservationInfo.class);
-        Type lastObsType = new TypeToken<Collection<ObservationInfo>>() {}.getType();
-        List<ObservationInfo> lastObservations = jsonToObject(response.getBody(), lastObsType,
-                Tuple.of(OffsetDateTime.class, el -> OffsetDateTime.parse(el, ofPattern("yyyy-MM-dd HH:mm:ssX")))
-        );
-
-        AzureSession session = previousSession.filter(AzureSession::isActive)
-                .orElse(new AzureSession(true));
-        logger.debug("Created a new session of {}.", AzureSession.class);
-
-        logger.debug("Filling the new session of last timestamps from observations per each station.");
-        for (ObservationInfo info : lastObservations) {
-            String azureId = convertIdToAzureId(info.unitId);
-            if (azureId != null) {
-                session.addLastUnitTime(azureId, info.timeStamp);
-            }
-        }
-
-        logger.info("Loaded and successfully parsed {} last observations from stations.", lastObservations.size());
-        return session;
-    }
-
-    @Override
-    protected void postProcessing(Optional<AzureModel> returnedModel, Optional<AzureSession> currentSession) {
-        if (currentSession.isPresent() && currentSession.get().isActive()) {
-            AzureSession session = currentSession.get();
-            // TODO persistence the current session
-        }
-    }
+package cz.senslog.connector.fetch.azure;
+
+import com.google.gson.reflect.TypeToken;
+import cz.senslog.common.http.*;
+import cz.senslog.common.util.Tuple;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.api.FetchProxySession;
+import cz.senslog.connector.model.azure.AzureModel;
+import cz.senslog.connector.model.config.HostConfig;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.OffsetDateTime;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static cz.senslog.connector.fetch.azure.AzureSession.emptySession;
+import static cz.senslog.connector.model.converter.AzureUnitConverter.convertUnitIdToEui;
+import static java.time.format.DateTimeFormatter.ofPattern;
+import static java.util.Collections.emptyList;
+
+class AzureProxySession extends FetchProxySession<AzureSession, AzureModel> {
+
+    private static final Logger logger = LogManager.getLogger(AzureProxySession.class);
+
+    protected static class ObservationInfo { OffsetDateTime timeStamp; Long unitId; }
+
+    private final HttpClient httpClient;
+    private final AzureSessionProxyConfig config;
+    private Tuple<Boolean, HttpCookie> authCookie;
+
+
+    public AzureProxySession(
+            ConnectorFetcher<AzureSession, AzureModel> instance,
+            AzureSessionProxyConfig config,
+            HttpClient httpClient
+    ) {
+        super(instance);
+        this.config = config;
+        this.httpClient = httpClient;
+        this.authCookie = Tuple.of(false, null);
+    }
+
+    private synchronized HttpCookie getAuthCookie() {
+        if (authCookie.getItem1()) {
+            return authCookie.getItem2();
+        }
+
+        final String baseUrl = config.getLastObservationHost().getDomain();
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(baseUrl, "/ControllerServlet")
+                        .addParam("username", "watchdog")
+                        .addParam("password", "HAFhaf")
+                        .build()
+                ).build();
+        logger.info("Getting new auth cookie from the server: {}.", baseUrl);
+
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received new auth token with the status '{}' from the server {}.", response.getStatus(), baseUrl);
+
+        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;
+        }
+
+        try {
+            URI baseUri = new URI(baseUrl);
+            String sessionId = (String) jsonResponse.get("sessionid");
+            HttpCookie cookie = new HttpCookie("JSESSIONID", sessionId, baseUri.getHost(), baseUri.getHost());
+            authCookie = Tuple.of(true, cookie);
+            return cookie;
+        } catch (URISyntaxException e) {
+            logger.catching(e);
+            HttpCookie cookie = HttpCookie.empty();
+            authCookie = Tuple.of(false, cookie);
+            return cookie;
+        }
+    }
+
+    @Override
+    protected AzureSession preProcessing(Optional<AzureSession> previousSession) {
+
+//        HttpCookie authCookie = getAuthCookie();
+//        if (!authCookie.isSecure()) {
+//            logger.warn("Auth cookie is not valid to be used.");
+//            return emptySession();
+//        }
+
+        HostConfig host = config.getLastObservationHost();
+        logger.info("Getting last observations from {}.", host.getDomain());
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+            //    .addCookie(authCookie)
+                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
+                        .addParam("Operation", "GetLastObservations")
+                        .addParam("group", config.getGroup())
+                        .addParam("user", config.getUser())
+                        .build())
+                .build();
+        logger.info("Creating a http request to {}.", request);
+
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
+
+        if (response.isError()) {
+            logger.error("Can not get data from the server {}. Error {} {}",
+                    host.getDomain(), response.getStatus(), response.getBody());
+            return emptySession();
+        }
+
+        logger.debug("Parsing body of the response to the list of class {}.", ObservationInfo.class);
+        Type lastObsType = new TypeToken<Collection<ObservationInfo>>() {}.getType();
+        List<ObservationInfo> lastObservations = jsonToObject(response.getBody(), lastObsType,
+                Tuple.of(OffsetDateTime.class, el -> OffsetDateTime.parse(el, ofPattern("yyyy-MM-dd HH:mm:ssX")))
+        );
+
+        AzureSession session = previousSession.filter(AzureSession::isActive)
+                .orElse(new AzureSession(true));
+        logger.debug("Created a new session of {}.", AzureSession.class);
+
+        logger.debug("Filling the new session of last timestamps from observations per each station.");
+        for (ObservationInfo info : lastObservations) {
+            String azureId = convertUnitIdToEui(info.unitId);
+            if (azureId != null) {
+                session.addLastUnitTime(azureId, info.timeStamp);
+            }
+        }
+
+        logger.info("Loaded and successfully parsed {} last observations from stations.", lastObservations.size());
+        return session;
+    }
+
+    @Override
+    protected void postProcessing(Optional<AzureModel> returnedModel, Optional<AzureSession> currentSession) {
+        if (currentSession.isPresent() && currentSession.get().isActive()) {
+            AzureSession session = currentSession.get();
+            // TODO persistence the current session
+        }
+    }
 }

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

@@ -1,62 +1,62 @@
-package cz.senslog.connector.fetch.azure;
-
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.api.ExecutableFetcher;
-import cz.senslog.connector.fetch.azure.auth.AzureAuthConfig;
-import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
-import cz.senslog.connector.model.azure.AzureModel;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import static cz.senslog.connector.fetch.azure.auth.AuthenticationService.newAuthService;
-import static cz.senslog.common.http.HttpClient.newHttpClient;
-
-/**
- * The class {@code ConnectorFetchAzureProvider} represents a concrete implementation of {@link ConnectorFetchProvider}.
- * Contains basic functionality to configure implementation of {@link ConnectorFetcher} from the default configuration.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class ConnectorFetchAzureProvider implements ConnectorFetchProvider {
-
-    private static Logger logger = LogManager.getLogger(ConnectorFetchAzureProvider.class);
-
-    @Override
-    public ExecutableFetcher<AzureModel> createExecutableFetcher(DefaultConfig defaultConfig) {
-        logger.info("Initialization a new fetch provider {}.", ConnectorFetchAzureProvider.class);
-
-        logger.debug("Creating a new configuration.");
-        AzureConfig config = new AzureConfig(defaultConfig);
-        logger.info("Configuration for {} was created successfully.", ConnectorFetcher.class);
-
-        logger.debug("Getting a configuration for authentication.");
-        AzureAuthConfig authConfig = config.getAuthentication();
-
-        logger.info("Initialization a new Azure authentication service.");
-        AuthenticationService authService = newAuthService(authConfig, newHttpClient());
-
-        logger.debug("Creating a new instance of {}.", AzureFetcher.class);
-        AzureFetcher fetcher = new AzureFetcher(config, authService, newHttpClient());
-        logger.info("Fetcher for {} was created successfully.", AzureFetcher.class);
-
-        logger.debug("Getting a configuration for proxy session.");
-        AzureSessionProxyConfig proxyConfig = config.getSessionProxy();
-
-        ExecutableFetcher<AzureModel> executor;
-        if (proxyConfig != null) {
-            logger.debug("Creating a new instance of {}.", AzureProxySession.class);
-            AzureProxySession proxySession = new AzureProxySession(fetcher, proxyConfig, newHttpClient());
-            logger.info("Fetcher session for {} was created successfully.", AzureProxySession.class);
-            executor = ExecutableFetcher.createWithProxySession(proxySession);
-        } else {
-            executor = ExecutableFetcher.create(fetcher);
-        }
-        logger.info("Fetcher executor for {} was created successfully.", AzureFetcher.class);
-
-        return executor;
-    }
-}
+package cz.senslog.connector.fetch.azure;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.fetch.azure.auth.AzureAuthConfig;
+import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
+import cz.senslog.connector.model.azure.AzureModel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static cz.senslog.connector.fetch.azure.auth.AuthenticationService.newAuthService;
+import static cz.senslog.common.http.HttpClient.newHttpClient;
+
+/**
+ * The class {@code ConnectorFetchAzureProvider} represents a concrete implementation of {@link ConnectorFetchProvider}.
+ * Contains basic functionality to configure implementation of {@link ConnectorFetcher} from the default configuration.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class ConnectorFetchAzureProvider implements ConnectorFetchProvider {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorFetchAzureProvider.class);
+
+    @Override
+    public ExecutableFetcher<AzureModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new fetch provider {}.", ConnectorFetchAzureProvider.class);
+
+        logger.debug("Creating a new configuration.");
+        AzureConfig config = new AzureConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", ConnectorFetcher.class);
+
+        logger.debug("Getting a configuration for authentication.");
+        AzureAuthConfig authConfig = config.getAuthentication();
+
+        logger.info("Initialization a new Azure authentication service.");
+        AuthenticationService authService = newAuthService(authConfig, newHttpClient());
+
+        logger.debug("Creating a new instance of {}.", AzureFetcher.class);
+        AzureFetcher fetcher = new AzureFetcher(config, authService, newHttpClient());
+        logger.info("Fetcher for {} was created successfully.", AzureFetcher.class);
+
+        logger.debug("Getting a configuration for proxy session.");
+        AzureSessionProxyConfig proxyConfig = config.getSessionProxy();
+
+        ExecutableFetcher<AzureModel> executor;
+        if (proxyConfig != null) {
+            logger.debug("Creating a new instance of {}.", AzureProxySession.class);
+            AzureProxySession proxySession = new AzureProxySession(fetcher, proxyConfig, newHttpClient());
+            logger.info("Fetcher session for {} was created successfully.", AzureProxySession.class);
+            executor = ExecutableFetcher.createWithProxySession(proxySession);
+        } else {
+            executor = ExecutableFetcher.create(fetcher);
+        }
+        logger.info("Fetcher executor for {} was created successfully.", AzureFetcher.class);
+
+        return executor;
+    }
+}

+ 167 - 167
connector-fetch-azure/src/main/java/cz/senslog/connector/fetch/azure/auth/AuthenticationService.java

@@ -1,167 +1,167 @@
-package cz.senslog.connector.fetch.azure.auth;
-
-import com.google.gson.reflect.TypeToken;
-import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.common.exception.SyntaxException;
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.lang.reflect.Type;
-import java.time.format.DateTimeParseException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static cz.senslog.common.http.HttpCode.UNAUTHORIZED;
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static cz.senslog.common.util.StringUtils.isNotBlank;
-import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
-
-/**
- * The class {@code AzureAuthenticationService} represents a service which wraps
- * all functionality around authentication. The main method is {@see AzureAuthenticationService#getAccessToken}
- * and provides a valid access token.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class AuthenticationService {
-
-    private static Logger logger = LogManager.getLogger(AuthenticationService.class);
-
-
-    /** Default period value if token can not be refreshed  */
-    private static final Integer DEFAULT_REFRESH_PERIOD = 5000;
-
-
-    /** Configuration */
-    private final AzureAuthConfig authConfig;
-
-    /** Http client */
-    private final HttpClient httpClient;
-
-
-    /** Received authorization information */
-    private AzureAuthorizationInfo authorizationInfo;
-
-    /**
-     * Static factory method to create a new instance of service.
-     * @param authConfig - authentication configuration.
-     * @param httpClient - http client.
-     * @return new instance of {@code AzureAuthenticationService}.
-     */
-    public static AuthenticationService newAuthService(AzureAuthConfig authConfig, HttpClient httpClient) {
-        return new AuthenticationService(authConfig, httpClient);
-    }
-
-    /**
-     * Constructor of the class sets all attributes.
-     * @param authConfig - authentication configuration.
-     * @param httpClient - http client.
-     */
-    private AuthenticationService(AzureAuthConfig authConfig, HttpClient httpClient) {
-        this.authConfig = authConfig;
-        this.httpClient = httpClient;
-    }
-
-    /**
-     * Creates a http request to get actual authorization information.
-     * Request contains credentials (email and password) in header
-     * and are gotten list of access information in json format.
-     * Each record contains token and expire time. The first
-     * non expired token si chosen to use.
-     * @return true or false if token is get successfully and valid.
-     */
-    private boolean refreshToken() {
-
-        HostConfig host = authConfig.getHost();
-        logger.info("Creating a http request to the Azure service at {}.", host);
-        HttpRequest request = HttpRequest.newBuilder()
-                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath()).build())
-                .header("Email", authConfig.getUsername())
-                .header("Password", authConfig.getPassword())
-                .POST().build();
-
-        logger.info("Sending the http request.");
-        HttpResponse response = httpClient.send(request);
-        logger.info("Received a response with a status: {}.", response.getStatus());
-
-        if (response.isError()) {
-            if (response.getStatus() == UNAUTHORIZED) {
-                logger.debug("Parsing response body to a Map.");
-                Map jsonMap = jsonToObject(response.getBody(), Map.class);
-                String message = (String) jsonMap.get("Message");
-                logger.error("Unauthorized {}: {}.", response.getStatus(), message);
-            } else {
-                logger.error("Error with the code {}: {}.", response.getStatus(), response.getBody());
-            }
-            return false;
-        }
-
-        try {
-            logger.debug("Parsing the response body.");
-            Type azureInfoListType = new TypeToken<Collection<AzureAuthorizationInfo>>(){}.getType();
-            List<AzureAuthorizationInfo> azureInfos = jsonToObject(response.getBody(), azureInfoListType);
-
-            if (azureInfos.isEmpty()) {
-                logger.error("Response does not contain authorization information.");
-                return false;
-            }
-
-            logger.info("Response body was parsed successfully and getting authorization information.");
-            for (AzureAuthorizationInfo azureInfo : azureInfos) {
-                if (azureInfo.isAuthorized()) {
-                    authorizationInfo = azureInfo;
-                    return true;
-                }
-            }
-
-            logger.warn("Received authorization information contains only expired tokens.");
-            return false;
-        } catch (SyntaxException | DateTimeParseException e) {
-            logger.catching(e);
-            return false;
-        }
-    }
-
-    /**
-     * Main public method of this service which provides a valid access token.
-     * Content of the method contains a loop where periodically is get access token.
-     * If token is expired or does not exist, it is refreshed. If refresh process fails,
-     * the service goes to sleep and tries it later.
-     * @return string valid access token.
-     */
-    public String getAccessToken() {
-        logger.info("Getting the actual authorization token to Azure.");
-
-        while (true) {
-            if (authorizationInfo != null && authorizationInfo.isAuthorized()) {
-
-                String token = authorizationInfo.getAccessToken();
-
-                if (isNotBlank(token)) {
-                    logger.debug("The access token is valid and will expired at {}.",
-                            authorizationInfo.getExpires().format(ISO_DATE_TIME));
-                    return token;
-                } else {
-                    logger.error("The token '{}' can not be used as an access token.", token);
-                }
-            }
-
-            if (!refreshToken()) {
-                try {
-                    int sleepPeriod = authConfig.getRefreshPeriodIfFail().orElse(DEFAULT_REFRESH_PERIOD);
-                    logger.warn("Can not get valid access token at this moment. Thread is going to sleep for {} ms.", sleepPeriod);
-                    Thread.sleep(sleepPeriod);
-                } catch (InterruptedException e) {
-                    logger.catching(e);
-                }
-            }
-        }
-    }
-}
+package cz.senslog.connector.fetch.azure.auth;
+
+import com.google.gson.reflect.TypeToken;
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.common.exception.SyntaxException;
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.time.format.DateTimeParseException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cz.senslog.common.http.HttpCode.UNAUTHORIZED;
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static cz.senslog.common.util.StringUtils.isNotBlank;
+import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
+
+/**
+ * The class {@code AzureAuthenticationService} represents a service which wraps
+ * all functionality around authentication. The main method is {@see AzureAuthenticationService#getAccessToken}
+ * and provides a valid access token.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class AuthenticationService {
+
+    private static final Logger logger = LogManager.getLogger(AuthenticationService.class);
+
+
+    /** Default period value if token can not be refreshed  */
+    private static final Integer DEFAULT_REFRESH_PERIOD = 5000;
+
+
+    /** Configuration */
+    private final AzureAuthConfig authConfig;
+
+    /** Http client */
+    private final HttpClient httpClient;
+
+
+    /** Received authorization information */
+    private AzureAuthorizationInfo authorizationInfo;
+
+    /**
+     * Static factory method to create a new instance of service.
+     * @param authConfig - authentication configuration.
+     * @param httpClient - http client.
+     * @return new instance of {@code AzureAuthenticationService}.
+     */
+    public static AuthenticationService newAuthService(AzureAuthConfig authConfig, HttpClient httpClient) {
+        return new AuthenticationService(authConfig, httpClient);
+    }
+
+    /**
+     * Constructor of the class sets all attributes.
+     * @param authConfig - authentication configuration.
+     * @param httpClient - http client.
+     */
+    private AuthenticationService(AzureAuthConfig authConfig, HttpClient httpClient) {
+        this.authConfig = authConfig;
+        this.httpClient = httpClient;
+    }
+
+    /**
+     * Creates a http request to get actual authorization information.
+     * Request contains credentials (email and password) in header
+     * and are gotten list of access information in json format.
+     * Each record contains token and expire time. The first
+     * non expired token si chosen to use.
+     * @return true or false if token is get successfully and valid.
+     */
+    private boolean refreshToken() {
+
+        HostConfig host = authConfig.getHost();
+        logger.info("Creating a http request to the Azure service at {}.", host);
+        HttpRequest request = HttpRequest.newBuilder()
+                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath()).build())
+                .header("Email", authConfig.getUsername())
+                .header("Password", authConfig.getPassword())
+                .POST().build();
+
+        logger.info("Sending the http request.");
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received a response with a status: {}.", response.getStatus());
+
+        if (response.isError()) {
+            if (response.getStatus() == UNAUTHORIZED) {
+                logger.debug("Parsing response body to a Map.");
+                Map<?,?> jsonMap = jsonToObject(response.getBody(), Map.class);
+                String message = (String) jsonMap.get("Message");
+                logger.error("Unauthorized {}: {}.", response.getStatus(), message);
+            } else {
+                logger.error("Error with the code {}: {}.", response.getStatus(), response.getBody());
+            }
+            return false;
+        }
+
+        try {
+            logger.debug("Parsing the response body.");
+            Type azureInfoListType = new TypeToken<Collection<AzureAuthorizationInfo>>(){}.getType();
+            List<AzureAuthorizationInfo> azureInfos = jsonToObject(response.getBody(), azureInfoListType);
+
+            if (azureInfos.isEmpty()) {
+                logger.error("Response does not contain authorization information.");
+                return false;
+            }
+
+            logger.info("Response body was parsed successfully and getting authorization information.");
+            for (AzureAuthorizationInfo azureInfo : azureInfos) {
+                if (azureInfo.isAuthorized()) {
+                    authorizationInfo = azureInfo;
+                    return true;
+                }
+            }
+
+            logger.warn("Received authorization information contains only expired tokens.");
+            return false;
+        } catch (SyntaxException | DateTimeParseException e) {
+            logger.catching(e);
+            return false;
+        }
+    }
+
+    /**
+     * Main public method of this service which provides a valid access token.
+     * Content of the method contains a loop where periodically is get access token.
+     * If token is expired or does not exist, it is refreshed. If refresh process fails,
+     * the service goes to sleep and tries it later.
+     * @return string valid access token.
+     */
+    public String getAccessToken() {
+        logger.info("Getting the actual authorization token to Azure.");
+
+        while (true) {
+            if (authorizationInfo != null && authorizationInfo.isAuthorized()) {
+
+                String token = authorizationInfo.getAccessToken();
+
+                if (isNotBlank(token)) {
+                    logger.debug("The access token is valid and will expired at {}.",
+                            authorizationInfo.getExpires().format(ISO_DATE_TIME));
+                    return token;
+                } else {
+                    logger.error("The token '{}' can not be used as an access token.", token);
+                }
+            }
+
+            if (!refreshToken()) {
+                try {
+                    int sleepPeriod = authConfig.getRefreshPeriodIfFail().orElse(DEFAULT_REFRESH_PERIOD);
+                    logger.warn("Can not get valid access token at this moment. Thread is going to sleep for {} ms.", sleepPeriod);
+                    Thread.sleep(sleepPeriod);
+                } catch (InterruptedException e) {
+                    logger.catching(e);
+                }
+            }
+        }
+    }
+}

+ 3 - 4
connector-fetch-azure/src/main/resources/schema/sensorInfoSchema.json

@@ -33,8 +33,7 @@
       "acronym": {
         "$id": "#/items/properties/acronym",
         "type": "string",
-        "title": "Acronym",
-        "pattern": "^[A-Z]+-[0-9]+"
+        "title": "Acronym"
       },
       "name": {
         "$id": "#/items/properties/name",
@@ -70,14 +69,14 @@
       },
       "sensorDataId": {
         "$id": "#/items/properties/sensorDataId",
-        "type": "integer",
+        "type": ["integer", "null"],
         "title": "Identifier of the sensor"
       },
       "sensorDataTime": {
         "$id": "#/items/properties/sensorDataTime",
         "type": "string",
         "title": "Sensor Data time",
-        "pattern": "^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})T(?<hour>[0-9]{2}):(?<minute>[0-9]{2}):(?<second>[0-9]{2})(.(?<millisecond>[0-9]+))?"
+        "pattern": "^(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})T(?<hour>[0-9]{2}):(?<minute>[0-9]{2}):(?<second>[0-9]{2})\\+(?<offset>[0-9]{2}:[0-9]{2})"
       },
       "created": {
         "$id": "#/items/properties/created",

+ 109 - 100
connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/AzureConfigTest.java

@@ -1,101 +1,110 @@
-package cz.senslog.connector.fetch.azure;
-
-import cz.senslog.connector.model.config.DefaultConfig;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-import java.util.HashMap;
-
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-class AzureConfigTest {
-
-    @Test
-    void newConfig_Full_True() {
-
-        DefaultConfig defaultConfig = new DefaultConfig("azure", null);
-        defaultConfig.setProperty("startDate", LocalDateTime.MIN);
-        defaultConfig.setProperty("limitPerSensor", 42);
-        defaultConfig.setProperty("sensorInfoHost", new HashMap<String, Object>(){{
-            put("domain", "http://test.com");
-            put("path", "test");
-        }});
-        defaultConfig.setProperty("sensorDataHost", new HashMap<String, Object>(){{
-            put("domain", "http://test.com");
-            put("path", "test");
-        }});
-        defaultConfig.setProperty("authentication", new HashMap<String, Object>(){{
-            put("username", "username");
-            put("password", "password");
-            put("refreshPeriodIfFail", 100);
-            put("host", new HashMap<String, Object>(){{
-                put("domain", "http://test.com");
-                put("path", "test");
-            }});
-        }});
-
-        AzureConfig config = new AzureConfig(defaultConfig);
-
-        assertEquals(42, config.getLimitPerSensor());
-        assertEquals(LocalDateTime.MIN, config.getStartDate());
-        assertNotNull(config.getSensorInfoHost());
-        assertEquals("http://test.com", config.getSensorInfoHost().getDomain());
-        assertEquals("test", config.getSensorInfoHost().getPath());
-        assertNotNull(config.getSensorDataHost());
-        assertEquals("http://test.com", config.getSensorDataHost().getDomain());
-        assertEquals("test", config.getSensorDataHost().getPath());
-        assertNotNull(config.getAuthentication());
-        assertEquals("username", config.getAuthentication().getUsername());
-        assertEquals("password", config.getAuthentication().getPassword());
-        assertEquals(100, config.getAuthentication().getRefreshPeriodIfFail().orElse(0));
-        assertNotNull(config.getAuthentication().getHost());
-        assertEquals("http://test.com", config.getAuthentication().getHost().getDomain());
-        assertEquals("test", config.getAuthentication().getHost().getPath());
-    }
-
-    @Test
-    void toString_ConvertJson_True() {
-
-        DefaultConfig defaultConfig = new DefaultConfig("azure",null);
-        defaultConfig.setProperty("startDate", LocalDateTime.MIN);
-        defaultConfig.setProperty("limitPerSensor", 42);
-        defaultConfig.setProperty("sensorInfoHost", new HashMap<String, Object>(){{
-            put("domain", "http://test.com");
-            put("path", "test");
-        }});
-        defaultConfig.setProperty("sensorDataHost", new HashMap<String, Object>(){{
-            put("domain", "http://test.com");
-            put("path", "test");
-        }});
-        defaultConfig.setProperty("authentication", new HashMap<String, Object>(){{
-            put("username", "username");
-            put("password", "password");
-            put("refreshPeriodIfFail", 100);
-            put("host", new HashMap<String, Object>(){{
-                put("domain", "http://test.com");
-                put("path", "test");
-            }});
-        }});
-
-        String jsonConfig = new AzureConfig(defaultConfig).toString();
-        AzureConfig config = jsonToObject(jsonConfig, AzureConfig.class);
-
-        assertEquals(42, config.getLimitPerSensor());
-        assertEquals(LocalDateTime.MIN, config.getStartDate());
-        assertNotNull(config.getSensorInfoHost());
-        assertEquals("http://test.com", config.getSensorInfoHost().getDomain());
-        assertEquals("test", config.getSensorInfoHost().getPath());
-        assertNotNull(config.getSensorDataHost());
-        assertEquals("http://test.com", config.getSensorDataHost().getDomain());
-        assertEquals("test", config.getSensorDataHost().getPath());
-        assertNotNull(config.getAuthentication());
-        assertEquals("username", config.getAuthentication().getUsername());
-        assertEquals("password", config.getAuthentication().getPassword());
-        assertEquals(100, config.getAuthentication().getRefreshPeriodIfFail().orElse(0));
-        assertNotNull(config.getAuthentication().getHost());
-        assertEquals("http://test.com", config.getAuthentication().getHost().getDomain());
-        assertEquals("test", config.getAuthentication().getHost().getPath());
-    }
+package cz.senslog.connector.fetch.azure;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static org.junit.jupiter.api.Assertions.*;
+
+class AzureConfigTest {
+
+    @Test
+    void newConfig_Full_True() {
+
+        DefaultConfig defaultConfig = new DefaultConfig("azure", null);
+        defaultConfig.setProperty("startDate", LocalDateTime.MIN);
+        defaultConfig.setProperty("limitPerSensor", 42);
+        defaultConfig.setProperty("sensorInfoHost", new HashMap<String, Object>(){{
+            put("domain", "http://test.com");
+            put("path", "test");
+        }});
+        defaultConfig.setProperty("sensorDataHost", new HashMap<String, Object>(){{
+            put("domain", "http://test.com");
+            put("path", "test");
+        }});
+        defaultConfig.setProperty("authentication", new HashMap<String, Object>(){{
+            put("username", "username");
+            put("password", "password");
+            put("refreshPeriodIfFail", 100);
+            put("host", new HashMap<String, Object>(){{
+                put("domain", "http://test.com");
+                put("path", "test");
+            }});
+        }});
+
+        defaultConfig.setProperty("allowedStation", new HashMap<String, Object>(){{
+            put("unit_1", null);
+        }});
+
+        AzureConfig config = new AzureConfig(defaultConfig);
+
+        assertEquals(42, config.getLimitPerSensor());
+        assertEquals(LocalDateTime.MIN, config.getStartDate());
+        assertNotNull(config.getSensorInfoHost());
+        assertEquals("http://test.com", config.getSensorInfoHost().getDomain());
+        assertEquals("test", config.getSensorInfoHost().getPath());
+        assertNotNull(config.getSensorDataHost());
+        assertEquals("http://test.com", config.getSensorDataHost().getDomain());
+        assertEquals("test", config.getSensorDataHost().getPath());
+        assertNotNull(config.getAuthentication());
+        assertEquals("username", config.getAuthentication().getUsername());
+        assertEquals("password", config.getAuthentication().getPassword());
+        assertEquals(100, config.getAuthentication().getRefreshPeriodIfFail().orElse(0));
+        assertNotNull(config.getAuthentication().getHost());
+        assertEquals("http://test.com", config.getAuthentication().getHost().getDomain());
+        assertEquals("test", config.getAuthentication().getHost().getPath());
+        assertTrue(config.getAllowedStation().isAllowed("unit_1"));
+    }
+
+    @Test
+    void toString_ConvertJson_True() {
+
+        DefaultConfig defaultConfig = new DefaultConfig("azure",null);
+        defaultConfig.setProperty("startDate", LocalDateTime.MIN);
+        defaultConfig.setProperty("limitPerSensor", 42);
+        defaultConfig.setProperty("sensorInfoHost", new HashMap<String, Object>(){{
+            put("domain", "http://test.com");
+            put("path", "test");
+        }});
+        defaultConfig.setProperty("sensorDataHost", new HashMap<String, Object>(){{
+            put("domain", "http://test.com");
+            put("path", "test");
+        }});
+        defaultConfig.setProperty("authentication", new HashMap<String, Object>(){{
+            put("username", "username");
+            put("password", "password");
+            put("refreshPeriodIfFail", 100);
+            put("host", new HashMap<String, Object>(){{
+                put("domain", "http://test.com");
+                put("path", "test");
+            }});
+        }});
+
+        defaultConfig.setProperty("allowedStation", new HashMap<String, Object>(){{
+            put("unit_1", null);
+        }});
+
+        String jsonConfig = new AzureConfig(defaultConfig).toString();
+        AzureConfig config = jsonToObject(jsonConfig, AzureConfig.class);
+
+        assertEquals(42, config.getLimitPerSensor());
+        assertEquals(LocalDateTime.MIN, config.getStartDate());
+        assertNotNull(config.getSensorInfoHost());
+        assertEquals("http://test.com", config.getSensorInfoHost().getDomain());
+        assertEquals("test", config.getSensorInfoHost().getPath());
+        assertNotNull(config.getSensorDataHost());
+        assertEquals("http://test.com", config.getSensorDataHost().getDomain());
+        assertEquals("test", config.getSensorDataHost().getPath());
+        assertNotNull(config.getAuthentication());
+        assertEquals("username", config.getAuthentication().getUsername());
+        assertEquals("password", config.getAuthentication().getPassword());
+        assertEquals(100, config.getAuthentication().getRefreshPeriodIfFail().orElse(0));
+        assertNotNull(config.getAuthentication().getHost());
+        assertEquals("http://test.com", config.getAuthentication().getHost().getDomain());
+        assertEquals("test", config.getAuthentication().getHost().getPath());
+        assertTrue(config.getAllowedStation().isAllowed("unit_1"));
+    }
 }

+ 334 - 312
connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/AzureFetcherTest.java

@@ -1,313 +1,335 @@
-
-package cz.senslog.connector.fetch.azure;
-
-
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpCode;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
-import cz.senslog.connector.model.azure.AzureModel;
-import cz.senslog.connector.model.azure.SensorInfo;
-import cz.senslog.connector.model.config.HostConfig;
-import org.junit.jupiter.api.Test;
-import org.mockito.stubbing.Answer;
-
-import java.time.LocalDateTime;
-import java.util.*;
-
-import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
-import static cz.senslog.common.json.BasicJson.objectToJson;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-class AzureFetcherTest {
-
-    @Test
-    void fetch_GetSensorsAndData_OneSensorOneData() throws Exception {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.equals("/sensorInfo")) {
-                SensorInfo sensorInfo = new SensorInfo();
-                sensorInfo.setEui("1234");
-                sensorInfo.setStatus(1);
-                List<SensorInfo> bodyList = new ArrayList<>();
-                bodyList.add(sensorInfo);
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson(bodyList)).build();
-            } else if (path.equals("/sensorData")) {
-                Map<String, Object> dataObj = new HashMap<>();
-                dataObj.put("temperature", 25.5F);
-                dataObj.put("time", "1970-01-01T00:00:00+00:00");
-                List<Object> data = new ArrayList<>();
-                data.add(dataObj);
-                Map<String, Object> sensorDataJson = new HashMap<>();
-                sensorDataJson.put("eui", "1234");
-                sensorDataJson.put("name", "name");
-                sensorDataJson.put("data", data);
-
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson(sensorDataJson)).build();
-            }
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-
-        });
-
-        AuthenticationService authService = mock(AuthenticationService.class);
-        when(authService.getAccessToken()).thenReturn("#12345");
-
-        AzureConfig config = mock(AzureConfig.class);
-        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
-        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
-        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
-
-        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
-
-        fetcher.init();
-
-        AzureModel model = fetcher.fetch(Optional.empty());
-
-        assertNotNull(model.getSensors());
-        assertEquals(1, model.getSensors().size());
-
-        SensorInfo sensorInfo = model.getSensors().get(0);
-        assertEquals("1234", sensorInfo.getEui());
-        assertEquals(1, sensorInfo.getData().size());
-        assertEquals(25.5F, sensorInfo.getData().get(0).getTemperature());
-    }
-
-    @Test
-    void fetch_EmptySensors_EmptySensors() {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.equals("/sensorInfo")) {
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson(new ArrayList<>())).build();
-            }
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-
-        });
-
-        AuthenticationService authService = mock(AuthenticationService.class);
-        when(authService.getAccessToken()).thenReturn("#12345");
-
-        AzureConfig config = mock(AzureConfig.class);
-        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
-        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
-        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
-
-        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
-
-        assertThrows(Exception.class, fetcher::init);
-
-        AzureModel model = fetcher.fetch(Optional.empty());
-
-        assertNotNull(model.getSensors());
-        assertEquals(0, model.getSensors().size());
-    }
-
-    @Test
-    void fetch_ErrorSensorsInfoRequest_EmptySensors() {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.equals("/sensorInfo")) {
-                HttpResponse.newBuilder().status(SERVER_ERROR).build();
-            }
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-
-        });
-
-        AuthenticationService authService = mock(AuthenticationService.class);
-        when(authService.getAccessToken()).thenReturn("#12345");
-
-        AzureConfig config = mock(AzureConfig.class);
-        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
-        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
-        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
-
-        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
-
-        assertThrows(Exception.class, fetcher::init);
-
-        AzureModel model = fetcher.fetch(Optional.empty());
-
-        assertNotNull(model.getSensors());
-        assertEquals(0, model.getSensors().size());
-    }
-
-    @Test
-    void fetch_ErrorSensorDataRequest_OneSensorEmptyData() throws Exception {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.equals("/sensorInfo")) {
-                SensorInfo sensorInfo = new SensorInfo();
-                sensorInfo.setEui("1234");
-                sensorInfo.setStatus(1);
-                List<SensorInfo> bodyList = new ArrayList<>();
-                bodyList.add(sensorInfo);
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson(bodyList)).build();
-            } else if (path.equals("/sensorData")) {
-                return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-            }
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-
-        });
-
-        AuthenticationService authService = mock(AuthenticationService.class);
-        when(authService.getAccessToken()).thenReturn("#12345");
-
-        AzureConfig config = mock(AzureConfig.class);
-        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
-        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
-        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
-
-        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
-
-        fetcher.init();
-
-        AzureModel model = fetcher.fetch(Optional.empty());
-
-        assertNotNull(model.getSensors());
-        assertEquals(1, model.getSensors().size());
-
-        SensorInfo sensorInfo = model.getSensors().get(0);
-        assertEquals("1234", sensorInfo.getEui());
-        assertEquals(0, sensorInfo.getData().size());
-    }
-
-    @Test
-    void fetch_InvalidJsonSensor_EmptySensors() {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.equals("/sensorInfo")) {
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson("invalid_json")).build();
-            }
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-        });
-
-        AuthenticationService authService = mock(AuthenticationService.class);
-        when(authService.getAccessToken()).thenReturn("#12345");
-
-        AzureConfig config = mock(AzureConfig.class);
-        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
-        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
-        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
-
-        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
-
-        assertThrows(Exception.class, fetcher::init);
-
-        AzureModel model = fetcher.fetch(Optional.empty());
-
-        assertNotNull(model.getSensors());
-        assertEquals(0, model.getSensors().size());
-    }
-
-    @Test
-    void fetch_InvalidJsonData_OneSensorEmptyData() throws Exception {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.equals("/sensorInfo")) {
-                SensorInfo sensorInfo = new SensorInfo();
-                sensorInfo.setEui("1234");
-                sensorInfo.setStatus(1);
-                List<SensorInfo> bodyList = new ArrayList<>();
-                bodyList.add(sensorInfo);
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson(bodyList)).build();
-            } else if (path.equals("/sensorData")) {
-                return HttpResponse.newBuilder().status(HttpCode.OK)
-                        .body(objectToJson("invalid_json")).build();
-            }
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-        });
-
-        AuthenticationService authService = mock(AuthenticationService.class);
-        when(authService.getAccessToken()).thenReturn("#12345");
-
-        AzureConfig config = mock(AzureConfig.class);
-        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
-        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
-        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
-
-        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
-
-        fetcher.init();
-
-        AzureModel model = fetcher.fetch(Optional.empty());
-
-        assertNotNull(model.getSensors());
-        assertEquals(1, model.getSensors().size());
-
-        assertEquals(0, model.getSensors().get(0).getData().size());
-    }
-
-    /*
-    @Test
-    void fetch() throws Exception {
-
-        DefaultConfig defConfig = new DefaultConfig("", null);
-
-        defConfig.setProperty("startDate", LocalDateTime.of(2020, 1, 1, 0, 0));
-        defConfig.setProperty("limitPerSensor", 1);
-        Map<String, String> infoHost = new HashMap<>();
-        infoHost.put("domain", "https://iotlorawan.azurewebsites.net"); infoHost.put("path", "api/sensors");
-        defConfig.setProperty("sensorInfoHost", infoHost);
-        Map<String, String> dataHost = new HashMap<>();
-        dataHost.put("domain", "https://iotlorawan.azurewebsites.net"); dataHost.put("path", "api/sensordata");
-        defConfig.setProperty("sensorDataHost", dataHost);
-
-        Map<String, Object> authConfig = new HashMap<>();
-        authConfig.put("username", "netluky@ima.cz");
-        authConfig.put("password", "SensLogIMA1");
-        authConfig.put("refreshPeriodIfFail", 1000);
-        Map<String, String> host = new HashMap<>();
-        host.put("domain", "https://iotlorawan.azurewebsites.net"); host.put("path", "api/accounts/login");
-        authConfig.put("host", host);
-
-        defConfig.setProperty("authentication", authConfig);
-
-        Map<String, Object> sessionProxy = new HashMap<>();
-        sessionProxy.put("lastObservationHost", new HashMap<String, String>() {{
-            put("domain", "http://51.15.45.95:8080/senslog1");
-            put("path", "SensorService");
-        }});
-        sessionProxy.put("user", "vilcini");
-        sessionProxy.put("group", "vilcini");
-        defConfig.setProperty("sessionProxy", sessionProxy);
-
-        AzureConfig config = new AzureConfig(defConfig);
-        AuthenticationService authService = AuthenticationService.newAuthService(config.getAuthentication(), newHttpSSLClient());
-        AzureFetcher fetcher = new AzureFetcher(config, authService, newHttpClient());
-        AzureProxySession proxySession = new AzureProxySession(fetcher, config.getSessionProxy(), newHttpClient());
-
-        ExecutableFetcher<AzureModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
-        executableFetcher.getRawFetcher().init();
-
-        executableFetcher.execute();
-        executableFetcher.execute();
-    }
-    */
+
+package cz.senslog.connector.fetch.azure;
+
+
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpCode;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.connector.fetch.azure.auth.AuthenticationService;
+import cz.senslog.connector.model.azure.AzureModel;
+import cz.senslog.connector.model.azure.SensorInfo;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.connector.model.config.PropertyConfig;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.*;
+
+import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
+import static cz.senslog.common.json.BasicJson.objectToJson;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class AzureFetcherTest {
+
+    private static final String TEST_EUI = "8CF9574000000948";
+
+    @Test
+    void fetch_GetSensorsAndData_OneSensorOneData() throws Exception {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.equals("/sensorInfo")) {
+                SensorInfo sensorInfo = new SensorInfo();
+                sensorInfo.setEui(TEST_EUI);
+                sensorInfo.setStatus(1);
+
+                LocalDateTime date = LocalDateTime.of(1970, 1, 1, 0, 0, 0);
+                sensorInfo.setSensorDataTime(OffsetDateTime.of(date, ZoneOffset.UTC));
+                sensorInfo.setCreated(date);
+
+                List<SensorInfo> bodyList = new ArrayList<>();
+                bodyList.add(sensorInfo);
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson(bodyList)).build();
+            } else if (path.equals("/sensorData")) {
+                Map<String, Object> dataObj = new HashMap<>();
+                dataObj.put("temperature", 25.5F);
+                dataObj.put("time", "1970-01-01T00:00:00+00:00");
+                List<Object> data = new ArrayList<>();
+                data.add(dataObj);
+                Map<String, Object> sensorDataJson = new HashMap<>();
+                sensorDataJson.put("eui", TEST_EUI);
+                sensorDataJson.put("name", "name");
+                sensorDataJson.put("data", data);
+
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson(sensorDataJson)).build();
+            }
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+
+        });
+        DefaultConfig defaultConfig = new DefaultConfig("", null);
+        defaultConfig.setProperty("allowedUnits", new HashMap<String, Object>(){{put(TEST_EUI, null);}});
+
+        AuthenticationService authService = mock(AuthenticationService.class);
+        when(authService.getAccessToken()).thenReturn("#12345");
+
+        AzureConfig config = mock(AzureConfig.class);
+        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
+        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
+        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
+        when(config.getAllowedStation()).thenReturn(new AllowedStation(defaultConfig.getPropertyConfig("allowedUnits")));
+
+        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
+
+        fetcher.init();
+
+        AzureModel model = fetcher.fetch(Optional.empty());
+
+        assertNotNull(model.getSensors());
+        assertEquals(1, model.getSensors().size());
+
+        SensorInfo sensorInfo = model.getSensors().get(0);
+        assertEquals(TEST_EUI, sensorInfo.getEui());
+        assertEquals(1, sensorInfo.getData().size());
+        assertEquals(25.5F, sensorInfo.getData().get(0).getTemperature());
+    }
+
+    @Test
+    void fetch_EmptySensors_EmptySensors() {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.equals("/sensorInfo")) {
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson(new ArrayList<>())).build();
+            }
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+
+        });
+
+        AuthenticationService authService = mock(AuthenticationService.class);
+        when(authService.getAccessToken()).thenReturn("#12345");
+
+        AzureConfig config = mock(AzureConfig.class);
+        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
+        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
+        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
+
+        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
+
+        assertThrows(Exception.class, fetcher::init);
+
+        AzureModel model = fetcher.fetch(Optional.empty());
+
+        assertNotNull(model.getSensors());
+        assertEquals(0, model.getSensors().size());
+    }
+
+    @Test
+    void fetch_ErrorSensorsInfoRequest_EmptySensors() {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.equals("/sensorInfo")) {
+                HttpResponse.newBuilder().status(SERVER_ERROR).build();
+            }
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+
+        });
+
+        AuthenticationService authService = mock(AuthenticationService.class);
+        when(authService.getAccessToken()).thenReturn("#12345");
+
+        AzureConfig config = mock(AzureConfig.class);
+        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
+        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
+        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
+
+        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
+
+        assertThrows(Exception.class, fetcher::init);
+
+        AzureModel model = fetcher.fetch(Optional.empty());
+
+        assertNotNull(model.getSensors());
+        assertEquals(0, model.getSensors().size());
+    }
+
+    @Test
+    void fetch_ErrorSensorDataRequest_OneSensorEmptyData() throws Exception {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.equals("/sensorInfo")) {
+                SensorInfo sensorInfo = new SensorInfo();
+                sensorInfo.setEui(TEST_EUI);
+                sensorInfo.setStatus(1);
+                LocalDateTime date = LocalDateTime.of(1970, 1, 1, 0, 0, 0);
+                sensorInfo.setSensorDataTime(OffsetDateTime.of(date, ZoneOffset.UTC));
+                sensorInfo.setCreated(date);
+                List<SensorInfo> bodyList = new ArrayList<>();
+                bodyList.add(sensorInfo);
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson(bodyList)).build();
+            } else if (path.equals("/sensorData")) {
+                return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+            }
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+
+        });
+
+        DefaultConfig defaultConfig = new DefaultConfig("", null);
+        defaultConfig.setProperty("allowedUnits", new HashMap<String, Object>(){{put(TEST_EUI, null);}});
+
+        AuthenticationService authService = mock(AuthenticationService.class);
+        when(authService.getAccessToken()).thenReturn("#12345");
+
+        AzureConfig config = mock(AzureConfig.class);
+        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
+        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
+        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
+        when(config.getAllowedStation()).thenReturn(new AllowedStation(defaultConfig.getPropertyConfig("allowedUnits")));
+
+        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
+
+        fetcher.init();
+
+        AzureModel model = fetcher.fetch(Optional.empty());
+
+        assertNotNull(model.getSensors());
+        assertEquals(0, model.getSensors().size());
+    }
+
+    @Test
+    void fetch_InvalidJsonSensor_EmptySensors() {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.equals("/sensorInfo")) {
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson("invalid_json")).build();
+            }
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+        });
+
+        AuthenticationService authService = mock(AuthenticationService.class);
+        when(authService.getAccessToken()).thenReturn("#12345");
+
+        AzureConfig config = mock(AzureConfig.class);
+        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
+        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
+        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
+
+        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
+
+        assertThrows(Exception.class, fetcher::init);
+
+        AzureModel model = fetcher.fetch(Optional.empty());
+
+        assertNotNull(model.getSensors());
+        assertEquals(0, model.getSensors().size());
+    }
+
+    @Test
+    void fetch_InvalidJsonData_EmptyData() throws Exception {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.equals("/sensorInfo")) {
+                SensorInfo sensorInfo = new SensorInfo();
+                sensorInfo.setEui(TEST_EUI);
+                sensorInfo.setStatus(1);
+                LocalDateTime date = LocalDateTime.of(1970, 1, 1, 0, 0, 0);
+                sensorInfo.setSensorDataTime(OffsetDateTime.of(date, ZoneOffset.UTC));
+                sensorInfo.setCreated(date);
+                List<SensorInfo> bodyList = new ArrayList<>();
+                bodyList.add(sensorInfo);
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson(bodyList)).build();
+            } else if (path.equals("/sensorData")) {
+                return HttpResponse.newBuilder().status(HttpCode.OK)
+                        .body(objectToJson("invalid_json")).build();
+            }
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+        });
+
+        DefaultConfig defaultConfig = new DefaultConfig("", null);
+        defaultConfig.setProperty("allowedUnits", new HashMap<String, Object>(){{put(TEST_EUI, null);}});
+
+        AuthenticationService authService = mock(AuthenticationService.class);
+        when(authService.getAccessToken()).thenReturn("#12345");
+
+        AzureConfig config = mock(AzureConfig.class);
+        when(config.getStartDate()).thenReturn(LocalDateTime.MIN);
+        when(config.getSensorInfoHost()).thenReturn(new HostConfig("http://test.com", "sensorInfo"));
+        when(config.getSensorDataHost()).thenReturn(new HostConfig("http://test.com", "sensorData"));
+        when(config.getAllowedStation()).thenReturn(new AllowedStation(defaultConfig.getPropertyConfig("allowedUnits")));
+
+        AzureFetcher fetcher = new AzureFetcher(config, authService, httpClient);
+
+        fetcher.init();
+
+        AzureModel model = fetcher.fetch(Optional.empty());
+
+        assertNotNull(model.getSensors());
+        assertEquals(0, model.getSensors().size());
+    }
+
+    /*
+    @Test
+    void fetch() throws Exception {
+
+        DefaultConfig defConfig = new DefaultConfig("", null);
+
+        defConfig.setProperty("startDate", LocalDateTime.of(2020, 1, 1, 0, 0));
+        defConfig.setProperty("limitPerSensor", 1);
+        Map<String, String> infoHost = new HashMap<>();
+        infoHost.put("domain", "https://iotlorawan.azurewebsites.net"); infoHost.put("path", "api/sensors");
+        defConfig.setProperty("sensorInfoHost", infoHost);
+        Map<String, String> dataHost = new HashMap<>();
+        dataHost.put("domain", "https://iotlorawan.azurewebsites.net"); dataHost.put("path", "api/sensordata");
+        defConfig.setProperty("sensorDataHost", dataHost);
+
+        Map<String, Object> authConfig = new HashMap<>();
+        authConfig.put("username", "netluky@ima.cz");
+        authConfig.put("password", "SensLogIMA1");
+        authConfig.put("refreshPeriodIfFail", 1000);
+        Map<String, String> host = new HashMap<>();
+        host.put("domain", "https://iotlorawan.azurewebsites.net"); host.put("path", "api/accounts/login");
+        authConfig.put("host", host);
+
+        defConfig.setProperty("authentication", authConfig);
+
+        Map<String, Object> sessionProxy = new HashMap<>();
+        sessionProxy.put("lastObservationHost", new HashMap<String, String>() {{
+            put("domain", "http://51.15.45.95:8080/senslog1");
+            put("path", "SensorService");
+        }});
+        sessionProxy.put("user", "vilcini");
+        sessionProxy.put("group", "vilcini");
+        defConfig.setProperty("sessionProxy", sessionProxy);
+
+        AzureConfig config = new AzureConfig(defConfig);
+        AuthenticationService authService = AuthenticationService.newAuthService(config.getAuthentication(), newHttpSSLClient());
+        AzureFetcher fetcher = new AzureFetcher(config, authService, newHttpClient());
+        AzureProxySession proxySession = new AzureProxySession(fetcher, config.getSessionProxy(), newHttpClient());
+
+        ExecutableFetcher<AzureModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
+        executableFetcher.getRawFetcher().init();
+
+        executableFetcher.execute();
+        executableFetcher.execute();
+    }
+    */
 }

+ 50 - 46
connector-fetch-azure/src/test/java/cz/senslog/connector/fetch/azure/ConnectorFetchAzureProviderTest.java

@@ -1,47 +1,51 @@
-package cz.senslog.connector.fetch.azure;
-
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
-import cz.senslog.connector.fetch.api.ExecutableFetcher;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-import java.util.HashMap;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-
-class ConnectorFetchAzureProviderTest {
-
-    @Test
-    void createFetcher_NewInstance_True() {
-
-        DefaultConfig defaultConfig = new DefaultConfig("azure", any());
-        defaultConfig.setProperty("startDate", LocalDateTime.MIN);
-        defaultConfig.setProperty("limitPerSensor", 42);
-        defaultConfig.setProperty("sensorInfoHost", new HashMap<String, Object>(){{
-            put("domain", "http://test.com");
-            put("path", "test");
-        }});
-        defaultConfig.setProperty("sensorDataHost", new HashMap<String, Object>(){{
-            put("domain", "http://test.com");
-            put("path", "test");
-        }});
-        defaultConfig.setProperty("authentication", new HashMap<String, Object>(){{
-            put("username", "username");
-            put("password", "password");
-            put("refreshPeriodIfFail", 100);
-            put("host", new HashMap<String, Object>(){{
-                put("domain", "http://test.com");
-                put("path", "test");
-            }});
-        }});
-
-        ConnectorFetchProvider provider = new ConnectorFetchAzureProvider();
-        ExecutableFetcher fetcher = provider.createExecutableFetcher(defaultConfig);
-
-        assertNotNull(fetcher);
-        assertEquals(AzureFetcher.class, fetcher.getRawFetcher().getClass());
-    }
+package cz.senslog.connector.fetch.azure;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+
+class ConnectorFetchAzureProviderTest {
+
+    @Test
+    void createFetcher_NewInstance_True() {
+
+        DefaultConfig defaultConfig = new DefaultConfig("azure", any());
+        defaultConfig.setProperty("startDate", LocalDateTime.MIN);
+        defaultConfig.setProperty("limitPerSensor", 42);
+        defaultConfig.setProperty("sensorInfoHost", new HashMap<String, Object>(){{
+            put("domain", "http://test.com");
+            put("path", "test");
+        }});
+        defaultConfig.setProperty("sensorDataHost", new HashMap<String, Object>(){{
+            put("domain", "http://test.com");
+            put("path", "test");
+        }});
+        defaultConfig.setProperty("authentication", new HashMap<String, Object>(){{
+            put("username", "username");
+            put("password", "password");
+            put("refreshPeriodIfFail", 100);
+            put("host", new HashMap<String, Object>(){{
+                put("domain", "http://test.com");
+                put("path", "test");
+            }});
+        }});
+
+        defaultConfig.setProperty("allowedStation", new HashMap<String, Object>() {{
+            put("unit_1", null);
+        }});
+
+        ConnectorFetchProvider provider = new ConnectorFetchAzureProvider();
+        ExecutableFetcher fetcher = provider.createExecutableFetcher(defaultConfig);
+
+        assertNotNull(fetcher);
+        assertEquals(AzureFetcher.class, fetcher.getRawFetcher().getClass());
+    }
 }

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

@@ -1,53 +1,53 @@
-package cz.senslog.connector.fetch.fieldclimate;
-
-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;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import static cz.senslog.common.http.HttpClient.newHttpClient;
-import static cz.senslog.common.http.HttpClient.newHttpSSLClient;
-
-
-public final class ConnectorFetchFieldClimateProvider implements ConnectorFetchProvider {
-
-    private static Logger logger = LogManager.getLogger(ConnectorFetchFieldClimateProvider.class);
-
-    @Override
-    public ExecutableFetcher<FieldClimateModel> createExecutableFetcher(DefaultConfig defaultConfig) {
-        logger.info("Initialization a new fetch provider {}.", ConnectorFetchFieldClimateProvider.class);
-
-        logger.debug("Creating a new configuration.");
-        FieldClimateConfig config = new FieldClimateConfig(defaultConfig);
-        logger.info("Configuration for {} was created successfully.", ConnectorFetcher.class);
-
-        logger.debug("Getting a configuration for authentication.");
-        AuthConfig authConfig = config.getAuthentication();
-
-        logger.info("Initialization a new Azure authentication service.");
-        AuthenticationService authService = new AuthenticationService(authConfig);
-
-        logger.debug("Creating a new instance of {}.", FieldClimateFetcher.class);
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, newHttpSSLClient());
-        logger.info("Fetcher for {} was created successfully.", FieldClimateFetcher.class);
-
-        logger.debug("Getting a configuration for proxy session.");
-        FieldClimateSessionProxyConfig proxyConfig = config.getSessionProxy();
-
-        ExecutableFetcher<FieldClimateModel> executor;
-        if (proxyConfig != null) {
-            logger.debug("Creating a new instance of {}.", FieldClimateProxySession.class);
-            FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, proxyConfig, newHttpClient());
-            logger.info("Fetcher session for {} was created successfully.", FieldClimateProxySession.class);
-            executor = ExecutableFetcher.createWithProxySession(proxySession);
-        } else {
-            executor = ExecutableFetcher.create(fetcher);
-        }
-        logger.info("Fetcher executor for {} was created successfully.", FieldClimateFetcher.class);
-
-        return executor;
-    }
-}
+package cz.senslog.connector.fetch.fieldclimate;
+
+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;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static cz.senslog.common.http.HttpClient.newHttpClient;
+import static cz.senslog.common.http.HttpClient.newHttpSSLClient;
+
+
+public final class ConnectorFetchFieldClimateProvider implements ConnectorFetchProvider {
+
+    private static final Logger logger = LogManager.getLogger(ConnectorFetchFieldClimateProvider.class);
+
+    @Override
+    public ExecutableFetcher<FieldClimateModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new fetch provider {}.", ConnectorFetchFieldClimateProvider.class);
+
+        logger.debug("Creating a new configuration.");
+        FieldClimateConfig config = new FieldClimateConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", ConnectorFetcher.class);
+
+        logger.debug("Getting a configuration for authentication.");
+        AuthConfig authConfig = config.getAuthentication();
+
+        logger.info("Initialization a new Azure authentication service.");
+        AuthenticationService authService = new AuthenticationService(authConfig);
+
+        logger.debug("Creating a new instance of {}.", FieldClimateFetcher.class);
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, newHttpSSLClient());
+        logger.info("Fetcher for {} was created successfully.", FieldClimateFetcher.class);
+
+        logger.debug("Getting a configuration for proxy session.");
+        FieldClimateSessionProxyConfig proxyConfig = config.getSessionProxy();
+
+        ExecutableFetcher<FieldClimateModel> executor;
+        if (proxyConfig != null) {
+            logger.debug("Creating a new instance of {}.", FieldClimateProxySession.class);
+            FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, proxyConfig, newHttpClient());
+            logger.info("Fetcher session for {} was created successfully.", FieldClimateProxySession.class);
+            executor = ExecutableFetcher.createWithProxySession(proxySession);
+        } else {
+            executor = ExecutableFetcher.create(fetcher);
+        }
+        logger.info("Fetcher executor for {} was created successfully.", FieldClimateFetcher.class);
+
+        return executor;
+    }
+}

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

@@ -1,311 +1,311 @@
-package cz.senslog.connector.fetch.fieldclimate;
-
-import com.google.gson.reflect.TypeToken;
-import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.common.exception.ParseException;
-import cz.senslog.common.exception.SyntaxException;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import cz.senslog.connector.model.api.ProxySessionModel;
-import cz.senslog.connector.model.fieldclimate.*;
-import cz.senslog.common.util.Tuple;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.lang.reflect.Type;
-import java.time.*;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
-import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
-import static cz.senslog.common.http.HttpHeader.*;
-import static cz.senslog.common.http.HttpMethod.GET;
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static cz.senslog.common.util.StringUtils.isBlank;
-import static java.lang.String.format;
-import static java.time.OffsetDateTime.MAX;
-import static java.time.OffsetDateTime.MIN;
-import static java.time.ZoneOffset.UTC;
-import static java.time.format.DateTimeFormatter.*;
-import static java.util.Collections.emptyList;
-import static org.apache.http.client.utils.DateUtils.formatDate;
-
-public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession, FieldClimateModel> {
-
-    private static Logger logger = LogManager.getLogger(FieldClimateFetcher.class);
-
-    private final AuthenticationService authService;
-    private final FieldClimateConfig config;
-    private final HttpClient httpClient;
-
-    private List<StationInfo> stationInfos;
-    private FieldClimateSession localSession;
-
-    public FieldClimateFetcher(FieldClimateConfig config, AuthenticationService authService, HttpClient httpClient) {
-        this.authService = authService;
-        this.config = config;
-        this.httpClient = httpClient;
-        this.localSession = FieldClimateSession.emptySession();
-    }
-
-    public FieldClimateFetcher() { this(null, null, null); }
-
-    @Override
-    public void init() throws Exception {
-        assert config != null; assert  authService != null; assert  httpClient != null;
-
-        String requestDate = formatDate(new Date());
-        HostConfig stationsHost = config.getStationsHost();
-        String seed = "GET" + stationsHost.getPath() + requestDate;
-
-        HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(stationsHost.getDomain(), stationsHost.getPath()).build())
-                .header(ACCEPT, APPLICATION_JSON)
-                .header(AUTHORIZATION, authService.getAccessToken(seed))
-                .header(DATE, requestDate)
-                .build();
-        logger.info("Creating a http request to {}.", request);
-
-        HttpResponse response = httpClient.send(request);
-
-        if (response.isError()) {
-            throw logger.throwing(new Exception(format(
-                    "Can not get information about the stations. %s", response.getBody()
-            )));
-        }
-
-        List<StationInfo> stations;
-        try {
-            logger.debug("Parsing body of the response to the class {}.", StationInfo.class);
-            Type sensorInfoListType = new TypeToken<Collection<StationInfo>>() {}.getType();
-            stations = jsonToObject(response.getBody(), sensorInfoListType);
-        } catch (SyntaxException e) {
-            throw logger.throwing(new Exception(format(
-                    "JSON response can not be parsed to the list of stations. %s", e.getMessage()
-            )));
-        }
-
-        if (stations.isEmpty()) {
-            throw logger.throwing(new Exception("Received empty list of stations."));
-        }
-
-        stationInfos = new ArrayList<>(stations.size());
-        for (StationInfo station : stations) {
-            if (config.getAllowedStation().isAllowed(station.getName().getOriginal())) {
-                stationInfos.add(station);
-            }
-        }
-
-        logger.info("{} stations were loaded.", stationInfos.size());
-        logger.info(stationInfos.toString());
-    }
-
-    @Override
-    public FieldClimateModel fetch(Optional<FieldClimateSession> persistenceSession) {
-        assert config != null; assert  authService != null; assert  httpClient != null;
-
-        FieldClimateSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
-        OffsetDateTime startDate = ZonedDateTime.of(config.getStartDate(), config.getTimeZone().toZoneId()).toOffsetDateTime();
-
-        OffsetDateTime globalFrom = MAX, globalTo = MIN;
-        List<StationData> stationDataList = new ArrayList<>(stationInfos.size());
-        int totalFetched = 0;
-
-        for (StationInfo station : stationInfos) {
-
-            String stationId = station.getName().getOriginal();
-            logger.info("Fetching data for the station {}.", stationId);
-
-            if (isBlank(stationId)) {
-                logger.warn("The station ID is empty, data can not be fetched."); continue;
-            }
-
-            logger.debug("Getting a start date time for station {}.", stationId);
-            OffsetDateTime localFrom = session.getLastUnitTime(stationId, startDate);
-
-            StationTimeRange timeRange = getTimeRangeForStation(stationId);
-            if (timeRange == null) continue;
-
-            OffsetDateTime stationMin = ZonedDateTime.of(timeRange.getMin_date(), config.getTimeZone().toZoneId()).toOffsetDateTime();
-            if (localFrom.isBefore(stationMin)) {
-                localFrom = stationMin;
-            }
-
-            OffsetDateTime localTo = localFrom.plusHours(config.getPeriod());
-            OffsetDateTime stationMax = ZonedDateTime.of(timeRange.getMax_date(), config.getTimeZone().toZoneId()).toOffsetDateTime();
-            if (localTo.isAfter(stationMax)) {
-                localTo = stationMax;
-            }
-
-            if (localFrom.equals(localTo)) {
-                logger.info("No new data for the station {} is available at this moment.", stationId); continue;
-            }
-
-            logger.info("New data for the station {} are going to be fetched from {} to {}.",
-                    stationId, localFrom, localTo);
-
-            HostConfig stationDataHost = config.getStationDataHost();
-            logger.info("Creating a http request to {}.", stationDataHost);
-            String path = stationDataHost.getPath()
-                    .replace("{station_id}", stationId)
-                    .replace("{from}", String.valueOf(localFrom.toLocalDateTime().toEpochSecond(UTC)))
-                    .replace("{to}", String.valueOf(localTo.toLocalDateTime().toEpochSecond(UTC)));
-
-            String requestDate = formatDate(new Date());
-            String authToken = authService.getAccessToken(GET.name() + path + requestDate);
-            HttpRequest request = HttpRequest.newBuilder().GET()
-                    .url(URLBuilder.newBuilder(stationDataHost.getDomain(), path).build())
-                    .header(ACCEPT, APPLICATION_JSON)
-                    .header(DATE, requestDate)
-                    .header(AUTHORIZATION, authToken)
-                    .build();
-
-            logger.info("Sending the http request: {}", request);
-            HttpResponse response = httpClient.send(request);
-            logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), stationDataHost.getDomain());
-
-
-            if (response.isOk()) {
-
-                StationData stationData;
-                try {
-                    logger.debug("Parsing body of the response to the class {}.", StationData.class);
-                    stationData = jsonToObject(response.getBody(), StationData.class);
-                    logger.info("Received {} records for the sensor {}.", stationData.getData().size(), stationId);
-                } catch (SyntaxException e) {
-                    logger.catching(e);
-                    continue;
-                }
-
-                List<SensorDataInfo> filteredSensors = new ArrayList<>(stationData.getSensors().size());
-                for (SensorDataInfo sensor : stationData.getSensors()) {
-                    if (SensorType.of(sensor.getCode()).isChanneled()) {
-                        if (config.getAllowedStation().isAllowed(stationId, sensor.getCode(), sensor.getCh())) {
-                            filteredSensors.add(sensor);
-                        }
-                    } else if (config.getAllowedStation().isAllowed(stationId, sensor.getCode())) {
-                        filteredSensors.add(sensor);
-                    }
-                }
-
-                stationData.setId(stationId);
-                stationData.setSensors(filteredSensors);
-
-                OffsetDateTime latestTimestamp = OffsetDateTime.MIN;
-                List<Map<String, String>> filteredDataList = new ArrayList<>(stationData.getData().size());
-                for (Map<String, String> sensorDataMap : stationData.getData()) {
-                    Map<String, String> filteredSensorData = new HashMap<>(sensorDataMap.size());
-
-                    String strDate = sensorDataMap.getOrDefault("date", "");
-                    final DateTimeFormatter formatter = ofPattern("yyyy-MM-dd HH:mm:ss");
-                    LocalDateTime date = LocalDateTime.parse(strDate, formatter);
-                    ZonedDateTime zonedDate = ZonedDateTime.of(date, config.getTimeZone().toZoneId());
-                    OffsetDateTime timestamp = zonedDate.toOffsetDateTime();
-                    filteredSensorData.put("date", timestamp.format(ISO_OFFSET_DATE_TIME));
-
-                    for (SensorDataInfo sensor : filteredSensors) {
-                        for (String aggrType : sensor.getAggr().keySet()) {
-                            String sensorDataHash = format("%s_%s_%s_%s_%s",
-                                    sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode(), aggrType);
-                            if (sensorDataMap.containsKey(sensorDataHash)) {
-                                filteredSensorData.put(sensorDataHash, sensorDataMap.get(sensorDataHash));
-                            }
-                        }
-                    }
-
-                    filteredDataList.add(filteredSensorData);
-
-                    if (latestTimestamp.isBefore(timestamp)) {
-                        latestTimestamp = timestamp;
-                    }
-                }
-                stationData.setData(filteredDataList);
-
-                localTo = latestTimestamp.isAfter(localTo) ? latestTimestamp : localTo;
-
-                stationDataList.add(stationData);
-
-                int records = stationData.getSensors().size();
-                totalFetched += records;
-
-                logger.info("Fetched {} records for the station {}.", records, stationId);
-
-            } else {
-
-                if (response.getStatus() >= 300) {
-                    logger.error("Can not get data from the station {}. Error {} {}",
-                            stationId, response.getStatus(), response.getBody());
-                    continue;
-                }
-            }
-
-            session.addLastUnitTime(stationId, localTo);
-
-            if (globalFrom.isAfter(localFrom)) {
-                  globalFrom = localFrom;
-            }
-
-            if (globalTo.isBefore(localTo)) {
-                globalTo = localTo;
-            }
-        }
-
-        FieldClimateModel model;
-        if (totalFetched != 0) {
-
-            logger.info("Fetched {} records from {} to {}.",
-                    totalFetched, globalFrom.format(ISO_OFFSET_DATE_TIME), globalTo.format(ISO_OFFSET_DATE_TIME));
-
-            model = new FieldClimateModel(stationDataList, globalFrom, globalTo);
-
-        } else {
-            logger.warn("No data was fetched.");
-            OffsetDateTime now = OffsetDateTime.now();
-            model = new FieldClimateModel(emptyList(), now, now);
-        }
-
-        return model;
-    }
-
-    private StationTimeRange getTimeRangeForStation(String stationId) {
-        logger.debug("Getting an actual time range for the station {}.", stationId);
-        String requestDate = formatDate(new Date());
-        HostConfig timeRangeHost = config.getStationTimeRangeHost();
-        String path = timeRangeHost.getPath().replace("{station_id}", stationId);
-        String authToken =  authService.getAccessToken(GET.name() + path + requestDate);
-
-        HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(timeRangeHost.getDomain(), path).build())
-                .header(ACCEPT, APPLICATION_JSON)
-                .header(DATE, requestDate)
-                .header(AUTHORIZATION, authToken)
-                .build();
-
-        logger.info("Sending the http request: {}", request);
-        HttpResponse response = httpClient.send(request);
-        logger.info("Received a response with a status: {}.", response.getStatus());
-
-        if (response.isError()) {
-            logger.error("Can not get data of the sensor {}. Error {}: {}",
-                    stationId, response.getStatus(), response.getBody());
-            return null;
-        }
-
-        StationTimeRange timeRange = null;
-        try {
-            logger.debug("Parsing body of the json response to the class {}.", StationTimeRange.class);
-            timeRange = jsonToObject(response.getBody(), StationTimeRange.class,
-                    Tuple.of(LocalDateTime.class,
-                            el -> LocalDateTime.parse(el, ofPattern("yyyy-MM-dd HH:mm:ss")))
-            );
-        } catch (SyntaxException | ParseException e) {
-            logger.catching(e);
-        }
-
-        return timeRange;
-    }
-}
+package cz.senslog.connector.fetch.fieldclimate;
+
+import com.google.gson.reflect.TypeToken;
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.common.exception.ParseException;
+import cz.senslog.common.exception.SyntaxException;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import cz.senslog.connector.model.api.ProxySessionModel;
+import cz.senslog.connector.model.fieldclimate.*;
+import cz.senslog.common.util.Tuple;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.Type;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
+import static cz.senslog.common.http.HttpHeader.*;
+import static cz.senslog.common.http.HttpMethod.GET;
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static cz.senslog.common.util.StringUtils.isBlank;
+import static java.lang.String.format;
+import static java.time.OffsetDateTime.MAX;
+import static java.time.OffsetDateTime.MIN;
+import static java.time.ZoneOffset.UTC;
+import static java.time.format.DateTimeFormatter.*;
+import static java.util.Collections.emptyList;
+import static org.apache.http.client.utils.DateUtils.formatDate;
+
+public class FieldClimateFetcher implements ConnectorFetcher<FieldClimateSession, FieldClimateModel> {
+
+    private static final Logger logger = LogManager.getLogger(FieldClimateFetcher.class);
+
+    private final AuthenticationService authService;
+    private final FieldClimateConfig config;
+    private final HttpClient httpClient;
+
+    private List<StationInfo> stationInfos;
+    private FieldClimateSession localSession;
+
+    public FieldClimateFetcher(FieldClimateConfig config, AuthenticationService authService, HttpClient httpClient) {
+        this.authService = authService;
+        this.config = config;
+        this.httpClient = httpClient;
+        this.localSession = FieldClimateSession.emptySession();
+    }
+
+    public FieldClimateFetcher() { this(null, null, null); }
+
+    @Override
+    public void init() throws Exception {
+        assert config != null; assert  authService != null; assert  httpClient != null;
+
+        String requestDate = formatDate(new Date());
+        HostConfig stationsHost = config.getStationsHost();
+        String seed = "GET" + stationsHost.getPath() + requestDate;
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(stationsHost.getDomain(), stationsHost.getPath()).build())
+                .header(ACCEPT, APPLICATION_JSON)
+                .header(AUTHORIZATION, authService.getAccessToken(seed))
+                .header(DATE, requestDate)
+                .build();
+        logger.info("Creating a http request to {}.", request);
+
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isError()) {
+            throw logger.throwing(new Exception(format(
+                    "Can not get information about the stations. %s", response.getBody()
+            )));
+        }
+
+        List<StationInfo> stations;
+        try {
+            logger.debug("Parsing body of the response to the class {}.", StationInfo.class);
+            Type sensorInfoListType = new TypeToken<Collection<StationInfo>>() {}.getType();
+            stations = jsonToObject(response.getBody(), sensorInfoListType,
+                    Tuple.of(LocalDateTime.class, s -> LocalDateTime.parse(s, ofPattern("yyyy-MM-dd HH:mm:ss")))
+            );
+        } catch (SyntaxException e) {
+            throw logger.throwing(new Exception(format(
+                    "JSON response can not be parsed to the list of stations. %s", e.getMessage()
+            )));
+        }
+
+        if (stations.isEmpty()) {
+            throw logger.throwing(new Exception("Received empty list of stations."));
+        }
+
+        stationInfos = new ArrayList<>(stations.size());
+        for (StationInfo station : stations) {
+            if (config.getAllowedStation().isAllowed(station.getName().getOriginal())) {
+                stationInfos.add(station);
+            }
+        }
+
+        logger.info("{} stations were loaded.", stationInfos.size());
+        logger.info(stationInfos.toString());
+    }
+
+    @Override
+    public FieldClimateModel fetch(Optional<FieldClimateSession> persistenceSession) {
+        assert config != null; assert  authService != null; assert  httpClient != null;
+
+        FieldClimateSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
+        ZoneId zoneId = config.getTimeZone().toZoneId();
+        OffsetDateTime startDate = ZonedDateTime.of(config.getStartDate(), zoneId).toOffsetDateTime();
+        ZoneOffset zoneOffset = startDate.getOffset();
+
+        OffsetDateTime globalFrom = MAX, globalTo = MIN;
+        List<StationData> stationDataList = new ArrayList<>(stationInfos.size());
+        int totalFetched = 0;
+
+        for (StationInfo station : stationInfos) {
+
+            String stationId = station.getName().getOriginal();
+            logger.info("Fetching data for the station {}.", stationId);
+
+            if (isBlank(stationId)) {
+                logger.warn("The station ID is empty, data can not be fetched."); continue;
+            }
+
+            OffsetDateTime firstValueDate = OffsetDateTime.of(station.getMinDate(), zoneOffset);
+            OffsetDateTime lastFetch = session.getLastUnitTime(stationId, MIN);
+
+            long fromDateEpoch = Math.max(startDate.toEpochSecond(), Math.max(firstValueDate.toEpochSecond(), lastFetch.toEpochSecond()));
+            logger.debug("Getting a start date time for station {}.", stationId);
+            OffsetDateTime localFrom = OffsetDateTime.ofInstant(Instant.ofEpochSecond(fromDateEpoch), zoneOffset);
+
+
+            StationTimeRange timeRange = getTimeRangeForStation(stationId);
+            if (timeRange == null) continue;
+
+
+            OffsetDateTime localTo = localFrom.plusHours(config.getPeriod());
+            OffsetDateTime stationMax = ZonedDateTime.of(timeRange.getMax_date(), config.getTimeZone().toZoneId()).toOffsetDateTime();
+            if (localTo.isAfter(stationMax)) {
+                localTo = stationMax;
+            }
+
+            if (localFrom.equals(localTo)) {
+                logger.info("No new data for the station {} is available at this moment.", stationId); continue;
+            }
+
+            logger.info("New data for the station {} are going to be fetched from {} to {}.",
+                    stationId, localFrom, localTo);
+
+            HostConfig stationDataHost = config.getStationDataHost();
+            logger.info("Creating a http request to {}.", stationDataHost);
+            String path = stationDataHost.getPath()
+                    .replace("{station_id}", stationId)
+                    .replace("{from}", String.valueOf(localFrom.toLocalDateTime().toEpochSecond(UTC)))
+                    .replace("{to}", String.valueOf(localTo.toLocalDateTime().toEpochSecond(UTC)));
+
+            String requestDate = formatDate(new Date());
+            String authToken = authService.getAccessToken(GET.name() + path + requestDate);
+            HttpRequest request = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(stationDataHost.getDomain(), path).build())
+                    .header(ACCEPT, APPLICATION_JSON)
+                    .header(DATE, requestDate)
+                    .header(AUTHORIZATION, authToken)
+                    .build();
+
+            logger.info("Sending the http request: {}", request);
+            HttpResponse response = httpClient.send(request);
+            logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), stationDataHost.getDomain());
+
+
+            if (response.isOk()) {
+
+                StationData stationData;
+                try {
+                    logger.debug("Parsing body of the response to the class {}.", StationData.class);
+                    stationData = jsonToObject(response.getBody(), StationData.class);
+                    logger.info("Received {} records for the sensor {}.", stationData.getData().size(), stationId);
+                } catch (SyntaxException e) {
+                    logger.catching(e);
+                    continue;
+                }
+
+                List<SensorDataInfo> filteredSensors = new ArrayList<>(stationData.getSensors().size());
+                for (SensorDataInfo sensor : stationData.getSensors()) {
+                    if (SensorType.of(sensor.getCode()).isChanneled()) {
+                        if (config.getAllowedStation().isAllowed(stationId, sensor.getCode(), sensor.getCh())) {
+                            filteredSensors.add(sensor);
+                        }
+                    } else if (config.getAllowedStation().isAllowed(stationId, sensor.getCode())) {
+                        filteredSensors.add(sensor);
+                    }
+                }
+
+                stationData.setId(stationId);
+                stationData.setSensors(filteredSensors);
+
+                OffsetDateTime latestTimestamp = OffsetDateTime.MIN;
+                List<Map<String, String>> filteredDataList = new ArrayList<>(stationData.getData().size());
+                for (Map<String, String> sensorDataMap : stationData.getData()) {
+                    Map<String, String> filteredSensorData = new HashMap<>(sensorDataMap.size());
+
+                    String strDate = sensorDataMap.getOrDefault("date", "");
+                    final DateTimeFormatter formatter = ofPattern("yyyy-MM-dd HH:mm:ss");
+                    LocalDateTime date = LocalDateTime.parse(strDate, formatter);
+                    ZonedDateTime zonedDate = ZonedDateTime.of(date, config.getTimeZone().toZoneId());
+                    OffsetDateTime timestamp = zonedDate.toOffsetDateTime();
+                    filteredSensorData.put("date", timestamp.format(ISO_OFFSET_DATE_TIME));
+
+                    for (SensorDataInfo sensor : filteredSensors) {
+                        for (String aggrType : sensor.getAggr().keySet()) {
+                            String sensorDataHash = format("%s_%s_%s_%s_%s",
+                                    sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode(), aggrType);
+                            if (sensorDataMap.containsKey(sensorDataHash)) {
+                                filteredSensorData.put(sensorDataHash, sensorDataMap.get(sensorDataHash));
+                            }
+                        }
+                    }
+
+                    filteredDataList.add(filteredSensorData);
+
+                    if (latestTimestamp.isBefore(timestamp)) {
+                        latestTimestamp = timestamp;
+                    }
+                }
+                stationData.setData(filteredDataList);
+
+                localTo = latestTimestamp.isAfter(localTo) ? latestTimestamp : localTo;
+
+                stationDataList.add(stationData);
+
+                int records = stationData.getSensors().size();
+                totalFetched += records;
+
+                logger.info("Fetched {} records for the station {}.", records, stationId);
+
+            } else {
+
+                if (response.getStatus() >= 300) {
+                    logger.error("Can not get data from the station {}. Error {} {}",
+                            stationId, response.getStatus(), response.getBody());
+                    continue;
+                }
+            }
+
+            session.addLastUnitTime(stationId, localTo);
+
+            if (globalFrom.isAfter(localFrom)) {
+                  globalFrom = localFrom;
+            }
+
+            if (globalTo.isBefore(localTo)) {
+                globalTo = localTo;
+            }
+        }
+
+
+        List<StationData> dataList = totalFetched != 0 ? stationDataList : emptyList();
+        OffsetDateTime now = OffsetDateTime.now();
+        OffsetDateTime from = globalFrom == MAX ? now : globalFrom;
+        OffsetDateTime to = globalTo == MIN ? now : globalTo;
+
+        logger.info("Fetched {} records from {} to {}.",
+                totalFetched, globalFrom.format(ISO_OFFSET_DATE_TIME), globalTo.format(ISO_OFFSET_DATE_TIME));
+
+        return new FieldClimateModel(dataList, from, to);
+    }
+
+    private StationTimeRange getTimeRangeForStation(String stationId) {
+        logger.debug("Getting an actual time range for the station {}.", stationId);
+        String requestDate = formatDate(new Date());
+        HostConfig timeRangeHost = config.getStationTimeRangeHost();
+        String path = timeRangeHost.getPath().replace("{station_id}", stationId);
+        String authToken =  authService.getAccessToken(GET.name() + path + requestDate);
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(timeRangeHost.getDomain(), path).build())
+                .header(ACCEPT, APPLICATION_JSON)
+                .header(DATE, requestDate)
+                .header(AUTHORIZATION, authToken)
+                .build();
+
+        logger.info("Sending the http request: {}", request);
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received a response with a status: {}.", response.getStatus());
+
+        if (response.isError()) {
+            logger.error("Can not get data of the sensor {}. Error {}: {}",
+                    stationId, response.getStatus(), response.getBody());
+            return null;
+        }
+
+        StationTimeRange timeRange = null;
+        try {
+            logger.debug("Parsing body of the json response to the class {}.", StationTimeRange.class);
+            timeRange = jsonToObject(response.getBody(), StationTimeRange.class,
+                    Tuple.of(LocalDateTime.class,
+                            el -> LocalDateTime.parse(el, ofPattern("yyyy-MM-dd HH:mm:ss")))
+            );
+        } catch (SyntaxException | ParseException e) {
+            logger.catching(e);
+        }
+
+        return timeRange;
+    }
+}

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

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

+ 614 - 611
connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateFetcherTest.java

@@ -1,612 +1,615 @@
-package cz.senslog.connector.fetch.fieldclimate;
-
-import cz.senslog.connector.fetch.api.ExecutableFetcher;
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.connector.model.converter.FieldClimateModelSenslogV1ModelConverter;
-import cz.senslog.connector.model.fieldclimate.*;
-import cz.senslog.connector.model.v1.SenslogV1Model;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.mockito.stubbing.Answer;
-
-import java.time.LocalDateTime;
-import java.util.*;
-
-import static cz.senslog.common.http.HttpClient.newHttpClient;
-import static cz.senslog.common.http.HttpClient.newHttpSSLClient;
-import static cz.senslog.common.http.HttpCode.OK;
-import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
-import static java.lang.String.format;
-import static java.time.format.DateTimeFormatter.ofPattern;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-class FieldClimateFetcherTest {
-
-    private static FieldClimateConfig config;
-    private static AuthenticationService authService;
-    private static LocalDateTime startDate = LocalDateTime.of(2019, 3, 6, 00, 00,0);
-
-    @BeforeAll
-    static void init() {
-        DefaultConfig fcDConfig = new DefaultConfig("fcConfig", null);
-        fcDConfig.setProperty("stationsHost", new HashMap<String, String>(){{
-            put("domain", "https://api.fieldclimate.com/v1");
-            put("path", "/user/stations");
-        }});
-
-        fcDConfig.setProperty("stationDataHost", new HashMap<String, String>(){{
-            put("domain", "https://api.fieldclimate.com/v1");
-            put("path", "/data/normal/{station_id}/raw/from/{from}/to/{to}");
-        }});
-
-        fcDConfig.setProperty("stationTimeRangeHost", new HashMap<String, String>(){{
-            put("domain", "https://api.fieldclimate.com/v1");
-            put("path", "/data/{station_id}");
-        }});
-
-        fcDConfig.setProperty("authentication", new HashMap<String, String>(){{
-            put("publicKey", "3737ed4fe98fae975e54991216ed473c8d7db48662deff19");
-            put("privateKey", "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b");
-        }});
-
-
-        fcDConfig.setProperty("sessionProxy", new HashMap<String, Object>(){{
-            put("user", "<username>");
-            put("group", "<group>");
-            put("lastObservationHost",  new HashMap<String, String>(){{
-                put("domain", "http://127.0.0.1:9080");
-                put("path", "SensorService");
-            }});
-        }});
-
-        Map<String, List<Integer>> allowedStation = new HashMap<>();
-        List<Integer> allowedSensor = new ArrayList<>();
-        allowedSensor.add((int)SensorType.HC_AIR_TEMPERATURE.getCode());
-        allowedStation.put("original_name", allowedSensor);
-
-        fcDConfig.setProperty("allowedStation", allowedStation);
-        fcDConfig.setProperty("timeZone", "Europe/Riga");
-
-        fcDConfig.setProperty("startDate", startDate);
-        fcDConfig.setProperty("period", 12);
-
-        config = new FieldClimateConfig(fcDConfig);
-        authService = new AuthenticationService(config.getAuthentication());
-    }
-
-    @Test
-    void init_GetStations() throws Exception {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name stationName = new StationInfo.Name();
-                stationName.setOriginal("original_name");
-                stationInfo.setName(stationName);
-                String stationsJson = singletonList(stationInfo).toString();
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stationsJson).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-
-        fetcher.init();
-    }
-
-    @Test
-    void init_StationsRequestError_Exception() {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock ->
-            HttpResponse.newBuilder().status(SERVER_ERROR).build()
-        );
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-
-        Assertions.assertThrows(Exception.class, fetcher::init);
-    }
-
-    @Test
-    void init_NoneStations_Exception() {
-
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                return HttpResponse.newBuilder()
-                        .status(OK).body(emptyList().toString()).build();
-            }
-
-            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-
-        Assertions.assertThrows(Exception.class, fetcher::init);
-    }
-
-    @Test
-    void fetch_EmptyStationName_EmptyModel() throws Exception {
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal("");
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model = fetcher.fetch(Optional.empty());
-
-        assertTrue(model.getStations().isEmpty());
-    }
-
-    @Test
-    void fetch_StationTimeRangeRequestError_EmptyModel() throws Exception {
-        String stationName = "original_name";
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal(stationName);
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            if (path.contains("/data/"+stationName)) {
-                return HttpResponse.newBuilder()
-                        .status(SERVER_ERROR).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model = fetcher.fetch(Optional.empty());
-
-        assertTrue(model.getStations().isEmpty());
-    }
-
-    @Test
-    void fetch_StationTimeRangeBadData_EmptyModel() throws Exception {
-        String stationName = "original_name";
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal(stationName);
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            if (path.contains("/data/"+stationName)) {
-                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}", "unknown", "unknown");
-                return HttpResponse.newBuilder().status(OK).body(json).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model = fetcher.fetch(Optional.empty());
-
-        assertTrue(model.getStations().isEmpty());
-    }
-
-    @Test
-    void fetch_StationDataErrorRequest_EmptyModel() throws Exception {
-        String stationName = "original_name";
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal(stationName);
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            if (path.contains("/data/"+stationName)) {
-                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
-                        startDate.minusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
-                        startDate.plusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
-                );
-                return HttpResponse.newBuilder().status(OK).body(json).build();
-            }
-            if (path.contains("/data/normal/"+stationName)) {
-                return HttpResponse.newBuilder()
-                        .status(SERVER_ERROR).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model = fetcher.fetch(Optional.empty());
-
-        assertTrue(model.getStations().isEmpty());
-    }
-
-    @Test
-    void fetch_StationDataBadJson_EmptyModel() throws Exception {
-        String stationName = "original_name";
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal(stationName);
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            if (path.contains("/data/"+stationName)) {
-                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
-                        startDate.minusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
-                        startDate.plusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
-                );
-                return HttpResponse.newBuilder().status(OK).body(json).build();
-            }
-            if (path.contains("/data/normal/"+stationName)) {
-                return HttpResponse.newBuilder()
-                        .status(OK).body("unknown").build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model = fetcher.fetch(Optional.empty());
-
-        assertTrue(model.getStations().isEmpty());
-    }
-
-    @Test
-    void fetch_Valid_Model() throws Exception {
-        String stationName = "original_name";
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal(stationName);
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            if (path.contains("/data/"+stationName)) {
-                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
-                        startDate.plusHours(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
-                        startDate.plusHours(2).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
-                );
-                return HttpResponse.newBuilder().status(OK).body(json).build();
-            }
-            if (path.contains("/data/normal/"+stationName)) {
-                StationData data = new StationData();
-                data.setId(stationName);
-                SensorDataInfo dataInfo = new SensorDataInfo();
-                dataInfo.setName("sensorName");
-                dataInfo.setCh(1L);
-                dataInfo.setCode(SensorType.HC_AIR_TEMPERATURE.getCode());
-                dataInfo.setMac("mac");
-                dataInfo.setSerial("serial");
-                Map<String, Integer> aggrMap = new HashMap<>();
-                aggrMap.put("avg", 1);
-                dataInfo.setAggr(aggrMap);
-                Map<String, String> dataMap = new HashMap<>();
-                String sensorDataHash = format("%s_%s_%s_%s_%s",
-                        dataInfo.getCh(), dataInfo.getMac(), dataInfo.getSerial(), dataInfo.getCode(), "avg");
-                dataMap.put(sensorDataHash, "100");
-                dataMap.put("date", startDate.plusHours(1).plusMinutes(5).format(ofPattern("yyyy-MM-dd HH:mm:ss")));
-                data.setSensors(singletonList(dataInfo));
-                data.setData(singletonList(dataMap));
-                String json = data.toString();
-                return HttpResponse.newBuilder()
-                        .status(OK).body(json).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model = fetcher.fetch(Optional.empty());
-
-        assertEquals(1, model.getStations().size());
-
-        StationData stationData = model.getStations().get(0);
-        assertEquals(stationName, stationData.getId());
-
-        assertEquals(1, stationData.getSensors().size());
-        assertEquals(1, stationData.getData().size());
-
-        SensorDataInfo dataInfo = stationData.getSensors().get(0);
-        Map<String, String> data = stationData.getData().get(0);
-        assertEquals("sensorName", dataInfo.getName());
-        String sensorDataHash = format("%s_%s_%s_%s_%s",
-                dataInfo.getCh(), dataInfo.getMac(), dataInfo.getSerial(), dataInfo.getCode(),
-                dataInfo.getAggr().entrySet().iterator().next().getKey());
-        assertEquals(100, Integer.valueOf(data.get(sensorDataHash)));
-    }
-
-    @Test
-    void fetch_TimeRange_EmptyModel() throws Exception {
-        String stationName = "original_name";
-        HttpClient httpClient = mock(HttpClient.class);
-        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
-            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
-            String path = request.getUrl().getPath();
-            if (path.contains("/user/stations")) {
-                StationInfo stationInfo = new StationInfo();
-                StationInfo.Name name = new StationInfo.Name();
-                name.setOriginal(stationName);
-                stationInfo.setName(name);
-                List<StationInfo> stations = singletonList(stationInfo);
-                return HttpResponse.newBuilder()
-                        .status(OK).body(stations.toString()).build();
-            }
-            if (path.contains("/data/"+stationName)) {
-                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
-                        startDate.minusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
-                        startDate.plusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
-                );
-                return HttpResponse.newBuilder().status(OK).body(json).build();
-            }
-            if (path.contains("/data/normal/"+stationName)) {
-                StationData data = new StationData();
-                data.setId(stationName);
-                SensorDataInfo dataInfo = new SensorDataInfo();
-                dataInfo.setName("sensorName");
-                dataInfo.setCh(1L);
-                dataInfo.setCode(SensorType.HC_AIR_TEMPERATURE.getCode());
-                Map<String, Integer> aggrMap = new HashMap<>();
-                aggrMap.put("avg", 1);
-                dataInfo.setAggr(aggrMap);
-                Map<String, String> dataMap = new HashMap<>();
-                dataMap.put("date", startDate.plusHours(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")));
-                data.setSensors(singletonList(dataInfo));
-                data.setData(singletonList(dataMap));
-                String json = data.toString();
-                return HttpResponse.newBuilder()
-                        .status(OK).body(json).build();
-            }
-            return HttpResponse.newBuilder()
-                    .status(SERVER_ERROR).build();
-        });
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
-        fetcher.init();
-
-        FieldClimateModel model1 = fetcher.fetch(Optional.empty());
-    }
-
-    /*
-    @Test
-    void proxyFetch() throws Exception {
-
-        DefaultConfig propertyConfig = new DefaultConfig("proxySession", null);
-        propertyConfig.setProperty("lastObservationHost", new HashMap<String, String>() {{
-            put("domain", "http://51.15.45.95:8080/senslog1");
-            put("path", "SensorService");
-        }});
-        propertyConfig.setProperty("user", "vilcini");
-        propertyConfig.setProperty("group", "vilcini");
-
-        FieldClimateSessionProxyConfig sessionProxyConfig = new FieldClimateSessionProxyConfig(propertyConfig);
-
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, newHttpSSLClient());
-        FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, sessionProxyConfig, newHttpClient());
-
-        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
-        executableFetcher.getRawFetcher().init();
-
-        executableFetcher.execute();
-        executableFetcher.execute();
-    }
-
-     */
-/*
-    @Test
-    void fetch() throws Exception {
-
-        DefaultConfig fcDConfig = new DefaultConfig("fcConfig", null);
-        fcDConfig.setProperty("stationsHost", new HashMap<String, String>(){{
-            put("domain", "https://api.fieldclimate.com/v1");
-            put("path", "/user/stations");
-        }});
-
-        fcDConfig.setProperty("stationDataHost", new HashMap<String, String>(){{
-            put("domain", "https://api.fieldclimate.com/v1");
-            put("path", "/data/normal/{station_id}/raw/from/{from}/to/{to}");
-        }});
-
-        fcDConfig.setProperty("stationTimeRangeHost", new HashMap<String, String>(){{
-            put("domain", "https://api.fieldclimate.com/v1");
-            put("path", "/data/{station_id}");
-        }});
-
-        fcDConfig.setProperty("authentication", new HashMap<String, String>(){{
-            put("publicKey", "3737ed4fe98fae975e54991216ed473c8d7db48662deff19");
-            put("privateKey", "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b");
-        }});
-
-        fcDConfig.setProperty("timeZone", "Europe/Riga");
-
-        fcDConfig.setProperty("blockedStations", new ArrayList<String>(){{
-             add("0120821D");
-             add("0120821E");
-            // add("00208048");
-        }});
-
-        Map<String, Object> sessionProxy = new HashMap<>();
-        sessionProxy.put("lastObservationHost", new HashMap<String, String>() {{
-            put("domain", "http://51.15.45.95:8080/senslog1");
-            put("path", "SensorService");
-        }});
-        sessionProxy.put("user", "vilcini");
-        sessionProxy.put("group", "vilcini");
-        fcDConfig.setProperty("sessionProxy", sessionProxy);
-
-        fcDConfig.setProperty("startDate", LocalDateTime.of(2020, 7, 1, 7, 45,0));
-        fcDConfig.setProperty("period", 1);
-
-        FieldClimateConfig config = new FieldClimateConfig(fcDConfig);
-        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, HttpClient.newHttpSSLClient());
-        FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, config.getSessionProxy(), newHttpClient());
-
-//        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
-        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.create(fetcher);
-
-        executableFetcher.getRawFetcher().init();
-
-
-        for (int i = 0; i < 10; i++) {
-            FieldClimateModel fieldClimateModel = executableFetcher.execute();
-            final FieldClimateModelSenslogV1ModelConverter converter = new FieldClimateModelSenslogV1ModelConverter();
-            SenslogV1Model senslogV1Model = converter.convert(fieldClimateModel);
-            System.out.println(senslogV1Model);
-        }
-    }
-*/
-
-    /*
-    private void saveToCSV(FieldClimateModel model) throws IOException {
-
-        String [] header = new String[] {
-                "name", "ch", "code", "serial", "mac", "timestamp"
-        };
-
-        String [] valueType = new String[] {
-                "avg", "min", "last", "max", "sum", "time"
-        };
-        
-        String delimiter = ";";
-
-        for (StationData station : model.getStations()) {
-
-            StringBuilder stationSheet = new StringBuilder();
-
-            // header
-            stationSheet.append(String.join(delimiter, header));
-            stationSheet.append(delimiter);
-            stationSheet.append(String.join(delimiter, valueType));
-            stationSheet.append("\n");
-
-            for (Map<String, String> stationData : station.getData()) {
-                String timestamp = stationData.getOrDefault("date", "");
-
-                Set<String> done = new HashSet<>();
-                for (Map.Entry<String, String> dataEntry : stationData.entrySet()) {
-                    String key = dataEntry.getKey();
-                    String value = dataEntry.getValue();
-
-                    if (key.contains("_")) {
-                        String [] cmp = key.split("_");
-
-                        String ch = cmp[0];
-                        String mac = cmp[1];
-                        String serial = cmp[2];
-                        String code = cmp[3];
-                        String aggr = cmp[4];
-
-                        SensorDataInfo sensor = findSensor(station.getSensors(), Integer.valueOf(ch), mac, serial, Integer.valueOf(code));
-
-                        String sensorHash = format("%s_%s_%s_%s", sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode());
-
-                        if (done.contains(sensorHash)) continue;
-
-                        StringBuilder sensorBuilder = new StringBuilder();
-                        sensorBuilder.append(sensor.getName()).append(delimiter);
-                        sensorBuilder.append(sensor.getCh()).append(delimiter);
-                        sensorBuilder.append(sensor.getCode()).append(delimiter);
-                        sensorBuilder.append(sensor.getSerial()).append(delimiter);
-                        sensorBuilder.append(sensor.getMac());
-                        String sensorLine = sensorBuilder.toString();
-
-                        StringBuilder valueBuilder = new StringBuilder();
-                        for (String type : valueType) {
-
-                            String sensorDataHash = format("%s_%s", sensorHash, type);
-
-                            if (stationData.containsKey(sensorDataHash)) {
-                                valueBuilder.append(stationData.get(sensorDataHash));
-                            }
-                            valueBuilder.append(delimiter);
-                        }
-                        String valuesLine = valueBuilder.toString();
-
-
-                        stationSheet.append(sensorLine).append(delimiter)
-                                .append(timestamp).append(delimiter)
-                                .append(valuesLine).append("\n");
-
-                        done.add(sensorHash);
-                    }
-                }
-            }
-
-            Path filePath = Paths.get("sensor_" + station.getStationId()+ ".csv");
-            Files.write(filePath, Collections.singleton(stationSheet.toString()));
-        }
-    }
-
-    private SensorDataInfo findSensor(List<SensorDataInfo> sensors, Integer ch, String mac, String serial, Integer code) {
-        for (SensorDataInfo sensor : sensors) {
-            if (sensor.getCode().equals(code) &&
-                    sensor.getCh().equals(ch) &&
-                    sensor.getMac().equals(mac) &&
-                    sensor.getSerial().equals(serial)
-            ) {
-                return sensor;
-            }
-        }
-
-        return null;
-    }
-    */
+package cz.senslog.connector.fetch.fieldclimate;
+
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.fetch.fieldclimate.auth.AuthenticationService;
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.connector.model.converter.FieldClimateModelSenslogV1ModelConverter;
+import cz.senslog.connector.model.fieldclimate.*;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static cz.senslog.common.http.HttpClient.newHttpClient;
+import static cz.senslog.common.http.HttpClient.newHttpSSLClient;
+import static cz.senslog.common.http.HttpCode.OK;
+import static cz.senslog.common.http.HttpCode.SERVER_ERROR;
+import static java.lang.String.format;
+import static java.time.format.DateTimeFormatter.ofPattern;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class FieldClimateFetcherTest {
+
+    private static FieldClimateConfig config;
+    private static AuthenticationService authService;
+    private static LocalDateTime startDate = LocalDateTime.of(2019, 3, 6, 00, 00,0);
+
+    @BeforeAll
+    static void init() {
+        DefaultConfig fcDConfig = new DefaultConfig("fcConfig", null);
+        fcDConfig.setProperty("stationsHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/user/stations");
+        }});
+
+        fcDConfig.setProperty("stationDataHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/data/normal/{station_id}/raw/from/{from}/to/{to}");
+        }});
+
+        fcDConfig.setProperty("stationTimeRangeHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/data/{station_id}");
+        }});
+
+        fcDConfig.setProperty("authentication", new HashMap<String, String>(){{
+            put("publicKey", "3737ed4fe98fae975e54991216ed473c8d7db48662deff19");
+            put("privateKey", "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b");
+        }});
+
+
+        fcDConfig.setProperty("sessionProxy", new HashMap<String, Object>(){{
+            put("user", "<username>");
+            put("group", "<group>");
+            put("lastObservationHost",  new HashMap<String, String>(){{
+                put("domain", "http://127.0.0.1:9080");
+                put("path", "SensorService");
+            }});
+        }});
+
+        Map<String, List<Integer>> allowedStation = new HashMap<>();
+        List<Integer> allowedSensor = new ArrayList<>();
+        allowedSensor.add((int)SensorType.HC_AIR_TEMPERATURE.getCode());
+        allowedStation.put("original_name", allowedSensor);
+
+        fcDConfig.setProperty("allowedStation", allowedStation);
+        fcDConfig.setProperty("timeZone", "Europe/Riga");
+
+        fcDConfig.setProperty("startDate", startDate);
+        fcDConfig.setProperty("period", 12);
+
+        config = new FieldClimateConfig(fcDConfig);
+        authService = new AuthenticationService(config.getAuthentication());
+    }
+
+    @Test
+    void init_GetStations() throws Exception {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name stationName = new StationInfo.Name();
+                stationName.setOriginal("original_name");
+                stationInfo.setName(stationName);
+                String stationsJson = singletonList(stationInfo).toString();
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stationsJson).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+
+        fetcher.init();
+    }
+
+    @Test
+    void init_StationsRequestError_Exception() {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock ->
+            HttpResponse.newBuilder().status(SERVER_ERROR).build()
+        );
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+
+        Assertions.assertThrows(Exception.class, fetcher::init);
+    }
+
+    @Test
+    void init_NoneStations_Exception() {
+
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                return HttpResponse.newBuilder()
+                        .status(OK).body(emptyList().toString()).build();
+            }
+
+            return HttpResponse.newBuilder().status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+
+        Assertions.assertThrows(Exception.class, fetcher::init);
+    }
+
+    @Test
+    void fetch_EmptyStationName_EmptyModel() throws Exception {
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal("");
+                stationInfo.setName(name);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model = fetcher.fetch(Optional.empty());
+
+        assertTrue(model.getStations().isEmpty());
+    }
+
+    @Test
+    void fetch_StationTimeRangeRequestError_EmptyModel() throws Exception {
+        String stationName = "original_name";
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal(stationName);
+                stationInfo.setName(name);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            if (path.contains("/data/"+stationName)) {
+                return HttpResponse.newBuilder()
+                        .status(SERVER_ERROR).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model = fetcher.fetch(Optional.empty());
+
+        assertTrue(model.getStations().isEmpty());
+    }
+
+    @Test
+    void fetch_StationTimeRangeBadData_EmptyModel() throws Exception {
+        String stationName = "original_name";
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal(stationName);
+                stationInfo.setName(name);
+                StationInfo.Dates dates = new StationInfo.Dates();
+                dates.setMin_date(startDate);
+                stationInfo.setDates(dates);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            if (path.contains("/data/"+stationName)) {
+                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}", "unknown", "unknown");
+                return HttpResponse.newBuilder().status(OK).body(json).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model = fetcher.fetch(Optional.empty());
+
+        assertTrue(model.getStations().isEmpty());
+    }
+
+    @Test
+    void fetch_StationDataErrorRequest_EmptyModel() throws Exception {
+        String stationName = "original_name";
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal(stationName);
+                stationInfo.setName(name);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            if (path.contains("/data/"+stationName)) {
+                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
+                        startDate.minusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
+                        startDate.plusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
+                );
+                return HttpResponse.newBuilder().status(OK).body(json).build();
+            }
+            if (path.contains("/data/normal/"+stationName)) {
+                return HttpResponse.newBuilder()
+                        .status(SERVER_ERROR).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model = fetcher.fetch(Optional.empty());
+
+        assertTrue(model.getStations().isEmpty());
+    }
+
+    @Test
+    void fetch_StationDataBadJson_EmptyModel() throws Exception {
+        String stationName = "original_name";
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal(stationName);
+                stationInfo.setName(name);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            if (path.contains("/data/"+stationName)) {
+                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
+                        startDate.minusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
+                        startDate.plusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
+                );
+                return HttpResponse.newBuilder().status(OK).body(json).build();
+            }
+            if (path.contains("/data/normal/"+stationName)) {
+                return HttpResponse.newBuilder()
+                        .status(OK).body("unknown").build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model = fetcher.fetch(Optional.empty());
+
+        assertTrue(model.getStations().isEmpty());
+    }
+
+    @Test
+    void fetch_Valid_Model() throws Exception {
+        String stationName = "original_name";
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal(stationName);
+                stationInfo.setName(name);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            if (path.contains("/data/"+stationName)) {
+                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
+                        startDate.plusHours(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
+                        startDate.plusHours(2).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
+                );
+                return HttpResponse.newBuilder().status(OK).body(json).build();
+            }
+            if (path.contains("/data/normal/"+stationName)) {
+                StationData data = new StationData();
+                data.setId(stationName);
+                SensorDataInfo dataInfo = new SensorDataInfo();
+                dataInfo.setName("sensorName");
+                dataInfo.setCh(1L);
+                dataInfo.setCode(SensorType.HC_AIR_TEMPERATURE.getCode());
+                dataInfo.setMac("mac");
+                dataInfo.setSerial("serial");
+                Map<String, Integer> aggrMap = new HashMap<>();
+                aggrMap.put("avg", 1);
+                dataInfo.setAggr(aggrMap);
+                Map<String, String> dataMap = new HashMap<>();
+                String sensorDataHash = format("%s_%s_%s_%s_%s",
+                        dataInfo.getCh(), dataInfo.getMac(), dataInfo.getSerial(), dataInfo.getCode(), "avg");
+                dataMap.put(sensorDataHash, "100");
+                dataMap.put("date", startDate.plusHours(1).plusMinutes(5).format(ofPattern("yyyy-MM-dd HH:mm:ss")));
+                data.setSensors(singletonList(dataInfo));
+                data.setData(singletonList(dataMap));
+                String json = data.toString();
+                return HttpResponse.newBuilder()
+                        .status(OK).body(json).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model = fetcher.fetch(Optional.empty());
+
+        assertEquals(1, model.getStations().size());
+
+        StationData stationData = model.getStations().get(0);
+        assertEquals(stationName, stationData.getId());
+
+        assertEquals(1, stationData.getSensors().size());
+        assertEquals(1, stationData.getData().size());
+
+        SensorDataInfo dataInfo = stationData.getSensors().get(0);
+        Map<String, String> data = stationData.getData().get(0);
+        assertEquals("sensorName", dataInfo.getName());
+        String sensorDataHash = format("%s_%s_%s_%s_%s",
+                dataInfo.getCh(), dataInfo.getMac(), dataInfo.getSerial(), dataInfo.getCode(),
+                dataInfo.getAggr().entrySet().iterator().next().getKey());
+        assertEquals(100, Integer.valueOf(data.get(sensorDataHash)));
+    }
+
+    @Test
+    void fetch_TimeRange_EmptyModel() throws Exception {
+        String stationName = "original_name";
+        HttpClient httpClient = mock(HttpClient.class);
+        when(httpClient.send(any(HttpRequest.class))).thenAnswer((Answer<HttpResponse>) invocationOnMock -> {
+            HttpRequest request = invocationOnMock.getArgument(0, HttpRequest.class);
+            String path = request.getUrl().getPath();
+            if (path.contains("/user/stations")) {
+                StationInfo stationInfo = new StationInfo();
+                StationInfo.Name name = new StationInfo.Name();
+                name.setOriginal(stationName);
+                stationInfo.setName(name);
+                List<StationInfo> stations = singletonList(stationInfo);
+                return HttpResponse.newBuilder()
+                        .status(OK).body(stations.toString()).build();
+            }
+            if (path.contains("/data/"+stationName)) {
+                String json = String.format("{\"min_date\":\"%s\",\"max_date\":\"%s\"}",
+                        startDate.minusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")),
+                        startDate.plusDays(1).format(ofPattern("yyyy-MM-dd HH:mm:ss"))
+                );
+                return HttpResponse.newBuilder().status(OK).body(json).build();
+            }
+            if (path.contains("/data/normal/"+stationName)) {
+                StationData data = new StationData();
+                data.setId(stationName);
+                SensorDataInfo dataInfo = new SensorDataInfo();
+                dataInfo.setName("sensorName");
+                dataInfo.setCh(1L);
+                dataInfo.setCode(SensorType.HC_AIR_TEMPERATURE.getCode());
+                Map<String, Integer> aggrMap = new HashMap<>();
+                aggrMap.put("avg", 1);
+                dataInfo.setAggr(aggrMap);
+                Map<String, String> dataMap = new HashMap<>();
+                dataMap.put("date", startDate.plusHours(1).format(ofPattern("yyyy-MM-dd HH:mm:ss")));
+                data.setSensors(singletonList(dataInfo));
+                data.setData(singletonList(dataMap));
+                String json = data.toString();
+                return HttpResponse.newBuilder()
+                        .status(OK).body(json).build();
+            }
+            return HttpResponse.newBuilder()
+                    .status(SERVER_ERROR).build();
+        });
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, httpClient);
+        fetcher.init();
+
+        FieldClimateModel model1 = fetcher.fetch(Optional.empty());
+    }
+
+    /*
+    @Test
+    void proxyFetch() throws Exception {
+
+        DefaultConfig propertyConfig = new DefaultConfig("proxySession", null);
+        propertyConfig.setProperty("lastObservationHost", new HashMap<String, String>() {{
+            put("domain", "http://51.15.45.95:8080/senslog1");
+            put("path", "SensorService");
+        }});
+        propertyConfig.setProperty("user", "vilcini");
+        propertyConfig.setProperty("group", "vilcini");
+
+        FieldClimateSessionProxyConfig sessionProxyConfig = new FieldClimateSessionProxyConfig(propertyConfig);
+
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, newHttpSSLClient());
+        FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, sessionProxyConfig, newHttpClient());
+
+        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
+        executableFetcher.getRawFetcher().init();
+
+        executableFetcher.execute();
+        executableFetcher.execute();
+    }
+
+     */
+/*
+    @Test
+    void fetch() throws Exception {
+
+        DefaultConfig fcDConfig = new DefaultConfig("fcConfig", null);
+        fcDConfig.setProperty("stationsHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/user/stations");
+        }});
+
+        fcDConfig.setProperty("stationDataHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/data/normal/{station_id}/raw/from/{from}/to/{to}");
+        }});
+
+        fcDConfig.setProperty("stationTimeRangeHost", new HashMap<String, String>(){{
+            put("domain", "https://api.fieldclimate.com/v1");
+            put("path", "/data/{station_id}");
+        }});
+
+        fcDConfig.setProperty("authentication", new HashMap<String, String>(){{
+            put("publicKey", "3737ed4fe98fae975e54991216ed473c8d7db48662deff19");
+            put("privateKey", "ed2e4abacdaad1d542eeabcec4ee4f6c8fbf3b8bb167b84b");
+        }});
+
+        fcDConfig.setProperty("timeZone", "Europe/Riga");
+
+        fcDConfig.setProperty("blockedStations", new ArrayList<String>(){{
+             add("0120821D");
+             add("0120821E");
+            // add("00208048");
+        }});
+
+        Map<String, Object> sessionProxy = new HashMap<>();
+        sessionProxy.put("lastObservationHost", new HashMap<String, String>() {{
+            put("domain", "http://51.15.45.95:8080/senslog1");
+            put("path", "SensorService");
+        }});
+        sessionProxy.put("user", "vilcini");
+        sessionProxy.put("group", "vilcini");
+        fcDConfig.setProperty("sessionProxy", sessionProxy);
+
+        fcDConfig.setProperty("startDate", LocalDateTime.of(2020, 7, 1, 7, 45,0));
+        fcDConfig.setProperty("period", 1);
+
+        FieldClimateConfig config = new FieldClimateConfig(fcDConfig);
+        FieldClimateFetcher fetcher = new FieldClimateFetcher(config, authService, HttpClient.newHttpSSLClient());
+        FieldClimateProxySession proxySession = new FieldClimateProxySession(fetcher, config.getSessionProxy(), newHttpClient());
+
+//        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.createWithProxySession(proxySession);
+        ExecutableFetcher<FieldClimateModel> executableFetcher = ExecutableFetcher.create(fetcher);
+
+        executableFetcher.getRawFetcher().init();
+
+
+        for (int i = 0; i < 10; i++) {
+            FieldClimateModel fieldClimateModel = executableFetcher.execute();
+            final FieldClimateModelSenslogV1ModelConverter converter = new FieldClimateModelSenslogV1ModelConverter();
+            SenslogV1Model senslogV1Model = converter.convert(fieldClimateModel);
+            System.out.println(senslogV1Model);
+        }
+    }
+*/
+
+    /*
+    private void saveToCSV(FieldClimateModel model) throws IOException {
+
+        String [] header = new String[] {
+                "name", "ch", "code", "serial", "mac", "timestamp"
+        };
+
+        String [] valueType = new String[] {
+                "avg", "min", "last", "max", "sum", "time"
+        };
+        
+        String delimiter = ";";
+
+        for (StationData station : model.getStations()) {
+
+            StringBuilder stationSheet = new StringBuilder();
+
+            // header
+            stationSheet.append(String.join(delimiter, header));
+            stationSheet.append(delimiter);
+            stationSheet.append(String.join(delimiter, valueType));
+            stationSheet.append("\n");
+
+            for (Map<String, String> stationData : station.getData()) {
+                String timestamp = stationData.getOrDefault("date", "");
+
+                Set<String> done = new HashSet<>();
+                for (Map.Entry<String, String> dataEntry : stationData.entrySet()) {
+                    String key = dataEntry.getKey();
+                    String value = dataEntry.getValue();
+
+                    if (key.contains("_")) {
+                        String [] cmp = key.split("_");
+
+                        String ch = cmp[0];
+                        String mac = cmp[1];
+                        String serial = cmp[2];
+                        String code = cmp[3];
+                        String aggr = cmp[4];
+
+                        SensorDataInfo sensor = findSensor(station.getSensors(), Integer.valueOf(ch), mac, serial, Integer.valueOf(code));
+
+                        String sensorHash = format("%s_%s_%s_%s", sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode());
+
+                        if (done.contains(sensorHash)) continue;
+
+                        StringBuilder sensorBuilder = new StringBuilder();
+                        sensorBuilder.append(sensor.getName()).append(delimiter);
+                        sensorBuilder.append(sensor.getCh()).append(delimiter);
+                        sensorBuilder.append(sensor.getCode()).append(delimiter);
+                        sensorBuilder.append(sensor.getSerial()).append(delimiter);
+                        sensorBuilder.append(sensor.getMac());
+                        String sensorLine = sensorBuilder.toString();
+
+                        StringBuilder valueBuilder = new StringBuilder();
+                        for (String type : valueType) {
+
+                            String sensorDataHash = format("%s_%s", sensorHash, type);
+
+                            if (stationData.containsKey(sensorDataHash)) {
+                                valueBuilder.append(stationData.get(sensorDataHash));
+                            }
+                            valueBuilder.append(delimiter);
+                        }
+                        String valuesLine = valueBuilder.toString();
+
+
+                        stationSheet.append(sensorLine).append(delimiter)
+                                .append(timestamp).append(delimiter)
+                                .append(valuesLine).append("\n");
+
+                        done.add(sensorHash);
+                    }
+                }
+            }
+
+            Path filePath = Paths.get("sensor_" + station.getStationId()+ ".csv");
+            Files.write(filePath, Collections.singleton(stationSheet.toString()));
+        }
+    }
+
+    private SensorDataInfo findSensor(List<SensorDataInfo> sensors, Integer ch, String mac, String serial, Integer code) {
+        for (SensorDataInfo sensor : sensors) {
+            if (sensor.getCode().equals(code) &&
+                    sensor.getCh().equals(ch) &&
+                    sensor.getMac().equals(mac) &&
+                    sensor.getSerial().equals(serial)
+            ) {
+                return sensor;
+            }
+        }
+
+        return null;
+    }
+    */
 }

+ 21 - 0
connector-fetch-senslog-v1/license.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 University of West Bohemia (UWB) https://www.zcu.cz/en, LESPROJEKT-SLUŽBY s.r.o. (LESP) https://www.lesprojekt.cz/en
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 41 - 38
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/AllowedStation.java

@@ -1,38 +1,41 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import cz.senslog.connector.model.config.PropertyConfig;
-
-import java.util.*;
-
-class AllowedStation {
-
-    private final Map<Long, Set<Long>> stations;
-
-    AllowedStation(PropertyConfig config) {
-        Set<String> attributes = config.getAttributes();
-        Map<Long, Set<Long>> stations = new HashMap<>(attributes.size());
-        for (String stationId : attributes) {
-            long unitId = Long.parseLong(stationId);
-            Object attrValue = config.getProperty(stationId);
-            if (attrValue instanceof List) {
-                List<?> sensors = (List<?>)attrValue;
-                for (Object sensor : sensors) {
-                    if (sensor instanceof Integer || sensor instanceof Long) {
-                        Long sensorId = Long.parseLong(sensor.toString());
-                        stations.computeIfAbsent(unitId, k -> new HashSet<>()).add(sensorId);
-                    }
-                }
-            }
-        }
-
-        this.stations = stations;
-    }
-
-    public boolean isAllowed(long unitId) {
-        return stations.containsKey(unitId);
-    }
-
-    public boolean isAllowed(long unitId, long sensorId) {
-        return stations.containsKey(unitId) && stations.get(unitId).contains(sensorId);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import cz.senslog.connector.model.config.PropertyConfig;
+
+import java.util.*;
+
+class AllowedStation {
+
+    private final Map<Long, Set<Long>> stations;
+
+    AllowedStation(PropertyConfig config) {
+        Set<String> attributes = config.getAttributes();
+        Map<Long, Set<Long>> stations = new HashMap<>(attributes.size());
+        for (String stationId : attributes) {
+            long unitId = Long.parseLong(stationId);
+            Object attrValue = config.getProperty(stationId);
+            if (attrValue instanceof List) {
+                List<?> sensors = (List<?>)attrValue;
+                for (Object sensor : sensors) {
+                    if (sensor instanceof Integer || sensor instanceof Long) {
+                        Long sensorId = Long.parseLong(sensor.toString());
+                        stations.computeIfAbsent(unitId, k -> new HashSet<>()).add(sensorId);
+                    }
+                }
+            }
+        }
+
+        this.stations = stations;
+    }
+
+    public boolean isAllowed(long unitId) {
+        return stations.containsKey(unitId);
+    }
+
+    public boolean isAllowed(long unitId, long sensorId) {
+        return stations.containsKey(unitId) && stations.get(unitId).contains(sensorId);
+    }
+}

+ 71 - 68
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SensLogSession.java

@@ -1,68 +1,71 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import cz.senslog.connector.model.api.ProxySessionModel;
-
-import java.time.OffsetDateTime;
-import java.util.HashMap;
-import java.util.Map;
-
-public class SensLogSession extends ProxySessionModel {
-
-    public static SensLogSession emptySession() {
-        return new SensLogSession(false);
-    }
-
-    protected static class Info {
-        OffsetDateTime lastFetch;
-
-        Info() {
-            this.lastFetch = OffsetDateTime.MIN;
-        }
-    }
-
-    private OffsetDateTime globalFrom, globalTo;
-    private Map<String, Info> data;
-
-    public SensLogSession(boolean isActive) {
-        super(isActive);
-        this.data = new HashMap<>();
-        this.globalFrom = OffsetDateTime.MAX;
-        this.globalTo = OffsetDateTime.MIN;
-    }
-
-    private String createId(long unitId, long sensorId) {
-        return String.format("%s/%s", unitId, sensorId);
-    }
-
-    public Info getLiveInfo(long unitId, long sensorId) {
-        return getLiveInfo(createId(unitId, sensorId));
-    }
-
-    private Info getLiveInfo(String id) {
-        return data.computeIfAbsent(id, k -> new Info());
-    }
-
-
-    public void updateLastFetch(long unitId, long sensorId, OffsetDateTime dateTime) {
-        Info info = getLiveInfo(createId(unitId, sensorId));
-
-        if (info.lastFetch.isBefore(dateTime)) {
-            info.lastFetch = dateTime;
-        }
-
-        if (dateTime.isBefore(globalFrom)) {
-            this.globalFrom = dateTime;
-        }
-        if (dateTime.isAfter(globalTo)) {
-            this.globalTo = dateTime;
-        }
-    }
-
-    public OffsetDateTime getGlobalFrom() {
-        return globalFrom;
-    }
-
-    public OffsetDateTime getGlobalTo() {
-        return globalTo;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import cz.senslog.connector.model.api.ProxySessionModel;
+
+import java.time.OffsetDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SensLogSession extends ProxySessionModel {
+
+    public static SensLogSession emptySession() {
+        return new SensLogSession(false);
+    }
+
+    protected static class Info {
+        OffsetDateTime lastFetch;
+
+        Info() {
+            this.lastFetch = OffsetDateTime.MIN;
+        }
+    }
+
+    private OffsetDateTime globalFrom, globalTo;
+    private Map<String, Info> data;
+
+    public SensLogSession(boolean isActive) {
+        super(isActive);
+        this.data = new HashMap<>();
+        this.globalFrom = OffsetDateTime.MAX;
+        this.globalTo = OffsetDateTime.MIN;
+    }
+
+    private String createId(long unitId, long sensorId) {
+        return String.format("%s/%s", unitId, sensorId);
+    }
+
+    public Info getLiveInfo(long unitId, long sensorId) {
+        return getLiveInfo(createId(unitId, sensorId));
+    }
+
+    private Info getLiveInfo(String id) {
+        return data.computeIfAbsent(id, k -> new Info());
+    }
+
+
+    public void updateLastFetch(long unitId, long sensorId, OffsetDateTime dateTime) {
+        Info info = getLiveInfo(createId(unitId, sensorId));
+
+        if (info.lastFetch.isBefore(dateTime)) {
+            info.lastFetch = dateTime;
+        }
+
+        if (dateTime.isBefore(globalFrom)) {
+            this.globalFrom = dateTime;
+        }
+        if (dateTime.isAfter(globalTo)) {
+            this.globalTo = dateTime;
+        }
+    }
+
+    public OffsetDateTime getGlobalFrom() {
+        return globalFrom;
+    }
+
+    public OffsetDateTime getGlobalTo() {
+        return globalTo;
+    }
+}

+ 67 - 64
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogConfig.java

@@ -1,64 +1,67 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.model.config.HostConfig;
-
-import java.time.LocalDateTime;
-
-public class SenslogConfig {
-
-    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;
-
-
-    SenslogConfig(DefaultConfig defaultConfig) {
-        this.startDate = defaultConfig.getLocalDateTimeProperty("startDate");
-        this.sensorServiceHost = new HostConfig(defaultConfig.getPropertyConfig("sensorServiceHost"));
-        this.dataServiceHost = new HostConfig(defaultConfig.getPropertyConfig("dataServiceHost"));
-        this.user = defaultConfig.getStringProperty("user");
-        this.group = defaultConfig.getStringProperty("group");
-        this.interval = defaultConfig.getIntegerProperty("interval");
-        this.allowedStation = new AllowedStation(defaultConfig.getPropertyConfig("allowedStation"));
-
-        this.sessionProxy = defaultConfig.containsProperty("sessionProxy") ?
-                new SenslogSessionProxyConfig(defaultConfig.getPropertyConfig("sessionProxy")) : null;
-    }
-
-    public LocalDateTime getStartDate() {
-        return startDate;
-    }
-
-    public HostConfig getSensorServiceHost() {
-        return sensorServiceHost;
-    }
-
-    public HostConfig getDataServiceHost() {
-        return dataServiceHost;
-    }
-
-    public String getUser() {
-        return user;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public Integer getInterval() {
-        return interval;
-    }
-
-    public AllowedStation getAllowedStation() {
-        return allowedStation;
-    }
-
-    public SenslogSessionProxyConfig getSessionProxy() {
-        return sessionProxy;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.config.HostConfig;
+
+import java.time.LocalDateTime;
+
+public class SenslogConfig {
+
+    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;
+
+
+    SenslogConfig(DefaultConfig defaultConfig) {
+        this.startDate = defaultConfig.getLocalDateTimeProperty("startDate");
+        this.sensorServiceHost = new HostConfig(defaultConfig.getPropertyConfig("sensorServiceHost"));
+        this.dataServiceHost = new HostConfig(defaultConfig.getPropertyConfig("dataServiceHost"));
+        this.user = defaultConfig.getStringProperty("user");
+        this.group = defaultConfig.getStringProperty("group");
+        this.interval = defaultConfig.getIntegerProperty("interval");
+        this.allowedStation = new AllowedStation(defaultConfig.getPropertyConfig("allowedStation"));
+
+        this.sessionProxy = defaultConfig.containsProperty("sessionProxy") ?
+                new SenslogSessionProxyConfig(defaultConfig.getPropertyConfig("sessionProxy")) : null;
+    }
+
+    public LocalDateTime getStartDate() {
+        return startDate;
+    }
+
+    public HostConfig getSensorServiceHost() {
+        return sensorServiceHost;
+    }
+
+    public HostConfig getDataServiceHost() {
+        return dataServiceHost;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public Integer getInterval() {
+        return interval;
+    }
+
+    public AllowedStation getAllowedStation() {
+        return allowedStation;
+    }
+
+    public SenslogSessionProxyConfig getSessionProxy() {
+        return sessionProxy;
+    }
+}

+ 48 - 45
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetchProvider.java

@@ -1,45 +1,48 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
-import cz.senslog.connector.fetch.api.ExecutableFetcher;
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.model.v1.SenslogV1Model;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import static cz.senslog.common.http.HttpClient.newHttpClient;
-
-public class SenslogFetchProvider implements ConnectorFetchProvider {
-
-    private static Logger logger = LogManager.getLogger(SenslogFetchProvider.class);
-
-    @Override
-    public ExecutableFetcher<SenslogV1Model> createExecutableFetcher(DefaultConfig defaultConfig) {
-        logger.info("Initialization a new fetch provider {}.", SenslogFetchProvider.class);
-
-        logger.debug("Creating a new configuration.");
-        SenslogConfig config = new SenslogConfig(defaultConfig);
-        logger.info("Configuration for {} was created successfully.", SenslogFetcher.class);
-
-
-        logger.debug("Creating a new instance of {}.", SenslogFetcher.class);
-        SenslogFetcher fetcher = new SenslogFetcher(config, newHttpClient());
-        logger.info("Fetcher for {} was created successfully.", SenslogFetcher.class);
-
-        logger.debug("Getting a configuration for proxy session.");
-        SenslogSessionProxyConfig proxyConfig = config.getSessionProxy();
-
-        ExecutableFetcher<SenslogV1Model> executor;
-        if (proxyConfig != null) {
-            logger.debug("Creating a new instance of {}.", SenslogProxySession.class);
-            SenslogProxySession proxySession = new SenslogProxySession(fetcher, proxyConfig, newHttpClient());
-            logger.info("Fetcher session for {} was created successfully.", SenslogProxySession.class);
-            executor = ExecutableFetcher.createWithProxySession(proxySession);
-        } else {
-            executor = ExecutableFetcher.create(fetcher);
-        }
-        logger.info("Fetcher executor for {} was created successfully.", SenslogFetcher.class);
-
-        return executor;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import cz.senslog.connector.fetch.api.ConnectorFetchProvider;
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static cz.senslog.common.http.HttpClient.newHttpClient;
+
+public class SenslogFetchProvider implements ConnectorFetchProvider {
+
+    private static final Logger logger = LogManager.getLogger(SenslogFetchProvider.class);
+
+    @Override
+    public ExecutableFetcher<SenslogV1Model> createExecutableFetcher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new fetch provider {}.", SenslogFetchProvider.class);
+
+        logger.debug("Creating a new configuration.");
+        SenslogConfig config = new SenslogConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", SenslogFetcher.class);
+
+
+        logger.debug("Creating a new instance of {}.", SenslogFetcher.class);
+        SenslogFetcher fetcher = new SenslogFetcher(config, newHttpClient());
+        logger.info("Fetcher for {} was created successfully.", SenslogFetcher.class);
+
+        logger.debug("Getting a configuration for proxy session.");
+        SenslogSessionProxyConfig proxyConfig = config.getSessionProxy();
+
+        ExecutableFetcher<SenslogV1Model> executor;
+        if (proxyConfig != null) {
+            logger.debug("Creating a new instance of {}.", SenslogProxySession.class);
+            SenslogProxySession proxySession = new SenslogProxySession(fetcher, proxyConfig, newHttpClient());
+            logger.info("Fetcher session for {} was created successfully.", SenslogProxySession.class);
+            executor = ExecutableFetcher.createWithProxySession(proxySession);
+        } else {
+            executor = ExecutableFetcher.create(fetcher);
+        }
+        logger.info("Fetcher executor for {} was created successfully.", SenslogFetcher.class);
+
+        return executor;
+    }
+}

+ 191 - 188
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcher.java

@@ -1,188 +1,191 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import com.google.gson.reflect.TypeToken;
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import cz.senslog.common.util.Tuple;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.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;
-
-import java.lang.reflect.Type;
-import java.time.*;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static java.time.OffsetDateTime.now;
-import static java.time.OffsetDateTime.parse;
-import static java.time.format.DateTimeFormatter.ofPattern;
-
-public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV1Model> {
-
-    private static Logger logger = LogManager.getLogger(SenslogFetcher.class);
-
-    private final SenslogConfig config;
-    private final HttpClient httpClient;
-
-    private final SensLogSession localSession;
-    private final Map<Long, UnitInfo> units;
-
-    SenslogFetcher(SenslogConfig config, HttpClient httpClient) {
-        this.config = config;
-        this.httpClient = httpClient;
-        this.localSession = SensLogSession.emptySession();
-        this.units = new HashMap<>();
-    }
-
-    SenslogFetcher() { this(null, null); }
-
-
-    protected static class ObservationInfo { Float value; OffsetDateTime time; }
-
-    @Override
-    public void init() {
-
-        HostConfig dataServiceHost = config.getDataServiceHost();
-        logger.info("Getting last observations from {}.", dataServiceHost.getDomain());
-
-        HttpRequest unitsRequest = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(dataServiceHost.getDomain(), dataServiceHost.getPath())
-                        .addParam("Operation", "GetUnitsList")
-                        .addParam("user", config.getUser())
-                        .build())
-                .build();
-        logger.info("Creating a http request to {}.", unitsRequest);
-
-        HttpResponse unitsResponse = httpClient.send(unitsRequest);
-        logger.info("Received a response with a status: {} for the domain {}.", unitsResponse.getStatus(), dataServiceHost.getDomain());
-
-        if (unitsResponse.isOk()) {
-            logger.debug("Parsing body of the response to the list of class {}.", UnitInfo.class);
-            Type unitInfoType = new TypeToken<Collection<UnitInfo>>() {}.getType();
-            List<UnitInfo> unitInfos = jsonToObject(unitsResponse.getBody(), unitInfoType);
-
-            for (UnitInfo unit : unitInfos) {
-                if (!config.getAllowedStation().isAllowed(unit.getUnitId())) {
-                    logger.info("Unit {} is not allowd 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())
-                                .addParam("Operation", "GetSensors")
-                                .addParam("user", config.getUser())
-                                .addParam("unit_id", unit.getUnitId())
-                                .build())
-                        .build();
-                logger.info("Creating a http request to {}.", sensorRequest);
-
-                HttpResponse sensorResponse = httpClient.send(sensorRequest);
-                logger.info("Received a response with a status: {} for the domain {}.", sensorResponse.getStatus(), sensorServiceHost.getDomain());
-
-                if (sensorResponse.isOk()) {
-                    logger.debug("Parsing body of the response to the list of class {}.", SensorInfo.class);
-                    Type sensorInfoType = new TypeToken<Collection<SensorInfo>>() {}.getType();
-                    List<SensorInfo> sensors = jsonToObject(sensorResponse.getBody(), 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());
-                    List<SensorInfo> allowedSensors = new ArrayList<>();
-                    for (SensorInfo sensor : sensors) {
-                        if (config.getAllowedStation().isAllowed(unit.getUnitId(), sensor.getSensorId())) {
-                            allowedSensors.add(sensor);
-                        }
-                    }
-                    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) {
-
-        SensLogSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
-
-        List<Record> records = new ArrayList<>();
-
-        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());
-
-                if (toDate.isAfter(now())) {
-                    continue;
-                }
-
-                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())
-                                .addParam("Operation", "GetObservations")
-                                .addParam("user", config.getUser())
-                                .addParam("unit_id", unit.getUnitId())
-                                .addParam("sensor_id", sensor.getSensorId())
-                                .addParam("from", fromDate.format(pattern))
-                                .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();
-                    List<ObservationInfo> observations = jsonToObject(observationResponse.getBody(), observationType,
-                            Tuple.of(OffsetDateTime.class, el -> parse(el + "00", ofPattern("yyyy-MM-dd HH:mm:ssZ")))
-                    );
-
-                    OffsetDateTime lastTimeStamp = toDate;
-                    for (ObservationInfo observation : observations) {
-                        Observation obs = new Observation();
-                        obs.setSensorId(sensor.getSensorId());
-                        obs.setUnitId(unit.getUnitId());
-                        obs.setValue(observation.value);
-                        obs.setTime(observation.time.toZonedDateTime().toOffsetDateTime());
-
-                        records.add(obs);
-
-                        if (observation.time.isAfter(lastTimeStamp)) {
-                            lastTimeStamp = observation.time;
-                        }
-                    }
-
-                    session.updateLastFetch(unit.getUnitId(), sensor.getSensorId(), lastTimeStamp);
-                }
-            }
-        }
-
-        return new SenslogV1Model(units, records, session.getGlobalFrom(), session.getGlobalTo());
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import com.google.gson.reflect.TypeToken;
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import cz.senslog.common.util.Tuple;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.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;
+
+import java.lang.reflect.Type;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static java.time.OffsetDateTime.now;
+import static java.time.OffsetDateTime.parse;
+import static java.time.format.DateTimeFormatter.ofPattern;
+
+public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV1Model> {
+
+    private static final Logger logger = LogManager.getLogger(SenslogFetcher.class);
+
+    private final SenslogConfig config;
+    private final HttpClient httpClient;
+
+    private final SensLogSession localSession;
+    private final Map<Long, UnitInfo> units;
+
+    SenslogFetcher(SenslogConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+        this.localSession = SensLogSession.emptySession();
+        this.units = new HashMap<>();
+    }
+
+    SenslogFetcher() { this(null, null); }
+
+
+    protected static class ObservationInfo { Float value; OffsetDateTime time; }
+
+    @Override
+    public void init() {
+
+        HostConfig dataServiceHost = config.getDataServiceHost();
+        logger.info("Getting last observations from {}.", dataServiceHost.getDomain());
+
+        HttpRequest unitsRequest = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(dataServiceHost.getDomain(), dataServiceHost.getPath())
+                        .addParam("Operation", "GetUnitsList")
+                        .addParam("user", config.getUser())
+                        .build())
+                .build();
+        logger.info("Creating a http request to {}.", unitsRequest);
+
+        HttpResponse unitsResponse = httpClient.send(unitsRequest);
+        logger.info("Received a response with a status: {} for the domain {}.", unitsResponse.getStatus(), dataServiceHost.getDomain());
+
+        if (unitsResponse.isOk()) {
+            logger.debug("Parsing body of the response to the list of class {}.", UnitInfo.class);
+            Type unitInfoType = new TypeToken<Collection<UnitInfo>>() {}.getType();
+            List<UnitInfo> unitInfos = jsonToObject(unitsResponse.getBody(), unitInfoType);
+
+            for (UnitInfo unit : unitInfos) {
+                if (!config.getAllowedStation().isAllowed(unit.getUnitId())) {
+                    logger.info("Unit {} is not allowd 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())
+                                .addParam("Operation", "GetSensors")
+                                .addParam("user", config.getUser())
+                                .addParam("unit_id", unit.getUnitId())
+                                .build())
+                        .build();
+                logger.info("Creating a http request to {}.", sensorRequest);
+
+                HttpResponse sensorResponse = httpClient.send(sensorRequest);
+                logger.info("Received a response with a status: {} for the domain {}.", sensorResponse.getStatus(), sensorServiceHost.getDomain());
+
+                if (sensorResponse.isOk()) {
+                    logger.debug("Parsing body of the response to the list of class {}.", SensorInfo.class);
+                    Type sensorInfoType = new TypeToken<Collection<SensorInfo>>() {}.getType();
+                    List<SensorInfo> sensors = jsonToObject(sensorResponse.getBody(), 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());
+                    List<SensorInfo> allowedSensors = new ArrayList<>();
+                    for (SensorInfo sensor : sensors) {
+                        if (config.getAllowedStation().isAllowed(unit.getUnitId(), sensor.getSensorId())) {
+                            allowedSensors.add(sensor);
+                        }
+                    }
+                    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) {
+
+        SensLogSession session = persistenceSession.filter(ProxySessionModel::isActive).orElse(localSession);
+
+        List<Record> records = new ArrayList<>();
+
+        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());
+
+                if (toDate.isAfter(now())) {
+                    continue;
+                }
+
+                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())
+                                .addParam("Operation", "GetObservations")
+                                .addParam("user", config.getUser())
+                                .addParam("unit_id", unit.getUnitId())
+                                .addParam("sensor_id", sensor.getSensorId())
+                                .addParam("from", fromDate.format(pattern))
+                                .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();
+                    List<ObservationInfo> observations = jsonToObject(observationResponse.getBody(), observationType,
+                            Tuple.of(OffsetDateTime.class, el -> parse(el + "00", ofPattern("yyyy-MM-dd HH:mm:ssZ")))
+                    );
+
+                    OffsetDateTime lastTimeStamp = toDate;
+                    for (ObservationInfo observation : observations) {
+                        Observation obs = new Observation();
+                        obs.setSensorId(sensor.getSensorId());
+                        obs.setUnitId(unit.getUnitId());
+                        obs.setValue(observation.value);
+                        obs.setTime(observation.time.toZonedDateTime().toOffsetDateTime());
+
+                        records.add(obs);
+
+                        if (observation.time.isAfter(lastTimeStamp)) {
+                            lastTimeStamp = observation.time;
+                        }
+                    }
+
+                    session.updateLastFetch(unit.getUnitId(), sensor.getSensorId(), lastTimeStamp);
+                }
+            }
+        }
+
+        return new SenslogV1Model(units, records, session.getGlobalFrom(), session.getGlobalTo());
+    }
+}

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

@@ -1,127 +1,129 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import cz.senslog.common.util.Tuple;
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.api.FetchProxySession;
-import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter;
-import cz.senslog.connector.model.v1.SenslogV1Model;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneId;
-import java.util.List;
-import java.util.Optional;
-
-import static cz.senslog.common.json.BasicJson.jsonToObject;
-import static cz.senslog.connector.fetch.senslog.v1.SensLogSession.emptySession;
-import static java.time.OffsetDateTime.ofInstant;
-
-
-public class SenslogProxySession extends FetchProxySession<SensLogSession, SenslogV1Model> {
-
-    private static Logger logger = LogManager.getLogger(SenslogProxySession.class);
-
-    private final HttpClient httpClient;
-    private final SenslogSessionProxyConfig config;
-
-    public SenslogProxySession(
-            ConnectorFetcher<SensLogSession, SenslogV1Model> instance,
-            SenslogSessionProxyConfig config,
-            HttpClient httpClient
-    ) {
-        super(instance);
-        this.config = config;
-        this.httpClient = httpClient;
-    }
-
-    @Override
-    protected SensLogSession preProcessing(Optional<SensLogSession> previousSession) {
-
-        HostConfig host = config.getLastObservationHost();
-        logger.info("Getting last observations from {}.", host.getDomain());
-
-        HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath()).build())
-                .build();
-        logger.info("Creating a http request to {}.", request);
-
-        HttpResponse response = httpClient.send(request);
-        logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
-
-        if (response.isError()) {
-            logger.error("Can not get data from the server {}. Error {} {}",
-                    host.getDomain(), response.getStatus(), response.getBody());
-            return emptySession();
-        }
-
-        AFCResponse afcResponse = jsonToObject(response.getBody(), AFCResponse.class);
-
-        SensLogSession session = previousSession.filter(SensLogSession::isActive)
-                .orElse(new SensLogSession(true));
-        logger.debug("Created a new session of {}.", SensLogSession.class);
-
-        if (afcResponse == null || afcResponse.results == null || afcResponse.results.resources == null) {
-            return session;
-        }
-
-
-        for (AFCResource resource : afcResponse.results.resources) {
-            String afcUnitId = resource.resource;
-            for (AFCMeasurement measurement : resource.measurements) {
-                String afcSensorId = measurement.measurement;
-
-                Tuple<Long, Long> sensLogIds = AFarCloudUnitSensorConverter.afcToSensLog(afcUnitId, afcSensorId);
-                if (sensLogIds == null) { continue; }
-
-                Long unitId = sensLogIds.getItem1();
-                Long sensorId = sensLogIds.getItem2();;
-
-                for (AFCObservation observation : measurement.observations) {
-                    Instant time = Instant.parse(observation.time);
-                    final ZoneId zoneId = config.getTimeZone().toZoneId();
-                    OffsetDateTime timestamp = ofInstant(time, zoneId);
-                    session.updateLastFetch(unitId, sensorId, timestamp);
-                }
-            }
-        }
-
-        return session;
-    }
-
-    @Override
-    protected void postProcessing(Optional<SenslogV1Model> model, Optional<SensLogSession> currentSession) {
-        if (currentSession.isPresent() && currentSession.get().isActive()) {
-            SensLogSession session = currentSession.get();
-            // TODO persistence the current session
-        }
-    }
-
-    private static class AFCResponse {
-        AFCResults results;
-    }
-
-    private static class AFCResults {
-        List<AFCResource> resources;
-    }
-
-    private static class AFCResource {
-        String resource;
-        List<AFCMeasurement> measurements;
-    }
-
-    private static class AFCMeasurement {
-        String measurement;
-        List<AFCObservation> observations;
-    }
-
-    private static class AFCObservation {
-        String time;
-    }
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import cz.senslog.common.util.Tuple;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.fetch.api.FetchProxySession;
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.Optional;
+
+import static cz.senslog.common.json.BasicJson.jsonToObject;
+import static cz.senslog.connector.fetch.senslog.v1.SensLogSession.emptySession;
+import static java.time.OffsetDateTime.ofInstant;
+
+
+public class SenslogProxySession extends FetchProxySession<SensLogSession, SenslogV1Model> {
+
+    private static final Logger logger = LogManager.getLogger(SenslogProxySession.class);
+
+    private final HttpClient httpClient;
+    private final SenslogSessionProxyConfig config;
+
+    public SenslogProxySession(
+            ConnectorFetcher<SensLogSession, SenslogV1Model> instance,
+            SenslogSessionProxyConfig config,
+            HttpClient httpClient
+    ) {
+        super(instance);
+        this.config = config;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    protected SensLogSession preProcessing(Optional<SensLogSession> previousSession) {
+
+        HostConfig host = config.getLastObservationHost();
+        logger.info("Getting last observations from {}.", host.getDomain());
+
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath()).build())
+                .build();
+        logger.info("Creating a http request to {}.", request);
+
+        HttpResponse response = httpClient.send(request);
+        logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
+
+        if (response.isError()) {
+            logger.error("Can not get data from the server {}. Error {} {}",
+                    host.getDomain(), response.getStatus(), response.getBody());
+            return previousSession.orElse(emptySession());
+        }
+
+        AFCResponse afcResponse = jsonToObject(response.getBody(), AFCResponse.class);
+
+        SensLogSession session = previousSession.filter(SensLogSession::isActive)
+                .orElse(new SensLogSession(true));
+        logger.debug("Created a new session of {}.", SensLogSession.class);
+
+        if (afcResponse == null || afcResponse.results == null || afcResponse.results.resources == null) {
+            return session;
+        }
+
+        for (AFCResource resource : afcResponse.results.resources) {
+            String afcUnitId = resource.resource;
+            for (AFCMeasurement measurement : resource.measurements) {
+                String afcSensorId = measurement.measurement;
+
+                Tuple<Long, Long> sensLogIds = AFarCloudUnitSensorConverter.afcToSensLog(afcUnitId, afcSensorId);
+                if (sensLogIds == null) { continue; }
+
+                Long unitId = sensLogIds.getItem1();
+                Long sensorId = sensLogIds.getItem2();;
+
+                for (AFCObservation observation : measurement.observations) {
+                    Instant time = Instant.parse(observation.time);
+                    final ZoneId zoneId = config.getTimeZone().toZoneId();
+                    OffsetDateTime timestamp = ofInstant(time, zoneId);
+                    session.updateLastFetch(unitId, sensorId, timestamp);
+                }
+            }
+        }
+
+        return session;
+    }
+
+    @Override
+    protected void postProcessing(Optional<SenslogV1Model> model, Optional<SensLogSession> currentSession) {
+        if (currentSession.isPresent() && currentSession.get().isActive()) {
+            SensLogSession session = currentSession.get();
+            // TODO persistence the current session
+        }
+    }
+
+    private static class AFCResponse {
+        AFCResults results;
+    }
+
+    private static class AFCResults {
+        List<AFCResource> resources;
+    }
+
+    private static class AFCResource {
+        String resource;
+        List<AFCMeasurement> measurements;
+    }
+
+    private static class AFCMeasurement {
+        String measurement;
+        List<AFCObservation> observations;
+    }
+
+    private static class AFCObservation {
+        String time;
+    }
 }

+ 28 - 25
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogSessionProxyConfig.java

@@ -1,25 +1,28 @@
-package cz.senslog.connector.fetch.senslog.v1;
-
-import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.connector.model.config.PropertyConfig;
-
-import java.util.TimeZone;
-
-public class SenslogSessionProxyConfig {
-
-    private final HostConfig lastObservationHost;
-    private final TimeZone timeZone;
-
-    public SenslogSessionProxyConfig(PropertyConfig config) {
-        this.lastObservationHost = new HostConfig(config.getPropertyConfig("lastObservationHost"));
-        this.timeZone = TimeZone.getTimeZone(config.getStringProperty("timeZone"));
-    }
-
-    public HostConfig getLastObservationHost() {
-        return lastObservationHost;
-    }
-
-    public TimeZone getTimeZone() {
-        return timeZone;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.fetch.senslog.v1;
+
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.connector.model.config.PropertyConfig;
+
+import java.util.TimeZone;
+
+public class SenslogSessionProxyConfig {
+
+    private final HostConfig lastObservationHost;
+    private final TimeZone timeZone;
+
+    public SenslogSessionProxyConfig(PropertyConfig config) {
+        this.lastObservationHost = new HostConfig(config.getPropertyConfig("lastObservationHost"));
+        this.timeZone = TimeZone.getTimeZone(config.getStringProperty("timeZone"));
+    }
+
+    public HostConfig getLastObservationHost() {
+        return lastObservationHost;
+    }
+
+    public TimeZone getTimeZone() {
+        return timeZone;
+    }
+}

+ 21 - 0
connector-model/license.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 University of West Bohemia (UWB) https://www.zcu.cz/en, LESPROJEKT-SLUŽBY s.r.o. (LESP) https://www.lesprojekt.cz/en
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 23 - 20
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/AFarCloudModel.java

@@ -1,20 +1,23 @@
-package cz.senslog.connector.model.afarcloud;
-
-import cz.senslog.connector.model.api.AbstractModel;
-
-import java.time.OffsetDateTime;
-import java.util.List;
-
-public class AFarCloudModel extends AbstractModel {
-
-    private final List<MultiSimpleObservation> unitAggObservations;
-
-    public AFarCloudModel(List<MultiSimpleObservation> unitAggObservations, OffsetDateTime from, OffsetDateTime to) {
-        super(from, to);
-        this.unitAggObservations = unitAggObservations;
-    }
-
-    public List<MultiSimpleObservation> getUnitAggObservations() {
-        return unitAggObservations;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+import cz.senslog.connector.model.api.AbstractModel;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+
+public class AFarCloudModel extends AbstractModel {
+
+    private final List<? extends MultiMeasurement> unitAggObservations;
+
+    public AFarCloudModel(List<? extends MultiMeasurement> unitAggObservations, OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+        this.unitAggObservations = unitAggObservations;
+    }
+
+    public List<? extends MultiMeasurement> getUnitAggObservations() {
+        return unitAggObservations;
+    }
+}

+ 33 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/CollarAcceleration.java

@@ -0,0 +1,33 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+public class CollarAcceleration {
+
+    private double accX, accY, accZ;
+
+    public double getAccX() {
+        return accX;
+    }
+
+    public void setAccX(double accX) {
+        this.accX = accX;
+    }
+
+    public double getAccY() {
+        return accY;
+    }
+
+    public void setAccY(double accY) {
+        this.accY = accY;
+    }
+
+    public double getAccZ() {
+        return accZ;
+    }
+
+    public void setAccZ(double accZ) {
+        this.accZ = accZ;
+    }
+}

+ 49 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/CollarAnomalies.java

@@ -0,0 +1,49 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+public class CollarAnomalies {
+
+    private boolean locationAnomaly, temperatureAnomaly, distanceAnomaly, activityAnomaly, positionAnomaly;
+
+    public boolean isLocationAnomaly() {
+        return locationAnomaly;
+    }
+
+    public void setLocationAnomaly(boolean locationAnomaly) {
+        this.locationAnomaly = locationAnomaly;
+    }
+
+    public boolean isTemperatureAnomaly() {
+        return temperatureAnomaly;
+    }
+
+    public void setTemperatureAnomaly(boolean temperatureAnomaly) {
+        this.temperatureAnomaly = temperatureAnomaly;
+    }
+
+    public boolean isDistanceAnomaly() {
+        return distanceAnomaly;
+    }
+
+    public void setDistanceAnomaly(boolean distanceAnomaly) {
+        this.distanceAnomaly = distanceAnomaly;
+    }
+
+    public boolean isActivityAnomaly() {
+        return activityAnomaly;
+    }
+
+    public void setActivityAnomaly(boolean activityAnomaly) {
+        this.activityAnomaly = activityAnomaly;
+    }
+
+    public boolean isPositionAnomaly() {
+        return positionAnomaly;
+    }
+
+    public void setPositionAnomaly(boolean positionAnomaly) {
+        this.positionAnomaly = positionAnomaly;
+    }
+}

+ 71 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/CollarMeasurement.java

@@ -0,0 +1,71 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+public class CollarMeasurement {
+
+    private String resourceId;
+    private Location location;
+    private long resultTime;
+    private boolean resourceAlarm;
+    private CollarAcceleration acceleration;
+    private CollarAcceleration anomalies;
+    private float temperature;
+
+    public String getResourceId() {
+        return resourceId;
+    }
+
+    public void setResourceId(String resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public Location getLocation() {
+        return location;
+    }
+
+    public void setLocation(Location location) {
+        this.location = location;
+    }
+
+    public long getResultTime() {
+        return resultTime;
+    }
+
+    public void setResultTime(long resultTime) {
+        this.resultTime = resultTime;
+    }
+
+    public boolean isResourceAlarm() {
+        return resourceAlarm;
+    }
+
+    public void setResourceAlarm(boolean resourceAlarm) {
+        this.resourceAlarm = resourceAlarm;
+    }
+
+    public CollarAcceleration getAcceleration() {
+        return acceleration;
+    }
+
+    public void setAcceleration(CollarAcceleration acceleration) {
+        this.acceleration = acceleration;
+    }
+
+    public CollarAcceleration getAnomalies() {
+        return anomalies;
+    }
+
+    public void setAnomalies(CollarAcceleration anomalies) {
+        this.anomalies = anomalies;
+    }
+
+    public float getTemperature() {
+        return temperature;
+    }
+
+    public void setTemperature(float temperature) {
+        this.temperature = temperature;
+    }
+}

+ 35 - 32
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/Location.java

@@ -1,32 +1,35 @@
-package cz.senslog.connector.model.afarcloud;
-
-public class Location {
-
-    private Double latitude;
-    private Double longitude;
-    private Double altitude;
-
-    public Double getLatitude() {
-        return latitude;
-    }
-
-    public void setLatitude(Double latitude) {
-        this.latitude = latitude;
-    }
-
-    public Double getLongitude() {
-        return longitude;
-    }
-
-    public void setLongitude(Double longitude) {
-        this.longitude = longitude;
-    }
-
-    public Double getAltitude() {
-        return altitude;
-    }
-
-    public void setAltitude(Double altitude) {
-        this.altitude = altitude;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+public class Location {
+
+    private Double latitude;
+    private Double longitude;
+    private Double altitude;
+
+    public Double getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(Double latitude) {
+        this.latitude = latitude;
+    }
+
+    public Double getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(Double longitude) {
+        this.longitude = longitude;
+    }
+
+    public Double getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(Double altitude) {
+        this.altitude = altitude;
+    }
+}

+ 8 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MeasurementType.java

@@ -0,0 +1,8 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+public enum MeasurementType {
+    SENSOR, COLLAR
+}

+ 39 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiCollarMeasurements.java

@@ -0,0 +1,39 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+import java.util.List;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+public class MultiCollarMeasurements extends MultiMeasurement {
+
+    private List<CollarMeasurement> collars;
+    private int sequenceNumber;
+
+    public MultiCollarMeasurements() {
+        super(MeasurementType.COLLAR);
+    }
+
+    public List<CollarMeasurement> getCollars() {
+        return collars;
+    }
+
+    public void setCollars(List<CollarMeasurement> collars) {
+        this.collars = collars;
+    }
+
+    public int getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    public void setSequenceNumber(int sequenceNumber) {
+        this.sequenceNumber = sequenceNumber;
+    }
+
+    @Override
+    public String toJson() {
+        return objectToJson(this);
+    }
+}

+ 18 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiMeasurement.java

@@ -0,0 +1,18 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+import cz.senslog.connector.model.api.JsonSerializable;
+
+public abstract class MultiMeasurement implements JsonSerializable {
+//    private final MeasurementType type;
+
+    protected MultiMeasurement(MeasurementType type) {
+//        this.type = type;
+    }
+
+//    public MeasurementType getType() {
+//        return type;
+//    }
+}

+ 48 - 0
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiSensorMeasurements.java

@@ -0,0 +1,48 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+import java.util.List;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+public class MultiSensorMeasurements extends MultiMeasurement {
+
+    private String resourceId;
+    private Location location;
+    private List<SensorMeasurement> observations;
+
+    public MultiSensorMeasurements() {
+        super(MeasurementType.SENSOR);
+    }
+
+    public String getResourceId() {
+        return resourceId;
+    }
+
+    public void setResourceId(String resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public Location getLocation() {
+        return location;
+    }
+
+    public void setLocation(Location location) {
+        this.location = location;
+    }
+
+    public List<SensorMeasurement> getObservations() {
+        return observations;
+    }
+
+    public void setObservations(List<SensorMeasurement> observations) {
+        this.observations = observations;
+    }
+
+    @Override
+    public String toJson() {
+        return objectToJson(this);
+    }
+}

+ 0 - 41
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/MultiSimpleObservation.java

@@ -1,41 +0,0 @@
-package cz.senslog.connector.model.afarcloud;
-
-import java.util.List;
-
-public class MultiSimpleObservation {
-
-    private String resourceId;
-    private Location location;
-    private List<SimpleObservation> observations;
-
-    public String getResourceId() {
-        return resourceId;
-    }
-
-    public void setResourceId(String resourceId) {
-        this.resourceId = resourceId;
-    }
-
-    public Location getLocation() {
-        return location;
-    }
-
-    public void setLocation(Location location) {
-        this.location = location;
-    }
-
-    public List<SimpleObservation> getObservations() {
-        return observations;
-    }
-
-    public void setObservations(List<SimpleObservation> observations) {
-        this.observations = observations;
-    }
-
-    @Override
-    public String toString() {
-        return "MultiSimpleObservation{" +
-                "resourceId='" + resourceId + '\'' +
-                '}';
-    }
-}

+ 48 - 45
connector-model/src/main/java/cz/senslog/connector/model/afarcloud/SimpleObservation.java → connector-model/src/main/java/cz/senslog/connector/model/afarcloud/SensorMeasurement.java

@@ -1,45 +1,48 @@
-package cz.senslog.connector.model.afarcloud;
-
-public class SimpleObservation {
-
-    protected static class Result {
-        Double value;
-        String uom;
-    }
-
-    private String observedProperty;
-    private Long resultTime;
-    private Result result;
-
-    public String getObservedProperty() {
-        return observedProperty;
-    }
-
-    public void setObservedProperty(String observedProperty) {
-        this.observedProperty = observedProperty;
-    }
-
-    public Long getResultTime() {
-        return resultTime;
-    }
-
-    public void setResultTime(Long resultTime) {
-        this.resultTime = resultTime;
-    }
-
-
-    public Result getResult() {
-        return result;
-    }
-
-    public void setResult(Double value, String uom) {
-        this.result = new Result();
-        this.result.value = value;
-        this.result.uom = uom;
-    }
-
-    public void setResult(Double value) {
-        this.result = new Result();
-        this.result.value = value;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.afarcloud;
+
+public class SensorMeasurement {
+
+    protected static class Result {
+        Double value;
+        String uom;
+    }
+
+    private String observedProperty;
+    private Long resultTime;
+    private Result result;
+
+    public String getObservedProperty() {
+        return observedProperty;
+    }
+
+    public void setObservedProperty(String observedProperty) {
+        this.observedProperty = observedProperty;
+    }
+
+    public Long getResultTime() {
+        return resultTime;
+    }
+
+    public void setResultTime(Long resultTime) {
+        this.resultTime = resultTime;
+    }
+
+
+    public Result getResult() {
+        return result;
+    }
+
+    public void setResult(Double value, String uom) {
+        this.result = new Result();
+        this.result.value = value;
+        this.result.uom = uom;
+    }
+
+    public void setResult(Double value) {
+        this.result = new Result();
+        this.result.value = value;
+    }
+}

+ 33 - 30
connector-model/src/main/java/cz/senslog/connector/model/api/AbstractModel.java

@@ -1,30 +1,33 @@
-package cz.senslog.connector.model.api;
-
-import java.time.OffsetDateTime;
-
-/**
- * The abstract class {@code AbstractModel} represents a base class
- * for all models which want to be used as a transfer model for a connector.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public abstract class AbstractModel {
-
-    /** Time range from until were gotten the data. */
-    private final OffsetDateTime from, to;
-
-    protected AbstractModel(OffsetDateTime from, OffsetDateTime to) {
-        this.from = from;
-        this.to = to;
-    }
-
-    public OffsetDateTime getFrom() {
-        return from;
-    }
-
-    public OffsetDateTime getTo() {
-        return to;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.api;
+
+import java.time.OffsetDateTime;
+
+/**
+ * The abstract class {@code AbstractModel} represents a base class
+ * for all models which want to be used as a transfer model for a connector.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public abstract class AbstractModel {
+
+    /** Time range from until were gotten the data. */
+    private final OffsetDateTime from, to;
+
+    protected AbstractModel(OffsetDateTime from, OffsetDateTime to) {
+        this.from = from;
+        this.to = to;
+    }
+
+    public OffsetDateTime getFrom() {
+        return from;
+    }
+
+    public OffsetDateTime getTo() {
+        return to;
+    }
+}

+ 27 - 24
connector-model/src/main/java/cz/senslog/connector/model/api/Converter.java

@@ -1,24 +1,27 @@
-package cz.senslog.connector.model.api;
-
-/**
- * The interface {@code Converter} provides a generic functionality
- * for converter. Each class which implements which interface can be registered
- * as a converter for a connector. For converter can be used only classes which extend {@link AbstractModel}.
- *
- *
- * @param <IN> which type of class will be used as an input of a converter.
- * @param <OUT> which type of class will be used as an output of a converter.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public interface Converter<IN extends AbstractModel, OUT extends AbstractModel> {
-
-    /**
-     * Provides an interface for converting from input to output model.
-     * @param model - model which is converted to output.
-     * @return converted input model.
-     */
-    OUT convert(IN model);
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.api;
+
+/**
+ * The interface {@code Converter} provides a generic functionality
+ * for converter. Each class which implements which interface can be registered
+ * as a converter for a connector. For converter can be used only classes which extend {@link AbstractModel}.
+ *
+ *
+ * @param <IN> which type of class will be used as an input of a converter.
+ * @param <OUT> which type of class will be used as an output of a converter.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public interface Converter<IN extends AbstractModel, OUT extends AbstractModel> {
+
+    /**
+     * Provides an interface for converting from input to output model.
+     * @param model - model which is converted to output.
+     * @return converted input model.
+     */
+    OUT convert(IN model);
+}

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

@@ -1,63 +1,66 @@
-package cz.senslog.connector.model.api;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.lang.reflect.ParameterizedType;
-import java.util.ArrayList;
-import java.util.List;
-
-public abstract class ConverterProvider {
-
-    private static Logger logger = LogManager.getLogger(ConverterProvider.class);
-
-    private static class Wrapper {
-        private final Class input;
-        private final Class output;
-        private final Converter converter;
-        private Wrapper(Class input, Class output, Converter converter) {
-            this.input = input;
-            this.output = output;
-            this.converter = converter;
-        }
-    }
-
-    private final List<Wrapper> CONVERTERS = new ArrayList<>();
-
-    protected ConverterProvider() {
-        config();
-    }
-
-    protected abstract void config();
-
-    protected void register(Class<? extends Converter> converterClass) {
-        logger.debug("Registering a new converter {}", converterClass);
-        try {
-            logger.debug("Getting a generic parameters from the class {}.", converterClass);
-            ParameterizedType converterTypes = (ParameterizedType) converterClass.getGenericInterfaces()[0];
-            Class fetchModel = (Class) converterTypes.getActualTypeArguments()[0];
-            Class pushModel = (Class) converterTypes.getActualTypeArguments()[1];
-
-            logger.debug("Creating a new instance of the class {}.", converterClass);
-            Converter converter = converterClass.newInstance();
-
-            CONVERTERS.add(new Wrapper(fetchModel, pushModel, converter));
-            logger.info("Registered a new converter {} for {} -> {}.", converterClass, fetchModel, pushModel);
-        } catch (InstantiationException | IllegalAccessException e) {
-            logger.error("Can not create an instance for {}.", converterClass);
-            logger.catching(e);
-        }
-
-    }
-
-    public Converter getConverter(Class<? extends AbstractModel> fetchModel, Class<? extends AbstractModel> pushModel) {
-        logger.info("Getting a converter for {} -> {}. ", fetchModel, pushModel);
-        for (Wrapper item : CONVERTERS) {
-            if (item.input.equals(fetchModel) && item.output.equals(pushModel)) {
-                return item.converter;
-            }
-        }
-        logger.warn("The converter for {} -> {} was not found.", fetchModel, pushModel);
-        return null;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.api;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class ConverterProvider {
+
+    private static final Logger logger = LogManager.getLogger(ConverterProvider.class);
+
+    private static class Wrapper {
+        private final Class<?> input;
+        private final Class<?> output;
+        private final Converter<?,?> converter;
+        private Wrapper(Class<?> input, Class<?> output, Converter<?,?> converter) {
+            this.input = input;
+            this.output = output;
+            this.converter = converter;
+        }
+    }
+
+    private final List<Wrapper> CONVERTERS = new ArrayList<>();
+
+    protected ConverterProvider() {
+        config();
+    }
+
+    protected abstract void config();
+
+    protected void register(Class<? extends Converter<?,?>> converterClass) {
+        logger.debug("Registering a new converter {}", converterClass);
+        try {
+            logger.debug("Getting a generic parameters from the class {}.", converterClass);
+            ParameterizedType converterTypes = (ParameterizedType) converterClass.getGenericInterfaces()[0];
+            Class<?> fetchModel = (Class<?>) converterTypes.getActualTypeArguments()[0];
+            Class<?> pushModel = (Class<?>) converterTypes.getActualTypeArguments()[1];
+
+            logger.debug("Creating a new instance of the class {}.", converterClass);
+            Converter<?,?> converter = converterClass.newInstance();
+
+            CONVERTERS.add(new Wrapper(fetchModel, pushModel, converter));
+            logger.info("Registered a new converter {} for {} -> {}.", converterClass, fetchModel, pushModel);
+        } catch (InstantiationException | IllegalAccessException e) {
+            logger.error("Can not create an instance for {}.", converterClass);
+            logger.catching(e);
+        }
+
+    }
+
+    public Converter getConverter(Class<? extends AbstractModel> fetchModel, Class<? extends AbstractModel> pushModel) {
+        logger.info("Getting a converter for {} -> {}. ", fetchModel, pushModel);
+        for (Wrapper item : CONVERTERS) {
+            if (item.input.equals(fetchModel) && item.output.equals(pushModel)) {
+                return item.converter;
+            }
+        }
+        logger.warn("The converter for {} -> {} was not found.", fetchModel, pushModel);
+        return null;
+    }
+}

+ 8 - 0
connector-model/src/main/java/cz/senslog/connector/model/api/JsonSerializable.java

@@ -0,0 +1,8 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.api;
+
+public interface JsonSerializable {
+    String toJson();
+}

+ 22 - 19
connector-model/src/main/java/cz/senslog/connector/model/api/ProxySessionModel.java

@@ -1,19 +1,22 @@
-package cz.senslog.connector.model.api;
-
-public abstract class ProxySessionModel {
-
-    private final boolean isActive;
-
-    public ProxySessionModel(boolean isActive) {
-        this.isActive = isActive;
-    }
-
-    public boolean isActive() {
-        return isActive;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s.%s", getClass().getSimpleName(), (isActive() ? "active" : "disable"));
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.api;
+
+public abstract class ProxySessionModel {
+
+    private final boolean isActive;
+
+    public ProxySessionModel(boolean isActive) {
+        this.isActive = isActive;
+    }
+
+    public boolean isActive() {
+        return isActive;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s.%s", getClass().getSimpleName(), (isActive() ? "active" : "disable"));
+    }
+}

+ 66 - 13
connector-model/src/main/java/cz/senslog/connector/model/azure/SensorData.java

@@ -4,12 +4,9 @@ import cz.senslog.common.util.NumberUtils;
 import org.apache.logging.log4j.util.BiConsumer;
 
 import java.time.OffsetDateTime;
-import java.time.ZonedDateTime;
 import java.util.HashMap;
 import java.util.Map;
 
-import static cz.senslog.connector.model.azure.SensorType.*;
-
 /**
  * The class {@code SensorData} represents transfer object of measure
  * data from each sensor depends on time.
@@ -41,6 +38,16 @@ public class SensorData {
     /** Value of battery level. */
     private Float batteryLevel;
 
+    private Integer vitality;
+
+    private Integer rumination;
+
+    private Integer pedometer;
+
+    private Float pH;
+
+    private Integer orp;
+
     public OffsetDateTime getTime() {
         return time;
     }
@@ -97,18 +104,64 @@ public class SensorData {
         this.batteryLevel = batteryLevel;
     }
 
-    public Map<SensorType, Float> getValues() {
-        Map<SensorType, Float> values = new HashMap<>();
-        BiConsumer<SensorType, Float> setValue = (type, value) -> {
-          if (value != null) values.put(type, value);
+    public Integer getVitality() {
+        return vitality;
+    }
+
+    public void setVitality(Integer vitality) {
+        this.vitality = vitality;
+    }
+
+    public Integer getRumination() {
+        return rumination;
+    }
+
+    public void setRumination(Integer rumination) {
+        this.rumination = rumination;
+    }
+
+    public Integer getPedometer() {
+        return pedometer;
+    }
+
+    public void setPedometer(Integer pedometer) {
+        this.pedometer = pedometer;
+    }
+
+    public Float getpH() {
+        return pH;
+    }
+
+    public void setpH(Float pH) {
+        this.pH = pH;
+    }
+
+    public Integer getOrp() {
+        return orp;
+    }
+
+    public void setOrp(Integer orp) {
+        this.orp = orp;
+    }
+
+    public Map<String, Float> getValues() {
+        Map<String, Float> values = new HashMap<>();
+        BiConsumer<String, Float> setValue = (type, value) -> {
+          if (value != null) { values.put(type, value); }
         };
 
-        setValue.accept(BATTERY_LEVEL, batteryLevel);
-        setValue.accept(SNR, snr);
-        setValue.accept(RSSI, NumberUtils.valueOf(rssi));
-        setValue.accept(CO2, co2);
-        setValue.accept(HUMIDITY, humidity);
-        setValue.accept(TEMPERATURE, temperature);
+        setValue.accept("batteryLevel", batteryLevel);
+        setValue.accept("snr", snr);
+        setValue.accept("rssi", NumberUtils.valueOf(rssi));
+        setValue.accept("co2", co2);
+        setValue.accept("humidity", humidity);
+        setValue.accept("temperature", temperature);
+
+        setValue.accept("vitality", NumberUtils.valueOf(vitality));
+        setValue.accept("rumination", NumberUtils.valueOf(rumination));
+        setValue.accept("pedometer", NumberUtils.valueOf(pedometer));
+        setValue.accept("pH", pH);
+        setValue.accept("ORP", NumberUtils.valueOf(orp));
 
         return values;
     }

+ 188 - 171
connector-model/src/main/java/cz/senslog/connector/model/azure/SensorInfo.java

@@ -1,171 +1,188 @@
-package cz.senslog.connector.model.azure;
-
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.util.List;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-import static java.lang.Boolean.TRUE;
-import static java.util.Collections.emptyList;
-
-/**
- * The class {@code SensorInfo} represents transfer object of sensors.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class SensorInfo {
-
-    /** Identifier of sensor. */
-    private String eui;
-
-    /** Name of sensor. */
-    private String name;
-
-    /** Acronym of sensor. */
-    private String acronym;
-
-    /**  */
-    private Integer period;
-
-    /**  */
-    private Integer tolerance;
-
-    /** Place where the sensor is located. */
-    private String location;
-
-    /** Description of what sensor does. */
-    private String description;
-
-    /** */
-    private Long sensorDataId;
-
-    /**  */
-    private OffsetDateTime sensorDataTime;
-
-    /** Time when sensor was created and started to produce data. */
-    private LocalDateTime created;
-
-    /** Time when sensor was deleted and stopped to produce data. */
-    private LocalDateTime deleted;
-
-    /** Status of sensor: 0 - inactive, 1 - active. */
-    private Integer status;
-
-    /** List of measured data */
-    private List<SensorData> data = emptyList();
-
-    public String getEui() {
-        return eui;
-    }
-
-    public void setEui(String eui) {
-        this.eui = eui;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getAcronym() {
-        return acronym;
-    }
-
-    public void setAcronym(String acronym) {
-        this.acronym = acronym;
-    }
-
-    public Integer getPeriod() {
-        return period;
-    }
-
-    public void setPeriod(Integer period) {
-        this.period = period;
-    }
-
-    public Integer getTolerance() {
-        return tolerance;
-    }
-
-    public void setTolerance(Integer tolerance) {
-        this.tolerance = tolerance;
-    }
-
-    public String getLocation() {
-        return location;
-    }
-
-    public void setLocation(String location) {
-        this.location = location;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public Long getSensorDataId() {
-        return sensorDataId;
-    }
-
-    public void setSensorDataId(Long sensorDataId) {
-        this.sensorDataId = sensorDataId;
-    }
-
-    public OffsetDateTime getSensorDataTime() {
-        return sensorDataTime;
-    }
-
-    public void setSensorDataTime(OffsetDateTime sensorDataTime) {
-        this.sensorDataTime = sensorDataTime;
-    }
-
-    public LocalDateTime getCreated() {
-        return created;
-    }
-
-    public void setCreated(LocalDateTime created) {
-        this.created = created;
-    }
-
-    public LocalDateTime getDeleted() {
-        return deleted;
-    }
-
-    public void setDeleted(LocalDateTime deleted) {
-        this.deleted = deleted;
-    }
-
-    public Boolean getStatus() {
-        return status != null && status > 0;
-    }
-
-    public boolean isActive() {
-        return getStatus().equals(TRUE);
-    }
-
-    public void setStatus(Integer status) {
-        this.status = status;
-    }
-
-    public List<SensorData> getData() {
-        return data;
-    }
-
-    public void setData(List<SensorData> data) {
-        this.data = data;
-    }
-
-    @Override
-    public String toString() {
-        return objectToJson(this);
-    }
-}
+package cz.senslog.connector.model.azure;
+
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+import static java.lang.Boolean.TRUE;
+
+/**
+ * The class {@code SensorInfo} represents transfer object of sensors.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class SensorInfo {
+
+    /** Identifier of sensor. */
+    private String eui;
+
+    /** Name of sensor. */
+    private String name;
+
+    /** Acronym of sensor. */
+    private String acronym;
+
+    /**  */
+    private Integer period;
+
+    /**  */
+    private Integer tolerance;
+
+    /** Place where the sensor is located. */
+    private String location;
+
+    /** Description of what sensor does. */
+    private String description;
+
+    /** */
+    private Long sensorDataId;
+
+    /**  */
+    private OffsetDateTime sensorDataTime;
+
+    /** Time when sensor was created and started to produce data. */
+    private LocalDateTime created;
+
+    /** Time when sensor was deleted and stopped to produce data. */
+    private LocalDateTime deleted;
+
+    /** Status of sensor: 0 - inactive, 1 - active. */
+    private Integer status;
+
+    /** List of measured data */
+    private List<SensorData> data;
+
+    public SensorInfo(SensorInfo sensorInfo, List<SensorData> newData) {
+        this.eui = sensorInfo.eui;
+        this.name = sensorInfo.name;
+        this.acronym = sensorInfo.acronym;
+        this.period = sensorInfo.period;
+        this.tolerance = sensorInfo.tolerance;
+        this.location = sensorInfo.location;
+        this.description = sensorInfo.description;
+        this.sensorDataId = sensorInfo.sensorDataId;
+        this.sensorDataTime = sensorInfo.sensorDataTime;
+        this.created = sensorInfo.created;
+        this.deleted = sensorInfo.deleted;
+        this.status = sensorInfo.status;
+        this.data = newData;
+    }
+
+    public SensorInfo() {}
+
+    public String getEui() {
+        return eui;
+    }
+
+    public void setEui(String eui) {
+        this.eui = eui;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getAcronym() {
+        return acronym;
+    }
+
+    public void setAcronym(String acronym) {
+        this.acronym = acronym;
+    }
+
+    public Integer getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(Integer period) {
+        this.period = period;
+    }
+
+    public Integer getTolerance() {
+        return tolerance;
+    }
+
+    public void setTolerance(Integer tolerance) {
+        this.tolerance = tolerance;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Long getSensorDataId() {
+        return sensorDataId;
+    }
+
+    public void setSensorDataId(Long sensorDataId) {
+        this.sensorDataId = sensorDataId;
+    }
+
+    public OffsetDateTime getSensorDataTime() {
+        return sensorDataTime;
+    }
+
+    public void setSensorDataTime(OffsetDateTime sensorDataTime) {
+        this.sensorDataTime = sensorDataTime;
+    }
+
+    public LocalDateTime getCreated() {
+        return created;
+    }
+
+    public void setCreated(LocalDateTime created) {
+        this.created = created;
+    }
+
+    public LocalDateTime getDeleted() {
+        return deleted;
+    }
+
+    public void setDeleted(LocalDateTime deleted) {
+        this.deleted = deleted;
+    }
+
+    public Boolean getStatus() {
+        return status != null && status > 0;
+    }
+
+    public boolean isActive() {
+        return getStatus().equals(TRUE);
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public List<SensorData> getData() {
+        return data;
+    }
+
+    public void setData(List<SensorData> data) {
+        this.data = data;
+    }
+
+    @Override
+    public String toString() {
+        return objectToJson(this);
+    }
+}

+ 0 - 34
connector-model/src/main/java/cz/senslog/connector/model/azure/SensorType.java

@@ -1,34 +0,0 @@
-package cz.senslog.connector.model.azure;
-
-/**
- * The enum {@code SensorType} represents type of sensor
- * which each physical quantity is represents by identifier.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public enum SensorType {
-
-    TEMPERATURE     (340020000),
-    HUMIDITY        (410010000),
-    CO2             (780020000),
-    RSSI            (380010000),
-    SNR             (380090000),
-    BATTERY_LEVEL   (560030000),
-
-    ;
-    private final long id;
-
-    SensorType(long id) {
-        this.id = id;
-    }
-
-    public long getId() {
-        return id;
-    }
-
-    public static int count() {
-        return values().length;
-    }
-}

+ 73 - 70
connector-model/src/main/java/cz/senslog/connector/model/config/ConnectorDescriptor.java

@@ -1,70 +1,73 @@
-package cz.senslog.connector.model.config;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-
-/**
- * The class {@code ConnectorDescriptor} represents a configuration class for a connector.
- * According to this descriptor is created a new connector.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class ConnectorDescriptor {
-
-    /** Name of a connector. */
-    private final String name;
-
-    /** Class of a fetcher. */
-    private final String fetcherId;
-
-    /** class of a pusher. */
-    private final String pusherId;
-
-    /** Period for scheduling. */
-    private final Integer period;
-
-    /** Initialization delay for scheduling. */
-    private final Integer delay;
-
-    /**
-     * Constructor sets all attributes.
-     * @param name - name of a connector.
-     * @param fetcherId - class of a fetcher.
-     * @param pusherId - class of a pusher.
-     * @param period - period for scheduling.
-     * @param delay - initialization delay for scheduling.
-     */
-    public ConnectorDescriptor(String name, String fetcherId, String pusherId, Integer period, Integer delay) {
-        this.name = name;
-        this.fetcherId = fetcherId;
-        this.pusherId = pusherId;
-        this.period = period;
-        this.delay = delay;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getFetcherId() {
-        return fetcherId;
-    }
-
-    public String getPusherId() {
-        return pusherId;
-    }
-
-    public Integer getPeriod() {
-        return period;
-    }
-
-    public Integer getDelay() {
-        return delay;
-    }
-
-    @Override
-    public String toString() {
-        return objectToJson(this);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.config;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+/**
+ * The class {@code ConnectorDescriptor} represents a configuration class for a connector.
+ * According to this descriptor is created a new connector.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class ConnectorDescriptor {
+
+    /** Name of a connector. */
+    private final String name;
+
+    /** Class of a fetcher. */
+    private final String fetcherId;
+
+    /** class of a pusher. */
+    private final String pusherId;
+
+    /** Period for scheduling. */
+    private final Integer period;
+
+    /** Initialization delay for scheduling. */
+    private final Integer delay;
+
+    /**
+     * Constructor sets all attributes.
+     * @param name - name of a connector.
+     * @param fetcherId - class of a fetcher.
+     * @param pusherId - class of a pusher.
+     * @param period - period for scheduling.
+     * @param delay - initialization delay for scheduling.
+     */
+    public ConnectorDescriptor(String name, String fetcherId, String pusherId, Integer period, Integer delay) {
+        this.name = name;
+        this.fetcherId = fetcherId;
+        this.pusherId = pusherId;
+        this.period = period;
+        this.delay = delay;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getFetcherId() {
+        return fetcherId;
+    }
+
+    public String getPusherId() {
+        return pusherId;
+    }
+
+    public Integer getPeriod() {
+        return period;
+    }
+
+    public Integer getDelay() {
+        return delay;
+    }
+
+    @Override
+    public String toString() {
+        return objectToJson(this);
+    }
+}

+ 45 - 42
connector-model/src/main/java/cz/senslog/connector/model/config/DefaultConfig.java

@@ -1,42 +1,45 @@
-package cz.senslog.connector.model.config;
-
-/**
- * The class {@code DefaultConfig} represents a major configuration class for all providers.
- * Represents a root node of configuration for a class defines as a {@see #provider}.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class DefaultConfig extends PropertyConfig {
-
-    /** Provider for which is gotten a configuration. */
-    private final Class<?> provider;
-
-    /**
-     * Constructors sets root name (id) and provider class.
-     * @param id - identifier of root node.
-     * @param provider - class provider.
-     */
-    public DefaultConfig(String id, Class<?> provider) {
-        super(id);
-        this.provider = provider;
-    }
-
-    public Class<?> getProvider() {
-        return provider;
-    }
-
-    @Override
-    public int hashCode() {
-        return provider.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DefaultConfig that = (DefaultConfig) o;
-        return hashCode() == that.hashCode();
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.config;
+
+/**
+ * The class {@code DefaultConfig} represents a major configuration class for all providers.
+ * Represents a root node of configuration for a class defines as a {@see #provider}.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class DefaultConfig extends PropertyConfig {
+
+    /** Provider for which is gotten a configuration. */
+    private final Class<?> provider;
+
+    /**
+     * Constructors sets root name (id) and provider class.
+     * @param id - identifier of root node.
+     * @param provider - class provider.
+     */
+    public DefaultConfig(String id, Class<?> provider) {
+        super(id);
+        this.provider = provider;
+    }
+
+    public Class<?> getProvider() {
+        return provider;
+    }
+
+    @Override
+    public int hashCode() {
+        return provider.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DefaultConfig that = (DefaultConfig) o;
+        return hashCode() == that.hashCode();
+    }
+}

+ 55 - 52
connector-model/src/main/java/cz/senslog/connector/model/config/HostConfig.java

@@ -1,52 +1,55 @@
-package cz.senslog.connector.model.config;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-
-/**
- * The class {@code HostConfig} represents a configuration class.
- * Contains basic information which are needed to create an url.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class HostConfig {
-
-    /** Domain of the host. */
-    private final String domain;
-
-    /** Path of the host. */
-    private final String path;
-
-    /**
-     * Constructor sets all attributes.
-     * @param domain - domain of the host.
-     * @param path - path of the host.
-     */
-    public HostConfig(String domain, String path) {
-        this.domain = domain;
-        this.path = path;
-    }
-
-    /**
-     * Constructor sets all attributes from generic property class {@link PropertyConfig}.
-     * @param config - generic configuration.
-     */
-    public HostConfig(PropertyConfig config) {
-        this.domain = config.getStringProperty("domain");
-        this.path = config.getStringProperty("path");
-    }
-
-    public String getDomain() {
-        return domain;
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    @Override
-    public String toString() {
-        return objectToJson(this);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.config;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+/**
+ * The class {@code HostConfig} represents a configuration class.
+ * Contains basic information which are needed to create an url.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class HostConfig {
+
+    /** Domain of the host. */
+    private final String domain;
+
+    /** Path of the host. */
+    private final String path;
+
+    /**
+     * Constructor sets all attributes.
+     * @param domain - domain of the host.
+     * @param path - path of the host.
+     */
+    public HostConfig(String domain, String path) {
+        this.domain = domain;
+        this.path = path;
+    }
+
+    /**
+     * Constructor sets all attributes from generic property class {@link PropertyConfig}.
+     * @param config - generic configuration.
+     */
+    public HostConfig(PropertyConfig config) {
+        this.domain = config.getStringProperty("domain");
+        this.path = config.getStringProperty("path");
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String toString() {
+        return objectToJson(this);
+    }
+}

+ 195 - 192
connector-model/src/main/java/cz/senslog/connector/model/config/PropertyConfig.java

@@ -1,192 +1,195 @@
-package cz.senslog.connector.model.config;
-
-import cz.senslog.common.exception.PropertyNotFoundException;
-import cz.senslog.common.util.ClassUtils;
-import cz.senslog.common.util.LocalDateTimeUtils;
-
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
-import static java.lang.String.format;
-import static java.util.Collections.emptySet;
-import static java.util.Optional.ofNullable;
-
-/**
- * The class {@code PropertyConfig} represents a general configuration class.
- * Contains map of properties which represents a tree of configuration.
- * Each node is a {@code PropertyConfig} which contains {@see #id}
- * and could be generally located. Each leave can be represented
- * as {@see Integer}, {@see String} or {@see LocalDateTime}.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class PropertyConfig {
-
-    /** Path delimiter separates nodes. */
-    private static final String PATH_DELIMITER = ".";
-
-    /** Identifier of path. */
-    private final String id;
-
-    /** Map of properties. */
-    private final Map<String, Object> properties;
-
-    /**
-     * Constructor sets new identifier of node.
-     * @param id - identifier of node.
-     */
-    protected PropertyConfig(String id) {
-        this.id = id;
-        this.properties = new HashMap<>();
-    }
-
-    /**
-     * Adds new property to properties.
-     * @param name - name of new property.
-     * @param value - value of new property.
-     */
-    public boolean setProperty(String name, Object value) {
-        Object res = properties.put(name, value);
-        return res == value;
-    }
-
-    /**
-     * Returns value. It could be anything.
-     * @param name - name of property.
-     * @return object of value.
-     */
-    public Object getProperty(String name) {
-        if (properties.containsKey(name)) {
-            return properties.get(name);
-        }
-
-        throw new PropertyNotFoundException(format(
-                "Property '%s' does not exist.", getNewPropertyId(name))
-        );
-    }
-
-    /**
-     * Checks if property key is presents in properties.
-     * @param name - name of property
-     * @return boolean
-     */
-    public boolean containsProperty(String name) {
-        return properties.containsKey(name);
-    }
-
-    /**
-     * Returns optional value. It could be anything.
-     * @param name - name of property.
-     * @return optional object
-     */
-    public Optional<Object> getOptionalProperty(String name) {
-        return ofNullable(properties.get(name));
-    }
-
-    /**
-     * Returns property as a String.
-     * @param name - name of property.
-     * @return string value.
-     */
-    public String getStringProperty(String name) {
-        return ClassUtils.cast(getProperty(name), String.class);
-    }
-
-    /**
-     * Returns property as an Integer.
-     * @param name - name of property.
-     * @return integer value.
-     */
-    public Integer getIntegerProperty(String name) {
-        return ClassUtils.cast(getProperty(name), Integer.class);
-    }
-
-    /**
-     * Returns property as a LocalDateTime.
-     * @param name - name of property.
-     * @return localDateTime value.
-     */
-    public LocalDateTime getLocalDateTimeProperty(String name) {
-        Object object = getProperty(name);
-
-        if (object instanceof LocalDateTime) {
-            return (LocalDateTime) object;
-        } else if (object instanceof Date) {
-            Date date = (Date) object;
-            return date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDateTime();
-        } else if (object instanceof String) {
-            return LocalDateTime.parse((String)object, DateTimeFormatter.ISO_DATE_TIME);
-
-        } else {
-            throw new ClassCastException(format(
-                    "Property '%s' can not be cast to %s", getNewPropertyId(name), LocalDateTime.class)
-            );
-        }
-    }
-
-    /**
-     * Returns property as a optional of LocalDateTime
-     * @param name - name of property.
-     * @return optional of localDateTime value.
-     */
-    public Optional<LocalDateTime> getOptionalLocalDateTimeProperty(String name) {
-        return properties.containsKey(name) ? Optional.of(getLocalDateTimeProperty(name)) : Optional.empty();
-    }
-
-    /**
-     * Returns property as a set of the 'type'.
-     * @param name - name of property.
-     * @param type - type of attributes
-     * @param <T> - generic type of attribute
-     * @return Set of attributes defined by type
-     */
-    public <T> Set<T> getSetProperty(String name, Class<T> type) {
-        Object value = properties.get(name);
-
-        if (value instanceof  List) {
-            List<Object> list = (List<Object>) value;
-            Set<T> res = new HashSet<>(list.size());
-            for (Object o : list) {
-                res.add(ClassUtils.cast(o, type));
-            }
-            return res;
-        }
-
-        return emptySet();
-    }
-
-    /**
-     * Returns new node of configuration.
-     * @param name - name of property.
-     * @return node of configuration.
-     */
-    public PropertyConfig getPropertyConfig(String name) {
-        Object property = getProperty(name);
-        PropertyConfig config = new PropertyConfig(getNewPropertyId(name));
-
-        if (property instanceof Map) {
-            Map<String, Object> properties = (Map<String, Object>) property;
-            for (Map.Entry<String, Object> propertyEntry : properties.entrySet()) {
-                config.setProperty(propertyEntry.getKey(), propertyEntry.getValue());
-            }
-        }
-
-        return config;
-    }
-
-    public Set<String> getAttributes() {
-        return properties.keySet();
-    }
-
-    private String getNewPropertyId(String name) {
-        return id + PATH_DELIMITER + name;
-    }
-
-    public String getId() {
-        return id;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.config;
+
+import cz.senslog.common.exception.PropertyNotFoundException;
+import cz.senslog.common.util.ClassUtils;
+import cz.senslog.common.util.LocalDateTimeUtils;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+import static java.lang.String.format;
+import static java.util.Collections.emptySet;
+import static java.util.Optional.ofNullable;
+
+/**
+ * The class {@code PropertyConfig} represents a general configuration class.
+ * Contains map of properties which represents a tree of configuration.
+ * Each node is a {@code PropertyConfig} which contains {@see #id}
+ * and could be generally located. Each leave can be represented
+ * as {@see Integer}, {@see String} or {@see LocalDateTime}.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class PropertyConfig {
+
+    /** Path delimiter separates nodes. */
+    private static final String PATH_DELIMITER = ".";
+
+    /** Identifier of path. */
+    private final String id;
+
+    /** Map of properties. */
+    private final Map<String, Object> properties;
+
+    /**
+     * Constructor sets new identifier of node.
+     * @param id - identifier of node.
+     */
+    protected PropertyConfig(String id) {
+        this.id = id;
+        this.properties = new HashMap<>();
+    }
+
+    /**
+     * Adds new property to properties.
+     * @param name - name of new property.
+     * @param value - value of new property.
+     */
+    public boolean setProperty(String name, Object value) {
+        Object res = properties.put(name, value);
+        return res == value;
+    }
+
+    /**
+     * Returns value. It could be anything.
+     * @param name - name of property.
+     * @return object of value.
+     */
+    public Object getProperty(String name) {
+        if (properties.containsKey(name)) {
+            return properties.get(name);
+        }
+
+        throw new PropertyNotFoundException(format(
+                "Property '%s' does not exist.", getNewPropertyId(name))
+        );
+    }
+
+    /**
+     * Checks if property key is presents in properties.
+     * @param name - name of property
+     * @return boolean
+     */
+    public boolean containsProperty(String name) {
+        return properties.containsKey(name);
+    }
+
+    /**
+     * Returns optional value. It could be anything.
+     * @param name - name of property.
+     * @return optional object
+     */
+    public Optional<Object> getOptionalProperty(String name) {
+        return ofNullable(properties.get(name));
+    }
+
+    /**
+     * Returns property as a String.
+     * @param name - name of property.
+     * @return string value.
+     */
+    public String getStringProperty(String name) {
+        return ClassUtils.cast(getProperty(name), String.class);
+    }
+
+    /**
+     * Returns property as an Integer.
+     * @param name - name of property.
+     * @return integer value.
+     */
+    public Integer getIntegerProperty(String name) {
+        return ClassUtils.cast(getProperty(name), Integer.class);
+    }
+
+    /**
+     * Returns property as a LocalDateTime.
+     * @param name - name of property.
+     * @return localDateTime value.
+     */
+    public LocalDateTime getLocalDateTimeProperty(String name) {
+        Object object = getProperty(name);
+
+        if (object instanceof LocalDateTime) {
+            return (LocalDateTime) object;
+        } else if (object instanceof Date) {
+            Date date = (Date) object;
+            return date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDateTime();
+        } else if (object instanceof String) {
+            return LocalDateTime.parse((String)object, DateTimeFormatter.ISO_DATE_TIME);
+
+        } else {
+            throw new ClassCastException(format(
+                    "Property '%s' can not be cast to %s", getNewPropertyId(name), LocalDateTime.class)
+            );
+        }
+    }
+
+    /**
+     * Returns property as a optional of LocalDateTime
+     * @param name - name of property.
+     * @return optional of localDateTime value.
+     */
+    public Optional<LocalDateTime> getOptionalLocalDateTimeProperty(String name) {
+        return properties.containsKey(name) ? Optional.of(getLocalDateTimeProperty(name)) : Optional.empty();
+    }
+
+    /**
+     * Returns property as a set of the 'type'.
+     * @param name - name of property.
+     * @param type - type of attributes
+     * @param <T> - generic type of attribute
+     * @return Set of attributes defined by type
+     */
+    public <T> Set<T> getSetProperty(String name, Class<T> type) {
+        Object value = properties.get(name);
+
+        if (value instanceof  List) {
+            List<Object> list = (List<Object>) value;
+            Set<T> res = new HashSet<>(list.size());
+            for (Object o : list) {
+                res.add(ClassUtils.cast(o, type));
+            }
+            return res;
+        }
+
+        return emptySet();
+    }
+
+    /**
+     * Returns new node of configuration.
+     * @param name - name of property.
+     * @return node of configuration.
+     */
+    public PropertyConfig getPropertyConfig(String name) {
+        Object property = getProperty(name);
+        PropertyConfig config = new PropertyConfig(getNewPropertyId(name));
+
+        if (property instanceof Map) {
+            Map<String, Object> properties = (Map<String, Object>) property;
+            for (Map.Entry<String, Object> propertyEntry : properties.entrySet()) {
+                config.setProperty(propertyEntry.getKey(), propertyEntry.getValue());
+            }
+        }
+
+        return config;
+    }
+
+    public Set<String> getAttributes() {
+        return properties.keySet();
+    }
+
+    private String getNewPropertyId(String name) {
+        return id + PATH_DELIMITER + name;
+    }
+
+    public String getId() {
+        return id;
+    }
+}

+ 10 - 7
connector-model/src/main/java/cz/senslog/connector/model/config/UnitSensorChecker.java

@@ -1,7 +1,10 @@
-package cz.senslog.connector.model.config;
-
-public interface UnitSensorChecker<U, S> {
-
-    boolean isValid(U unit);
-    boolean isValid(U unit, S sensor);
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.config;
+
+public interface UnitSensorChecker<U, S> {
+
+    boolean isValid(U unit);
+    boolean isValid(U unit, S sensor);
+}

+ 19 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/AFarCloudUnitProperties.java

@@ -0,0 +1,19 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.afarcloud.MeasurementType;
+
+public class AFarCloudUnitProperties {
+
+    private final MeasurementType type;
+
+    public AFarCloudUnitProperties(MeasurementType type) {
+        this.type = type;
+    }
+
+    public MeasurementType getType() {
+        return type;
+    }
+}

+ 162 - 161
connector-model/src/main/java/cz/senslog/connector/model/converter/AFarCloudUnitSensorConverter.java

@@ -1,161 +1,162 @@
-package cz.senslog.connector.model.converter;
-
-
-import cz.senslog.common.util.Tuple;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public final class AFarCloudUnitSensorConverter {
-
-    private final static class Mapping<K, V> extends HashMap<K, V> {
-        private final Map<V, K> reverseMap = new HashMap<>();
-
-        @Override
-        public V put(K key, V value) {
-            reverseMap.put(value, key);
-            return super.put(key, value);
-        }
-
-        public V getValue(K key) {
-            return super.get(key);
-        }
-
-        public K getKey(V value) {
-            return reverseMap.get(value);
-        }
-    }
-
-    private final static Mapping<Long, String> UNITS = new Mapping<>();
-    private final static Map<Long, Mapping<Long, String>> SENSORS = new HashMap<>();
-
-    private static void register(long unitId, String resourceId, Mapping<Long, String> sensorsMapping) {
-        UNITS.put(unitId, resourceId);
-        SENSORS.put(unitId, sensorsMapping);
-    }
-
-    static {
-        // 10002376L
-        Mapping<Long, String> sensors = new Mapping<>();
-
-        sensors.put(340020000L, "air_temperature");
-        sensors.put(410010000L, "air_humidity");
-        sensors.put(560030000L, "battery");
-
-        register(10002376L, "10002376", sensors);
-    }
-
-    static {
-        // 10002222L
-        Mapping<Long, String> sensors = new Mapping<>();
-
-        sensors.put(340020000L, "air_temperature");
-        sensors.put(410010000L, "air_humidity");
-        sensors.put(560030000L, "battery");
-
-        register(10002222L, "10002222", sensors);
-    }
-
-    static {
-        // 1305167561991327L
-        Mapping<Long, String> sensors = new Mapping<>();
-
-        sensors.put(340380097L, "air_temperature");
-        sensors.put(410150097L, "air_humidity");
-        sensors.put(360200000L, "battery");
-        sensors.put(780010097L, "co2");
-
-        register(1305167561991327L, "1305167561991327", sensors);
-    }
-
-    static {
-        // 1305167562028072L
-        Mapping<Long, String> sensors = new Mapping<>();
-
-        sensors.put(340380097L, "air_temperature");
-        sensors.put(410150097L, "air_humidity");
-        sensors.put(360200000L, "battery");
-        sensors.put(780010097L, "co2");
-
-        register(1305167562028072L, "1305167562028072", sensors);
-    }
-
-    static {
-        // 1305167562287832
-        Mapping<Long, String> sensors = new Mapping<>();
-
-        sensors.put(340420000L, "air_temperature");
-        sensors.put(410180000L, "air_humidity");
-        sensors.put(360200000L, "battery");
-        sensors.put(460090000L, "air_pressure");
-        sensors.put(470160000L, "wind_speed");
-        sensors.put(470180000L, "wind_direction");
-        sensors.put(480080000L, "rainfall");
-        sensors.put(620030000L, "solar_radiation");
-
-        register(1305167562287832L, "1305167562287832", sensors);
-    }
-
-    static {
-        // 1305167562017824
-        Mapping<Long, String> sensors = new Mapping<>();
-
-        sensors.put(340380097L, "air_temperature");
-        sensors.put(410150097L, "air_humidity");
-        sensors.put(360200000L, "battery");
-        sensors.put(780010097L, "co2");
-
-        register(1305167562017824L, "1305167562017824", sensors);
-    }
-
-    public static long afcResourceIdToSensLogUnitId(String resourceId) {
-        Long unitId = UNITS.getKey(resourceId);
-        return unitId != null ? unitId : -1;
-    }
-
-    public static Tuple<Long, Long> afcToSensLog(String resourceId, String observedProperty) {
-        Long unitId = UNITS.getKey(resourceId);
-        if (unitId == null) {
-            return null;
-        }
-        Mapping<Long, String> sensors = SENSORS.get(unitId);
-        if (sensors == null) {
-            return null;
-        }
-
-        Long sensorId = sensors.getKey(observedProperty);
-        if (sensorId == null) {
-            return null;
-        }
-
-        return Tuple.of(unitId, sensorId);
-    }
-
-    public static String sensLogUnitIdToAfcResourceId(long unitId) {
-        return UNITS.getValue(unitId);
-    }
-
-    public static String sensLogSensorIdToAfcObservedProperty(long unitId, long sensorId) {
-        Mapping<Long, String> sensors = SENSORS.get(unitId);
-        if (sensors == null) {
-            return null;
-        }
-        return sensors.getValue(sensorId);
-    }
-
-    public static Tuple<String, String> sensLogToAfc(long unitId, long sensorId) {
-        String resourceId = UNITS.getValue(unitId);
-        if (resourceId == null) {
-            return null;
-        }
-        Mapping<Long, String> sensors = SENSORS.get(unitId);
-        if (sensors == null) {
-            return null;
-        }
-        String observedProperty = sensors.getValue(sensorId);
-        if (observedProperty == null) {
-            return null;
-        }
-        return Tuple.of(resourceId, observedProperty);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+
+import cz.senslog.common.util.Tuple;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static cz.senslog.connector.model.afarcloud.MeasurementType.SENSOR;
+
+public final class AFarCloudUnitSensorConverter {
+
+    private final static ReverseMap<Long, String> UNITS = new ReverseMap<>();
+    private final static Map<Long, ReverseMap<Long, String>> SENSORS = new HashMap<>();
+    private final static Map<Long, AFarCloudUnitProperties> UNIT_PROPERTIES = new HashMap<>();
+
+    private static void register(long unitId, String resourceId, ReverseMap<Long, String> sensorsReverseMap, AFarCloudUnitProperties properties) {
+        UNITS.put(unitId, resourceId);
+        SENSORS.put(unitId, sensorsReverseMap);
+        UNIT_PROPERTIES.put(unitId, properties);
+    }
+
+    static {
+        // 10002376L
+        ReverseMap<Long, String> sensors = new ReverseMap<>();
+
+        sensors.put(340020000L, "air_temperature");
+        sensors.put(410010000L, "air_humidity");
+        sensors.put(560030000L, "battery");
+
+        register(10002376L, "10002376", sensors,
+                new AFarCloudUnitProperties(SENSOR)
+        );
+    }
+
+    static {
+        // 10002222L
+        ReverseMap<Long, String> sensors = new ReverseMap<>();
+
+        sensors.put(340020000L, "air_temperature");
+        sensors.put(410010000L, "air_humidity");
+        sensors.put(560030000L, "battery");
+
+        register(10002222L, "10002222", sensors,
+                new AFarCloudUnitProperties(SENSOR)
+        );
+    }
+
+    static {
+        // 1305167561991327L
+        ReverseMap<Long, String> sensors = new ReverseMap<>();
+
+        sensors.put(340380097L, "air_temperature");
+        sensors.put(410150097L, "air_humidity");
+        sensors.put(360200000L, "battery");
+        sensors.put(780010097L, "co2");
+
+        register(1305167561991327L, "1305167561991327", sensors,
+                new AFarCloudUnitProperties(SENSOR)
+        );
+    }
+
+    static {
+        // 1305167562028072L
+        ReverseMap<Long, String> sensors = new ReverseMap<>();
+
+        sensors.put(340380097L, "air_temperature");
+        sensors.put(410150097L, "air_humidity");
+        sensors.put(360200000L, "battery");
+        sensors.put(780010097L, "co2");
+
+        register(1305167562028072L, "1305167562028072", sensors,
+                new AFarCloudUnitProperties(SENSOR)
+        );
+    }
+
+    static {
+        // 1305167562287832
+        ReverseMap<Long, String> sensors = new ReverseMap<>();
+
+        sensors.put(340420000L, "air_temperature");
+        sensors.put(410180000L, "air_humidity");
+        sensors.put(360200000L, "battery");
+        sensors.put(460090000L, "air_pressure");
+        sensors.put(470160000L, "wind_speed");
+        sensors.put(470180000L, "wind_direction");
+        sensors.put(480080000L, "rainfall");
+        sensors.put(620030000L, "solar_radiation");
+
+        register(1305167562287832L, "1305167562287832", sensors,
+                new AFarCloudUnitProperties(SENSOR)
+        );
+    }
+
+    static {
+        // 1305167562017824
+        ReverseMap<Long, String> sensors = new ReverseMap<>();
+
+        sensors.put(340380097L, "air_temperature");
+        sensors.put(410150097L, "air_humidity");
+        sensors.put(360200000L, "battery");
+        sensors.put(780010097L, "co2");
+
+        register(1305167562017824L, "1305167562017824", sensors,
+                new AFarCloudUnitProperties(SENSOR)
+        );
+    }
+
+    public static long afcResourceIdToSensLogUnitId(String resourceId) {
+        Long unitId = UNITS.getKey(resourceId);
+        return unitId != null ? unitId : -1;
+    }
+
+    public static Tuple<Long, Long> afcToSensLog(String resourceId, String observedProperty) {
+        Long unitId = UNITS.getKey(resourceId);
+        if (unitId == null) {
+            return null;
+        }
+        ReverseMap<Long, String> sensors = SENSORS.get(unitId);
+        if (sensors == null) {
+            return null;
+        }
+
+        Long sensorId = sensors.getKey(observedProperty);
+        if (sensorId == null) {
+            return null;
+        }
+
+        return Tuple.of(unitId, sensorId);
+    }
+
+    public static Tuple<String, AFarCloudUnitProperties> sensLogUnitIdToAfcResourceId(long unitId) {
+        return Tuple.of(UNITS.getValue(unitId), UNIT_PROPERTIES.get(unitId));
+    }
+
+    public static String sensLogSensorIdToAfcObservedProperty(long unitId, long sensorId) {
+        ReverseMap<Long, String> sensors = SENSORS.get(unitId);
+        if (sensors == null) {
+            return null;
+        }
+        return sensors.getValue(sensorId);
+    }
+
+    public static Tuple<String, String> sensLogToAfc(long unitId, long sensorId) {
+        String resourceId = UNITS.getValue(unitId);
+        if (resourceId == null) {
+            return null;
+        }
+        ReverseMap<Long, String> sensors = SENSORS.get(unitId);
+        if (sensors == null) {
+            return null;
+        }
+        String observedProperty = sensors.getValue(sensorId);
+        if (observedProperty == null) {
+            return null;
+        }
+        return Tuple.of(resourceId, observedProperty);
+    }
+}

+ 95 - 84
connector-model/src/main/java/cz/senslog/connector/model/converter/AzureModelSenslogV1ModelConverter.java

@@ -1,84 +1,95 @@
-package cz.senslog.connector.model.converter;
-
-import cz.senslog.connector.model.api.Converter;
-import cz.senslog.connector.model.azure.AzureModel;
-import cz.senslog.connector.model.azure.SensorData;
-import cz.senslog.connector.model.azure.SensorInfo;
-import cz.senslog.connector.model.azure.SensorType;
-import cz.senslog.connector.model.v1.Observation;
-import cz.senslog.connector.model.v1.Record;
-import cz.senslog.connector.model.v1.SenslogV1Model;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The class {@code AzureModelSenslogV1ModelConverter} represents an implementation of converter
- * between {@link AzureModel} and {@link SenslogV1Model}.
- *
- * Converter converts input model of sensors data to observations. Each physical quantity of sensor
- * is converted as a single observation.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public final class AzureModelSenslogV1ModelConverter implements Converter<AzureModel, SenslogV1Model> {
-
-    private static Logger logger = LogManager.getLogger(AzureModelSenslogV1ModelConverter.class);
-
-    @Override
-    public SenslogV1Model convert(AzureModel inputModel) {
-
-        if (inputModel == null || inputModel.getSensors() == null) {
-            logger.warn("Nothing to convert. Received model is empty.");
-            return null;
-        }
-
-        List<SensorInfo> sensorInfos = inputModel.getSensors();
-
-        int size = 0, records = 0;
-        for (SensorInfo sensorInfo : sensorInfos) {
-            records += sensorInfo.getData().size();
-            size += sensorInfo.getData().size() * SensorType.count();
-        }
-        logger.info("Received {} sensors and {} records to convert.", sensorInfos.size(), records);
-
-        logger.debug("Creating an empty list of observation with init size {}.", size);
-        List<Record> observations = new ArrayList<>(size);
-
-        for (SensorInfo sensorInfo : sensorInfos) {
-            logger.debug("Converting sensor with Eui {}.", sensorInfo.getEui());
-
-            Long unitId = AzureUnitConverter.convertIdToSensLogId(sensorInfo.getEui());
-            if (unitId == null) {
-                logger.error("EUI converter for '{}' does not exist.", sensorInfo.getEui());
-                continue;
-            }
-
-            for (SensorData sensorData : sensorInfo.getData()) {
-                for (Map.Entry<SensorType, Float> sensorDataEntry : sensorData.getValues().entrySet()) {
-                    logger.debug("Creating a new observation.");
-                    Observation observation = new Observation();
-                    observation.setUnitId(unitId);
-                    observation.setTime(sensorData.getTime());
-                    observation.setValue(sensorDataEntry.getValue());
-                    observation.setSensorId(sensorDataEntry.getKey().getId());
-
-                    logger.debug("Saving the new observation.");
-                    observations.add(observation);
-                }
-            }
-        }
-
-        logger.debug("Creating a new SenslogV1 model.");
-        SenslogV1Model outputModel = new SenslogV1Model(observations, inputModel.getFrom(), inputModel.getTo());
-
-        logger.info("Conversion was completed successfully.");
-
-        return outputModel;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.azure.AzureModel;
+import cz.senslog.connector.model.azure.SensorData;
+import cz.senslog.connector.model.azure.SensorInfo;
+import cz.senslog.connector.model.v1.Observation;
+import cz.senslog.connector.model.v1.Record;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cz.senslog.connector.model.converter.AzureUnitConverter.convertEuiToUnitId;
+import static cz.senslog.connector.model.converter.AzureUnitConverter.convertSensorTypeToSensorId;
+import static java.util.Collections.emptyList;
+
+/**
+ * The class {@code AzureModelSenslogV1ModelConverter} represents an implementation of converter
+ * between {@link AzureModel} and {@link SenslogV1Model}.
+ *
+ * Converter converts input model of sensors data to observations. Each physical quantity of sensor
+ * is converted as a single observation.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public final class AzureModelSenslogV1ModelConverter implements Converter<AzureModel, SenslogV1Model> {
+
+    private static final Logger logger = LogManager.getLogger(AzureModelSenslogV1ModelConverter.class);
+
+    @Override
+    public SenslogV1Model convert(AzureModel inputModel) {
+
+        if (inputModel == null || inputModel.getSensors() == null) {
+            logger.warn("Nothing to convert. Received model is empty.");
+            return null;
+        }
+
+        List<SensorInfo> sensorInfos = inputModel.getSensors();
+
+        int records = 0;
+        for (SensorInfo sensorInfo : sensorInfos) {
+            records += sensorInfo.getData() != null ? sensorInfo.getData().size() : 0;
+        }
+        logger.info("Received {} sensors and {} records to convert.", sensorInfos.size(), records);
+
+        logger.debug("Creating an empty list of observation.");
+        List<Record> observations = new ArrayList<>();
+
+        for (SensorInfo sensorInfo : sensorInfos) {
+            logger.debug("Converting sensor with Eui {}.", sensorInfo.getEui());
+
+            Long unitId = convertEuiToUnitId(sensorInfo.getEui());
+            if (unitId == null) {
+                logger.error("EUI converter for '{}' does not exist.", sensorInfo.getEui()); continue;
+            }
+
+            List<SensorData> dataOfSensor = sensorInfo.getData() != null ? sensorInfo.getData() : emptyList();
+            for (SensorData sensorData : dataOfSensor) {
+                for (Map.Entry<String, Float> sensorDataEntry : sensorData.getValues().entrySet()) {
+                    String sensorType = sensorDataEntry.getKey();
+                    Long sensorId = convertSensorTypeToSensorId(sensorInfo.getEui(), sensorType);
+                    if (sensorId == null) {
+                        logger.error("Converter for the sensor type '{}' does not exist.", sensorType); continue;
+                    }
+
+                    logger.debug("Creating a new observation.");
+                    Observation observation = new Observation();
+                    observation.setSensorId(sensorId);
+                    observation.setUnitId(unitId);
+                    observation.setTime(sensorData.getTime());
+                    observation.setValue(sensorDataEntry.getValue());
+
+                    logger.debug("Saving the new observation.");
+                    observations.add(observation);
+                }
+            }
+        }
+
+        logger.debug("Creating a new SenslogV1 model.");
+        SenslogV1Model outputModel = new SenslogV1Model(observations, inputModel.getFrom(), inputModel.getTo());
+
+        logger.info("Conversion was completed successfully.");
+
+        return outputModel;
+    }
+}

+ 82 - 85
connector-model/src/main/java/cz/senslog/connector/model/converter/AzureModelSenslogV2ModelConverter.java

@@ -1,85 +1,82 @@
-package cz.senslog.connector.model.converter;
-
-import cz.senslog.connector.model.api.Converter;
-import cz.senslog.connector.model.azure.AzureModel;
-import cz.senslog.connector.model.azure.SensorData;
-import cz.senslog.connector.model.azure.SensorInfo;
-import cz.senslog.connector.model.azure.SensorType;
-import cz.senslog.connector.model.v2.Observation;
-import cz.senslog.connector.model.v2.SenslogV2Model;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The class {@code AzureModelSenslogV2ModelConverter} represents an implementation of converter
- * between {@link AzureModel} and {@link SenslogV2Model}.
- *
- * Converter converts input model of sensors data to observations. Each physical quantity of sensor
- * is converted as a single observation.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class AzureModelSenslogV2ModelConverter implements Converter<AzureModel, SenslogV2Model> {
-
-    private static Logger logger = LogManager.getLogger(AzureModelSenslogV2ModelConverter.class);
-
-
-    @Override
-    public SenslogV2Model convert(AzureModel inputModel) {
-
-        if (inputModel == null || inputModel.getSensors() == null) {
-            logger.warn("Nothing to convert. Received model is empty.");
-            return null;
-        }
-
-        List<SensorInfo> sensorInfos = inputModel.getSensors();
-
-        int size = 0;
-        for (SensorInfo sensorInfo : sensorInfos) {
-            size += sensorInfo.getData().size() * SensorType.count();
-        }
-
-        logger.debug("Creating an empty list of observation with init size {}.", size);
-        List<Observation> observations = new ArrayList<>(size);
-
-        for (SensorInfo sensorInfo : sensorInfos) {
-            logger.debug("Converting sensor with Eui {}.", sensorInfo.getEui());
-
-            // TODO EUI will be converted the same way like for the version 1 or will be used more efficient way to transfer the data
-            Long unitId = 1L;
-            if (unitId == null) {
-                logger.error("EUI converter for '{}' does not exist.", sensorInfo.getEui());
-                continue;
-            }
-
-            for (SensorData sensorData : sensorInfo.getData()) {
-                for (Map.Entry<SensorType, Float> sensorDataEntry : sensorData.getValues().entrySet()) {
-                    logger.debug("Creating a new observation.");
-                    Observation observation = new Observation();
-                    observation.setTimestamp(sensorData.getTime().toLocalDateTime());
-                    observation.setValue(sensorDataEntry.getValue().doubleValue());
-
-                    // TODO
-                    observation.setId(unitId);
-                    // observation.setSensorId(sensorDataEntry.getKey().getId());
-
-                    logger.debug("Saving the new observation.");
-                    observations.add(observation);
-                }
-            }
-        }
-
-        logger.debug("Creating a new SenslogV2 model.");
-        SenslogV2Model outputModel = new SenslogV2Model(observations, inputModel.getFrom(), inputModel.getTo());;
-
-        logger.info("Conversion was completed successfully.");
-
-        return outputModel;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.azure.AzureModel;
+import cz.senslog.connector.model.azure.SensorData;
+import cz.senslog.connector.model.azure.SensorInfo;
+import cz.senslog.connector.model.v2.Observation;
+import cz.senslog.connector.model.v2.SenslogV2Model;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The class {@code AzureModelSenslogV2ModelConverter} represents an implementation of converter
+ * between {@link AzureModel} and {@link SenslogV2Model}.
+ *
+ * Converter converts input model of sensors data to observations. Each physical quantity of sensor
+ * is converted as a single observation.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class AzureModelSenslogV2ModelConverter implements Converter<AzureModel, SenslogV2Model> {
+
+    private static final Logger logger = LogManager.getLogger(AzureModelSenslogV2ModelConverter.class);
+
+
+    @Override
+    public SenslogV2Model convert(AzureModel inputModel) {
+
+        if (inputModel == null || inputModel.getSensors() == null) {
+            logger.warn("Nothing to convert. Received model is empty.");
+            return null;
+        }
+
+        List<SensorInfo> sensorInfos = inputModel.getSensors();
+
+        logger.debug("Creating an empty list of observation.");
+        List<Observation> observations = new ArrayList<>();
+
+        for (SensorInfo sensorInfo : sensorInfos) {
+            logger.debug("Converting sensor with Eui {}.", sensorInfo.getEui());
+
+            // TODO EUI will be converted the same way like for the version 1 or will be used more efficient way to transfer the data
+            Long unitId = 1L;
+            if (unitId == null) {
+                logger.error("EUI converter for '{}' does not exist.", sensorInfo.getEui());
+                continue;
+            }
+
+            for (SensorData sensorData : sensorInfo.getData()) {
+                for (Map.Entry<String, Float> sensorDataEntry : sensorData.getValues().entrySet()) {
+                    logger.debug("Creating a new observation.");
+                    Observation observation = new Observation();
+                    observation.setTimestamp(sensorData.getTime().toLocalDateTime());
+                    observation.setValue(sensorDataEntry.getValue().doubleValue());
+
+                    // TODO
+                    observation.setId(unitId);
+                    // observation.setSensorId(sensorDataEntry.getKey().getId());
+
+                    logger.debug("Saving the new observation.");
+                    observations.add(observation);
+                }
+            }
+        }
+
+        logger.debug("Creating a new SenslogV2 model.");
+        SenslogV2Model outputModel = new SenslogV2Model(observations, inputModel.getFrom(), inputModel.getTo());;
+
+        logger.info("Conversion was completed successfully.");
+
+        return outputModel;
+    }
+}

+ 124 - 42
connector-model/src/main/java/cz/senslog/connector/model/converter/AzureUnitConverter.java

@@ -1,42 +1,124 @@
-package cz.senslog.connector.model.converter;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Converter sensor EUI in hex value to sensor ID in dec value.
- */
-public class AzureUnitConverter {
-
-    private static final Map<String, Long> CONVERT_TABLE;
-
-    static {
-        CONVERT_TABLE = new HashMap<>();
-
-        CONVERT_TABLE.put("8CF9574000000948", 10002376L);
-        CONVERT_TABLE.put("8CF95740000008AE", 10002222L);
-    }
-
-    private static Long getIdForType(String azureID) {
-        if (azureID == null) return null;
-        return CONVERT_TABLE.getOrDefault(azureID, null);
-    }
-
-    private static String getAzureId(Long id) {
-        if (id == null) return null;
-        for (Map.Entry<String, Long> idEntry : CONVERT_TABLE.entrySet()) {
-            if (idEntry.getValue().equals(id)) {
-                return idEntry.getKey();
-            }
-        }
-        return null;
-    }
-
-    public static Long convertIdToSensLogId(String fcUnitId) {
-        return getIdForType(fcUnitId);
-    }
-
-    public static String convertIdToAzureId(Long slUnitId) {
-        return getAzureId(slUnitId);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.common.util.Tuple;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converter sensor EUI in hex value to sensor ID in dec value.
+ */
+public class AzureUnitConverter {
+
+    private final static ReverseMap<String, Long> UNITS = new ReverseMap<>();
+    private final static Map<Long, ReverseMap<String, Long>> SENSORS = new HashMap<>();
+
+    private static void register(String eui, long unitId, ReverseMap<String, Long> sensorsReverseMap) {
+        UNITS.put(eui, unitId); SENSORS.put(unitId, sensorsReverseMap);
+    }
+
+
+    static {
+        ReverseMap<String, Long> sensors = new ReverseMap<>();
+
+        sensors.put("temperature", 340020000L);
+        sensors.put("humidity", 410010000L);
+        sensors.put("co2", 780020000L);
+        sensors.put("rssi", 380010000L);
+        sensors.put("snr", 380090000L);
+        sensors.put("batteryLevel", 560030000L);
+
+        register("8CF9574000000948", 10002376L, sensors);
+    }
+
+    static {
+        ReverseMap<String, Long> sensors = new ReverseMap<>();
+
+        sensors.put("temperature", 340020000L);
+        sensors.put("humidity", 410010000L);
+        sensors.put("co2", 780020000L);
+        sensors.put("rssi", 380010000L);
+        sensors.put("snr", 380090000L);
+        sensors.put("batteryLevel", 560030000L);
+
+        register("8CF95740000008AE", 10002222L, sensors);
+    }
+
+    static {
+        ReverseMap<String, Long> sensors = new ReverseMap<>();
+
+        sensors.put("vitality", 860010000L);
+        sensors.put("rumination", 860020000L);
+        sensors.put("pedometer", 860030000L);
+        sensors.put("temperature", 340470000L);
+        sensors.put("pH", 590030000L);
+        sensors.put("ORP", 600030000L);
+
+        register("086BD7D4B501", 20046337L, sensors);
+    }
+
+
+    static {
+        ReverseMap<String, Long> sensors = new ReverseMap<>();
+
+        sensors.put("vitality", 860010000L);
+        sensors.put("rumination", 860020000L);
+        sensors.put("pedometer", 860030000L);
+        sensors.put("temperature", 340470000L);
+        sensors.put("pH", 590030000L);
+        sensors.put("ORP", 600030000L);
+
+        register("90FD9FA5F420", 20062496L, sensors);
+    }
+
+
+    public static Long convertEuiToUnitId(String eui) {
+        return UNITS.getValue(eui);
+    }
+
+    public static Long convertSensorTypeToSensorId(String eui, String sensorType) {
+        if (UNITS.containsKey(eui)) {
+            return SENSORS.get(convertEuiToUnitId(eui)).getValue(sensorType);
+        }
+        return null;
+    }
+
+    public static Tuple<Long , Long> convertToSensLog(String eui, String sensorType) {
+        if (!UNITS.containsKey(eui)) {
+            return null;
+        }
+        long unitId = UNITS.getValue(eui);
+
+        if (!SENSORS.containsKey(unitId)) {
+            return null;
+        }
+        long sensorId = SENSORS.get(unitId).getValue(sensorType);
+
+        return Tuple.of(unitId, sensorId);
+    }
+
+    public static String convertUnitIdToEui(long unitId) {
+        return UNITS.getKey(unitId);
+    }
+
+    public static String convertSensorIdToSensorType(long unitId, long sensorId) {
+        return SENSORS.get(unitId).getKey(sensorId);
+    }
+
+    public static Tuple<String, String> convertToIma(long unitId, long sensorId) {
+        String eui = convertUnitIdToEui(unitId);
+        if (eui == null) {
+            return null;
+        }
+
+        String sensorType = convertSensorIdToSensorType(unitId, sensorId);
+        if (sensorType == null) {
+            return null;
+        }
+
+        return Tuple.of(eui, sensorType);
+    }
+}

+ 156 - 153
connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV1ModelConverter.java

@@ -1,154 +1,157 @@
-package cz.senslog.connector.model.converter;
-
-import cz.senslog.connector.model.api.Converter;
-import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
-import cz.senslog.connector.model.fieldclimate.SensorDataInfo;
-import cz.senslog.connector.model.fieldclimate.SensorType;
-import cz.senslog.connector.model.fieldclimate.StationData;
-import cz.senslog.connector.model.v1.Observation;
-import cz.senslog.connector.model.v1.Position;
-import cz.senslog.connector.model.v1.Record;
-import cz.senslog.connector.model.v1.SenslogV1Model;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.time.OffsetDateTime;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static cz.senslog.connector.model.fieldclimate.SensorType.Group.POSITION;
-import static cz.senslog.connector.model.fieldclimate.SensorType.countOfGroup;
-import static java.lang.String.format;
-import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
-
-public class FieldClimateModelSenslogV1ModelConverter implements Converter<FieldClimateModel, SenslogV1Model> {
-
-    private static Logger logger = LogManager.getLogger(FieldClimateModelSenslogV1ModelConverter.class);
-
-    @Override
-    public SenslogV1Model convert(FieldClimateModel model) {
-
-        if (model == null || model.getStations() == null) {
-            logger.warn("Nothing to convert. Received model is empty.");
-            return null;
-        }
-
-        List<StationData> stations = model.getStations();
-
-        int size = 0;
-        for (StationData sensor : stations) {
-            for (Map<String, String> sensorData : sensor.getData()) {
-                size += sensorData.size();
-            }
-        }
-
-        List<Record> observations = new ArrayList<>(size);
-        for (StationData station : stations) {
-
-            int locationMapInitSize = station.getData().size() * countOfGroup(POSITION);
-            Map<OffsetDateTime, Position> locationData = new HashMap<>(locationMapInitSize);
-
-            Long unitId = FieldClimateUnitConverter.convertIdToSensLogId(station.getId());
-
-            for (SensorDataInfo sensor : station.getSensors()) {
-
-                SensorType sensorType = SensorType.of(sensor.getCode());
-
-                if (sensorType == null) {
-                    logger.warn("Sensor type is not registered: {}", sensor); continue;
-                }
-
-                Map<String, Integer> aggregateInfos = sensor.getAggr();
-
-                if (aggregateInfos.size() != 1) {
-                    Integer avgActive = aggregateInfos.getOrDefault("avg", 0);
-                    if (avgActive == 1) {
-                        aggregateInfos.clear();
-                        aggregateInfos.put("avg", 1);
-                    } else {
-                        Map.Entry<String, Integer> entry = null;
-                        for (Map.Entry<String, Integer> aggrEntry : aggregateInfos.entrySet()) {
-                            if (aggrEntry.getValue() == 1) {
-                                entry = aggrEntry;
-                                break;
-                            }
-                        }
-                        aggregateInfos.clear();
-                        if (entry != null) {
-                            aggregateInfos.put(entry.getKey(), entry.getValue());
-                        }
-                    }
-                }
-
-                for (Map.Entry<String, Integer> aggrEntry : aggregateInfos.entrySet()) {
-
-                    String aggrType = aggrEntry.getKey();
-                    String sensorDataHash = format("%s_%s_%s_%s_%s",
-                            sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode(), aggrType);
-
-                    for (Map<String, String> dataMap : station.getData()) {
-
-                        if (!dataMap.containsKey(sensorDataHash)) {
-                            logger.warn("For the sensor {}({}) at the station {} does not exist aggregate '{}' value.",
-                                    sensor.getName(), sensor.getCode(), station.getId(), aggrType);
-                            continue;
-                        }
-
-                        String strValue = dataMap.get(sensorDataHash);
-
-                        if (strValue == null) continue;
-
-                        Float value = Float.valueOf(strValue);
-                        String strDate = dataMap.getOrDefault("date", "");
-                        OffsetDateTime time = OffsetDateTime.parse(strDate, ISO_OFFSET_DATE_TIME);
-
-                        if (sensorType.isGrouped()) {
-                            switch (sensorType.getGroup()) {
-                                case POSITION: {
-                                    Position position = locationData.getOrDefault(time, null);
-                                    if (position == null) {
-                                        position = new Position();
-                                        position.setUnitId(unitId);
-                                        position.setTime(time);
-                                        locationData.put(time, position);
-                                    }
-
-                                    switch (sensorType) {
-                                        case LATITUDE:  position.setLatitude(value);                        break;
-                                        case LONGITUDE: position.setLongitude(value);                       break;
-                                        case ALTITUDE:  position.setAltitude(value);                        break;
-                                        case HDOP:      position.setDilutionOfPrecision(value.intValue());  break;
-                                    }
-                                } break;
-                                case MULTICHANNEL: {
-                                    Long sensorId = sensorType.getId() + sensor.getCh();
-                                    Observation observation = new Observation();
-                                    observation.setTime(time);
-                                    observation.setUnitId(unitId);
-                                    observation.setSensorId(sensorId);
-                                    observation.setValue(value);
-
-                                    observations.add(observation);
-                                } break;
-                            }
-                        } else {
-                            Observation observation = new Observation();
-                            observation.setTime(time);
-                            observation.setUnitId(unitId);
-                            observation.setSensorId(sensorType.getId());
-                            observation.setValue(value);
-
-                            observations.add(observation);
-                        }
-                    }
-                }
-            }
-
-            observations.addAll(locationData.values());
-        }
-
-        return new SenslogV1Model(observations, model.getFrom(), model.getTo());
-    }
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
+import cz.senslog.connector.model.fieldclimate.SensorDataInfo;
+import cz.senslog.connector.model.fieldclimate.SensorType;
+import cz.senslog.connector.model.fieldclimate.StationData;
+import cz.senslog.connector.model.v1.Observation;
+import cz.senslog.connector.model.v1.Position;
+import cz.senslog.connector.model.v1.Record;
+import cz.senslog.connector.model.v1.SenslogV1Model;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cz.senslog.connector.model.fieldclimate.SensorType.Group.POSITION;
+import static cz.senslog.connector.model.fieldclimate.SensorType.countOfGroup;
+import static java.lang.String.format;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+
+public class FieldClimateModelSenslogV1ModelConverter implements Converter<FieldClimateModel, SenslogV1Model> {
+
+    private static final Logger logger = LogManager.getLogger(FieldClimateModelSenslogV1ModelConverter.class);
+
+    @Override
+    public SenslogV1Model convert(FieldClimateModel model) {
+
+        if (model == null || model.getStations() == null) {
+            logger.warn("Nothing to convert. Received model is empty.");
+            return null;
+        }
+
+        List<StationData> stations = model.getStations();
+
+        int size = 0;
+        for (StationData sensor : stations) {
+            for (Map<String, String> sensorData : sensor.getData()) {
+                size += sensorData.size();
+            }
+        }
+
+        List<Record> observations = new ArrayList<>(size);
+        for (StationData station : stations) {
+
+            int locationMapInitSize = station.getData().size() * countOfGroup(POSITION);
+            Map<OffsetDateTime, Position> locationData = new HashMap<>(locationMapInitSize);
+
+            Long unitId = FieldClimateUnitConverter.convertIdToSensLogId(station.getId());
+
+            for (SensorDataInfo sensor : station.getSensors()) {
+
+                SensorType sensorType = SensorType.of(sensor.getCode());
+
+                if (sensorType == null) {
+                    logger.warn("Sensor type is not registered: {}", sensor); continue;
+                }
+
+                Map<String, Integer> aggregateInfos = sensor.getAggr();
+
+                if (aggregateInfos.size() != 1) {
+                    Integer avgActive = aggregateInfos.getOrDefault("avg", 0);
+                    if (avgActive == 1) {
+                        aggregateInfos.clear();
+                        aggregateInfos.put("avg", 1);
+                    } else {
+                        Map.Entry<String, Integer> entry = null;
+                        for (Map.Entry<String, Integer> aggrEntry : aggregateInfos.entrySet()) {
+                            if (aggrEntry.getValue() == 1) {
+                                entry = aggrEntry;
+                                break;
+                            }
+                        }
+                        aggregateInfos.clear();
+                        if (entry != null) {
+                            aggregateInfos.put(entry.getKey(), entry.getValue());
+                        }
+                    }
+                }
+
+                for (Map.Entry<String, Integer> aggrEntry : aggregateInfos.entrySet()) {
+
+                    String aggrType = aggrEntry.getKey();
+                    String sensorDataHash = format("%s_%s_%s_%s_%s",
+                            sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode(), aggrType);
+
+                    for (Map<String, String> dataMap : station.getData()) {
+
+                        if (!dataMap.containsKey(sensorDataHash)) {
+                            logger.warn("For the sensor {}({}) at the station {} does not exist aggregate '{}' value.",
+                                    sensor.getName(), sensor.getCode(), station.getId(), aggrType);
+                            continue;
+                        }
+
+                        String strValue = dataMap.get(sensorDataHash);
+
+                        if (strValue == null) continue;
+
+                        Float value = Float.valueOf(strValue);
+                        String strDate = dataMap.getOrDefault("date", "");
+                        OffsetDateTime time = OffsetDateTime.parse(strDate, ISO_OFFSET_DATE_TIME);
+
+                        if (sensorType.isGrouped()) {
+                            switch (sensorType.getGroup()) {
+                                case POSITION: {
+                                    Position position = locationData.getOrDefault(time, null);
+                                    if (position == null) {
+                                        position = new Position();
+                                        position.setUnitId(unitId);
+                                        position.setTime(time);
+                                        locationData.put(time, position);
+                                    }
+
+                                    switch (sensorType) {
+                                        case LATITUDE:  position.setLatitude(value);                        break;
+                                        case LONGITUDE: position.setLongitude(value);                       break;
+                                        case ALTITUDE:  position.setAltitude(value);                        break;
+                                        case HDOP:      position.setDilutionOfPrecision(value.intValue());  break;
+                                    }
+                                } break;
+                                case MULTICHANNEL: {
+                                    Long sensorId = sensorType.getId() + sensor.getCh();
+                                    Observation observation = new Observation();
+                                    observation.setTime(time);
+                                    observation.setUnitId(unitId);
+                                    observation.setSensorId(sensorId);
+                                    observation.setValue(value);
+
+                                    observations.add(observation);
+                                } break;
+                            }
+                        } else {
+                            Observation observation = new Observation();
+                            observation.setTime(time);
+                            observation.setUnitId(unitId);
+                            observation.setSensorId(sensorType.getId());
+                            observation.setValue(value);
+
+                            observations.add(observation);
+                        }
+                    }
+                }
+            }
+
+            observations.addAll(locationData.values());
+        }
+
+        return new SenslogV1Model(observations, model.getFrom(), model.getTo());
+    }
 }

+ 18 - 15
connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateModelSenslogV2ModelConverter.java

@@ -1,15 +1,18 @@
-package cz.senslog.connector.model.converter;
-
-import cz.senslog.connector.model.api.Converter;
-import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
-import cz.senslog.connector.model.v2.SenslogV2Model;
-
-import java.util.Collections;
-
-public class FieldClimateModelSenslogV2ModelConverter implements Converter<FieldClimateModel, SenslogV2Model> {
-
-    @Override
-    public SenslogV2Model convert(FieldClimateModel model) {
-        return new SenslogV2Model(Collections.emptyList(), model.getFrom(), model.getTo());
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.Converter;
+import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
+import cz.senslog.connector.model.v2.SenslogV2Model;
+
+import java.util.Collections;
+
+public class FieldClimateModelSenslogV2ModelConverter implements Converter<FieldClimateModel, SenslogV2Model> {
+
+    @Override
+    public SenslogV2Model convert(FieldClimateModel model) {
+        return new SenslogV2Model(Collections.emptyList(), model.getFrom(), model.getTo());
+    }
+}

+ 23 - 20
connector-model/src/main/java/cz/senslog/connector/model/converter/FieldClimateUnitConverter.java

@@ -1,20 +1,23 @@
-package cz.senslog.connector.model.converter;
-
-public class FieldClimateUnitConverter {
-
-    public static Long convertIdToSensLogId(String fcUnitId) {
-        if (fcUnitId == null) return null;
-        return Long.parseLong(fcUnitId, 16);
-    }
-
-    public static String convertIdToFieldClimateId(Long slUnitId) {
-        if (slUnitId == null) return null;
-        String hexVal = Long.toHexString(slUnitId);
-        StringBuilder res = new StringBuilder();
-        for (int i = hexVal.length(); i < 8; i++) {
-            res.append("0");
-        }
-        res.append(hexVal);
-        return res.toString();
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+public class FieldClimateUnitConverter {
+
+    public static Long convertIdToSensLogId(String fcUnitId) {
+        if (fcUnitId == null) return null;
+        return Long.parseLong(fcUnitId, 16);
+    }
+
+    public static String convertIdToFieldClimateId(Long slUnitId) {
+        if (slUnitId == null) return null;
+        String hexVal = Long.toHexString(slUnitId);
+        StringBuilder res = new StringBuilder();
+        for (int i = hexVal.length(); i < 8; i++) {
+            res.append("0");
+        }
+        res.append(hexVal);
+        return res.toString();
+    }
+}

+ 28 - 25
connector-model/src/main/java/cz/senslog/connector/model/converter/ModelConverterProvider.java

@@ -1,25 +1,28 @@
-package cz.senslog.connector.model.converter;
-
-import cz.senslog.connector.model.api.ConverterProvider;
-
-/**
- * The class {@see ModelConverterProvider} represents a configuration class
- * for all converters which can be used for models between fetchers and pushers.
- *
- * The method {@see ModelConverterProvider#config} provides the registration
- * for a new converter.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class ModelConverterProvider extends ConverterProvider {
-
-    @Override
-    protected void config() {
-        register(AzureModelSenslogV1ModelConverter.class);
-        register(AzureModelSenslogV2ModelConverter.class);
-        register(FieldClimateModelSenslogV1ModelConverter.class);
-        register(SenslogV1ModelAFarCloudModelConverter.class);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.connector.model.api.ConverterProvider;
+
+/**
+ * The class {@see ModelConverterProvider} represents a configuration class
+ * for all converters which can be used for models between fetchers and pushers.
+ *
+ * The method {@see ModelConverterProvider#config} provides the registration
+ * for a new converter.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class ModelConverterProvider extends ConverterProvider {
+
+    @Override
+    protected void config() {
+        register(AzureModelSenslogV1ModelConverter.class);
+        register(AzureModelSenslogV2ModelConverter.class);
+        register(FieldClimateModelSenslogV1ModelConverter.class);
+        register(SenslogV1ModelAFarCloudModelConverter.class);
+    }
+}

+ 25 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/ReverseMap.java

@@ -0,0 +1,25 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ReverseMap<K, V> extends HashMap<K, V> {
+    private final Map<V, K> reverseMap = new HashMap<>();
+
+    @Override
+    public V put(K key, V value) {
+        reverseMap.put(value, key);
+        return super.put(key, value);
+    }
+
+    public V getValue(K key) {
+        return super.get(key);
+    }
+
+    public K getKey(V value) {
+        return reverseMap.get(value);
+    }
+}

+ 103 - 78
connector-model/src/main/java/cz/senslog/connector/model/converter/SenslogV1ModelAFarCloudModelConverter.java

@@ -1,78 +1,103 @@
-package cz.senslog.connector.model.converter;
-
-import cz.senslog.connector.model.afarcloud.AFarCloudModel;
-import cz.senslog.connector.model.afarcloud.MultiSimpleObservation;
-import cz.senslog.connector.model.afarcloud.SimpleObservation;
-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 cz.senslog.connector.model.v1.UnitInfo;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter.sensLogSensorIdToAfcObservedProperty;
-import static cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter.sensLogUnitIdToAfcResourceId;
-
-public final class SenslogV1ModelAFarCloudModelConverter implements Converter<SenslogV1Model, AFarCloudModel> {
-
-    private static Logger logger = LogManager.getLogger(SenslogV1ModelAFarCloudModelConverter.class);
-
-    @Override
-    public AFarCloudModel convert(SenslogV1Model model) {
-
-        Map<UnitInfo, List<Observation>> unitObservations = new HashMap<>();
-        for (Record record : model.getObservations()) {
-            UnitInfo unitInfo = model.getUnitInfos().get(record.getUnitId());
-            if (record instanceof Observation) {
-                unitObservations.computeIfAbsent(unitInfo, k -> new ArrayList<>())
-                        .add((Observation)record);
-            }
-        }
-
-        List<MultiSimpleObservation> result = new ArrayList<>();
-        for (Map.Entry<UnitInfo, List<Observation>> unitEntry : unitObservations.entrySet()) {
-            MultiSimpleObservation multiObservation = createSimpleObservationSimplified(unitEntry.getKey(), unitEntry.getValue());
-
-            if (multiObservation != null) {
-                result.add(multiObservation);
-            }
-        }
-
-        return new AFarCloudModel(result, model.getFrom(), model.getTo());
-    }
-
-    private static MultiSimpleObservation createSimpleObservationSimplified(UnitInfo unitInfo,  List<Observation> observations) {
-        MultiSimpleObservation result = new MultiSimpleObservation();
-
-        String resourceId = sensLogUnitIdToAfcResourceId(unitInfo.getUnitId());
-        if (resourceId == null) {
-            logger.warn("Can not find resourceId for the unitId '{}'.", unitInfo.getUnitId());
-            return null;
-        }
-
-        result.setResourceId(resourceId);
-        result.setObservations(new ArrayList<>(observations.size()));
-
-        for (Observation observation : observations) {
-            String observedProperty = sensLogSensorIdToAfcObservedProperty(unitInfo.getUnitId(), observation.getSensorId());
-            if (observedProperty == null) {
-                logger.warn("Can not find observedProperty for the sensorId '{}'.", observation.getSensorId());
-                continue;
-            }
-
-            SimpleObservation sObs = new SimpleObservation();
-            sObs.setObservedProperty(observedProperty);
-            sObs.setResult(observation.getValue().doubleValue());
-            sObs.setResultTime(observation.getTime().toEpochSecond());
-            result.getObservations().add(sObs);
-        }
-
-        return result;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.converter;
+
+import cz.senslog.common.util.Tuple;
+import cz.senslog.connector.model.afarcloud.AFarCloudModel;
+import cz.senslog.connector.model.afarcloud.MultiMeasurement;
+import cz.senslog.connector.model.afarcloud.MultiSensorMeasurements;
+import cz.senslog.connector.model.afarcloud.SensorMeasurement;
+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 cz.senslog.connector.model.v1.UnitInfo;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter.sensLogSensorIdToAfcObservedProperty;
+import static cz.senslog.connector.model.converter.AFarCloudUnitSensorConverter.sensLogUnitIdToAfcResourceId;
+import static java.time.OffsetDateTime.MIN;
+import static java.util.Collections.emptyList;
+
+public final class SenslogV1ModelAFarCloudModelConverter implements Converter<SenslogV1Model, AFarCloudModel> {
+
+    private static final Logger logger = LogManager.getLogger(SenslogV1ModelAFarCloudModelConverter.class);
+
+    @Override
+    public AFarCloudModel convert(SenslogV1Model model) {
+        if (model == null) {
+            return new AFarCloudModel(emptyList(), MIN, MIN);
+        }
+
+        if (model.getObservations() == null) {
+            return new AFarCloudModel(emptyList(), model.getFrom(), model.getTo());
+        }
+
+        Map<UnitInfo, List<Observation>> unitObservations = new HashMap<>();
+        for (Record record : model.getObservations()) {
+            UnitInfo unitInfo = model.getUnitInfos().get(record.getUnitId());
+            if (record instanceof Observation) {
+                unitObservations.computeIfAbsent(unitInfo, k -> new ArrayList<>())
+                        .add((Observation)record);
+            }
+        }
+
+        List<MultiMeasurement> result = new ArrayList<>();
+        for (Map.Entry<UnitInfo, List<Observation>> unitEntry : unitObservations.entrySet()) {
+            MultiSensorMeasurements multiObservation = createSimpleObservationSimplified(unitEntry.getKey(), unitEntry.getValue());
+
+            if (multiObservation != null) {
+                result.add(multiObservation);
+            }
+        }
+
+        return new AFarCloudModel(result, model.getFrom(), model.getTo());
+    }
+
+    private static MultiSensorMeasurements createSimpleObservationSimplified(UnitInfo unitInfo, List<Observation> observations) {
+        MultiSensorMeasurements result = new MultiSensorMeasurements();
+
+        Tuple<String, AFarCloudUnitProperties> resourceIdTuple = sensLogUnitIdToAfcResourceId(unitInfo.getUnitId());
+        String resourceId = resourceIdTuple.getItem1();
+        if (resourceId == null) {
+            logger.warn("Can not find resourceId for the unitId '{}'.", unitInfo.getUnitId());
+            return null;
+        }
+
+        // TODO
+        switch (resourceIdTuple.getItem2().getType()) {
+            case COLLAR: {
+
+            }
+            case SENSOR: {
+
+            }
+        }
+
+        result.setResourceId(resourceId);
+        result.setObservations(new ArrayList<>(observations.size()));
+
+        for (Observation observation : observations) {
+            String observedProperty = sensLogSensorIdToAfcObservedProperty(unitInfo.getUnitId(), observation.getSensorId());
+            if (observedProperty == null) {
+                logger.warn("Can not find observedProperty for the sensorId '{}'.", observation.getSensorId());
+                continue;
+            }
+
+            SensorMeasurement sObs = new SensorMeasurement();
+            sObs.setObservedProperty(observedProperty);
+            sObs.setResult(observation.getValue().doubleValue());
+            sObs.setResultTime(observation.getTime().toEpochSecond());
+            result.getObservations().add(sObs);
+        }
+
+        return result;
+    }
+}

+ 59 - 35
connector-model/src/main/java/cz/senslog/connector/model/fieldclimate/StationInfo.java

@@ -1,35 +1,59 @@
-package cz.senslog.connector.model.fieldclimate;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-
-public class StationInfo {
-
-    public static class Name {
-
-        private String original;
-
-        public String getOriginal() {
-            return original;
-        }
-
-        public void setOriginal(String original) {
-            this.original = original;
-        }
-    }
-
-    private Name name;
-
-
-    public Name getName() {
-        return name;
-    }
-
-    public void setName(Name name) {
-        this.name = name;
-    }
-
-    @Override
-    public String toString() {
-        return objectToJson(this);
-    }
-}
+package cz.senslog.connector.model.fieldclimate;
+
+import java.time.LocalDateTime;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+public class StationInfo {
+
+    public static class Name {
+
+        private String original;
+
+        public String getOriginal() {
+            return original;
+        }
+
+        public void setOriginal(String original) {
+            this.original = original;
+        }
+    }
+
+    public static class Dates {
+        
+        private LocalDateTime min_date;
+
+        public LocalDateTime getMin_date() {
+            return min_date;
+        }
+
+        public void setMin_date(LocalDateTime min_date) {
+            this.min_date = min_date;
+        }
+    }
+
+    private Name name;
+
+    private Dates dates;
+
+    public Name getName() {
+        return name;
+    }
+
+    public void setName(Name name) {
+        this.name = name;
+    }
+
+    public void setDates(Dates dates) {
+        this.dates = dates;
+    }
+    
+    public LocalDateTime getMinDate() {
+        return dates != null ? dates.min_date : null;
+    }
+
+    @Override
+    public String toString() {
+        return objectToJson(this);
+    }
+}

+ 42 - 39
connector-model/src/main/java/cz/senslog/connector/model/v1/Observation.java

@@ -1,40 +1,43 @@
-package cz.senslog.connector.model.v1;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-
-/**
- * The class {@code Observation} represents a record for Senslog V1.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Observation extends Record {
-
-    /** Identifier of specific sensor which measured the value (e.q. temperature, humidity, etc...). */
-    private Long sensorId;
-
-    /** Concrete value of the sensor. */
-    private Float value;
-
-    public Long getSensorId() {
-        return sensorId;
-    }
-
-    public void setSensorId(Long sensorId) {
-        this.sensorId = sensorId;
-    }
-
-    public Float getValue() {
-        return value;
-    }
-
-    public void setValue(Float value) {
-        this.value = value;
-    }
-
-    @Override
-    public String toString() {
-        return objectToJson(this);
-    }
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.v1;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+/**
+ * The class {@code Observation} represents a record for Senslog V1.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Observation extends Record {
+
+    /** Identifier of specific sensor which measured the value (e.q. temperature, humidity, etc...). */
+    private Long sensorId;
+
+    /** Concrete value of the sensor. */
+    private Float value;
+
+    public Long getSensorId() {
+        return sensorId;
+    }
+
+    public void setSensorId(Long sensorId) {
+        this.sensorId = sensorId;
+    }
+
+    public Float getValue() {
+        return value;
+    }
+
+    public void setValue(Float value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return objectToJson(this);
+    }
 }

+ 76 - 73
connector-model/src/main/java/cz/senslog/connector/model/v1/Position.java

@@ -1,73 +1,76 @@
-package cz.senslog.connector.model.v1;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-
-/**
- * The class {@code Position} represents a record of position for Senslog V1.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Position extends Record {
-
-    /** Latitude of position */
-    private Float latitude;
-
-    /** Longitude of position */
-    private Float longitude;
-
-    /** Altitude of position */
-    private Float altitude;
-
-    /** Speed of the unit */
-    private Float speed;
-
-    /** Horizontal dilution  of position */
-    private Integer dilutionOfPrecision;
-
-    public Float getLatitude() {
-        return latitude;
-    }
-
-    public void setLatitude(Float latitude) {
-        this.latitude = latitude;
-    }
-
-    public Float getLongitude() {
-        return longitude;
-    }
-
-    public void setLongitude(Float longitude) {
-        this.longitude = longitude;
-    }
-
-    public Float getAltitude() {
-        return altitude;
-    }
-
-    public void setAltitude(Float altitude) {
-        this.altitude = altitude;
-    }
-
-    public Float getSpeed() {
-        return speed;
-    }
-
-    public void setSpeed(Float speed) {
-        this.speed = speed;
-    }
-
-    public Integer getDilutionOfPrecision() {
-        return dilutionOfPrecision;
-    }
-
-    public void setDilutionOfPrecision(Integer dilutionOfPrecision) {
-        this.dilutionOfPrecision = dilutionOfPrecision;
-    }
-
-    @Override
-    public String toString() {
-        return objectToJson(this);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.v1;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+/**
+ * The class {@code Position} represents a record of position for Senslog V1.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Position extends Record {
+
+    /** Latitude of position */
+    private Float latitude;
+
+    /** Longitude of position */
+    private Float longitude;
+
+    /** Altitude of position */
+    private Float altitude;
+
+    /** Speed of the unit */
+    private Float speed;
+
+    /** Horizontal dilution  of position */
+    private Integer dilutionOfPrecision;
+
+    public Float getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(Float latitude) {
+        this.latitude = latitude;
+    }
+
+    public Float getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(Float longitude) {
+        this.longitude = longitude;
+    }
+
+    public Float getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(Float altitude) {
+        this.altitude = altitude;
+    }
+
+    public Float getSpeed() {
+        return speed;
+    }
+
+    public void setSpeed(Float speed) {
+        this.speed = speed;
+    }
+
+    public Integer getDilutionOfPrecision() {
+        return dilutionOfPrecision;
+    }
+
+    public void setDilutionOfPrecision(Integer dilutionOfPrecision) {
+        this.dilutionOfPrecision = dilutionOfPrecision;
+    }
+
+    @Override
+    public String toString() {
+        return objectToJson(this);
+    }
+}

+ 40 - 37
connector-model/src/main/java/cz/senslog/connector/model/v1/Record.java

@@ -1,37 +1,40 @@
-package cz.senslog.connector.model.v1;
-
-import java.time.OffsetDateTime;
-import java.time.ZonedDateTime;
-
-import static java.time.format.DateTimeFormatter.ofPattern;
-
-
-public abstract class Record {
-
-    /** Identifier of area for sensors */
-    private Long unitId;
-
-    /** Timestamp when the value was measured. */
-    private OffsetDateTime time;
-
-    public Long getUnitId() {
-        return unitId;
-    }
-
-    public void setUnitId(Long unitId) {
-        this.unitId = unitId;
-    }
-
-    public OffsetDateTime getTime() {
-        return time;
-    }
-
-    public String getFormattedTime() {
-        return time != null ? time.format(ofPattern("yyyy-MM-dd HH:mm:ssZ")) : "";
-    }
-
-    public void setTime(OffsetDateTime time) {
-        this.time = time;
-    }
-
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.v1;
+
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+
+import static java.time.format.DateTimeFormatter.ofPattern;
+
+
+public abstract class Record {
+
+    /** Identifier of area for sensors */
+    private Long unitId;
+
+    /** Timestamp when the value was measured. */
+    private OffsetDateTime time;
+
+    public Long getUnitId() {
+        return unitId;
+    }
+
+    public void setUnitId(Long unitId) {
+        this.unitId = unitId;
+    }
+
+    public OffsetDateTime getTime() {
+        return time;
+    }
+
+    public String getFormattedTime() {
+        return time != null ? time.format(ofPattern("yyyy-MM-dd HH:mm:ssZ")) : "";
+    }
+
+    public void setTime(OffsetDateTime time) {
+        this.time = time;
+    }
+
+}

+ 54 - 51
connector-model/src/main/java/cz/senslog/connector/model/v1/SenslogV1Model.java

@@ -1,51 +1,54 @@
-package cz.senslog.connector.model.v1;
-
-import cz.senslog.connector.model.api.AbstractModel;
-
-import java.time.OffsetDateTime;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The class {@code SenslogV1Model} represents a model which contains
- * transfer data for Senslog V1 pusher.
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class SenslogV1Model extends AbstractModel {
-
-    /** List of observations */
-    private final List<Record> observations;
-
-    /** Map of units related to the observations  */
-    private final Map<Long, UnitInfo> unitInfos;
-
-    /**
-     * Constructor of the class sets all attributes.
-     * @param observations - list of observations.
-     * @param from - start of the time range.
-     * @param to - end of the time range.
-     */
-    public SenslogV1Model(List<Record> observations, OffsetDateTime from, OffsetDateTime to) {
-        super(from, to);
-        this.observations = observations;
-        this.unitInfos = Collections.emptyMap();
-    }
-
-    public SenslogV1Model(Map<Long, UnitInfo> unitInfos, List<Record> observations, OffsetDateTime from, OffsetDateTime to) {
-        super(from, to);
-        this.observations = observations;
-        this.unitInfos = unitInfos;
-    }
-
-    public Map<Long, UnitInfo> getUnitInfos() {
-        return unitInfos;
-    }
-
-    public List<Record> getObservations() {
-        return  observations;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.v1;
+
+import cz.senslog.connector.model.api.AbstractModel;
+
+import java.time.OffsetDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The class {@code SenslogV1Model} represents a model which contains
+ * transfer data for Senslog V1 pusher.
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class SenslogV1Model extends AbstractModel {
+
+    /** List of observations */
+    private final List<Record> observations;
+
+    /** Map of units related to the observations  */
+    private final Map<Long, UnitInfo> unitInfos;
+
+    /**
+     * Constructor of the class sets all attributes.
+     * @param observations - list of observations.
+     * @param from - start of the time range.
+     * @param to - end of the time range.
+     */
+    public SenslogV1Model(List<Record> observations, OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+        this.observations = observations;
+        this.unitInfos = Collections.emptyMap();
+    }
+
+    public SenslogV1Model(Map<Long, UnitInfo> unitInfos, List<Record> observations, OffsetDateTime from, OffsetDateTime to) {
+        super(from, to);
+        this.observations = observations;
+        this.unitInfos = unitInfos;
+    }
+
+    public Map<Long, UnitInfo> getUnitInfos() {
+        return unitInfos;
+    }
+
+    public List<Record> getObservations() {
+        return  observations;
+    }
+}

+ 86 - 83
connector-model/src/main/java/cz/senslog/connector/model/v1/SensorInfo.java

@@ -1,83 +1,86 @@
-package cz.senslog.connector.model.v1;
-
-import java.time.OffsetDateTime;
-
-public class SensorInfo {
-
-    public static class Phenomenon {
-
-        private String phenomenonName;
-        private String unit;
-
-        public String getPhenomenonName() {
-            return phenomenonName;
-        }
-
-        public void setPhenomenonName(String phenomenonName) {
-            this.phenomenonName = phenomenonName;
-        }
-
-        public String getUnit() {
-            return unit;
-        }
-
-        public void setUnit(String unit) {
-            this.unit = unit;
-        }
-    }
-
-    private OffsetDateTime firstObservationTime;
-    private OffsetDateTime lastObservationTime;
-    private Phenomenon phenomenon;
-    private Long sensorId;
-    private String sensorName;
-    private String sensorType;
-
-    public OffsetDateTime getFirstObservationTime() {
-        return firstObservationTime;
-    }
-
-    public void setFirstObservationTime(OffsetDateTime firstObservationTime) {
-        this.firstObservationTime = firstObservationTime;
-    }
-
-    public OffsetDateTime getLastObservationTime() {
-        return lastObservationTime;
-    }
-
-    public void setLastObservationTime(OffsetDateTime lastObservationTime) {
-        this.lastObservationTime = lastObservationTime;
-    }
-
-    public Phenomenon getPhenomenon() {
-        return phenomenon;
-    }
-
-    public void setPhenomenon(Phenomenon phenomenon) {
-        this.phenomenon = phenomenon;
-    }
-
-    public Long getSensorId() {
-        return sensorId;
-    }
-
-    public void setSensorId(Long sensorId) {
-        this.sensorId = sensorId;
-    }
-
-    public String getSensorName() {
-        return sensorName;
-    }
-
-    public void setSensorName(String sensorName) {
-        this.sensorName = sensorName;
-    }
-
-    public String getSensorType() {
-        return sensorType;
-    }
-
-    public void setSensorType(String sensorType) {
-        this.sensorType = sensorType;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.v1;
+
+import java.time.OffsetDateTime;
+
+public class SensorInfo {
+
+    public static class Phenomenon {
+
+        private String phenomenonName;
+        private String unit;
+
+        public String getPhenomenonName() {
+            return phenomenonName;
+        }
+
+        public void setPhenomenonName(String phenomenonName) {
+            this.phenomenonName = phenomenonName;
+        }
+
+        public String getUnit() {
+            return unit;
+        }
+
+        public void setUnit(String unit) {
+            this.unit = unit;
+        }
+    }
+
+    private OffsetDateTime firstObservationTime;
+    private OffsetDateTime lastObservationTime;
+    private Phenomenon phenomenon;
+    private Long sensorId;
+    private String sensorName;
+    private String sensorType;
+
+    public OffsetDateTime getFirstObservationTime() {
+        return firstObservationTime;
+    }
+
+    public void setFirstObservationTime(OffsetDateTime firstObservationTime) {
+        this.firstObservationTime = firstObservationTime;
+    }
+
+    public OffsetDateTime getLastObservationTime() {
+        return lastObservationTime;
+    }
+
+    public void setLastObservationTime(OffsetDateTime lastObservationTime) {
+        this.lastObservationTime = lastObservationTime;
+    }
+
+    public Phenomenon getPhenomenon() {
+        return phenomenon;
+    }
+
+    public void setPhenomenon(Phenomenon phenomenon) {
+        this.phenomenon = phenomenon;
+    }
+
+    public Long getSensorId() {
+        return sensorId;
+    }
+
+    public void setSensorId(Long sensorId) {
+        this.sensorId = sensorId;
+    }
+
+    public String getSensorName() {
+        return sensorName;
+    }
+
+    public void setSensorName(String sensorName) {
+        this.sensorName = sensorName;
+    }
+
+    public String getSensorType() {
+        return sensorType;
+    }
+
+    public void setSensorType(String sensorType) {
+        this.sensorType = sensorType;
+    }
+}

+ 68 - 65
connector-model/src/main/java/cz/senslog/connector/model/v1/UnitInfo.java

@@ -1,65 +1,68 @@
-package cz.senslog.connector.model.v1;
-
-import java.util.*;
-
-public class UnitInfo {
-
-    private String description;
-    private Integer holderId;
-    private Long unitId;
-
-    Map<Long, SensorInfo> sensors;
-
-    public UnitInfo() {
-        this.sensors = new HashMap<>();
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public Integer getHolderId() {
-        return holderId;
-    }
-
-    public void setHolderId(Integer holderId) {
-        this.holderId = holderId;
-    }
-
-    public Long getUnitId() {
-        return unitId;
-    }
-
-    public void setUnitId(Long unitId) {
-        this.unitId = unitId;
-    }
-
-    public Collection<SensorInfo> getSensors() {
-        return sensors.values();
-    }
-
-    public SensorInfo getSensor(long id) {
-        return sensors.get(id);
-    }
-
-    public void setSensors(List<SensorInfo> sensorInfos) {
-        sensorInfos.forEach(s -> this.sensors.putIfAbsent(s.getSensorId(), s));
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        UnitInfo unitInfo = (UnitInfo) o;
-        return Objects.equals(unitId, unitInfo.unitId);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(unitId);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.model.v1;
+
+import java.util.*;
+
+public class UnitInfo {
+
+    private String description;
+    private Integer holderId;
+    private Long unitId;
+
+    Map<Long, SensorInfo> sensors;
+
+    public UnitInfo() {
+        this.sensors = new HashMap<>();
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Integer getHolderId() {
+        return holderId;
+    }
+
+    public void setHolderId(Integer holderId) {
+        this.holderId = holderId;
+    }
+
+    public Long getUnitId() {
+        return unitId;
+    }
+
+    public void setUnitId(Long unitId) {
+        this.unitId = unitId;
+    }
+
+    public Collection<SensorInfo> getSensors() {
+        return sensors.values();
+    }
+
+    public SensorInfo getSensor(long id) {
+        return sensors.get(id);
+    }
+
+    public void setSensors(List<SensorInfo> sensorInfos) {
+        sensorInfos.forEach(s -> this.sensors.putIfAbsent(s.getSensorId(), s));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        UnitInfo unitInfo = (UnitInfo) o;
+        return Objects.equals(unitId, unitInfo.unitId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(unitId);
+    }
+}

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

@@ -12,7 +12,7 @@ import java.time.*;
 import java.util.ArrayList;
 import java.util.List;
 
-import static cz.senslog.connector.model.azure.SensorType.*;
+import static cz.senslog.connector.model.converter.AzureUnitConverter.convertSensorTypeToSensorId;
 import static java.time.OffsetDateTime.MAX;
 import static java.time.OffsetDateTime.MIN;
 import static java.util.Collections.emptyList;
@@ -57,17 +57,17 @@ class AzureModelSenslogV1ModelConverterTest {
             assertEquals(10002376L, observation.getUnitId());
             assertEquals("1970-01-01 00:00:00+0000", observation.getFormattedTime());
 
-            if (observation.getSensorId() == BATTERY_LEVEL.getId()) {
+            if (observation.getSensorId().equals(convertSensorTypeToSensorId("8CF9574000000948", "batteryLevel"))) {
                 assertEquals(100.0F, observation.getValue());
-            } else if (observation.getSensorId() == HUMIDITY.getId()) {
+            } else if (observation.getSensorId().equals(convertSensorTypeToSensorId("8CF9574000000948", "humidity"))) {
                 assertEquals(50.0F, observation.getValue());
-            } else if (observation.getSensorId() == CO2.getId()) {
+            } else if (observation.getSensorId().equals(convertSensorTypeToSensorId("8CF9574000000948", "co2"))) {
                 assertEquals(600.0F, observation.getValue());
-            } else if (observation.getSensorId() == RSSI.getId()) {
+            } else if (observation.getSensorId().equals(convertSensorTypeToSensorId("8CF9574000000948", "rssi"))) {
                 assertEquals(-150, observation.getValue());
-            } else if (observation.getSensorId() == SNR.getId()) {
+            } else if (observation.getSensorId().equals(convertSensorTypeToSensorId("8CF9574000000948", "snr"))) {
                 assertEquals(3F, observation.getValue());
-            } else if (observation.getSensorId() == TEMPERATURE.getId()) {
+            } else if (observation.getSensorId().equals(convertSensorTypeToSensorId("8CF9574000000948", "temperature"))) {
                 assertEquals(25.0F, observation.getValue());
             }
         }

+ 21 - 0
connector-push-afarcloud/license.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 University of West Bohemia (UWB) https://www.zcu.cz/en, LESPROJEKT-SLUŽBY s.r.o. (LESP) https://www.lesprojekt.cz/en
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 20 - 17
connector-push-afarcloud/src/main/java/cz/senslog/connector/push/afarcloud/AFarCloudConfig.java

@@ -1,17 +1,20 @@
-package cz.senslog.connector.push.afarcloud;
-
-import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.model.config.HostConfig;
-
-class AFarCloudConfig {
-
-    private final HostConfig telemetryHost;
-
-    AFarCloudConfig(DefaultConfig defaultConfig) {
-        this.telemetryHost = new HostConfig(defaultConfig.getPropertyConfig("telemetryHost"));
-    }
-
-    public HostConfig getTelemetryHost() {
-        return telemetryHost;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.push.afarcloud;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.config.HostConfig;
+
+class AFarCloudConfig {
+
+    private final HostConfig telemetryHost;
+
+    AFarCloudConfig(DefaultConfig defaultConfig) {
+        this.telemetryHost = new HostConfig(defaultConfig.getPropertyConfig("telemetryHost"));
+    }
+
+    public HostConfig getTelemetryHost() {
+        return telemetryHost;
+    }
+}

+ 32 - 29
connector-push-afarcloud/src/main/java/cz/senslog/connector/push/afarcloud/AFarCloudPushProvider.java

@@ -1,30 +1,33 @@
-package cz.senslog.connector.push.afarcloud;
-
-import cz.senslog.connector.model.afarcloud.AFarCloudModel;
-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.common.http.HttpClient.newHttpSSLClient;
-
-public class AFarCloudPushProvider implements ConnectorPushProvider {
-
-    private static Logger logger = LogManager.getLogger(AFarCloudPushProvider.class);
-
-    @Override
-    public ConnectorPusher<AFarCloudModel> createPusher(DefaultConfig defaultConfig) {
-        logger.info("Initialization a new push provider {}.", AFarCloudPushProvider.class);
-
-        logger.debug("Creating a new configuration.");
-        AFarCloudConfig config = new AFarCloudConfig(defaultConfig);
-        logger.info("Configuration for {} was created successfully.", AFarCloudPusher.class);
-
-        logger.debug("Creating a new instance of {}.", AFarCloudPusher.class);
-        AFarCloudPusher pusher = new AFarCloudPusher(config, newHttpSSLClient());
-        logger.info("Pusher for {} was created successfully.", AFarCloudPusher.class);
-
-        return pusher;
-    }
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.push.afarcloud;
+
+import cz.senslog.connector.model.afarcloud.AFarCloudModel;
+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.common.http.HttpClient.newHttpSSLClient;
+
+public class AFarCloudPushProvider implements ConnectorPushProvider {
+
+    private static final Logger logger = LogManager.getLogger(AFarCloudPushProvider.class);
+
+    @Override
+    public ConnectorPusher<AFarCloudModel> createPusher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new push provider {}.", AFarCloudPushProvider.class);
+
+        logger.debug("Creating a new configuration.");
+        AFarCloudConfig config = new AFarCloudConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", AFarCloudPusher.class);
+
+        logger.debug("Creating a new instance of {}.", AFarCloudPusher.class);
+        AFarCloudPusher pusher = new AFarCloudPusher(config, newHttpSSLClient());
+        logger.info("Pusher for {} was created successfully.", AFarCloudPusher.class);
+
+        return pusher;
+    }
 }

+ 75 - 66
connector-push-afarcloud/src/main/java/cz/senslog/connector/push/afarcloud/AFarCloudPusher.java

@@ -1,67 +1,76 @@
-package cz.senslog.connector.push.afarcloud;
-
-import cz.senslog.common.http.HttpClient;
-import cz.senslog.common.http.HttpRequest;
-import cz.senslog.common.http.HttpResponse;
-import cz.senslog.common.http.URLBuilder;
-import cz.senslog.connector.model.afarcloud.AFarCloudModel;
-import cz.senslog.connector.model.afarcloud.MultiSimpleObservation;
-import cz.senslog.connector.model.config.HostConfig;
-import cz.senslog.connector.push.api.ConnectorPusher;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.UUID;
-
-import static cz.senslog.common.json.BasicJson.objectToJson;
-
-
-public class AFarCloudPusher implements ConnectorPusher<AFarCloudModel> {
-
-    private static Logger logger = LogManager.getLogger(AFarCloudPusher.class);
-
-    private final AFarCloudConfig config;
-    private final HttpClient httpClient;
-
-    AFarCloudPusher(AFarCloudConfig config, HttpClient httpClient) {
-        this.config = config;
-        this.httpClient = httpClient;
-    }
-
-    @Override
-    public void init() {}
-
-    @Override
-    public void push(AFarCloudModel model) {
-
-        HostConfig host = config.getTelemetryHost();
-
-        int counter = 0;
-        for (MultiSimpleObservation aggObservations : model.getUnitAggObservations()) {
-
-            String id = UUID.randomUUID().toString();
-            logger.info("Observation {} {}", id, aggObservations);
-
-            logger.debug("Creating a request for the observation {}.", id);
-            HttpRequest request = HttpRequest.newBuilder().POST()
-                    .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
-//                            .addParam("test", "")
-                            .build())
-                    .body(objectToJson(aggObservations))
-                    .build();
-
-            logger.info("Sending request for the {}", id);
-            HttpResponse response = httpClient.send(request);
-            logger.info("Response of {} with status {}.", id, response.getStatus());
-
-            if (response.isOk()) {
-                counter += 1;
-                logger.info("Observation with id {} was uploaded successfully.", id);
-            } else {
-                logger.error("Observation with id {} was not send. Reason {}.", id, response.getBody());
-            }
-        }
-
-        logger.info("Total uploaded multiple observation {}/{}.", counter, model.getUnitAggObservations().size());
-    }
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package cz.senslog.connector.push.afarcloud;
+
+import cz.senslog.common.http.HttpClient;
+import cz.senslog.common.http.HttpRequest;
+import cz.senslog.common.http.HttpResponse;
+import cz.senslog.common.http.URLBuilder;
+import cz.senslog.connector.model.afarcloud.AFarCloudModel;
+import cz.senslog.connector.model.afarcloud.CollarMeasurement;
+import cz.senslog.connector.model.afarcloud.MultiMeasurement;
+import cz.senslog.connector.model.afarcloud.MultiSensorMeasurements;
+import cz.senslog.connector.model.config.HostConfig;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.UUID;
+
+import static cz.senslog.common.json.BasicJson.objectToJson;
+
+
+public class AFarCloudPusher implements ConnectorPusher<AFarCloudModel> {
+
+    private static final Logger logger = LogManager.getLogger(AFarCloudPusher.class);
+
+    private final AFarCloudConfig config;
+    private final HttpClient httpClient;
+
+    AFarCloudPusher(AFarCloudConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    public void init() {}
+
+    @Override
+    public void push(AFarCloudModel model) {
+
+        if (model == null || model.getUnitAggObservations() == null) {
+            logger.warn("Nothing to push. The model is empty."); return;
+        }
+
+        HostConfig host = config.getTelemetryHost();
+
+        int counter = 0;
+        for (MultiMeasurement aggObservations : model.getUnitAggObservations()) {
+
+            String id = UUID.randomUUID().toString();
+            logger.info("Observation {} {}", id, aggObservations);
+
+            logger.debug("Creating a request for the observation {}.", id);
+            HttpRequest request = HttpRequest.newBuilder().POST()
+                    .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
+//                            .addParam("test", "")
+                            .build())
+                    .body(aggObservations.toJson())
+                    .build();
+
+            logger.info("Sending request for the {}", id);
+            HttpResponse response = httpClient.send(request);
+            logger.info("Response of {} with status {}.", id, response.getStatus());
+
+            if (response.isOk()) {
+                counter += 1;
+                logger.info("Observation with id {} was uploaded successfully.", id);
+            } else {
+                logger.error("Observation with id {} was not send. Reason {}.", id, response.getBody());
+            }
+        }
+
+        logger.info("Total uploaded multiple observation {}/{}.", counter, model.getUnitAggObservations().size());
+    }
 }

+ 21 - 0
connector-push-api/license.txt

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2020 University of West Bohemia (UWB) https://www.zcu.cz/en, LESPROJEKT-SLUŽBY s.r.o. (LESP) https://www.lesprojekt.cz/en
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor