Bladeren bron

Fix multi-thread access to SensLog token

Lukas Cerny 3 jaren geleden
bovenliggende
commit
b65e90aa8a
26 gewijzigde bestanden met toevoegingen van 822 en 561 verwijderingen
  1. 7 3
      README.md
  2. 52 51
      build.gradle
  3. 125 20
      config/foodie.yaml
  4. 139 62
      config/test-foodie.yaml
  5. 2 2
      src/main/java/cz/senslog/watchdog/app/Application.java
  6. 3 2
      src/main/java/cz/senslog/watchdog/config/Configuration.java
  7. 35 35
      src/main/java/cz/senslog/watchdog/config/IMMessageBrokerConfig.java
  8. 23 23
      src/main/java/cz/senslog/watchdog/config/MessageBrokerType.java
  9. 18 11
      src/main/java/cz/senslog/watchdog/core/Watcher.java
  10. 10 2
      src/main/java/cz/senslog/watchdog/core/connection/EmailServerConnection.java
  11. 12 2
      src/main/java/cz/senslog/watchdog/core/connection/SensLogWSConnection.java
  12. 0 6
      src/main/java/cz/senslog/watchdog/messagebroker/BlockingMessageBroker.java
  13. 4 4
      src/main/java/cz/senslog/watchdog/messagebroker/MessageBrokerManager.java
  14. 113 20
      src/main/java/cz/senslog/watchdog/messagebroker/broker/EmailMessageBroker.java
  15. 4 4
      src/main/java/cz/senslog/watchdog/messagebroker/broker/IMMessageBroker.java
  16. 6 6
      src/main/java/cz/senslog/watchdog/provider/DataProvider.java
  17. 1 1
      src/main/java/cz/senslog/watchdog/provider/DataProviderManager.java
  18. 34 34
      src/main/java/cz/senslog/watchdog/provider/database/DBDataProvider.java
  19. 6 4
      src/main/java/cz/senslog/watchdog/provider/ws/WSSensLogDataProvider.java
  20. 27 28
      src/main/java/cz/senslog/watchdog/util/http/HttpClient.java
  21. 15 25
      src/main/java/cz/senslog/watchdog/util/http/HttpCookie.java
  22. 13 10
      src/main/java/cz/senslog/watchdog/util/http/HttpHeader.java
  23. 29 38
      src/main/java/cz/senslog/watchdog/util/schedule/Scheduler.java
  24. 49 74
      src/main/java/cz/senslog/watchdog/util/schedule/SchedulerBuilderImpl.java
  25. 63 62
      src/main/java/cz/senslog/watchdog/util/schedule/SchedulerImpl.java
  26. 32 32
      src/test/java/cz/senslog/watchdog/config/ConfigurationTest.java

+ 7 - 3
README.md

@@ -1,3 +1,7 @@
-## Docker build
-
-``` docker build -t <image-name> --build-arg config_file=<config-file> -f ./Dockerfile .```
+## Docker build
+
+* Docker build
+```docker build -t watchdog --build-arg config_file=./config/foodie.yaml .```
+
+* Docker save
+```docker save -o watchdog.tar watchdog```

+ 52 - 51
build.gradle

@@ -1,51 +1,52 @@
-plugins {
-    id 'java'
-}
-
-group 'cz.senslog'
-version '1.1'
-
-repositories {
-    mavenLocal()
-    mavenCentral()
-}
-
-test {
-    useJUnitPlatform()
-}
-
-java {
-    sourceCompatibility = JavaVersion.VERSION_11
-    targetCompatibility = JavaVersion.VERSION_11
-}
-
-jar {
-    manifest {
-        attributes(
-                'Main-Class': 'cz.senslog.watchdog.app.Main'
-        )
-    }
-    from {
-        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
-    }
-}
-
-dependencies {
-
-    implementation group: 'com.beust', name: 'jcommander', version: '1.78'
-    implementation group: 'org.yaml', name: 'snakeyaml', version: '1.24'
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.12.0'
-    implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.12.0'
-    implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.9'
-    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
-
-    implementation group: 'org.jdbi', name: 'jdbi3-postgres', version: '3.12.2'
-    implementation group: 'org.jdbi', name: 'jdbi3-jodatime2', version: '3.12.2'
-    implementation group: 'com.zaxxer', name: 'HikariCP', version: '3.4.2'
-    implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.10'
-
-    implementation group: 'javax.mail', name: 'javax.mail-api', version: '1.6.2'
-    implementation group: 'com.sun.mail', name: 'javax.mail', version: '1.6.2'
-
-    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
-}
+plugins {
+    id 'java'
+}
+
+group 'cz.senslog'
+version '1.1'
+
+repositories {
+    mavenLocal()
+    mavenCentral()
+}
+
+test {
+    useJUnitPlatform()
+}
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_11
+    targetCompatibility = JavaVersion.VERSION_11
+}
+
+jar {
+    manifest {
+        attributes(
+                'Main-Class': 'cz.senslog.watchdog.app.Main'
+        )
+    }
+    from {
+        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
+    }
+}
+
+dependencies {
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.18.0'
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.18.0'
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.18.0'
+
+    implementation group: 'com.beust', name: 'jcommander', version: '1.78'
+    implementation group: 'org.yaml', name: 'snakeyaml', version: '1.26'
+    implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.1.3'
+    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
+
+    implementation group: 'org.jdbi', name: 'jdbi3-postgres', version: '3.12.2'
+    implementation group: 'org.jdbi', name: 'jdbi3-jodatime2', version: '3.12.2'
+    implementation group: 'com.zaxxer', name: 'HikariCP', version: '3.4.2'
+    implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.26'
+
+    implementation group: 'javax.mail', name: 'javax.mail-api', version: '1.6.2'
+    implementation group: 'com.sun.mail', name: 'javax.mail', version: '1.6.2'
+
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
+}

+ 125 - 20
config/foodie.yaml

@@ -22,9 +22,10 @@ messageBrokers:
       server: lspEmail
       senderEmail: "watchdog@senslog.org"
       recipientEmail:
-        - "luccerny@ntis.zcu.cz"
+#        - "luccerny@ntis.zcu.cz"
         - "fialar@kgm.zcu.cz"
-      subject: "[Watchdog] Report SensLog (CZ)"
+        - "kepka@ccss.cz"
+      subject: "[Watchdog] Report SensLog (CZ) - $group.name"
 
   mikeEmail:
     type: EMAIL
@@ -32,9 +33,19 @@ messageBrokers:
       server: lspEmail
       senderEmail: "watchdog@senslog.org"
       recipientEmail:
-        - "luccerny@ntis.zcu.cz"
+#        - "luccerny@ntis.zcu.cz"
         - "kepka@ccss.cz"
-      subject: "[Watchdog] Report SensLog (CZ)"
+      subject: "[Watchdog] Report SensLog (CZ) - $group.name"
+
+  krivanekEmail:
+    type: EMAIL
+    config:
+      server: lspEmail
+      senderEmail: "watchdog@senslog.org"
+      recipientEmail:
+#        - "luccerny@ntis.zcu.cz"
+        - "krivanek@lesprojekt.cz"
+      subject: "[Watchdog] Report SensLog (CZ) - $group.name"
 
   lspServisEmail:
     type: EMAIL
@@ -42,10 +53,20 @@ messageBrokers:
       server: lspEmail
       senderEmail: "watchdog@senslog.org"
       recipientEmail:
-        - "luccerny@ntis.zcu.cz"
+#        - "luccerny@ntis.zcu.cz"
         - "servis@lesprojekt.cz"
         - "kepka@ccss.cz"
-      subject: "[Watchdog] Report SensLog (CZ)"
+      subject: "[Watchdog] Report SensLog (CZ) - $group.name"
+
+  lspOnlyServisEmail:
+    type: EMAIL
+    config:
+      server: lspEmail
+      senderEmail: "watchdog@senslog.org"
+      recipientEmail:
+        - "luccerny@ntis.zcu.cz"
+        - "servis@lesprojekt.cz"
+      subject: "[Watchdog] Report SensLog (CZ) - $group.name"
 
 dataProviders:
   wsSensLogKynsperk:
@@ -82,7 +103,43 @@ dataProviders:
     type: WEB_SERVICE
     config:
       server: lspSenslog15
-      groupName: "Meņģele"
+      groupName: "Mengele"
+
+  wsSensLogZabcice:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "neudert"
+
+  wsSensLogInnovar:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "Innovar"
+
+  wsSensLogZelenec:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "zelenec"
+
+  wsSensLogCepirohy:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "agriclima"
+
+  wsSensLogSanJuan:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "sanjuan"
+
+  wsSensLogSVSMP:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "svsmp"
 
 groups:
   kynsperk:
@@ -111,7 +168,7 @@ groups:
 
   osek:
     name: "Osek"
-    active: false
+    active: true
     dataProvider: wsSensLogOsek
     messageBroker: mikeEmail
     resultType: FAIL
@@ -119,34 +176,82 @@ groups:
 
   latviaSmiltene:
     name: "Latvia Smiltene"
-    active: false
+    active: true
     dataProvider: wsSensLogSmiltene
     messageBroker: lspServisEmail
     resultType: FAIL
     period: 86400
 
   latviaMengele:
-    name: "Latvia Meņģele"
-    active: false
+    name: "Latvia Meņgele"
+    active: true
     dataProvider: wsSensLogMengele
     messageBroker: lspServisEmail
     resultType: FAIL
     period: 86400
 
-superGroups:
-  osek&zcu:
-    name: "Osek & ZČU Robčice"
-    messageBroker: mikeEmail
+  zabcice:
+    name: "Žabčice"
+    active: true
+    dataProvider: wsSensLogZabcice
+    messageBroker: lspServisEmail
     resultType: FAIL
     period: 86400
-    groups: [ osek, zcu ]
 
-  latvia:
-    name: "Latvia Meņģele & Smiltene"
-    messageBroker: mikeEmail
+  innovar:
+    name: "InnoVar"
+    active: true
+    dataProvider: wsSensLogInnovar
+    messageBroker: lspServisEmail
+    resultType: FAIL
+    period: 86400
+
+  zelenec:
+    name: "Zeleneč"
+    active: true
+    dataProvider: wsSensLogZelenec
+    messageBroker: krivanekEmail
+    resultType: FAIL
+    period: 86400
+
+  cepirohy:
+    name: "Čepirohy"
+    active: true
+    dataProvider: wsSensLogCepirohy
+    messageBroker: lspServisEmail
+    resultType: FAIL
+    period: 86400
+
+  sanJuan:
+    name: "San Juan (Argentina)"
+    active: true
+    dataProvider: wsSensLogSanJuan
+    messageBroker: lspServisEmail
     resultType: FAIL
     period: 86400
-    groups: [ latviaSmiltene, latviaMengele ]
+
+  svsmpSadky:
+    name: "SVSMP Sádky"
+    active: true
+    dataProvider: wsSensLogSVSMP
+    messageBroker: lspOnlyServisEmail
+    resultType: FAIL
+    period: 86400
+
+superGroups:
+#  osek&zcu:
+#    name: "Osek & ZČU Robčice"
+#    messageBroker: mikeEmail
+#    resultType: FAIL
+#    period: 86400
+#    groups: [ osek, zcu ]
+
+#  lspService:
+#    name: "Latvia, Žabčice, InnoVar"
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+#    groups: [ latviaSmiltene, latviaMengele, zabcice, innovar ]
 
 monitoredObjects:
 

+ 139 - 62
config/test-foodie.yaml

@@ -1,5 +1,5 @@
 general:
-  firstStartAt: "14:11:00" # hh:mm:ss
+  firstStartAt: "00:20:00" # hh:mm:ss
 
 emailServers:
   lspEmail:
@@ -23,7 +23,7 @@ messageBrokers:
       senderEmail: "watchdog@senslog.org"
       recipientEmail:
         - "luccerny@ntis.zcu.cz"
-      subject: "[Watchdog] Test Report SensLog (CZ)"
+      subject: "[Watchdog] Test Report SensLog (CZ) - $group.name"
 
   lspServisEmail:
     type: EMAIL
@@ -32,7 +32,7 @@ messageBrokers:
       senderEmail: "watchdog@senslog.org"
       recipientEmail:
         - "luccerny@ntis.zcu.cz"
-      subject: "[Watchdog] Test Report SensLog (CZ)"
+      subject: "[Watchdog] Test Report SensLog (CZ) - $group.name"
 
   zcuOnlyEmail:
     type: EMAIL
@@ -41,8 +41,7 @@ messageBrokers:
       senderEmail: "watchdog@senslog.org"
       recipientEmail:
         - "luccerny@ntis.zcu.cz"
-      subject: "[Watchdog] Test Report SensLog (CZ)"
-
+      subject: "[Watchdog] Test Report SensLog (CZ) - $group.name"
 
 dataProviders:
   wsSensLogKynsperk:
@@ -79,74 +78,152 @@ dataProviders:
     type: WEB_SERVICE
     config:
       server: lspSenslog15
-      groupName: "Meņģele"
+      groupName: "Mengele"
 
-groups:
-  kynsperk:
-    name: "Kynšperk"
-    active: true
-    dataProvider: wsSensLogKynsperk
-    messageBroker: lspServisEmail
-    resultType: FAIL
-    period: 86400
+  wsSensLogZabcice:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "neudert"
 
-  rostenice:
-    name: "Rostěnice"
-    active: true
-    dataProvider: wsSensLogRostenice
-    messageBroker: mikeEmail
-    resultType: FAIL
-    period: 86400
+  wsSensLogInnovar:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "Innovar"
 
-  zcu:
-    name: "ZČU Robčice"
-    active: true
-    dataProvider: wsSensLogZcu
-    messageBroker: zcuOnlyEmail
-    resultType: FAIL
-    period: 86400
+  wsSensLogZelenec:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "zelenec"
 
-  osek:
-    name: "Osek"
-    active: false
-    dataProvider: wsSensLogOsek
-    messageBroker: mikeEmail
-    resultType: FAIL
-    period: 86400
+  wsSensLogCepirohy:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "agriclima"
+
+  wsSensLogSanJuan:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "sanjuan"
+
+  wsSensLogSVSMP:
+    type: WEB_SERVICE
+    config:
+      server: lspSenslog15
+      groupName: "svsmp"
 
-  latviaSmiltene:
-    name: "Latvia Smiltene"
-    active: false
-    dataProvider: wsSensLogSmiltene
-    messageBroker: lspServisEmail
-    resultType: FAIL
-    period: 86400
 
-  latviaMengele:
-    name: "Latvia Meņģele"
-    active: false
-    dataProvider: wsSensLogMengele
+groups:
+#  kynsperk:
+#    name: "Kynšperk"
+#    active: true
+#    dataProvider: wsSensLogKynsperk
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+
+#  rostenice:
+#    name: "Rostěnice"
+#    active: true
+#    dataProvider: wsSensLogRostenice
+#    messageBroker: mikeEmail
+#    resultType: FAIL
+#    period: 86400
+#
+#  zcu:
+#    name: "ZČU Robčice"
+#    active: true
+#    dataProvider: wsSensLogZcu
+#    messageBroker: zcuOnlyEmail
+#    resultType: FAIL
+#    period: 86400
+#
+#  osek:
+#    name: "Osek"
+#    active: true
+#    dataProvider: wsSensLogOsek
+#    messageBroker: mikeEmail
+#    resultType: FAIL
+#    period: 86400
+#
+#  latviaSmiltene:
+#    name: "Latvia Smiltene"
+#    active: true
+#    dataProvider: wsSensLogSmiltene
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+#
+#  latviaMengele:
+#    name: "Latvia Meņģele"
+#    active: true
+#    dataProvider: wsSensLogMengele
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+
+#  zabcice:
+#    name: "Žabčice"
+#    active: true
+#    dataProvider: wsSensLogZabcice
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+
+#  zelenec:
+#    name: "Zeleneč"
+#    active: true
+#    dataProvider: wsSensLogZelenec
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+#
+#  cepirohy:
+#    name: "Čepirohy"
+#    active: true
+#    dataProvider: wsSensLogCepirohy
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+#
+#  sanJuan:
+#    name: "San Juan (Argentina)"
+#    active: true
+#    dataProvider: wsSensLogSanJuan
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+
+#  innovar:
+#    name: "InnoVar"
+#    active: true
+#    dataProvider: wsSensLogInnovar
+#    messageBroker: lspServisEmail
+#    resultType: FAIL
+#    period: 86400
+
+  svsmpSadky:
+    name: "SVSMP Sádky"
+    active: true
+    dataProvider: wsSensLogSVSMP
     messageBroker: lspServisEmail
     resultType: FAIL
     period: 86400
 
 superGroups:
-  osek&zcu:
-    name: "Osek & ZČU Robčice"
-    messageBroker: mikeEmail
-    resultType: FAIL
-    period: 86400
-    groups: [ osek, zcu ]
-
-  latvia:
-    name: "Latvia Meņģele & Smiltene"
-    messageBroker: mikeEmail
-    resultType: FAIL
-    period: 86400
-    groups: [ latviaSmiltene, latviaMengele ]
+#  osek&zcu:
+#    name: "ZČU Robčice"
+#    messageBroker: zcuOnlyEmail
+#    resultType: FAIL
+#    period: 86400
+#    groups: [ zcu, osek ]
 
 monitoredObjects:
 
-  1305167562258386:
-    period: 86400
-    groups: [ zcu ]
+#  1305167562258386:
+#    period: 86400
+#    groups: [ zcu ]

+ 2 - 2
src/main/java/cz/senslog/watchdog/app/Application.java

@@ -116,7 +116,7 @@ public class Application extends Thread {
                 watchers.add(Watcher.create(execGroup, dataProvider, accumulatorMsgBroker));
             }
 
-            String taskName = execSuperGroup.getConfig().getName();
+            String taskName = execSuperGroup.getConfig().getId();
             Runnable runnableTask = () -> watchers.forEach(Watcher::check);
 
             if (params.isExecuteImmediately()) {
@@ -128,6 +128,6 @@ public class Application extends Thread {
 
         Scheduler scheduler = schedulerBuilder.build();
 
-        scheduler.start();
+        scheduler.startAll();
     }
 }

+ 3 - 2
src/main/java/cz/senslog/watchdog/config/Configuration.java

@@ -65,16 +65,17 @@ public class Configuration {
             }
             sensors = !sensors.isEmpty() ? sensors : singletonList(new TempAllSensorsConfig(mObject.period));
             for (TempSensorConfig sensor : sensors) {
+                String sensorId = sensor.id != null ? sensor.id : mObject.id;
                 Set<String> sensorGroups = sensor.groups;
                 if (unitGroups.isEmpty() && sensorGroups.isEmpty()) {
-                    throw new IllegalStateException("The monitored object '"+sensor.id+"' is not assigned to any groups.");
+                    throw new IllegalStateException("The monitored object '"+sensorId+"' is not assigned to any groups.");
                 }
                 sensorGroups.addAll(unitGroups);
                 for (String groupId : sensorGroups) {
                     GroupConfig groupConfig = groups.get(groupId);
                     if (groupConfig == null) {
                         throw new IllegalStateException(
-                                "Assigned group '"+groupId+"' to the monitored object '"+sensor.id+"' does not exist."
+                                "Assigned group '"+groupId+"' to the monitored object '"+sensorId+"' does not exist."
                         );
                     }
                     Integer period = groupConfig.getPeriod() != null ? groupConfig.getPeriod() : mObject.period;

+ 35 - 35
src/main/java/cz/senslog/watchdog/config/SignalMessageBrokerConfig.java → src/main/java/cz/senslog/watchdog/config/IMMessageBrokerConfig.java

@@ -1,35 +1,35 @@
-package cz.senslog.watchdog.config;
-
-public class SignalMessageBrokerConfig extends MessageBrokerConfig {
-
-    private final String recipientNumber;
-    private final String senderNumber;
-    private final String messagePattern;
-
-    public static SignalMessageBrokerConfig create(String id, PropertyConfig config) {
-        return new SignalMessageBrokerConfig(id,
-                config.getStringProperty("recipientNumber"),
-                config.getStringProperty("senderNumber"),
-                config.getStringProperty("messagePattern")
-        );
-    }
-
-    public SignalMessageBrokerConfig(String id, String recipientNumber, String senderNumber, String messagePattern) {
-        super(id, MessageBrokerType.SIGNAL);
-        this.recipientNumber = recipientNumber;
-        this.senderNumber = senderNumber;
-        this.messagePattern = messagePattern;
-    }
-
-    public String getRecipientNumber() {
-        return recipientNumber;
-    }
-
-    public String getSenderNumber() {
-        return senderNumber;
-    }
-
-    public String getMessagePattern() {
-        return messagePattern;
-    }
-}
+package cz.senslog.watchdog.config;
+
+public class IMMessageBrokerConfig extends MessageBrokerConfig {
+
+    private final String recipientNumber;
+    private final String senderNumber;
+    private final String messagePattern;
+
+    public static IMMessageBrokerConfig create(String id, PropertyConfig config) {
+        return new IMMessageBrokerConfig(id,
+                config.getStringProperty("recipientNumber"),
+                config.getStringProperty("senderNumber"),
+                config.getStringProperty("messagePattern")
+        );
+    }
+
+    public IMMessageBrokerConfig(String id, String recipientNumber, String senderNumber, String messagePattern) {
+        super(id, MessageBrokerType.IM);
+        this.recipientNumber = recipientNumber;
+        this.senderNumber = senderNumber;
+        this.messagePattern = messagePattern;
+    }
+
+    public String getRecipientNumber() {
+        return recipientNumber;
+    }
+
+    public String getSenderNumber() {
+        return senderNumber;
+    }
+
+    public String getMessagePattern() {
+        return messagePattern;
+    }
+}

+ 23 - 23
src/main/java/cz/senslog/watchdog/config/MessageBrokerType.java

@@ -1,23 +1,23 @@
-package cz.senslog.watchdog.config;
-
-import java.util.function.BiFunction;
-
-public enum MessageBrokerType {
-
-    EMAIL       (EmailMessageBrokerConfig::create),
-    SIGNAL      (SignalMessageBrokerConfig::create),
-    CONSOLE     (ConsoleMessageBrokerConfig::create);
-
-    private final BiFunction<String, PropertyConfig, MessageBrokerConfig> fnc;
-    MessageBrokerType(BiFunction<String, PropertyConfig, MessageBrokerConfig> fnc) {
-        this.fnc = fnc;
-    }
-
-    public static MessageBrokerType of(String value) {
-        return valueOf(value.toUpperCase());
-    }
-
-    public MessageBrokerConfig createConfig(String id, PropertyConfig config) {
-        return fnc.apply(id, config);
-    }
-}
+package cz.senslog.watchdog.config;
+
+import java.util.function.BiFunction;
+
+public enum MessageBrokerType {
+
+    EMAIL       (EmailMessageBrokerConfig::create),
+    IM          (IMMessageBrokerConfig::create),
+    CONSOLE     (ConsoleMessageBrokerConfig::create);
+
+    private final BiFunction<String, PropertyConfig, MessageBrokerConfig> fnc;
+    MessageBrokerType(BiFunction<String, PropertyConfig, MessageBrokerConfig> fnc) {
+        this.fnc = fnc;
+    }
+
+    public static MessageBrokerType of(String value) {
+        return valueOf(value.toUpperCase());
+    }
+
+    public MessageBrokerConfig createConfig(String id, PropertyConfig config) {
+        return fnc.apply(id, config);
+    }
+}

+ 18 - 11
src/main/java/cz/senslog/watchdog/core/Watcher.java

@@ -12,8 +12,7 @@ import cz.senslog.watchdog.provider.ProvidedObject;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.time.Instant;
-import java.time.ZoneId;
+import java.time.*;
 import java.util.*;
 
 import static cz.senslog.watchdog.domain.StatusReport.*;
@@ -21,7 +20,7 @@ import static java.time.LocalDateTime.ofInstant;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 
-public class Watcher {
+public final class Watcher {
 
     private static final Logger logger = LogManager.getLogger(Watcher.class);
 
@@ -41,8 +40,9 @@ public class Watcher {
 
     public void check() {
 
-        Instant now = Instant.now();
-        ProvidedData data = dataProvider.getLastData();
+        // LocalDateTime fakedNow = LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 20));
+        Instant now = /* ZonedDateTime.of(fakedNow, ZoneId.systemDefault()).toInstant(); */ Instant.now();
+        ProvidedData data = dataProvider.getData();
 
         List<SimpleReport> reports = new ArrayList<>();
         List<String> reportedMessages = new ArrayList<>();
@@ -70,9 +70,10 @@ public class Watcher {
                                     pObject.getTimestamp().toInstant().isBefore(minTimestamp) ? FAIL : OK
                             );
 
-                            if (isAllowedToReport(simpleReport)) {
+                            // ResultType from config is ignored
+//                            if (isAllowedToReport(simpleReport)) {
                                 reports.add(simpleReport);
-                            }
+//                            }
                         }
                     }
                 } else if (!data.containsNode(unitId)) {
@@ -106,19 +107,25 @@ public class Watcher {
                                     pObject.getTimestamp().toInstant().isBefore(minTimestamp) ? FAIL : OK
                             );
 
-                            if (isAllowedToReport(simpleReport)) {
+                            // ResultType from config is ignored
+                         //   if (isAllowedToReport(simpleReport)) {
                                 reports.add(simpleReport);
-                            }
+                       //     }
                         }
                     }
                 }
             }
+
+            if (reports.isEmpty()) {
+                reportedMessages.add("All sensors received data up to date.");
+            }
         }
 
         Map<String, String> operationProperties = new HashMap<>();
-        operationProperties.put("Group name", group.getConfig().getName());
+        operationProperties.put("$group.name", group.getConfig().getName());
 
-        messageBroker.send(new Report(ofInstant(now, ZoneId.systemDefault()), reports, reportedMessages, operationProperties), status -> {
+        LocalDateTime reportCreated = ofInstant(now, ZoneId.systemDefault());
+        messageBroker.send(new Report(reportCreated, reports, reportedMessages, operationProperties), status -> {
             String brokerType = status.getBrokerName().toLowerCase();
             if (status.isSuccess()) {
                 logger.info("The report at '{}' was send via '{}' broker successfully for the group's name '{}'.",

+ 10 - 2
src/main/java/cz/senslog/watchdog/core/connection/EmailServerConnection.java

@@ -8,6 +8,7 @@ import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
+import java.util.Map;
 import java.util.Properties;
 
 import static javax.mail.Message.RecipientType.TO;
@@ -36,13 +37,20 @@ public class EmailServerConnection {
         return Session.getInstance(props, auth);
     }
 
-    public void send(String content, EmailMessageBrokerConfig messageConfig) throws MessagingException {
+    public void send(String content, Map<String, String> variables, EmailMessageBrokerConfig messageConfig) throws MessagingException {
         Session session = openSession(config);
         Message message = new MimeMessage(session);
         message.setFrom(new InternetAddress(messageConfig.getSenderEmail()));
         String recipients = String.join(",",messageConfig.getRecipientEmails());
         message.setRecipients(TO, InternetAddress.parse(recipients));
-        message.setSubject(messageConfig.getSubject());
+
+        String subjectPattern = messageConfig.getSubject();
+        for (Map.Entry<String, String> varEntry : variables.entrySet()) {
+            if (subjectPattern.contains(varEntry.getKey())) {
+                subjectPattern = subjectPattern.replace(varEntry.getKey(), varEntry.getValue());
+            }
+        }
+        message.setSubject(subjectPattern);
 
         MimeBodyPart mimeBodyPart = new MimeBodyPart();
         mimeBodyPart.setContent(content, "text/html;charset=UTF-8");

+ 12 - 2
src/main/java/cz/senslog/watchdog/core/connection/SensLogWSConnection.java

@@ -19,17 +19,27 @@ import static java.util.Collections.emptyList;
 
 public class SensLogWSConnection {
 
-    private static final int MAX_AUTH_ERRORS = 2;
+    private static final int MAX_AUTH_ERRORS = 3;
 
     private static final Logger logger = LogManager.getLogger(SensLogWSConnection.class);
 
+    private static final Map<String, SensLogWSConnection> INSTANCES;
+
     private final HttpClient httpClient;
     private final SensLogServerConfig config;
     private final Map<Long, AtomicInteger> authError;
 
     private Tuple<Boolean, HttpCookie> authCookie;
 
-    public SensLogWSConnection(SensLogServerConfig config) {
+    static {
+        INSTANCES = new HashMap<>();
+    }
+
+    public static SensLogWSConnection getConnection(SensLogServerConfig config) {
+        return INSTANCES.computeIfAbsent(config.getId(), id -> new SensLogWSConnection(config));
+    }
+
+    private SensLogWSConnection(SensLogServerConfig config) {
         this.config = config;
         this.httpClient = HttpClient.newHttpClient();
         this.authCookie = Tuple.of(false, null);

+ 0 - 6
src/main/java/cz/senslog/watchdog/messagebroker/BlockingMessageBroker.java

@@ -1,6 +0,0 @@
-package cz.senslog.watchdog.messagebroker;
-
-public abstract class BlockingMessageBroker implements MessageBroker {
-
-
-}

+ 4 - 4
src/main/java/cz/senslog/watchdog/messagebroker/MessageBrokerManager.java

@@ -4,7 +4,7 @@ import cz.senslog.watchdog.config.*;
 import cz.senslog.watchdog.core.connection.EmailServerConnection;
 import cz.senslog.watchdog.messagebroker.broker.ConsoleMessageBroker;
 import cz.senslog.watchdog.messagebroker.broker.EmailMessageBroker;
-import cz.senslog.watchdog.messagebroker.broker.SignalMessageBroker;
+import cz.senslog.watchdog.messagebroker.broker.IMMessageBroker;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -32,9 +32,9 @@ public class MessageBrokerManager {
                     EmailServerConnection connection = emailServers.get(emailConfig.getServerId());
                     instances.put(id, new EmailMessageBroker(connection, emailConfig));
                 } break;
-                case SIGNAL: {
-                    SignalMessageBrokerConfig signalConfig = (SignalMessageBrokerConfig) config;
-                    instances.put(id, new SignalMessageBroker(signalConfig));
+                case IM: {
+                    IMMessageBrokerConfig signalConfig = (IMMessageBrokerConfig) config;
+                    instances.put(id, new IMMessageBroker(signalConfig));
                 } break;
                 case CONSOLE: {
                     ConsoleMessageBrokerConfig consoleConfig = (ConsoleMessageBrokerConfig) config;

+ 113 - 20
src/main/java/cz/senslog/watchdog/messagebroker/broker/EmailMessageBroker.java

@@ -8,6 +8,7 @@ import cz.senslog.watchdog.messagebroker.MessageStatus;
 import cz.senslog.watchdog.messagebroker.MultiMessageBroker;
 import cz.senslog.watchdog.messagebroker.writer.HtmlTableWriter;
 import cz.senslog.watchdog.messagebroker.writer.TableWriter;
+import cz.senslog.watchdog.util.Tuple;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -16,6 +17,8 @@ import java.time.LocalDateTime;
 import java.util.*;
 
 import static cz.senslog.watchdog.config.MessageBrokerType.EMAIL;
+import static cz.senslog.watchdog.domain.StatusReport.FAIL;
+import static cz.senslog.watchdog.domain.StatusReport.OK;
 import static java.time.format.DateTimeFormatter.ofPattern;
 
 public class EmailMessageBroker extends MultiMessageBroker {
@@ -23,6 +26,8 @@ public class EmailMessageBroker extends MultiMessageBroker {
     private static final String BREAK_LINE = "<br />";
     private static final String HORIZONTAL_SEPARATOR = "<hr>";
 
+    private static final String SUBSTITUTABLE_VARIABLE_PREFIX = "$";
+
     private static final Logger logger = LogManager.getLogger(EmailMessageBroker.class);
 
     private final EmailServerConnection serverConnection;
@@ -33,26 +38,36 @@ public class EmailMessageBroker extends MultiMessageBroker {
         this.messageConfig = messageConfig;
     }
 
-    private String createMessage(Report report) {
+    private Tuple<String, Map<String, String>> createMessage(Report report) {
         StringBuilder content = new StringBuilder();
         final String rowStyle = "border: 1px solid #dddddd; text-align: left; padding: 8px;";
 
         boolean isMessages = !report.getMessages().isEmpty();
         boolean isRecords = !report.getReports().isEmpty();
 
-        if (!report.getOperationProperties().isEmpty()) {
+        Map<String, String> operationProperties = report.getOperationProperties();
+        Map<String, String> substitutableVariables = new HashMap<>(operationProperties.size());
+        if (!operationProperties.isEmpty()) {
+            for (Map.Entry<String, String> property : operationProperties.entrySet()) {
+                if (property.getKey().startsWith(SUBSTITUTABLE_VARIABLE_PREFIX)) {
+                    substitutableVariables.put(property.getKey(), property.getValue());
+                    operationProperties.remove(property.getKey());
+                }
+            }
 
-            TableWriter tableSourceWriter = HtmlTableWriter.createWithHeader("width: 100%;", "background-color: #dddddd")
-                    .cell("Operation Type").cell("Operation Value").end();
+            if (!operationProperties.isEmpty()) {
+                TableWriter tableSourceWriter = HtmlTableWriter.createWithHeader("width: 100%;", "background-color: #dddddd")
+                        .cell("Operation Type").cell("Operation Value").end();
 
-            for (Map.Entry<String, String> operationEntry : report.getOperationProperties().entrySet()) {
-                tableSourceWriter.row(rowStyle)
+                for (Map.Entry<String, String> operationEntry : operationProperties.entrySet()) {
+                    tableSourceWriter.row(rowStyle)
                             .cell(operationEntry.getKey(), rowStyle)
                             .cell(operationEntry.getValue(), rowStyle)
-                        .end();
-            }
+                            .end();
+                }
 
-            content.append(tableSourceWriter.table()).append(BREAK_LINE);
+                content.append(tableSourceWriter.table()).append(BREAK_LINE);
+            }
         }
 
         if (isMessages || !isRecords) {
@@ -71,16 +86,80 @@ public class EmailMessageBroker extends MultiMessageBroker {
         }
 
         if (isRecords) {
-            TableWriter tableReportWriter = HtmlTableWriter.createWithHeader("width: 100%;", "background-color: #dddddd")
-                    .cell("unitName (unitId)").cell("sensorName (sensorId)")
-                    .cell("timestamp")/*.cell("reported")*/.cell("status").end();
 
-        //    String reportedTime = report.getCreated().format(ofPattern("yyyy-MM-dd HH:mm:ss"));
+            boolean renderReportTable = false;
+            Map<Unit, List<ObservationInfo>> unitsToReport = new HashMap<>();
+            Map<Unit, Tuple<Unit, StatusReport>> unitToStatus = new HashMap<>();
+            for (SimpleReport simpleReport : report.getReports()) {
+                StatusReport status = simpleReport.getStatus();
+                boolean isOk = status.equals(OK);
+                if (simpleReport.getRecord() instanceof ObservationInfo) {
+                    ObservationInfo observation = (ObservationInfo) simpleReport.getRecord();
+                    Unit unit = observation.getSource().getUnit();
+
+                    StatusReport updatedStatus;
+                    List<ObservationInfo> observations;
+                    if (unitToStatus.containsKey(unit)) {
+                        observations = unitsToReport.get(unit);
+                        updatedStatus = isOk ? unitToStatus.get(unit).getItem2() : FAIL;
+                    } else {
+                        updatedStatus = status;
+                        observations = new ArrayList<>();
+                    }
+
+                    if (!isOk) {
+                        observations.add(observation);
+                        renderReportTable = true;
+                    }
+                    unitToStatus.put(unit, Tuple.of(unit, updatedStatus));
+                    unitsToReport.put(unit, observations);
+                }
+            }
 
+            TableWriter tableUnitStatusWriter = HtmlTableWriter.createWithHeader("width: 100%;", "background-color: #dddddd")
+                    .cell("unitName").cell("unitId").end();
+
+            List<Tuple<Unit, StatusReport>> statuses = new ArrayList<>(unitToStatus.values());
+            statuses.sort(Comparator.comparing(Tuple::getItem2, Comparator.reverseOrder()));
+            for (Tuple<Unit, StatusReport> unitEntry : statuses) {
+                Unit unit = unitEntry.getItem1();
+                boolean isOk = unitEntry.getItem2().equals(OK);
+                tableUnitStatusWriter.row(rowStyle + "background-color: " + (isOk ? "#CCFFCC" : "#FFCCCC"))
+                        .cell(unit.getName(), rowStyle)
+                        .cell(String.valueOf(unit.getId()), rowStyle)
+                        .end();
+            }
+
+            content.append(tableUnitStatusWriter.table());
+
+            if (renderReportTable) {
+
+                content.append(HORIZONTAL_SEPARATOR).append(HORIZONTAL_SEPARATOR).append(BREAK_LINE);
+
+                TableWriter tableReportWriter = HtmlTableWriter.createWithHeader("width: 100%;", "background-color: #dddddd")
+                        .cell("unitName (unitId)").cell("sensorName (sensorId)").cell("timestamp").end();
+
+                for (Map.Entry<Unit, List<ObservationInfo>> unitEntry : unitsToReport.entrySet()) {
+                    for (ObservationInfo observation : unitEntry.getValue()) {
+                        Source source = observation.getSource();
+
+                        String unitCell = String.format("%s (%s)", source.getUnit().getName(), source.getUnit().getId());
+                        String sensorCell = String.format("%s (%s)", source.getSensor().getName(), source.getSensor().getId());
+
+                        tableReportWriter.row(rowStyle + "background-color: #FFCCCC")
+                                .cell(unitCell, rowStyle)
+                                .cell(sensorCell, rowStyle)
+                                .cell(observation.getTimestamp().toString(), rowStyle)
+                                .end();
+                    }
+                }
+
+            /*
             report.getReports().sort(Comparator.comparing(SimpleReport::getStatus).reversed());
             for (SimpleReport simpleReport : report.getReports()) {
-                boolean isOk = simpleReport.getStatus().equals(StatusReport.OK);
-                if (simpleReport.getRecord() instanceof ObservationInfo) {
+                boolean isOk = simpleReport.getStatus().equals(OK);
+                // ALLOW ONLY NOT OK REPORTS -> // TODO create a template system
+                if (!isOk && simpleReport.getRecord() instanceof ObservationInfo) {
                     ObservationInfo observation = (ObservationInfo) simpleReport.getRecord();
                     Source source = observation.getSource();
 
@@ -92,14 +171,17 @@ public class EmailMessageBroker extends MultiMessageBroker {
                             .cell(sensorCell, rowStyle)
                             .cell(observation.getTimestamp().toString(), rowStyle)
                           //  .cell(reportedTime, rowStyle)
-                            .cell(simpleReport.getStatus().name(), rowStyle)
+                          //  .cell(simpleReport.getStatus().name(), rowStyle)
                             .end();
                 }
             }
-            content.append(tableReportWriter.table()).append(BREAK_LINE);
+             */
+
+                content.append(tableReportWriter.table()).append(BREAK_LINE);
+            }
         }
 
-        return content.toString();
+        return Tuple.of(content.toString(), substitutableVariables);
     }
 
 
@@ -112,13 +194,24 @@ public class EmailMessageBroker extends MultiMessageBroker {
 
         try {
             StringBuilder reportMessage = new StringBuilder();
+            Map<String, String> substitutableVariables = new HashMap<>();
             for (Report report : reports) {
-                reportMessage.append(createMessage(report))
+                Tuple<String, Map<String, String>> message = createMessage(report);
+                for (Map.Entry<String, String> varEntry : message.getItem2().entrySet()) {
+                    if (substitutableVariables.containsKey(varEntry.getKey())) {
+                        String oldValue = substitutableVariables.get(varEntry.getKey());
+                        substitutableVariables.put(varEntry.getKey(), oldValue + " & " + varEntry.getValue());
+                    } else {
+                        substitutableVariables.put(varEntry.getKey(), varEntry.getValue());
+                    }
+                }
+
+                reportMessage.append(message.getItem1())
                         .append(HORIZONTAL_SEPARATOR).append(HORIZONTAL_SEPARATOR)
                         .append(BREAK_LINE);
             }
             logger.info("Sending a message via email.");
-            serverConnection.send(reportMessage.toString(), messageConfig);
+            serverConnection.send(reportMessage.toString(), substitutableVariables, messageConfig);
             logger.info("The message was send successfully.");
             status.handle(MessageStatus.success(LocalDateTime.now(), EMAIL.name()));
         } catch (MessagingException e) {

+ 4 - 4
src/main/java/cz/senslog/watchdog/messagebroker/broker/SignalMessageBroker.java → src/main/java/cz/senslog/watchdog/messagebroker/broker/IMMessageBroker.java

@@ -1,15 +1,15 @@
 package cz.senslog.watchdog.messagebroker.broker;
 
-import cz.senslog.watchdog.config.SignalMessageBrokerConfig;
+import cz.senslog.watchdog.config.IMMessageBrokerConfig;
 import cz.senslog.watchdog.domain.Report;
 import cz.senslog.watchdog.messagebroker.MessageBrokerHandler;
 import cz.senslog.watchdog.messagebroker.MultiMessageBroker;
 
-public class SignalMessageBroker extends MultiMessageBroker {
+public class IMMessageBroker extends MultiMessageBroker {
 
-    private final SignalMessageBrokerConfig config;
+    private final IMMessageBrokerConfig config;
 
-    public SignalMessageBroker(SignalMessageBrokerConfig config) {
+    public IMMessageBroker(IMMessageBrokerConfig config) {
         this.config = config;
     }
     @Override

+ 6 - 6
src/main/java/cz/senslog/watchdog/provider/DataProvider.java

@@ -1,6 +1,6 @@
-package cz.senslog.watchdog.provider;
-
-public interface DataProvider {
-
-    ProvidedData getLastData();
-}
+package cz.senslog.watchdog.provider;
+
+public interface DataProvider {
+
+    ProvidedData getData();
+}

+ 1 - 1
src/main/java/cz/senslog/watchdog/provider/DataProviderManager.java

@@ -21,7 +21,7 @@ public class DataProviderManager {
     ) {
         Map<String, SensLogWSConnection> connectionMap = new HashMap<>(sensLogServerConfigs.size());
         for (SensLogServerConfig config : sensLogServerConfigs) {
-            connectionMap.put(config.getId(), new SensLogWSConnection(config));
+            connectionMap.put(config.getId(), SensLogWSConnection.getConnection(config));
         }
 
         Map<String, DataProvider> instances = new HashMap<>();

+ 34 - 34
src/main/java/cz/senslog/watchdog/provider/database/DBDataProvider.java

@@ -1,34 +1,34 @@
-package cz.senslog.watchdog.provider.database;
-
-import cz.senslog.watchdog.config.DatabaseDataProviderConfig;
-import cz.senslog.watchdog.provider.DataProvider;
-import cz.senslog.watchdog.domain.ObservationInfo;
-import cz.senslog.watchdog.provider.ProvidedData;
-import cz.senslog.watchdog.provider.ProviderInfo;
-import cz.senslog.watchdog.provider.Record;
-import org.jdbi.v3.core.Jdbi;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static cz.senslog.watchdog.config.DataProviderType.DATABASE;
-
-public class DBDataProvider implements DataProvider {
-
-    private final SensLogRepository repository;
-    private final String groupName;
-
-    public DBDataProvider(DatabaseDataProviderConfig config) {
-        Connection<Jdbi> connection = Connection.create(
-                config.getConnectionUrl(), config.getUsername(), config.getPassword(), config.getConnectionPoolSize()
-        );
-        this.repository = new SensLogRepository(connection);
-        this.groupName = config.getGroupName();
-    }
-
-    @Override
-    public ProvidedData getLastData() {
-        List<ObservationInfo> lastObservations = repository.getLastObservations(groupName);
-        return new ProvidedData(new ProviderInfo("db", DATABASE, false), new ArrayList<>());
-    }
-}
+package cz.senslog.watchdog.provider.database;
+
+import cz.senslog.watchdog.config.DatabaseDataProviderConfig;
+import cz.senslog.watchdog.provider.DataProvider;
+import cz.senslog.watchdog.domain.ObservationInfo;
+import cz.senslog.watchdog.provider.ProvidedData;
+import cz.senslog.watchdog.provider.ProviderInfo;
+import cz.senslog.watchdog.provider.Record;
+import org.jdbi.v3.core.Jdbi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static cz.senslog.watchdog.config.DataProviderType.DATABASE;
+
+public class DBDataProvider implements DataProvider {
+
+    private final SensLogRepository repository;
+    private final String groupName;
+
+    public DBDataProvider(DatabaseDataProviderConfig config) {
+        Connection<Jdbi> connection = Connection.create(
+                config.getConnectionUrl(), config.getUsername(), config.getPassword(), config.getConnectionPoolSize()
+        );
+        this.repository = new SensLogRepository(connection);
+        this.groupName = config.getGroupName();
+    }
+
+    @Override
+    public ProvidedData getData() {
+        List<ObservationInfo> lastObservations = repository.getLastObservations(groupName);
+        return new ProvidedData(new ProviderInfo("db", DATABASE, false), new ArrayList<>());
+    }
+}

+ 6 - 4
src/main/java/cz/senslog/watchdog/provider/ws/WSSensLogDataProvider.java

@@ -14,6 +14,7 @@ import static cz.senslog.watchdog.config.DataProviderType.WEB_SERVICE;
 import static java.time.format.DateTimeFormatter.ofPattern;
 import static java.util.Collections.emptyList;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Optional.ofNullable;
 
 public class WSSensLogDataProvider implements DataProvider {
 
@@ -31,7 +32,7 @@ public class WSSensLogDataProvider implements DataProvider {
     }
 
     @Override
-    public ProvidedData getLastData() {
+    public ProvidedData getData() {
 
         List<String> errorMessages = new ArrayList<>();
         boolean isServerAlive = true;
@@ -49,10 +50,11 @@ public class WSSensLogDataProvider implements DataProvider {
         if (isServerAlive) {
             for (Map<String, Object> obMap : lastObservations) {
                 long unitId = ((Double) obMap.get("unitId")).longValue();
-                String unitName = new String(obMap.getOrDefault("unitName", DEFAULT_NAME).toString().getBytes(), UTF_8);
+                String unitName = new String(ofNullable(obMap.get("unitName")).orElse(DEFAULT_NAME).toString().getBytes(), UTF_8);
                 long sensorId = ((Double) obMap.get("sensorId")).longValue();
-                String sensorName = new String(obMap.getOrDefault("sensorName", DEFAULT_NAME).toString().getBytes(), UTF_8);
-                OffsetDateTime timestamp = OffsetDateTime.parse((String)obMap.get("lastTimest"), TIMESTAMP_PATTERN);
+                String sensorName = new String(ofNullable(obMap.get("sensorName")).orElse(DEFAULT_NAME).toString().getBytes(UTF_8));
+                String timestampStr = (String)obMap.get("lastTimest");
+                OffsetDateTime timestamp = timestampStr != null ? OffsetDateTime.parse(timestampStr, TIMESTAMP_PATTERN) : OffsetDateTime.MIN;
 
                 String unitIdStr = String.format("%d", unitId);
                 String sensorIdStr = String.format("%d", sensorId);

+ 27 - 28
src/main/java/cz/senslog/watchdog/util/http/HttpClient.java

@@ -1,19 +1,18 @@
 package cz.senslog.watchdog.util.http;
 
 import cz.senslog.watchdog.util.StringUtils;
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpMessage;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.conn.ssl.TrustStrategy;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.BasicCookieStore;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.ssl.SSLContextBuilder;
-import org.apache.http.util.EntityUtils;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
+import org.apache.hc.client5.http.cookie.BasicCookieStore;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpMessage;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -21,17 +20,17 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.security.KeyManagementException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
 import java.util.Map;
 
-import static org.apache.http.HttpHeaders.*;
+import static cz.senslog.watchdog.util.http.HttpHeader.CONTENT_TYPE;
+import static cz.senslog.watchdog.util.http.HttpHeader.USER_AGENT;
+import static cz.senslog.watchdog.util.http.HttpHeader.CACHE_CONTROL;
+
 
 /**
- * The class {@code HttpClient} represents a wrapper for {@link org.apache.http.client.HttpClient}.
- * Provides functionality of sending GET and POST request. Otherwise is returned response with {@see #BAD_REQUEST}.
+ * The class {@code HttpClient} represents a wrapper for {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient}.
+ * Provides functionality of sending GET and POST request. Otherwise, is returned response with {@see #BAD_REQUEST}.
  *
  * @author Lukas Cerny
  * @version 1.0
@@ -40,7 +39,7 @@ import static org.apache.http.HttpHeaders.*;
 public class HttpClient {
 
     /** Instance of http client. */
-    private final org.apache.http.client.HttpClient client;
+    private final CloseableHttpClient client;
 
     private final BasicCookieStore cookieStore;
 
@@ -100,13 +99,13 @@ public class HttpClient {
 
         cookieStore.clear();
         for (HttpCookie cookie : request.getCookies()) {
-            cookieStore.addCookie(cookie);
+            cookieStore.addCookie(cookie.getCookie());
         }
 
-        org.apache.http.HttpResponse responseGet = client.execute(requestGet);
+        CloseableHttpResponse responseGet = client.execute(requestGet);
 
         HttpResponse response = HttpResponse.newBuilder()
-                .status(responseGet.getStatusLine().getStatusCode())
+                .status(responseGet.getCode())
                 .headers(getHeaders(responseGet))
                 .body(getBody(responseGet.getEntity()))
                 .build();
@@ -137,14 +136,14 @@ public class HttpClient {
 
         cookieStore.clear();
         for (HttpCookie cookie : request.getCookies()) {
-            cookieStore.addCookie(cookie);
+            cookieStore.addCookie(cookie.getCookie());
         }
 
-        org.apache.http.HttpResponse responsePost = client.execute(requestPost);
+        CloseableHttpResponse responsePost = client.execute(requestPost);
 
         HttpResponse response = HttpResponse.newBuilder()
                 .headers(getHeaders(requestPost))
-                .status(responsePost.getStatusLine().getStatusCode())
+                .status(responsePost.getCode())
                 .body(getBody(responsePost.getEntity()))
                 .build();
 
@@ -158,7 +157,7 @@ public class HttpClient {
      * @param userRequest - virtual request.
      * @param httpRequest - real request prepared to send.
      */
-    private void setBasicHeaders(HttpRequest userRequest, HttpRequestBase httpRequest) {
+    private void setBasicHeaders(HttpRequest userRequest, HttpUriRequestBase httpRequest) {
 
         httpRequest.setHeader(USER_AGENT, "SenslogConnector/1.0");
         httpRequest.setHeader(CACHE_CONTROL, "no-cache");
@@ -175,7 +174,7 @@ public class HttpClient {
      */
     private Map<String, String> getHeaders(HttpMessage response) {
         Map<String, String> headers = new HashMap<>();
-        for (Header header : response.getAllHeaders()) {
+        for (Header header : response.getHeaders()) {
             headers.put(header.getName(), header.getValue());
         }
         return headers;

+ 15 - 25
src/main/java/cz/senslog/watchdog/util/http/HttpCookie.java

@@ -1,39 +1,29 @@
 package cz.senslog.watchdog.util.http;
 
-import org.apache.http.impl.cookie.BasicClientCookie;
+import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
 
-public class HttpCookie extends BasicClientCookie {
+public class HttpCookie {
+
+    private final BasicClientCookie cookie;
 
     public static HttpCookie empty() {
-        HttpCookie cookie = new HttpCookie("", "", "", "");
-        cookie.setSecure(false);
-        return cookie;
+        HttpCookie httpCookie = new HttpCookie("", "", "", "");
+        httpCookie.cookie.setSecure(false);
+        return httpCookie;
     }
 
     public HttpCookie(String name, String value, String domain, String path) {
-        super(name, value);
-        super.setDomain(domain);
-        super.setPath(path);
-        super.setSecure(true);
-    }
-
-    @Override
-    public String getName() {
-        return super.getName();
+        cookie = new BasicClientCookie(name, value);
+        cookie.setDomain(domain);
+        cookie.setPath(path);
+        cookie.setSecure(true);
     }
 
-    @Override
-    public String getValue() {
-        return super.getValue();
+    public boolean isSecure() {
+        return cookie.isSecure();
     }
 
-    @Override
-    public String getDomain() {
-        return super.getDomain();
-    }
-
-    @Override
-    public String getPath() {
-        return super.getPath();
+    public BasicClientCookie getCookie() {
+        return cookie;
     }
 }

+ 13 - 10
src/main/java/cz/senslog/watchdog/util/http/HttpHeader.java

@@ -1,10 +1,13 @@
-package cz.senslog.watchdog.util.http;
-
-import org.apache.http.HttpHeaders;
-
-public final class HttpHeader {
-    public static final String AUTHORIZATION = HttpHeaders.AUTHORIZATION;
-    public static final String DATE = HttpHeaders.DATE;
-    public static final String ACCEPT = HttpHeaders.ACCEPT;
-    public static final String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE;
-}
+package cz.senslog.watchdog.util.http;
+
+
+import org.apache.hc.core5.http.HttpHeaders;
+
+public final class HttpHeader {
+    public static final String AUTHORIZATION = HttpHeaders.AUTHORIZATION;
+    public static final String DATE = HttpHeaders.DATE;
+    public static final String ACCEPT = HttpHeaders.ACCEPT;
+    public static final String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE;
+    public static final String CACHE_CONTROL = HttpHeaders.CACHE_CONTROL;
+    public static final String USER_AGENT = HttpHeaders.USER_AGENT;
+}

+ 29 - 38
src/main/java/cz/senslog/watchdog/util/schedule/Scheduler.java

@@ -1,38 +1,29 @@
-package cz.senslog.watchdog.util.schedule;
-
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.util.Set;
-
-public interface Scheduler {
-
-    static SchedulerBuilder createBuilder() {
-        return new SchedulerBuilderImpl();
-    }
-
-    void start();
-    void stop();
-
-    Status getStatus();
-    Set<TaskDescription> getTaskDescriptions();
-
-    interface SchedulerBuilder {
-
-        SchedulerBuilder addTask(String name, Runnable task, long period);
-        SchedulerBuilder addTask(Runnable task, long period);
-
-        SchedulerBuilder addTask(String name, Runnable task, long period, int delaySec);
-        SchedulerBuilder addTask(Runnable task, long period, int delaySec);
-
-        SchedulerBuilder addTask(String name, Runnable task, long period, LocalDateTime startAt);
-        SchedulerBuilder addTask(Runnable task, long period, LocalDateTime startAt);
-
-        SchedulerBuilder addTask(String name, Runnable task, int repeat);
-        SchedulerBuilder addTask(Runnable task, int repeat);
-
-        SchedulerBuilder addTask(String name, Runnable task);
-        SchedulerBuilder addTask(Runnable task);
-
-        Scheduler build();
-    }
-}
+package cz.senslog.watchdog.util.schedule;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Set;
+
+public interface Scheduler {
+
+    static SchedulerBuilder createBuilder() {
+        return new SchedulerBuilderImpl();
+    }
+
+    void startAll();
+    void start(String name);
+
+    void stopAll();
+    void stop(String name);
+
+    interface SchedulerBuilder {
+
+        SchedulerBuilder addTask(String name, Runnable task, long period);
+        SchedulerBuilder addTask(String name, Runnable task, long period, int delaySec);
+        SchedulerBuilder addTask(String name, Runnable task, long period, LocalDateTime startAt);
+        SchedulerBuilder addTask(String name, Runnable task, int repeat);
+        SchedulerBuilder addTask(String name, Runnable task);
+
+        Scheduler build();
+    }
+}

+ 49 - 74
src/main/java/cz/senslog/watchdog/util/schedule/SchedulerBuilderImpl.java

@@ -1,74 +1,49 @@
-package cz.senslog.watchdog.util.schedule;
-
-import java.time.LocalDateTime;
-import java.util.HashSet;
-import java.util.Set;
-
-public class SchedulerBuilderImpl implements Scheduler.SchedulerBuilder {
-
-    private final Set<Task> tasks;
-
-    public SchedulerBuilderImpl() {
-        this.tasks = new HashSet<>();
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, long period) {
-        tasks.add(new ScheduleTask(name, task, period));
-        return this;
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, long period, int delaySec) {
-        tasks.add(new ScheduleTask(name, task, period, delaySec));
-        return this;
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, long period, LocalDateTime startAt) {
-        tasks.add(new ScheduleTask(name, task, period, startAt));
-        return this;
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(String name, Runnable task) {
-        tasks.add(new PeriodicalTask(name, task, 1));
-        return this;
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, int repeat) {
-        tasks.add(new PeriodicalTask(name, task, repeat));
-        return this;
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(Runnable task, long period) {
-        return addTask(task.getClass().getSimpleName(), task, period);
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(Runnable task, long period, int delaySec) {
-        return addTask(task.getClass().getSimpleName(), task, period, delaySec);
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(Runnable task, long period, LocalDateTime startAt) {
-        return addTask(task.getClass().getSimpleName(), task, period, startAt);
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(Runnable task, int repeat) {
-        return addTask(task.getClass().getSimpleName(), task, repeat);
-    }
-
-    @Override
-    public Scheduler.SchedulerBuilder addTask(Runnable task) {
-        return addTask(task.getClass().getSimpleName(), task);
-    }
-
-    @Override
-    public Scheduler build() {
-        return new SchedulerImpl(tasks);
-    }
-}
+package cz.senslog.watchdog.util.schedule;
+
+import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SchedulerBuilderImpl implements Scheduler.SchedulerBuilder {
+
+    private final Set<Task> tasks;
+
+    public SchedulerBuilderImpl() {
+        this.tasks = new HashSet<>();
+    }
+
+    @Override
+    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, long period) {
+        tasks.add(new ScheduleTask(name, task, period));
+        return this;
+    }
+
+    @Override
+    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, long period, int delaySec) {
+        tasks.add(new ScheduleTask(name, task, period, delaySec));
+        return this;
+    }
+
+    @Override
+    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, long period, LocalDateTime startAt) {
+        tasks.add(new ScheduleTask(name, task, period, startAt));
+        return this;
+    }
+
+    @Override
+    public Scheduler.SchedulerBuilder addTask(String name, Runnable task) {
+        tasks.add(new PeriodicalTask(name, task, 1));
+        return this;
+    }
+
+    @Override
+    public Scheduler.SchedulerBuilder addTask(String name, Runnable task, int repeat) {
+        tasks.add(new PeriodicalTask(name, task, repeat));
+        return this;
+    }
+
+    @Override
+    public Scheduler build() {
+        return new SchedulerImpl(tasks);
+    }
+}

+ 63 - 62
src/main/java/cz/senslog/watchdog/util/schedule/SchedulerImpl.java

@@ -1,62 +1,63 @@
-package cz.senslog.watchdog.util.schedule;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public class SchedulerImpl implements Scheduler {
-
-    private final Set<Task> tasks;
-
-    private ScheduledExecutorService scheduler;
-    private CountDownLatch latch;
-
-    public SchedulerImpl(Set<Task> tasks) {
-        this.tasks = tasks;
-    }
-
-    @Override
-    public void start() {
-
-        if (!tasks.isEmpty()) {
-            scheduler = Executors.newScheduledThreadPool(tasks.size());
-
-            latch = new CountDownLatch(tasks.size());
-            tasks.forEach(t -> t.schedule(scheduler, latch));
-
-            try {
-                latch.await();
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        } else {
-            // TODO no tasks
-        }
-    }
-
-    @Override
-    public void stop() {
-        if (getStatus() == Status.RUNNING) {
-            scheduler.shutdown();
-            scheduler = null;
-        }
-    }
-
-    @Override
-    public Status getStatus() {
-        boolean active = scheduler != null && !scheduler.isShutdown();
-        return active ? Status.RUNNING : Status.STOPPED;
-    }
-
-    @Override
-    public Set<TaskDescription> getTaskDescriptions() {
-        Set<TaskDescription> descriptions = new HashSet<>(tasks.size());
-        for (Task task : tasks) {
-            descriptions.add(task.getDescription());
-        }
-        return descriptions;
-    }
-}
+package cz.senslog.watchdog.util.schedule;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class SchedulerImpl implements Scheduler {
+
+    private final Map<String, Task> tasks;
+
+    private ScheduledExecutorService scheduler;
+    private CountDownLatch latch;
+
+    public SchedulerImpl(Set<Task> tasks) {
+        this.tasks = new HashMap<>(tasks.size());
+        for (Task task : tasks) {
+            this.tasks.put(task.getDescription().getName(), task);
+        }
+    }
+
+    @Override
+    public void startAll() {
+
+        if (!tasks.isEmpty()) {
+            scheduler = Executors.newScheduledThreadPool(tasks.size());
+
+            latch = new CountDownLatch(tasks.size());
+            tasks.values().forEach(t -> t.schedule(scheduler, latch));
+
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        } else {
+            // TODO no tasks
+        }
+    }
+
+    @Override
+    public void start(String name) {
+
+    }
+
+    @Override
+    public void stopAll() {
+        if (scheduler != null && !scheduler.isShutdown()) {
+            scheduler.shutdown();
+            scheduler = null;
+        }
+    }
+
+    @Override
+    public void stop(String name) {
+
+    }
+
+}

+ 32 - 32
src/test/java/cz/senslog/watchdog/config/ConfigurationTest.java

@@ -1,33 +1,33 @@
-package cz.senslog.watchdog.config;
-
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
-
-import static java.nio.file.Files.readAllBytes;
-import static java.nio.file.Paths.get;
-import static java.util.Objects.requireNonNull;
-
-class ConfigurationTest {
-
-    private String readResourceFile(String relativePath) throws IOException {
-        try {
-            URL url = getClass().getClassLoader().getResource(relativePath);
-            byte[] bytes = readAllBytes(get(requireNonNull(url).toURI()));
-            return new String(bytes);
-        } catch (URISyntaxException e) {
-            throw new IOException(e);
-        }
-    }
-
-    @Test
-    void load() throws IOException {
-
-        URL url = getClass().getClassLoader().getResource("configExample.yaml");
-        Configuration configuration = Configuration.load(requireNonNull(url).getPath());
-
-
-    }
+package cz.senslog.watchdog.config;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import static java.nio.file.Files.readAllBytes;
+import static java.nio.file.Paths.get;
+import static java.util.Objects.requireNonNull;
+
+class ConfigurationTest {
+
+    private String readResourceFile(String relativePath) throws IOException {
+        try {
+            URL url = getClass().getClassLoader().getResource(relativePath);
+            byte[] bytes = readAllBytes(get(requireNonNull(url).toURI()));
+            return new String(bytes);
+        } catch (URISyntaxException e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Test
+    void load() throws IOException {
+
+//        URL url = getClass().getClassLoader().getResource("configExample.yaml");
+//        Configuration configuration = Configuration.load(requireNonNull(url).getPath());
+
+
+    }
 }