Browse Source

New configuration that allows hiearchy of groups

Lukas Cerny 4 years ago
parent
commit
ec6e2da5fe
61 changed files with 2006 additions and 641 deletions
  1. 0 43
      config/lv_senslog.yaml
  2. 105 0
      config/new_example.yaml
  3. 76 0
      config/new_foodie.yaml
  4. 66 0
      config/new_lv.yaml
  5. 39 16
      src/main/java/cz/senslog/watchdog/app/Application.java
  6. 67 25
      src/main/java/cz/senslog/watchdog/app/Watcher.java
  7. 8 0
      src/main/java/cz/senslog/watchdog/config/AllMonitoredObjects.java
  8. 297 27
      src/main/java/cz/senslog/watchdog/config/Configuration.java
  9. 10 0
      src/main/java/cz/senslog/watchdog/config/ConsoleLogLevel.java
  10. 28 0
      src/main/java/cz/senslog/watchdog/config/ConsoleMessageBrokerConfig.java
  11. 6 12
      src/main/java/cz/senslog/watchdog/config/DataProviderConfig.java
  12. 12 1
      src/main/java/cz/senslog/watchdog/config/DataProviderType.java
  13. 6 6
      src/main/java/cz/senslog/watchdog/config/DatabaseDataProviderConfig.java
  14. 18 39
      src/main/java/cz/senslog/watchdog/config/EmailMessageBrokerConfig.java
  15. 38 0
      src/main/java/cz/senslog/watchdog/config/EmailServerConfig.java
  16. 35 0
      src/main/java/cz/senslog/watchdog/config/ExecutableGroup.java
  17. 53 0
      src/main/java/cz/senslog/watchdog/config/GroupConfig.java
  18. 6 22
      src/main/java/cz/senslog/watchdog/config/MessageBrokerConfig.java
  19. 14 2
      src/main/java/cz/senslog/watchdog/config/MessageBrokerType.java
  20. 33 0
      src/main/java/cz/senslog/watchdog/config/MonitoredObject.java
  21. 0 177
      src/main/java/cz/senslog/watchdog/config/MonitoredObjectsConfig.java
  22. 25 3
      src/main/java/cz/senslog/watchdog/config/PropertyConfig.java
  23. 10 0
      src/main/java/cz/senslog/watchdog/config/ResultType.java
  24. 17 0
      src/main/java/cz/senslog/watchdog/config/SensLogGroupConfig.java
  25. 35 0
      src/main/java/cz/senslog/watchdog/config/SignalMessageBrokerConfig.java
  26. 10 0
      src/main/java/cz/senslog/watchdog/config/SuperGroup.java
  27. 17 0
      src/main/java/cz/senslog/watchdog/config/SuperGroupConfig.java
  28. 0 35
      src/main/java/cz/senslog/watchdog/config/WebServiceConfig.java
  29. 36 0
      src/main/java/cz/senslog/watchdog/config/WebServiceDataProviderConfig.java
  30. 17 0
      src/main/java/cz/senslog/watchdog/core/DataProviderInstanceCreator.java
  31. 55 0
      src/main/java/cz/senslog/watchdog/core/EmailServerConnection.java
  32. 4 0
      src/main/java/cz/senslog/watchdog/core/InstanceCreator.java
  33. 6 0
      src/main/java/cz/senslog/watchdog/core/MessageBrokerInstanceCreator.java
  34. 82 0
      src/main/java/cz/senslog/watchdog/core/adt/Graph.java
  35. 56 0
      src/main/java/cz/senslog/watchdog/core/adt/Node.java
  36. 7 2
      src/main/java/cz/senslog/watchdog/domain/Report.java
  37. 1 1
      src/main/java/cz/senslog/watchdog/domain/StatusReport.java
  38. 0 13
      src/main/java/cz/senslog/watchdog/messagebroker/MessageBroker.java
  39. 63 0
      src/main/java/cz/senslog/watchdog/messagebroker/MessageBrokerManager.java
  40. 11 1
      src/main/java/cz/senslog/watchdog/messagebroker/broker/ConsoleMessageBroker.java
  41. 109 0
      src/main/java/cz/senslog/watchdog/messagebroker/broker/EmailMessageBroker.java
  42. 26 0
      src/main/java/cz/senslog/watchdog/messagebroker/broker/SignalMessageBroker.java
  43. 0 140
      src/main/java/cz/senslog/watchdog/messagebroker/email/EmailMessageBroker.java
  44. 2 14
      src/main/java/cz/senslog/watchdog/provider/DataProvider.java
  45. 52 0
      src/main/java/cz/senslog/watchdog/provider/DataProviderManager.java
  46. 25 0
      src/main/java/cz/senslog/watchdog/provider/ProvidedData.java
  47. 28 0
      src/main/java/cz/senslog/watchdog/provider/ProvidedObject.java
  48. 28 0
      src/main/java/cz/senslog/watchdog/provider/ProviderInfo.java
  49. 5 6
      src/main/java/cz/senslog/watchdog/provider/database/Connection.java
  50. 14 3
      src/main/java/cz/senslog/watchdog/provider/database/DatabaseDataProvider.java
  51. 75 31
      src/main/java/cz/senslog/watchdog/provider/ws/WebServiceDataProvider.java
  52. 8 0
      src/main/java/cz/senslog/watchdog/util/StringUtils.java
  53. 36 0
      src/main/java/cz/senslog/watchdog/util/schedule/PeriodicalTask.java
  54. 13 7
      src/main/java/cz/senslog/watchdog/util/schedule/ScheduleTask.java
  55. 6 0
      src/main/java/cz/senslog/watchdog/util/schedule/Scheduler.java
  56. 30 12
      src/main/java/cz/senslog/watchdog/util/schedule/SchedulerBuilderImpl.java
  57. 3 3
      src/main/java/cz/senslog/watchdog/util/schedule/SchedulerImpl.java
  58. 12 0
      src/main/java/cz/senslog/watchdog/util/schedule/Task.java
  59. 33 0
      src/test/java/cz/senslog/watchdog/config/ConfigurationTest.java
  60. 27 0
      src/test/java/cz/senslog/watchdog/config/graph/GraphTest.java
  61. 105 0
      src/test/resources/configExample.yaml

+ 0 - 43
config/lv_senslog.yaml

@@ -1,43 +0,0 @@
-dataProvider:
-  type: DATABASE
-  config:
-    url: "jdbc:postgresql://localhost:5432/senslog1"
-    username: "senslog_app"
-    password: "SENSlog"
-    connectionPoolSize: 6
-    groupName: "admin"
-
-messageBroker:
-  type: EMAIL # EMAIL, CONSOLE (for testing), WHATSAPP (not yet), TELEGRAM (not yet)]
-  vanishPeriod: 3 # number of days when already send report could be sent again
-  config:
-    smtpHost: "mail.lesprojekt.cz"
-    smtpPort: 465
-    authUsername: "watchdog@senslog.org"
-    authPassword: "5jspdD"
-    senderEmail: "watchdog@senslog.org"
-    recipientEmail: "luc.cerny@gmail.com" # "kepka@ccss.cz"
-    subject: "[Watchdog] Report SAH SensLog (LV)"
-
-monitoredObjects:
-  18907678:
-    interval: 86400 # 1 day
-    sensors: [340414062, 410164038, 800014050]
-
-  18907677:
-    interval: 86400 # 1 day
-    sensors: [340414062, 410164038, 800014050]
-
-  2129992:
-    interval: 86400 # 1 day
-
-#  4280003:
-#    interval: 86400
-
-  4640825685:
-    interval: 86400 # 1 day
-    sensors: [850010000, 850020000, 850030000]
-
-  4638425647:
-    interval: 86400 # 1 day
-    sensors: [850010000, 850020000, 850030000 ]

+ 105 - 0
config/new_example.yaml

@@ -0,0 +1,105 @@
+emailServers:
+  exampleEmailServer:
+    smtpHost: "<smtp_server>"
+    smtpPort: 465
+    authUsername: "username"
+    authPassword: "password"
+
+messageBrokers:
+  emailToAll:
+    type: EMAIL
+    config:
+      server: exampleEmailServer
+      senderEmail: "from@example.org"
+      recipientEmail: "to@example.org"
+      subject: "[watchdog] Example Report"
+  emailToTeam:
+    type: EMAIL
+    config:
+      server: exampleEmailServer
+      senderEmail: "from@example.org"
+      recipientEmail: "to@example.org"
+      subject: "[watchdog] Example Report"
+  signalEmergency:
+    type: SIGNAL
+    config:
+      recipientNumber: "+420 123 456 789"
+      senderNumber: "+420 123 456 789"
+      messagePattern: "No actual data from: $unit_id/$sensor_id"  # available props: $unit_id, $sensor_id, $timestamp, $value
+
+dataProviders:
+  wsServer1:
+    type: WEB_SERVICE
+    config:
+      baseUrl: "<domain_url>"
+  wsServer2:
+    type: WEB_SERVICE
+    config:
+      baseUrl: "<domain_url>"
+  dbServer:
+    type: DATABASE
+    config:
+      url: "<jdbc_url>"
+      username: "<username>"
+      password: "<password>"
+      connectionPoolSize: 6
+      groupName: "<group_name>"
+
+groups:
+  group1Checker:
+    dataProvider: wsServer1
+    messageBroker: emailToTeam
+    resultType: ALL
+    period: 86400
+  group2Checker:
+    dataProvider: dbServer
+    messageBroker: emailToTeam
+    resultType: ALL
+    period: 86400
+  groupRealTime:
+    dataProvider: wsServer1
+    messageBroker: signalEmergency
+    resultType: FAIL
+
+senslogGroups:
+  senslogGAdmin:
+    dataProvider: wsServer1
+    messageBroker: emailToAll
+    resultType: FAIL
+    period: 86400
+    senslogGroups: [ "admin", "...." ]
+
+superGroups:
+  sumarizeError:
+    messageBroker: emailToAll
+    resultType: FAIL
+    period: 86400
+    groups: [ group1Checker, group2Checker ]
+
+monitoredObjects:
+  unit_id_1:
+    groups: [group1Checker, group2Checker]
+    period: 1440
+
+  unit_id_2:
+    period: 1440
+    groups: [groupRealTime]
+    sensors: [210000000, 220000000]
+
+  unit_id_3:
+    period: 1440
+    groups: [groupRealTime]
+    sensors:
+      310000000:
+        groups: [group1Checker]
+      320000000:
+        period: 2880
+
+  unit_id_4:
+    sensors:
+      410000000:
+        period: 2880
+        groups: [group1Checker]
+      420000000:
+        period: 1440
+        groups: [group1Checker]

+ 76 - 0
config/new_foodie.yaml

@@ -0,0 +1,76 @@
+emailServers:
+  lspEmail:
+    smtpHost: "mail.lesprojekt.cz"
+    smtpPort: 465
+    authUsername: "watchdog@senslog.org"
+    authPassword: "5jspdD"
+
+messageBrokers:
+  emailToTest:
+    type: EMAIL
+    config:
+      server: lspEmail
+      senderEmail: "watchdog@senslog.org"
+      recipientEmail: "luc.cerny@gmail.com"
+      subject: "[Watchdog] TEST Report Foodie SensLog (CZ)"
+  emailToAll:
+    type: EMAIL
+    config:
+      server: lspEmail
+      senderEmail: "watchdog@senslog.org"
+      recipientEmail: "luc.cerny@gmail.com, kepka@ccss.cz"
+      subject: "[Watchdog] Report Foodie SensLog (CZ)"
+
+dataProviders:
+  wsOldFoodie:
+    type: WEB_SERVICE
+    config:
+      baseUrl: "http://foodie.lesprojekt.cz:8080/MapLogOT"
+      user: "kynsperk"
+      group: "kynsperk"
+
+groups:
+  kynsperk:
+    dataProvider: wsOldFoodie
+    messageBroker: emailToTest
+    resultType: FAIL
+    period: 86400
+
+monitoredObjects:
+  1305167562293765:
+    period: 86400
+    groups: [kynsperk]
+    sensors: [ 340340092, 360200000, 410130092 ]
+
+  1305167562275270:
+    period: 86400
+    groups: [kynsperk]
+    sensors: [ 340240003, 360200000, 410090003 ]
+
+  1305167562292824:
+    period: 86400
+    groups: [kynsperk]
+
+  1305167549144045:
+    period: 86400
+    groups: [ kynsperk ]
+
+  1305167549158198:
+    period: 86400
+    groups: [ kynsperk ]
+
+  1305167549167050:
+    period: 86400
+    groups: [ kynsperk ]
+
+  1305167549173046:
+    period: 86400
+    groups: [ kynsperk ]
+
+  1305167549167886:
+    period: 86400
+    groups: [ kynsperk ]
+
+  1305167549149707:
+    period: 86400
+    groups: [ kynsperk ]

+ 66 - 0
config/new_lv.yaml

@@ -0,0 +1,66 @@
+emailServers:
+  lspEmail:
+    smtpHost: "mail.lesprojekt.cz"
+    smtpPort: 465
+    authUsername: "watchdog@senslog.org"
+    authPassword: "5jspdD"
+
+messageBrokers:
+  emailToTest:
+    type: EMAIL
+    config:
+      server: lspEmail
+      senderEmail: "watchdog@senslog.org"
+      recipientEmail: "luc.cerny@gmail.com"
+      subject: "[Watchdog] TEST Report SAH SensLog (LV)"
+  emailToAll:
+    type: EMAIL
+    config:
+      server: lspEmail
+      senderEmail: "watchdog@senslog.org"
+      recipientEmail: "luc.cerny@gmail.com, kepka@ccss.cz"
+      subject: "[Watchdog] Report SAH SensLog (LV)"
+
+dataProviders:
+  wsVilcini:
+    type: WEB_SERVICE
+    config:
+      baseUrl: "http://51.15.45.95:8080/senslog1"
+      user: "vilcini"
+      group: "vilcini"
+
+groups:
+  vilcini:
+    dataProvider: wsVilcini
+    messageBroker: emailToTest
+    resultType: FAIL
+    period: 86400
+
+monitoredObjects:
+  18907678:
+    period: 86400
+    groups: [vilcini]
+    sensors: [340414062, 410164038, 800014050]
+
+  18907677:
+    period: 86400
+    groups: [ vilcini ]
+    sensors: [340414062, 410164038, 800014050]
+
+  4640825685:
+    period: 86400
+    groups: [ vilcini ]
+    sensors: [850010000, 850020000, 850030000]
+
+  4638425647:
+    period: 86400
+    groups: [ vilcini ]
+    sensors: [850010000, 850020000, 850030000 ]
+
+  2129992:
+    period: 86400
+    groups: [vilcini]
+
+  4280003:
+    period: 86400
+    groups: [ vilcini ]

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

@@ -2,8 +2,11 @@ package cz.senslog.watchdog.app;
 
 
 import cz.senslog.watchdog.config.Configuration;
+import cz.senslog.watchdog.config.ExecutableGroup;
 import cz.senslog.watchdog.messagebroker.MessageBroker;
+import cz.senslog.watchdog.messagebroker.MessageBrokerManager;
 import cz.senslog.watchdog.provider.DataProvider;
+import cz.senslog.watchdog.provider.DataProviderManager;
 import cz.senslog.watchdog.util.DateTrunc;
 import cz.senslog.watchdog.util.schedule.Scheduler;
 import org.apache.logging.log4j.LogManager;
@@ -12,6 +15,8 @@ import org.apache.logging.log4j.Logger;
 import java.io.IOException;
 import java.time.LocalDateTime;
 
+import static cz.senslog.watchdog.util.DateTrunc.trunc;
+
 public class Application extends Thread {
 
     private static final Logger logger = LogManager.getLogger(Application.class);
@@ -42,31 +47,49 @@ public class Application extends Thread {
     @Override
     public void run() {
 
-        String configFile = params.getConfigFileName();
+        String configFilePath = params.getConfigFileName();
         Configuration config = null;
         try {
-            config = Configuration.load(configFile);
+            config = Configuration.load(configFilePath);
         } catch (IOException e) {
             logger.catching(e);
             System.exit(1);
         }
 
-        DataProvider dataProvider = DataProvider.create(config.getDataProviderConfig());
-        MessageBroker messageBroker = MessageBroker.create(config.getMessageBrokerConfig());
-        Watcher watcher = Watcher.create(config.getWatchingObjectsConfig(), dataProvider, messageBroker);
+        MessageBrokerManager messageBrokerManager = MessageBrokerManager.createInstance(
+                config.getEmailServerConfigs(), config.getMessageBrokerConfigs()
+        );
+
+        DataProviderManager dataProviderManager = DataProviderManager.createInstance(
+                config.getDataProviderConfigs()
+        );
+
+        LocalDateTime now = LocalDateTime.now();
+        Scheduler.SchedulerBuilder schedulerBuilder = Scheduler.createBuilder();
+        for (ExecutableGroup exeGroup : config.getExecutableGroups()) {
+            MessageBroker msgBroker = messageBrokerManager.getInstance(exeGroup.getConfig().getMessageBrokerId());
+            DataProvider dataProvider = dataProviderManager.getInstance(exeGroup.getConfig().getDataProviderId());
+
+            Watcher watcher = Watcher.create(exeGroup, dataProvider, msgBroker);
+            Integer period = exeGroup.getConfig().getPeriod();
+            if (period == null) {
+                throw new IllegalStateException("Period for the group '"+exeGroup.getConfig().getId()+"' is not specified.");
+            }
+
+            String taskName = exeGroup.getConfig().getId();
+            if (params.isExecuteImmediately()) {
+                schedulerBuilder.addTask(taskName, watcher::check);
+            } else {
+                schedulerBuilder.addTask(taskName, watcher::check, period, trunc(now, period).plusSeconds(period));
+            }
+        }
 
-        if (params.isExecuteImmediately()) {
-            watcher.check();
-        } else {
-            int period = config.getWatchingObjectsConfig().getMinInterval();
-            LocalDateTime startAtTime = DateTrunc.trunc(LocalDateTime.now(), period).plusSeconds(period);
+        // TODO super groups
+
+        Scheduler scheduler = schedulerBuilder.build();
+
+        scheduler.start();
 
-            Scheduler scheduler = Scheduler.createBuilder()
-                    .addTask(watcher::check, period, startAtTime)
-//                    .addTask(watcher::check, period)
-                    .build();
 
-            scheduler.start();
-        }
     }
 }

+ 67 - 25
src/main/java/cz/senslog/watchdog/app/Watcher.java

@@ -1,57 +1,93 @@
 package cz.senslog.watchdog.app;
 
-import cz.senslog.watchdog.config.MonitoredObjectsConfig;
+import cz.senslog.watchdog.config.AllMonitoredObjects;
+import cz.senslog.watchdog.config.ExecutableGroup;
+import cz.senslog.watchdog.config.MonitoredObject;
+import cz.senslog.watchdog.core.adt.Node;
+import cz.senslog.watchdog.domain.*;
 import cz.senslog.watchdog.messagebroker.MessageBroker;
-import cz.senslog.watchdog.domain.Report;
-import cz.senslog.watchdog.domain.SimpleReport;
 import cz.senslog.watchdog.provider.DataProvider;
-import cz.senslog.watchdog.provider.Record;
-import cz.senslog.watchdog.util.Tuple;
+import cz.senslog.watchdog.provider.ProvidedData;
+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.LocalDateTime;
 import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.List;
-import java.util.stream.Collectors;
+import java.util.*;
 
-import static cz.senslog.watchdog.domain.StatusReport.FAIL;
-import static cz.senslog.watchdog.domain.StatusReport.OK;
+import static cz.senslog.watchdog.domain.StatusReport.*;
 import static java.time.LocalDateTime.ofInstant;
-import static java.time.ZoneOffset.UTC;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 
 public class Watcher {
 
     private static final Logger logger = LogManager.getLogger(Watcher.class);
 
-    private final MonitoredObjectsConfig config;
+    private final ExecutableGroup group;
     private final DataProvider dataProvider;
     private final MessageBroker messageBroker;
 
-    public static Watcher create(MonitoredObjectsConfig config, DataProvider dataProvider, MessageBroker messageBroker) {
-        return new Watcher(config, dataProvider, messageBroker);
+    public static Watcher create(ExecutableGroup group, DataProvider dataProvider, MessageBroker messageBroker) {
+        return new Watcher(group, dataProvider, messageBroker);
     }
 
-    private Watcher(MonitoredObjectsConfig config, DataProvider dataProvider, MessageBroker messageBroker) {
-        this.config = config;
+    private Watcher(ExecutableGroup group, DataProvider dataProvider, MessageBroker messageBroker) {
+        this.group = group;
         this.dataProvider = dataProvider;
         this.messageBroker = messageBroker;
     }
 
     public void check() {
 
-        List<Record> records = dataProvider.getLastRecords();
-
         Instant now = Instant.now();
-        List<SimpleReport> reports = records.parallelStream()
-                .map(r -> Tuple.of(r, config.getInterval(r)))
-                .filter(t -> t.getItem2().isPresent())
-                .map(t -> new SimpleReport(t.getItem1(), t.getItem1().isValid(t.getItem2().get(), now) ? OK : FAIL))
-                .collect(Collectors.toList());
+        ProvidedData data = dataProvider.getLastData();
+
+        List<SimpleReport> reports = new ArrayList<>();
+        List<String> reportedMessages = new ArrayList<>(data.getErrorMessages());
+
+        for (String unitId : group.getKeys()) {
+            if (!data.containsNode(unitId)) {
+                reportedMessages.add(String.format("No data for: unit_id - %s", unitId)); continue;
+            }
+            Node<String, MonitoredObject> mUnitNode = group.getNode(unitId);
+            Node<String, ProvidedObject> pUnitNode = data.getNode(unitId);
+            String unitName = pUnitNode.getValue().getName();
+            for (MonitoredObject mSensor : mUnitNode.getValues()) {
+                int period = mSensor.getPeriod();
+                Instant minTimestamp = now.minusSeconds(period);
+                Collection<ProvidedObject> providedObjects = mSensor instanceof AllMonitoredObjects ?
+                        pUnitNode.getValues() : pUnitNode.containsKey(mSensor.getId()) ?
+                                singletonList(pUnitNode.getNode(mSensor.getId()).getValue()) : emptyList();
+
+                if (providedObjects.isEmpty()) {
+                    reports.add(new SimpleReport(new ObservationInfo(
+                            new Unit(Long.parseLong(unitId), unitName),
+                            new Sensor(Long.parseLong(mSensor.getId()), "unknown"),
+                            null
+                    ),
+                            NO_DATA
+                    ));
+                }
+
+                for (ProvidedObject pObject : providedObjects) {
+                    SimpleReport simpleReport = new SimpleReport(new ObservationInfo(
+                            new Unit(Long.parseLong(unitId), unitName),
+                            new Sensor(Long.parseLong(pObject.getId()), pObject.getName()),
+                            pObject.getTimestamp()
+                    ),
+                            pObject.getTimestamp().toInstant().isBefore(minTimestamp) ? FAIL : OK
+                    );
 
-        messageBroker.send(new Report(ofInstant(now, ZoneId.systemDefault()), reports), status -> {
+                    if (isAllowedToReport(simpleReport)) {
+                        reports.add(simpleReport);
+                    }
+                }
+            }
+        }
+
+        messageBroker.send(new Report(ofInstant(now, ZoneId.systemDefault()), reports, reportedMessages), status -> {
             String brokerType = messageBroker.getType().name().toLowerCase();
             if (status.isSuccess()) {
                 logger.info("The report at '{}' was send via '{}' broker successfully.",
@@ -64,4 +100,10 @@ public class Watcher {
             }
         });
     }
+
+
+    private boolean isAllowedToReport(SimpleReport report) {
+        if (report == null) { return false; }
+        return report.getStatus().ordinal() >= group.getConfig().getResultType().ordinal();
+    }
 }

+ 8 - 0
src/main/java/cz/senslog/watchdog/config/AllMonitoredObjects.java

@@ -0,0 +1,8 @@
+package cz.senslog.watchdog.config;
+
+public class AllMonitoredObjects extends MonitoredObject {
+
+    public AllMonitoredObjects(Integer period) {
+        super("__all", period);
+    }
+}

+ 297 - 27
src/main/java/cz/senslog/watchdog/config/Configuration.java

@@ -11,32 +11,128 @@ import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Map;
+import java.util.*;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 
 public class Configuration {
 
     private static final Logger logger = LogManager.getLogger(Configuration.class);
 
-    private final DataProviderConfig dataProviderConfig;
-    private final MessageBrokerConfig messageBrokerConfig;
-    private final MonitoredObjectsConfig monitoredObjectsConfig;
+    private final Collection<ExecutableGroup> executableGroups;
+    private final Collection<MessageBrokerConfig> messageBrokerConfigs;
+    private final Collection<EmailServerConfig> emailServerConfigs;
+    private final Collection<DataProviderConfig> dataProviderConfigs;
 
-    private Configuration(DataProviderConfig dataProvider, MessageBrokerConfig messageBroker, MonitoredObjectsConfig monitoredObjects) {
-        this.dataProviderConfig = dataProvider;
-        this.messageBrokerConfig = messageBroker;
-        this.monitoredObjectsConfig = monitoredObjects;
+    private Configuration(
+            Collection<ExecutableGroup> executableGroups,
+            Collection<EmailServerConfig> emailServerConfigs,
+            Collection<MessageBrokerConfig> messageBrokerConfigs,
+            Collection<DataProviderConfig> dataProviderConfigs
+    ){
+        this.executableGroups = executableGroups;
+        this.messageBrokerConfigs = messageBrokerConfigs;
+        this.emailServerConfigs = emailServerConfigs;
+        this.dataProviderConfigs = dataProviderConfigs;
     }
 
-    public DataProviderConfig getDataProviderConfig() {
-        return dataProviderConfig;
-    }
+    private static Configuration make(
+            List<TempUnitConfig> monitoredObjects,
+            Map<String, EmailServerConfig> emailServers,
+            Map<String, MessageBrokerConfig> messageBrokers,
+            Map<String, DataProviderConfig> dataProviders,
+            Map<String, GroupConfig> groups,
+            Map<String, SensLogGroupConfig> sensLogGroups,
+            Map<String, SuperGroupConfig> superGroups
+    ) {
 
-    public MessageBrokerConfig getMessageBrokerConfig() {
-        return messageBrokerConfig;
-    }
+        Map<String, ExecutableGroup> executableGroupsMap = new HashMap<>();
+        for (TempUnitConfig mObject : monitoredObjects) {
+            Set<String> unitGroups = mObject.groups;
+            List<TempSensorConfig> sensors = mObject.sensors;
+            if (unitGroups.isEmpty() && sensors.isEmpty()) {
+                throw new IllegalStateException("The monitored object '"+mObject.id+"' is not assigned to any groups.");
+            }
+            sensors = !sensors.isEmpty() ? sensors : singletonList(new TempAllSensorsConfig(mObject.period));
+            for (TempSensorConfig sensor : sensors) {
+                Set<String> sensorGroups = sensor.groups;
+                if (unitGroups.isEmpty() && sensorGroups.isEmpty()) {
+                    throw new IllegalStateException("The monitored object '"+sensor.id+"' 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."
+                        );
+                    }
+                    Integer period = groupConfig.getPeriod() != null ? groupConfig.getPeriod() : mObject.period;
+                    period = period != null ? period : sensor.period;
+                    if (period == null) {
+                        throw new IllegalStateException(
+                                "The monitored object '"+sensor.id+"' does not contain a valid 'period' value."
+                        );
+                    }
+
+                    if (!dataProviders.containsKey(groupConfig.getDataProviderId())) {
+                        throw new IllegalStateException(
+                                "Assigned data provider '"+groupConfig.getDataProviderId()+"' does not exist."
+                        );
+                    }
+
+                    if (!messageBrokers.containsKey(groupConfig.getMessageBrokerId())) {
+                        throw new IllegalStateException(
+                                "Assigned message broker '"+groupConfig.getMessageBrokerId()+"' does not exist."
+                        );
+                    }
 
-    public MonitoredObjectsConfig getWatchingObjectsConfig() {
-        return monitoredObjectsConfig;
+                    ExecutableGroup executableGroup = executableGroupsMap.computeIfAbsent(
+                            groupId, k -> new ExecutableGroup(groupConfig)
+                    );
+
+                    MonitoredObject sensorMObj = sensor.id != null ? new MonitoredObject(sensor.id, period) : new AllMonitoredObjects(period);
+                    executableGroup.swapPeriod(period);
+                    executableGroup.computeIfAbsent(mObject.id, MonitoredObject::new)
+                            .addNode(sensorMObj.getId(), sensorMObj);
+                }
+            }
+        }
+
+        for (SensLogGroupConfig sensLogGroup : sensLogGroups.values()) {
+            executableGroupsMap.put(sensLogGroup.getId(), new ExecutableGroup(sensLogGroup));
+        }
+
+        Map<String, SuperGroup> executableSGroupsMap = new HashMap<>();
+        for (SuperGroupConfig sGConfig : superGroups.values()) {
+            if (sGConfig.getPeriod() == null) {
+                throw new IllegalStateException(
+                        "The monitored object '"+sGConfig.getId()+"' does not contain a valid 'period' value."
+                );
+            }
+
+            SuperGroup superGroup = new SuperGroup(sGConfig);
+            for (String groupId : sGConfig.getGroups()) {
+                ExecutableGroup group = executableGroupsMap.get(groupId);
+                if (group == null) {
+                    throw new IllegalStateException("Assigned group '"+groupId+"' does not exist.");
+                }
+                superGroup.addNode(groupId, group);
+            }
+            executableSGroupsMap.put(sGConfig.getId(), superGroup);
+        }
+
+        // TODO return
+        Collection<ExecutableGroup> execGroups = executableGroupsMap.values();
+        Collection<SuperGroup> execSGroups = executableSGroupsMap.values();
+        Collection<EmailServerConfig> emailServerConfigs = emailServers.values();
+        Collection<DataProviderConfig> dataProviderConfigs = dataProviders.values();
+        Collection<MessageBrokerConfig> messageBrokerConfigs = messageBrokers.values();
+
+        return new Configuration(
+                execGroups, emailServerConfigs, messageBrokerConfigs, dataProviderConfigs
+        );
     }
 
     public static Configuration load(String fileName) throws IOException {
@@ -68,16 +164,15 @@ public class Configuration {
         }
 
         try {
-            PropertyConfig dataProviderProperties = createPropertyConfig(properties, "dataProvider");
-            DataProviderConfig dataProviderConfig = DataProviderConfig.create(dataProviderProperties);
-
-            PropertyConfig messageBrokerProperties = createPropertyConfig(properties, "messageBroker");
-            MessageBrokerConfig messageBrokerConfig = MessageBrokerConfig.create(messageBrokerProperties);
-
-            PropertyConfig monitoredObjectsProperties = createPropertyConfig(properties, "monitoredObjects");
-            MonitoredObjectsConfig monitoredObjectsConfig = MonitoredObjectsConfig.create(monitoredObjectsProperties);
-
-            return new Configuration(dataProviderConfig, messageBrokerConfig, monitoredObjectsConfig);
+            return make(
+                    parseMonitoredObjects(createPropertyConfig(properties, "monitoredObjects")),
+                    parseEmailServers(createPropertyConfig(properties, "emailServers")),
+                    parseMessageBrokers(createPropertyConfig(properties, "messageBrokers")),
+                    parseDataProviders(createPropertyConfig(properties, "dataProviders")),
+                    parseGroups(createPropertyConfig(properties, "groups")),
+                    parseSensLogGroups(createPropertyConfig(properties, "senslogGroups")),
+                    parseSuperGroups(createPropertyConfig(properties, "superGroups"))
+            );
         } catch (IOException e) {
             throw new IOException(String.format(
                     "Configuration file '%s' contains an error at '%s' attribute.", fileName, e.getMessage()
@@ -85,9 +180,136 @@ public class Configuration {
         }
     }
 
+    private static Map<String, EmailServerConfig> parseEmailServers(PropertyConfig config) {
+        Set<String> serverIds = config.getAttributes();
+        Map<String, EmailServerConfig> emailServers = new HashMap<>();
+        for (String id : serverIds) {
+            PropertyConfig serverConfig = config.getPropertyConfig(id);
+            emailServers.put(id, new EmailServerConfig(id,
+               serverConfig.getStringProperty("smtpHost"),
+               serverConfig.getIntegerProperty("smtpPort"),
+               serverConfig.getStringProperty("authUsername"),
+               serverConfig.getStringProperty("authPassword")
+            ));
+        }
+        return emailServers;
+    }
+
+    private static Map<String, MessageBrokerConfig> parseMessageBrokers(PropertyConfig config) {
+        Set<String> brokerIds = config.getAttributes();
+        Map<String, MessageBrokerConfig> messageBrokers = new HashMap<>(brokerIds.size());
+        for (String id : brokerIds) {
+            PropertyConfig brokerConfig = config.getPropertyConfig(id);
+            MessageBrokerType type = MessageBrokerType.of(brokerConfig.getStringProperty("type"));
+            messageBrokers.put(id, type.createConfig(id, brokerConfig.getPropertyConfig("config"))
+            );
+        }
+        return messageBrokers;
+    }
+
+    private static Map<String, DataProviderConfig> parseDataProviders(PropertyConfig config) {
+        Set<String> providerIds = config.getAttributes();
+        Map<String, DataProviderConfig> dataProviders = new HashMap<>(providerIds.size());
+        for (String id : providerIds) {
+            PropertyConfig providerConfig = config.getPropertyConfig(id);
+            DataProviderType type = DataProviderType.of(providerConfig.getStringProperty("type"));
+            dataProviders.put(id, type.createConfig(id, providerConfig.getPropertyConfig("config")));
+        }
+        return dataProviders;
+    }
+
+    private static Map<String, GroupConfig> parseGroups(PropertyConfig config) {
+        Set<String> groupIds = config.getAttributes();
+        Map<String, GroupConfig> groups = new HashMap<>(groupIds.size());
+        for (String id : groupIds) {
+            PropertyConfig groupConfig = config.getPropertyConfig(id);
+            groups.put(id, new GroupConfig(id,
+                    groupConfig.getStringProperty("dataProvider"),
+                    groupConfig.getStringProperty("messageBroker"),
+                    ResultType.of(groupConfig.getStringProperty("resultType")),
+                    groupConfig.getOptionalProperty("period", Integer.class).orElse(null)
+            ));
+        }
+        return groups;
+    }
+
+    private static Map<String, SuperGroupConfig> parseSuperGroups(PropertyConfig config) {
+        Set<String> groupIds = config.getAttributes();
+        Map<String, SuperGroupConfig> groups = new HashMap<>(groupIds.size());
+        for (String id : groupIds) {
+            PropertyConfig groupConfig = config.getPropertyConfig(id);
+
+            List<String> subGroupList = groupConfig.getArrayPropertyOf("groups", String.class);
+            Set<String> subGroupNames = new HashSet<>(subGroupList);
+            groups.put(id, new SuperGroupConfig(id,
+                    groupConfig.getStringProperty("messageBroker"),
+                    ResultType.of(groupConfig.getStringProperty("resultType")),
+                    groupConfig.getOptionalProperty("period", Integer.class).orElse(null),
+                    subGroupNames
+            ));
+        }
+        return groups;
+    }
+
+    private static Map<String, SensLogGroupConfig> parseSensLogGroups(PropertyConfig config) {
+        Set<String> groupIds = config.getAttributes();
+        Map<String, SensLogGroupConfig> groups = new HashMap<>(groupIds.size());
+        for (String id : groupIds) {
+            PropertyConfig groupConfig = config.getPropertyConfig(id);
+
+            List<String> subGroupList = groupConfig.getArrayPropertyOf("groups", String.class);
+            Set<String> subGroupNames = new HashSet<>(subGroupList);
+            groups.put(id, new SensLogGroupConfig(id,
+                    groupConfig.getStringProperty("dataProvider"),
+                    groupConfig.getStringProperty("messageBroker"),
+                    ResultType.of(groupConfig.getStringProperty("resultType")),
+                    groupConfig.getOptionalProperty("period", Integer.class).orElse(null),
+                    subGroupNames
+            ));
+        }
+        return groups;
+    }
+
+    private static List<TempUnitConfig> parseMonitoredObjects(PropertyConfig config) {
+        Set<String> unitIds = config.getAttributes();
+        List<TempUnitConfig> unitConfigs = new ArrayList<>(unitIds.size());
+        for (String unitId : unitIds) {
+            PropertyConfig unitConfig = config.getPropertyConfig(unitId);
+            Integer unitPeriod = unitConfig.getOptionalProperty("period", Integer.class).orElse(null);
+            List<String> unitGroupList = unitConfig.containsProperty("groups") ? unitConfig.getArrayPropertyOf("groups", String.class) : null;
+            Set<String> unitGroups = unitGroupList != null ? new HashSet<>(unitGroupList) : new HashSet<>();
+            List<TempSensorConfig> sensorConfigList = new ArrayList<>();
+            if (unitConfig.containsProperty("sensors")) {
+                Object sensorsObj = unitConfig.getProperty("sensors");
+                if (sensorsObj instanceof List) {
+                    List<Integer> sensors = unitConfig.getArrayPropertyOf("sensors", Integer.class);
+                    for (Integer sensorId : sensors) {
+                        sensorConfigList.add(new TempSensorConfig(Integer.toString(sensorId), unitPeriod, new HashSet<>()));
+                    }
+                } else if (sensorsObj instanceof Map) {
+                    PropertyConfig sensorsConfig = unitConfig.getPropertyConfig("sensors");
+                    Set<String> sensorIds = sensorsConfig.getAttributes();
+                    for (String sensorId : sensorIds) {
+                        PropertyConfig sensorConfig = sensorsConfig.getPropertyConfig(sensorId);
+                        Integer sensorPeriod = sensorConfig.getOptionalProperty("period", Integer.class).orElse(unitPeriod);
+                        List<String> sensorGroupList = sensorConfig.containsProperty("groups") ? sensorConfig.getArrayPropertyOf("groups", String.class) : emptyList();
+                        sensorConfigList.add(new TempSensorConfig(sensorId, sensorPeriod, new HashSet<>(sensorGroupList)));
+                    }
+                }
+            }
+            unitConfigs.add(new TempUnitConfig(unitId, unitPeriod, sensorConfigList, unitGroups));
+        }
+        return unitConfigs;
+    }
+
     private static PropertyConfig createPropertyConfig(Map<Object, Object> properties, String propertyName) throws IOException {
         Object generalConfig = properties.get(propertyName);
-        if (!(generalConfig instanceof Map)) { throw new IOException(propertyName); }
+        if (generalConfig == null) {
+            return new PropertyConfig(propertyName);
+        }
+        if (!(generalConfig instanceof Map)) {
+            throw new IOException(propertyName);
+        }
 
         Map<?, ?> generalConfigMap = (Map<?, ?>) generalConfig;
         PropertyConfig propertyConfig = new PropertyConfig(propertyName);
@@ -98,4 +320,52 @@ public class Configuration {
         }
         return propertyConfig;
     }
+
+    public Collection<MessageBrokerConfig> getMessageBrokerConfigs() {
+        return messageBrokerConfigs;
+    }
+
+    public Collection<EmailServerConfig> getEmailServerConfigs() {
+        return emailServerConfigs;
+    }
+
+    public Collection<DataProviderConfig> getDataProviderConfigs() {
+        return dataProviderConfigs;
+    }
+
+    public Collection<ExecutableGroup> getExecutableGroups() {
+        return executableGroups;
+    }
+
+    private static class TempUnitConfig {
+        private final String id;
+        private final Integer period;
+        private final List<TempSensorConfig> sensors;
+        private final Set<String> groups;
+
+        private TempUnitConfig(String id, Integer period, List<TempSensorConfig> sensors, Set<String> groups) {
+            this.id = id;
+            this.period = period;
+            this.sensors = sensors;
+            this.groups = groups;
+        }
+    }
+
+    private static class TempSensorConfig {
+        private final String id;
+        private final Integer period;
+        private final Set<String> groups;
+
+        private TempSensorConfig(String id, Integer period, Set<String> groups) {
+            this.id = id;
+            this.period = period;
+            this.groups = groups;
+        }
+    }
+
+    private static class TempAllSensorsConfig extends TempSensorConfig {
+        private TempAllSensorsConfig(Integer period) {
+            super(null, period, new HashSet<>());
+        }
+    }
 }

+ 10 - 0
src/main/java/cz/senslog/watchdog/config/ConsoleLogLevel.java

@@ -0,0 +1,10 @@
+package cz.senslog.watchdog.config;
+
+public enum ConsoleLogLevel {
+    ERROR, WARNING, INFO
+
+    ;
+    public static ConsoleLogLevel of(String value) {
+        return valueOf(value.toUpperCase());
+    }
+}

+ 28 - 0
src/main/java/cz/senslog/watchdog/config/ConsoleMessageBrokerConfig.java

@@ -0,0 +1,28 @@
+package cz.senslog.watchdog.config;
+
+public class ConsoleMessageBrokerConfig extends MessageBrokerConfig {
+
+    private final ConsoleLogLevel level;
+    private final String messagePattern;
+
+    public static ConsoleMessageBrokerConfig create(String id, PropertyConfig config) {
+        return new ConsoleMessageBrokerConfig(id,
+                config.getStringProperty("messagePattern"),
+                ConsoleLogLevel.of(config.getStringProperty("level"))
+        );
+    }
+
+    public ConsoleMessageBrokerConfig(String id, String messagePattern, ConsoleLogLevel level) {
+        super(id, MessageBrokerType.CONSOLE);
+        this.messagePattern = messagePattern;
+        this.level = level;
+    }
+
+    public String getMessagePattern() {
+        return messagePattern;
+    }
+
+    public ConsoleLogLevel getLevel() {
+        return level;
+    }
+}

+ 6 - 12
src/main/java/cz/senslog/watchdog/config/DataProviderConfig.java

@@ -2,22 +2,16 @@ package cz.senslog.watchdog.config;
 
 public class DataProviderConfig {
 
+    private final String id;
     private final DataProviderType type;
 
-    public static DataProviderConfig create(PropertyConfig config) {
-        DataProviderType type = DataProviderType.of(config.getStringProperty("type"));
-        PropertyConfig propertyConfig = config.getPropertyConfig("config");
-
-        switch (type) {
-            case DATABASE: return DatabaseConfig.create(propertyConfig);
-            case WEB_SERVICE: return WebServiceConfig.create(propertyConfig);
-        }
-
-        return new DataProviderConfig(type);
+    public DataProviderConfig(String id, DataProviderType type) {
+        this.id = id;
+        this.type = type;
     }
 
-    protected DataProviderConfig(DataProviderType type) {
-        this.type = type;
+    public String getId() {
+        return id;
     }
 
     public DataProviderType getType() {

+ 12 - 1
src/main/java/cz/senslog/watchdog/config/DataProviderType.java

@@ -1,10 +1,21 @@
 package cz.senslog.watchdog.config;
 
+import java.util.function.BiFunction;
+
 public enum DataProviderType {
-    DATABASE, WEB_SERVICE
+    DATABASE        (DatabaseDataProviderConfig::create),
+    WEB_SERVICE     (WebServiceDataProviderConfig::create)
 
     ;
+    private final BiFunction<String, PropertyConfig, DataProviderConfig> fnc;
+    DataProviderType(BiFunction<String, PropertyConfig, DataProviderConfig> creator) {
+        this.fnc = creator;
+    }
     public static DataProviderType of(String value) {
         return valueOf(value.toUpperCase());
     }
+
+    public DataProviderConfig createConfig(String id, PropertyConfig config) {
+        return fnc.apply(id, config);
+    }
 }

+ 6 - 6
src/main/java/cz/senslog/watchdog/config/DatabaseConfig.java → src/main/java/cz/senslog/watchdog/config/DatabaseDataProviderConfig.java

@@ -1,6 +1,6 @@
 package cz.senslog.watchdog.config;
 
-public class DatabaseConfig extends DataProviderConfig {
+public class DatabaseDataProviderConfig extends DataProviderConfig {
 
     private final String connectionUrl;
     private final String username;
@@ -8,8 +8,8 @@ public class DatabaseConfig extends DataProviderConfig {
     private final Integer connectionPoolSize;
     private final String groupName;
 
-    public static DatabaseConfig create(PropertyConfig config) {
-        return new DatabaseConfig(
+    public static DatabaseDataProviderConfig create(String id, PropertyConfig config) {
+        return new DatabaseDataProviderConfig(id,
                 config.getStringProperty("url"),
                 config.getStringProperty("username"),
                 config.getStringProperty("password"),
@@ -18,8 +18,8 @@ public class DatabaseConfig extends DataProviderConfig {
         );
     }
 
-    public DatabaseConfig(String connectionUrl, String username, String password, Integer poolSize, String groupName) {
-        super(DataProviderType.DATABASE);
+    public DatabaseDataProviderConfig(String id, String connectionUrl, String username, String password, Integer poolSize, String groupName) {
+        super(id, DataProviderType.DATABASE);
         this.connectionUrl = connectionUrl;
         this.username = username;
         this.password = password;
@@ -46,4 +46,4 @@ public class DatabaseConfig extends DataProviderConfig {
     public String getGroupName() {
         return groupName;
     }
-}
+}

+ 18 - 39
src/main/java/cz/senslog/watchdog/config/EmailMessageBrokerConfig.java

@@ -2,62 +2,41 @@ package cz.senslog.watchdog.config;
 
 public class EmailMessageBrokerConfig extends MessageBrokerConfig {
 
-    private final String host;
-    private final int port;
-    private final String username;
-    private final String pass;
-    private final String sender;
-    private final String recipient;
+    private final String serverId;
+    private final String senderEmail;
+    private final String recipientEmail;
     private final String subject;
 
-    public static EmailMessageBrokerConfig create(PropertyConfig config) {
-        return new EmailMessageBrokerConfig(
-                config.getStringProperty("smtpHost"),
-                config.getIntegerProperty("smtpPort"),
-                config.getStringProperty("authUsername"),
-                config.getStringProperty("authPassword"),
+    public static EmailMessageBrokerConfig create(String id, PropertyConfig config) {
+        return new EmailMessageBrokerConfig(id,
+                config.getStringProperty("server"),
                 config.getStringProperty("senderEmail"),
                 config.getStringProperty("recipientEmail"),
                 config.getStringProperty("subject")
         );
     }
 
-    public EmailMessageBrokerConfig(String host, int port, String username, String pass, String sender, String recipient, String subject) {
-        super(MessageBrokerType.EMAIL);
-        this.host = host;
-        this.port = port;
-        this.username = username;
-        this.pass = pass;
-        this.sender = sender;
-        this.recipient = recipient;
+    public EmailMessageBrokerConfig(String id, String serverId, String senderEmail, String recipientEmail, String subject) {
+        super(id, MessageBrokerType.EMAIL);
+        this.serverId = serverId;
+        this.senderEmail = senderEmail;
+        this.recipientEmail = recipientEmail;
         this.subject = subject;
     }
 
-    public String getHost() {
-        return host;
+    public String getServerId() {
+        return serverId;
     }
 
-    public int getPort() {
-        return port;
+    public String getSenderEmail() {
+        return senderEmail;
     }
 
-    public String getUsername() {
-        return username;
-    }
-
-    public String getPass() {
-        return pass;
-    }
-
-    public String getSender() {
-        return sender;
-    }
-
-    public String getRecipient() {
-        return recipient;
+    public String getRecipientEmail() {
+        return recipientEmail;
     }
 
     public String getSubject() {
         return subject;
     }
-}
+}

+ 38 - 0
src/main/java/cz/senslog/watchdog/config/EmailServerConfig.java

@@ -0,0 +1,38 @@
+package cz.senslog.watchdog.config;
+
+public class EmailServerConfig {
+
+    private final String id;
+    private final String smtpHost;
+    private final int smtpPort;
+    private final String authUsername;
+    private final String authPassword;
+
+    public EmailServerConfig(String id, String smtpHost, int smtpPort, String authUsername, String authPassword) {
+        this.id = id;
+        this.smtpHost = smtpHost;
+        this.smtpPort = smtpPort;
+        this.authUsername = authUsername;
+        this.authPassword = authPassword;
+    }
+
+    public String getSmtpHost() {
+        return smtpHost;
+    }
+
+    public int getSmtpPort() {
+        return smtpPort;
+    }
+
+    public String getAuthUsername() {
+        return authUsername;
+    }
+
+    public String getAuthPassword() {
+        return authPassword;
+    }
+
+    public String getId() {
+        return id;
+    }
+}

+ 35 - 0
src/main/java/cz/senslog/watchdog/config/ExecutableGroup.java

@@ -0,0 +1,35 @@
+package cz.senslog.watchdog.config;
+
+import cz.senslog.watchdog.core.adt.Graph;
+
+public class ExecutableGroup extends Graph<String, MonitoredObject> {
+
+    private final MonitoredObject root;
+    private final GroupConfig config;
+
+    private final boolean enableSwap;
+
+    public ExecutableGroup(GroupConfig config) {
+        this(config, new MonitoredObject(config.getId(), config.getPeriod()));
+    }
+
+    private ExecutableGroup(GroupConfig config, MonitoredObject monitoredObject) {
+        super(monitoredObject.getId(), monitoredObject);
+        this.root = monitoredObject;
+        this.config = config;
+        this.enableSwap = config.getPeriod() == null;
+        this.root.setPeriod(enableSwap ? Integer.MAX_VALUE : config.getPeriod());
+    }
+
+    public void swapPeriod(Integer newPeriod) {
+        if (enableSwap && newPeriod != null) {
+            root.setPeriod(Math.min(newPeriod, root.getPeriod()));
+        }
+    }
+
+    public GroupConfig getConfig() {
+        return new GroupConfig(config.getId(), config.getDataProviderId(),
+                config.getMessageBrokerId(), config.getResultType(), root.getPeriod()
+        );
+    }
+}

+ 53 - 0
src/main/java/cz/senslog/watchdog/config/GroupConfig.java

@@ -0,0 +1,53 @@
+package cz.senslog.watchdog.config;
+
+import java.util.Objects;
+
+public class GroupConfig {
+
+    private final String id;
+    private final String dataProviderId;
+    private final String messageBrokerId;
+    private final ResultType resultType;
+    private final Integer period;
+
+    public GroupConfig(String id, String dataProviderId, String messageBrokerId, ResultType resultType, Integer period) {
+        this.id = id;
+        this.dataProviderId = dataProviderId;
+        this.messageBrokerId = messageBrokerId;
+        this.resultType = resultType;
+        this.period = period;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getDataProviderId() {
+        return dataProviderId;
+    }
+
+    public String getMessageBrokerId() {
+        return messageBrokerId;
+    }
+
+    public ResultType getResultType() {
+        return resultType;
+    }
+
+    public Integer getPeriod() {
+        return period;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GroupConfig that = (GroupConfig) o;
+        return id.equals(that.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+}

+ 6 - 22
src/main/java/cz/senslog/watchdog/config/MessageBrokerConfig.java

@@ -2,35 +2,19 @@ package cz.senslog.watchdog.config;
 
 public class MessageBrokerConfig {
 
+    private final String id;
     private final MessageBrokerType type;
 
-    private int vanishPeriodDays;
-
-    public static MessageBrokerConfig create(PropertyConfig config) {
-        MessageBrokerType type = MessageBrokerType.of(config.getStringProperty("type"));
-        int vanishPeriodDays = config.getIntegerProperty("vanishPeriod", 0);
-        PropertyConfig propertyConfig = config.getPropertyConfig("config");
-        MessageBrokerConfig messageBrokerConfig;
-        switch (type) {
-            case EMAIL: return EmailMessageBrokerConfig.create(propertyConfig);
-            default: messageBrokerConfig = new MessageBrokerConfig(type);
-        }
-
-        messageBrokerConfig.vanishPeriodDays = vanishPeriodDays;
-
-        return messageBrokerConfig;
-    }
-
-    protected MessageBrokerConfig(MessageBrokerType type) {
+    public MessageBrokerConfig(String id, MessageBrokerType type) {
+        this.id = id;
         this.type = type;
     }
 
+    public String getId() {
+        return id;
+    }
 
     public MessageBrokerType getType() {
         return type;
     }
-
-    public int getVanishPeriodDays() {
-        return vanishPeriodDays;
-    }
 }

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

@@ -1,11 +1,23 @@
 package cz.senslog.watchdog.config;
 
+import java.util.function.BiFunction;
+
 public enum MessageBrokerType {
 
-    EMAIL, WHATSAPP, TELEGRAM, CONSOLE
+    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);
+    }
 }

+ 33 - 0
src/main/java/cz/senslog/watchdog/config/MonitoredObject.java

@@ -0,0 +1,33 @@
+package cz.senslog.watchdog.config;
+
+public class MonitoredObject {
+
+    private final String id;
+    private Integer period;
+
+    public MonitoredObject(String id) {
+        this(id, null);
+    }
+
+    public MonitoredObject(String id, Integer period) {
+        this.id = id;
+        this.period = period;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Integer getPeriod() {
+        return period;
+    }
+
+    public void setPeriod(Integer period) {
+        this.period = period;
+    }
+
+    @Override
+    public String toString() {
+        return period != null ? Integer.toString(period) : "null";
+    }
+}

+ 0 - 177
src/main/java/cz/senslog/watchdog/config/MonitoredObjectsConfig.java

@@ -1,177 +0,0 @@
-package cz.senslog.watchdog.config;
-
-import cz.senslog.watchdog.domain.Source;
-import cz.senslog.watchdog.provider.Record;
-import cz.senslog.watchdog.util.Tuple;
-
-import java.time.LocalTime;
-import java.util.*;
-
-public class MonitoredObjectsConfig {
-
-    private static class Unit {
-        final String id;
-        final long interval;
-        final boolean includeAll;
-        final Map<String, Sensor> sensors;
-
-        static Unit empty() {
-            return new Unit("__empty", Long.MAX_VALUE);
-        }
-
-        Unit(String id, long interval) {
-            if (interval == -1) {
-                throw new IllegalArgumentException(String.format(
-                        "The unit '%s' does not contain 'interval' argument.", id
-                ));
-            }
-            this.id = id;
-            this.interval = interval;
-            this.includeAll = true;
-            this.sensors = Collections.emptyMap();
-        }
-
-        Unit(String id, Map<String, Sensor> sensors) {
-            this.id = id;
-            this.interval = -1;
-            this.includeAll = false;
-            this.sensors = sensors;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Unit unit = (Unit) o;
-            return id.equals(unit.id);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(id);
-        }
-    }
-
-    private static class Sensor {
-        final String id;
-        final long interval;
-
-        static Sensor empty() {
-            return new Sensor("__empty", Long.MAX_VALUE);
-        }
-
-        Sensor(String id, long interval) {
-            if (interval == -1) {
-                throw new IllegalArgumentException(String.format(
-                        "The sensor '%s' does not contain a correct 'interval' argument.", id
-                ));
-            }
-            this.id = id;
-            this.interval = interval;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Sensor sensor = (Sensor) o;
-            return id.equals(sensor.id);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(id);
-        }
-    }
-
-    private final Map<String, Unit> units;
-    private final long minInterval, maxInterval;
-
-    public static MonitoredObjectsConfig create(PropertyConfig config) {
-        Set<String> unitIds = config.getAttributes();
-        Map<String, Unit> units = new HashMap<>(unitIds.size());
-        long minInterval = Long.MAX_VALUE, maxInterval = Long.MIN_VALUE;
-//        LocalTime start = LocalTime.now().plusMinutes(1);
-
-        for (String unitId : unitIds) {
-            PropertyConfig unitConfig = config.getPropertyConfig(unitId);
-            long unitInterval = -1;
-            if (unitConfig.containsProperty("interval")) {
-                unitInterval = unitConfig.getIntegerProperty("interval");
-            }
-
-//            if (unitConfig.containsProperty("startAt")) {
-//                String stringStartAt = unitConfig.getStringProperty("startAt");
-//                String[] split = stringStartAt.split(":"); // 0 - hours, 1 - minutes
-
-//                start = LocalTime.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
-//            }
-            if (unitConfig.containsProperty("sensors")) {
-                Map<String, Sensor> sensors = new HashMap<>();
-                Object unitSensorsObj = unitConfig.getProperty("sensors");
-                if (unitSensorsObj instanceof List) {
-                    List<?> unitSensors = (List<?>) unitSensorsObj;
-                    for (Object sensorIdObj : unitSensors) {
-                        if (sensorIdObj instanceof Integer) {
-                            long sensorId = (Integer) sensorIdObj;
-                            String sensorIdStr = String.valueOf(sensorId);
-                            sensors.put(sensorIdStr, new Sensor(sensorIdStr, unitInterval));
-                        }
-                    }
-                } else if (unitSensorsObj instanceof Map) {
-                    PropertyConfig sensorsConfig = unitConfig.getPropertyConfig("sensors");
-                    Set<String> unitSensorIds = sensorsConfig.getAttributes();
-                    for (String sensorId : unitSensorIds) {
-                        Object sensorIntervalObj = sensorsConfig.getProperty(sensorId);
-                        long sensorInterval = sensorIntervalObj != null ? (Integer) sensorIntervalObj : unitInterval;
-                        sensors.put(sensorId, new Sensor(sensorId, sensorInterval));
-
-                        minInterval = Math.min(sensorInterval, minInterval);
-                        maxInterval = Math.max(sensorInterval, maxInterval);
-                    }
-                }
-                units.put(unitId, new Unit(unitId, sensors));
-            } else {
-                units.put(unitId, new Unit(unitId, unitInterval));
-                minInterval = Math.min(unitInterval, minInterval);
-                maxInterval = Math.max(unitInterval, maxInterval);
-            }
-        }
-
-        return new MonitoredObjectsConfig(units, minInterval, maxInterval);
-    }
-
-    public MonitoredObjectsConfig(Map<String, Unit> units, long minInterval, long maxInterval) {
-        this.units = units;
-        this.minInterval = minInterval;
-        this.maxInterval = maxInterval;
-    }
-
-    public Optional<Long> getInterval(Record record) {
-        Source source = record.getSource();
-        String unitId = String.valueOf(source.getUnit().getId());
-        String sensorId = String.valueOf(source.getSensor().getId());
-        Unit unit = units.get(unitId);
-        if (unit == null) {
-            return Optional.empty();
-        }
-        if (unit.includeAll) {
-            return Optional.of(unit.interval);
-        }
-
-        Sensor sensor = unit.sensors.get(sensorId);
-        if (sensor == null) {
-            return Optional.empty();
-        }
-
-        return Optional.of(sensor.interval);
-    }
-
-    public int getMinInterval() {
-        return (int) minInterval;
-    }
-
-    public long getMaxInterval() {
-        return maxInterval;
-    }
-}

+ 25 - 3
src/main/java/cz/senslog/watchdog/config/PropertyConfig.java

@@ -12,7 +12,7 @@ import java.util.*;
 
 import static java.lang.String.format;
 import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
-import static java.util.Optional.ofNullable;
+import static java.util.Optional.*;
 
 /**
  *
@@ -77,8 +77,30 @@ public class PropertyConfig {
      * @param name - name of property.
      * @return optional object
      */
-    public Optional<Object> getOptionalProperty(String name) {
-        return ofNullable(properties.get(name));
+    @SuppressWarnings("unchecked")
+    public <T> Optional<T> getOptionalProperty(String name, Class<T> aClass) {
+        Object value = properties.get(name);
+        if (value == null || value.getClass() != aClass) {
+            return empty();
+        }
+
+        return of((T)value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> List<T> getArrayPropertyOf(String name, Class<T> aClass) {
+        Object value = getProperty(name);
+        List<?> list = value instanceof List ? (List<?>)value : null;
+        if (list == null) {
+            throw new IllegalArgumentException("Cast error " + value.getClass() + " to " + List.class);
+        }
+        List<T> result = new ArrayList<>(list.size());
+        for (Object v : list) {
+            if (v.getClass() == aClass) {
+                result.add((T)v);
+            }
+        }
+        return result;
     }
 
     /**

+ 10 - 0
src/main/java/cz/senslog/watchdog/config/ResultType.java

@@ -0,0 +1,10 @@
+package cz.senslog.watchdog.config;
+
+public enum ResultType {
+    ALL, FAIL
+
+    ;
+    public static ResultType of(String value) {
+        return valueOf(value.toUpperCase());
+    }
+}

+ 17 - 0
src/main/java/cz/senslog/watchdog/config/SensLogGroupConfig.java

@@ -0,0 +1,17 @@
+package cz.senslog.watchdog.config;
+
+import java.util.Set;
+
+public class SensLogGroupConfig extends GroupConfig {
+
+    private final Set<String> groups;
+
+    public SensLogGroupConfig(String id, String dataProviderId, String messageBrokerId, ResultType resultType, Integer period, Set<String> groups) {
+        super(id, dataProviderId, messageBrokerId, resultType, period);
+        this.groups = groups;
+    }
+
+    public Set<String> getGroups() {
+        return groups;
+    }
+}

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

@@ -0,0 +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;
+    }
+}

+ 10 - 0
src/main/java/cz/senslog/watchdog/config/SuperGroup.java

@@ -0,0 +1,10 @@
+package cz.senslog.watchdog.config;
+
+import cz.senslog.watchdog.core.adt.Graph;
+
+public class SuperGroup extends Graph<String, ExecutableGroup> {
+
+    public SuperGroup(GroupConfig config) {
+        super(config.getId(), new ExecutableGroup(config));
+    }
+}

+ 17 - 0
src/main/java/cz/senslog/watchdog/config/SuperGroupConfig.java

@@ -0,0 +1,17 @@
+package cz.senslog.watchdog.config;
+
+import java.util.Set;
+
+public class SuperGroupConfig extends GroupConfig {
+
+    private final Set<String> groups;
+
+    public SuperGroupConfig(String id, String messageBrokerId, ResultType resultType, Integer period, Set<String> groups) {
+        super(id, null, messageBrokerId, resultType, period);
+        this.groups = groups;
+    }
+
+    public Set<String> getGroups() {
+        return groups;
+    }
+}

+ 0 - 35
src/main/java/cz/senslog/watchdog/config/WebServiceConfig.java

@@ -1,35 +0,0 @@
-package cz.senslog.watchdog.config;
-
-public class WebServiceConfig extends DataProviderConfig {
-
-    private final String url;
-    private final String group;
-    private final String user;
-
-    public static DataProviderConfig create(PropertyConfig config) {
-        return new WebServiceConfig(
-                config.getStringProperty("url"),
-                config.getStringProperty("group"),
-                config.getStringProperty("user")
-        );
-    }
-
-    public WebServiceConfig(String url, String group, String user) {
-        super(DataProviderType.WEB_SERVICE);
-        this.url = url;
-        this.group = group;
-        this.user = user;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public String getGroup() {
-        return group;
-    }
-
-    public String getUser() {
-        return user;
-    }
-}

+ 36 - 0
src/main/java/cz/senslog/watchdog/config/WebServiceDataProviderConfig.java

@@ -0,0 +1,36 @@
+package cz.senslog.watchdog.config;
+
+public class WebServiceDataProviderConfig extends DataProviderConfig {
+
+    private final String baseUrl;
+
+    private final String userName;
+    private final String groupName;
+
+    public static WebServiceDataProviderConfig create(String id, PropertyConfig config) {
+        return new WebServiceDataProviderConfig(id,
+                config.getStringProperty("baseUrl"),
+                config.getStringProperty("user"),
+                config.getStringProperty("group")
+        );
+    }
+
+    public WebServiceDataProviderConfig(String id, String baseUrl, String userName, String groupName) {
+        super(id, DataProviderType.WEB_SERVICE);
+        this.baseUrl = baseUrl;
+        this.userName = userName;
+        this.groupName = groupName;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+}

+ 17 - 0
src/main/java/cz/senslog/watchdog/core/DataProviderInstanceCreator.java

@@ -0,0 +1,17 @@
+package cz.senslog.watchdog.core;
+
+import cz.senslog.watchdog.config.DatabaseDataProviderConfig;
+import cz.senslog.watchdog.config.WebServiceDataProviderConfig;
+import cz.senslog.watchdog.provider.database.DatabaseDataProvider;
+import cz.senslog.watchdog.provider.ws.WebServiceDataProvider;
+
+public class DataProviderInstanceCreator implements InstanceCreator {
+
+    DatabaseDataProvider create(DatabaseDataProviderConfig config) {
+        return new DatabaseDataProvider(null);
+    }
+
+    WebServiceDataProvider create(WebServiceDataProviderConfig config) {
+        return new WebServiceDataProvider(null);
+    }
+}

+ 55 - 0
src/main/java/cz/senslog/watchdog/core/EmailServerConnection.java

@@ -0,0 +1,55 @@
+package cz.senslog.watchdog.core;
+
+import cz.senslog.watchdog.config.EmailMessageBrokerConfig;
+import cz.senslog.watchdog.config.EmailServerConfig;
+
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import java.util.Properties;
+
+import static javax.mail.Message.RecipientType.TO;
+
+public class EmailServerConnection {
+
+    private final EmailServerConfig config;
+
+    public EmailServerConnection(EmailServerConfig config) {
+        this.config = config;
+    }
+
+    private Session openSession(EmailServerConfig config) {
+        Properties props = new Properties();
+        props.put("mail.smtp.host", config.getSmtpHost());
+        props.put("mail.smtp.port", config.getSmtpPort());
+        props.put("mail.smtp.auth", "true");
+        props.put("mail.smtp.starttls.enable", "true");
+        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(config.getAuthUsername(), config.getAuthPassword());
+            }
+        };
+        return Session.getInstance(props, auth);
+    }
+
+    public void send(String content, EmailMessageBrokerConfig messageConfig) throws MessagingException {
+        Session session = openSession(config);
+        Message message = new MimeMessage(session);
+        message.setFrom(new InternetAddress(messageConfig.getSenderEmail()));
+        message.setRecipients(TO, InternetAddress.parse(messageConfig.getRecipientEmail()));
+        message.setSubject(messageConfig.getSubject());
+
+        MimeBodyPart mimeBodyPart = new MimeBodyPart();
+        mimeBodyPart.setContent(content, "text/html");
+
+        Multipart multipart = new MimeMultipart();
+        multipart.addBodyPart(mimeBodyPart);
+        message.setContent(multipart);
+
+        Transport.send(message);
+    }
+}

+ 4 - 0
src/main/java/cz/senslog/watchdog/core/InstanceCreator.java

@@ -0,0 +1,4 @@
+package cz.senslog.watchdog.core;
+
+public interface InstanceCreator {
+}

+ 6 - 0
src/main/java/cz/senslog/watchdog/core/MessageBrokerInstanceCreator.java

@@ -0,0 +1,6 @@
+package cz.senslog.watchdog.core;
+
+public class MessageBrokerInstanceCreator implements InstanceCreator {
+
+
+}

+ 82 - 0
src/main/java/cz/senslog/watchdog/core/adt/Graph.java

@@ -0,0 +1,82 @@
+package cz.senslog.watchdog.core.adt;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+
+public class Graph<K extends Comparable<K>, V> {
+
+    private final Node<K, V> root;
+
+    public Graph(Node<K, V> root) {
+        this.root = root;
+    }
+
+    public Graph(K key, V value) {
+        this(new Node<>(key, value));
+    }
+
+    public void addNode(K key, V value) {
+        addNode(new Node<>(key, value));
+    }
+
+    protected void addNode(Node<K, V> node) {
+        root.addNode(node);
+    }
+
+    public Node<K, V> computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+        Objects.requireNonNull(mappingFunction);
+        Node<K, V> v;
+        V newValue;
+        if ((v = this.getNode(key)) == null && (newValue = mappingFunction.apply(key)) != null) {
+            Node<K, V> newNode = new Node<>(key, newValue);
+            this.addNode(newNode);
+            return newNode;
+        } else {
+            return v;
+        }
+    }
+
+    public Node<K, V> getNode(K key) {
+        return root.getNode(key);
+    }
+
+    public Node<K, V> getRoot() {
+        return root;
+    }
+
+    public boolean containsNode(K key) {
+        return getNode(key) != null;
+    }
+
+    public Set<K> getKeys() {
+        return root.getKeys();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("--- ").append(root.getKey()).append(": ").append(root.getValue()).append("\n");
+        for (K key : getKeys()) {
+            printNode(builder, root.getNode(key), 1);
+        }
+        return builder.toString();
+    }
+
+    private void printNode(StringBuilder builder, Node<K, V> node, int level) {
+        if (node == null) return;
+
+        for (int i = 0; i < level; i++) {
+            builder.append("\t|");
+        }
+        builder.append("\n");
+        for (int i = 0; i < level; i++) {
+            builder.append("\t|");
+        }
+        builder.append("--- ").append(node.getKey()).append(": ").append(node.getValue()).append("\n");
+
+        for (K key : node.getKeys()) {
+            printNode(builder, node.getNode(key), level+1);
+        }
+    }
+}

+ 56 - 0
src/main/java/cz/senslog/watchdog/core/adt/Node.java

@@ -0,0 +1,56 @@
+package cz.senslog.watchdog.core.adt;
+
+import java.util.*;
+
+public class Node<K extends Comparable<K>, V> {
+
+    private final K key;
+    private final V value;
+
+    private final Map<K, Node<K, V>> children;
+
+    public Node(K key, V value) {
+        this.children = new HashMap<>();
+        this.key = key;
+        this.value = value;
+    }
+
+    protected Node<K, V> addNode(Node<K, V> node) {
+        children.put(node.key, node);
+        return this;
+    }
+
+    public Node<K, V> addNode(K key, V value) {
+        addNode(new Node<>(key, value));
+        return this;
+    }
+
+    public Node<K, V> getNode(K key) {
+        return children.get(key);
+    }
+
+    public boolean containsKey(K key) {
+        return children.containsKey(key);
+    }
+
+    public V getValue() {
+        return value;
+    }
+
+    public K getKey() {
+        return key;
+    }
+
+    public Set<K> getKeys() {
+        return children.keySet();
+    }
+
+    public Collection<V> getValues() {
+        Collection<Node<K, V>> childrenValues = children.values();
+        List<V> values = new ArrayList<>(childrenValues.size());
+        for (Node<K, V> node : childrenValues) {
+            values.add(node.value);
+        }
+        return values;
+    }
+}

+ 7 - 2
src/main/java/cz/senslog/watchdog/domain/Report.java

@@ -6,12 +6,13 @@ import java.util.List;
 public class Report {
 
     private final LocalDateTime created;
-
     private final List<SimpleReport> reports;
+    private final List<String> messages;
 
-    public Report(LocalDateTime created, List<SimpleReport> reports) {
+    public Report(LocalDateTime created, List<SimpleReport> reports, List<String> messages) {
         this.created = created;
         this.reports = reports;
+        this.messages = messages;
     }
 
     public LocalDateTime getCreated() {
@@ -21,4 +22,8 @@ public class Report {
     public List<SimpleReport> getReports() {
         return reports;
     }
+
+    public List<String> getMessages() {
+        return messages;
+    }
 }

+ 1 - 1
src/main/java/cz/senslog/watchdog/domain/StatusReport.java

@@ -1,5 +1,5 @@
 package cz.senslog.watchdog.domain;
 
 public enum StatusReport {
-    OK, FAIL
+    OK, FAIL, NO_DATA
 }

+ 0 - 13
src/main/java/cz/senslog/watchdog/messagebroker/MessageBroker.java

@@ -1,23 +1,10 @@
 package cz.senslog.watchdog.messagebroker;
 
-import cz.senslog.watchdog.config.EmailMessageBrokerConfig;
-import cz.senslog.watchdog.config.MessageBrokerConfig;
 import cz.senslog.watchdog.config.MessageBrokerType;
 import cz.senslog.watchdog.domain.Report;
-import cz.senslog.watchdog.messagebroker.email.EmailMessageBroker;
 
 public interface MessageBroker {
 
-    static MessageBroker create(MessageBrokerConfig config) {
-        switch (config.getType()) {
-            case EMAIL: return new EmailMessageBroker((EmailMessageBrokerConfig) config);
-            case CONSOLE: return new ConsoleMessageBroker();
-            default: throw new RuntimeException(String.format(
-                    "The message broker '%s' is not implemented yet.", config.getType())
-            );
-        }
-    }
-
     void send(Report report, MessageBrokerHandler status);
 
     MessageBrokerType getType();

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

@@ -0,0 +1,63 @@
+package cz.senslog.watchdog.messagebroker;
+
+import cz.senslog.watchdog.config.*;
+import cz.senslog.watchdog.core.EmailServerConnection;
+import cz.senslog.watchdog.messagebroker.broker.ConsoleMessageBroker;
+import cz.senslog.watchdog.messagebroker.broker.EmailMessageBroker;
+import cz.senslog.watchdog.messagebroker.broker.SignalMessageBroker;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MessageBrokerManager {
+
+    private final Map<String, MessageBroker> brokerInstances;
+
+    public static MessageBrokerManager createInstance(
+            Collection<EmailServerConfig> emailServerConfigs,
+            Collection<MessageBrokerConfig> brokerConfigs
+    ) {
+        Map<String, EmailServerConnection> emailServers = new HashMap<>(emailServerConfigs.size());
+        for (EmailServerConfig config : emailServerConfigs) {
+            emailServers.put(config.getId(), new EmailServerConnection(config));
+        }
+
+        Map<String, MessageBroker> instances = new HashMap<>();
+        for (MessageBrokerConfig config : brokerConfigs) {
+            String id = config.getId();
+            switch (config.getType()) {
+                case EMAIL: {
+                    EmailMessageBrokerConfig emailConfig = (EmailMessageBrokerConfig) config;
+                    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));
+                } break;
+                case CONSOLE: {
+                    ConsoleMessageBrokerConfig consoleConfig = (ConsoleMessageBrokerConfig) config;
+                    instances.put(id, new ConsoleMessageBroker(consoleConfig));
+                } break;
+                default: throw new IllegalStateException(
+                        "Message broker for the type of '"+config.getType()+"' is not implemented yet"
+                );
+            }
+        }
+
+        return new MessageBrokerManager(instances);
+    }
+
+    public MessageBrokerManager(Map<String, MessageBroker> brokerInstances) {
+        this.brokerInstances = brokerInstances;
+    }
+
+    public MessageBroker getInstance(String messageBrokerId) {
+        MessageBroker instance = brokerInstances.get(messageBrokerId);
+        if (instance != null) {
+            return instance;
+        }
+        throw new IllegalStateException("Message broker '"+messageBrokerId+"' does not exist.");
+    }
+}

+ 11 - 1
src/main/java/cz/senslog/watchdog/messagebroker/ConsoleMessageBroker.java → src/main/java/cz/senslog/watchdog/messagebroker/broker/ConsoleMessageBroker.java

@@ -1,8 +1,12 @@
-package cz.senslog.watchdog.messagebroker;
+package cz.senslog.watchdog.messagebroker.broker;
 
+import cz.senslog.watchdog.config.ConsoleMessageBrokerConfig;
 import cz.senslog.watchdog.config.MessageBrokerType;
 import cz.senslog.watchdog.domain.Report;
 import cz.senslog.watchdog.domain.SimpleReport;
+import cz.senslog.watchdog.messagebroker.MessageBroker;
+import cz.senslog.watchdog.messagebroker.MessageBrokerHandler;
+import cz.senslog.watchdog.messagebroker.MessageStatus;
 import cz.senslog.watchdog.messagebroker.writer.DelimiterTableWriter;
 import cz.senslog.watchdog.messagebroker.writer.TableWriter;
 import org.apache.logging.log4j.LogManager;
@@ -12,6 +16,12 @@ public class ConsoleMessageBroker implements MessageBroker {
 
     private static final Logger logger = LogManager.getLogger(ConsoleMessageBroker.class);
 
+    private final ConsoleMessageBrokerConfig config;
+
+    public ConsoleMessageBroker(ConsoleMessageBrokerConfig config) {
+        this.config = config;
+    }
+
     @Override
     public void send(Report report, MessageBrokerHandler status) {
         TableWriter tableWriter = DelimiterTableWriter.create(";")

+ 109 - 0
src/main/java/cz/senslog/watchdog/messagebroker/broker/EmailMessageBroker.java

@@ -0,0 +1,109 @@
+package cz.senslog.watchdog.messagebroker.broker;
+
+import cz.senslog.watchdog.config.EmailMessageBrokerConfig;
+import cz.senslog.watchdog.config.MessageBrokerType;
+import cz.senslog.watchdog.core.EmailServerConnection;
+import cz.senslog.watchdog.domain.*;
+import cz.senslog.watchdog.messagebroker.MessageBroker;
+import cz.senslog.watchdog.messagebroker.MessageBrokerHandler;
+import cz.senslog.watchdog.messagebroker.MessageStatus;
+import cz.senslog.watchdog.messagebroker.writer.HtmlTableWriter;
+import cz.senslog.watchdog.messagebroker.writer.TableWriter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.mail.*;
+import java.util.*;
+
+import static java.time.format.DateTimeFormatter.ofPattern;
+
+public class EmailMessageBroker implements MessageBroker {
+
+    private static final String BREAK_LINE = "<br />";
+
+    private static final Logger logger = LogManager.getLogger(EmailMessageBroker.class);
+
+    private final EmailServerConnection serverConnection;
+    private final EmailMessageBrokerConfig messageConfig;
+
+    public EmailMessageBroker(EmailServerConnection serverConnection, EmailMessageBrokerConfig messageConfig) {
+        this.serverConnection = serverConnection;
+        this.messageConfig = messageConfig;
+    }
+
+    private 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 (isMessages || !isRecords) {
+            TableWriter tableMsgWriter = HtmlTableWriter.createWithHeader("width: 100%;", "background-color: #dddddd")
+                    .cell("Messages").end();
+
+            for (String message : report.getMessages()) {
+                tableMsgWriter.row(rowStyle).cell(message, rowStyle).end();
+            }
+
+            if (!isRecords) {
+                tableMsgWriter.row(rowStyle).cell("All received observations are valid.");
+            }
+
+            content.append(tableMsgWriter.table()).append(BREAK_LINE);
+        }
+
+        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"));
+
+            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) {
+                    ObservationInfo observation = (ObservationInfo) simpleReport.getRecord();
+                    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: " + (isOk ? "#CCFFCC" : "#FFCCCC"))
+                            .cell(unitCell, rowStyle)
+                            .cell(sensorCell, rowStyle)
+                            .cell(observation.getTimestamp().toString(), rowStyle)
+                            .cell(reportedTime, rowStyle)
+                            .cell(simpleReport.getStatus().name(), rowStyle)
+                            .end();
+                }
+            }
+            content.append(tableReportWriter.table()).append(BREAK_LINE);
+        }
+
+        return content.toString();
+    }
+
+    @Override
+    public void send(Report report, MessageBrokerHandler status) {
+        if (report == null) {
+            logger.info("Nothing to send. The receive report is null.");
+            status.handle(new MessageStatus(null, "No report to send.")); return;
+        }
+
+        try {
+            logger.info("Sending a message via email.");
+            serverConnection.send(createMessage(report), messageConfig);
+            logger.info("The message was send successfully.");
+            status.handle(new MessageStatus(report));
+        } catch (MessagingException e) {
+            logger.catching(e);
+            status.handle(new MessageStatus(report, e.getMessage()));
+        }
+    }
+
+    @Override
+    public MessageBrokerType getType() {
+        return MessageBrokerType.EMAIL;
+    }
+}

+ 26 - 0
src/main/java/cz/senslog/watchdog/messagebroker/broker/SignalMessageBroker.java

@@ -0,0 +1,26 @@
+package cz.senslog.watchdog.messagebroker.broker;
+
+import cz.senslog.watchdog.config.MessageBrokerType;
+import cz.senslog.watchdog.config.SignalMessageBrokerConfig;
+import cz.senslog.watchdog.domain.Report;
+import cz.senslog.watchdog.messagebroker.MessageBroker;
+import cz.senslog.watchdog.messagebroker.MessageBrokerHandler;
+
+public class SignalMessageBroker implements MessageBroker {
+
+    private final SignalMessageBrokerConfig config;
+
+    public SignalMessageBroker(SignalMessageBrokerConfig config) {
+        this.config = config;
+    }
+
+    @Override
+    public void send(Report report, MessageBrokerHandler status) {
+
+    }
+
+    @Override
+    public MessageBrokerType getType() {
+        return MessageBrokerType.SIGNAL;
+    }
+}

+ 0 - 140
src/main/java/cz/senslog/watchdog/messagebroker/email/EmailMessageBroker.java

@@ -1,140 +0,0 @@
-package cz.senslog.watchdog.messagebroker.email;
-
-import cz.senslog.watchdog.config.EmailMessageBrokerConfig;
-import cz.senslog.watchdog.config.MessageBrokerType;
-import cz.senslog.watchdog.domain.*;
-import cz.senslog.watchdog.messagebroker.MessageBroker;
-import cz.senslog.watchdog.messagebroker.MessageBrokerHandler;
-import cz.senslog.watchdog.messagebroker.MessageStatus;
-import cz.senslog.watchdog.messagebroker.writer.HtmlTableWriter;
-import cz.senslog.watchdog.messagebroker.writer.TableWriter;
-import cz.senslog.watchdog.provider.Record;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import javax.mail.*;
-import javax.mail.internet.*;
-import java.time.Instant;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
-import static cz.senslog.watchdog.util.TimeConverter.dayToSec;
-import static java.time.format.DateTimeFormatter.ofPattern;
-import static javax.mail.Message.RecipientType.TO;
-
-public class EmailMessageBroker implements MessageBroker {
-
-    private static final String BREAK_LINE = "<br />";
-
-    private static final Logger logger = LogManager.getLogger(EmailMessageBroker.class);
-
-    private final EmailMessageBrokerConfig config;
-    private final Session session;
-
-
-    public EmailMessageBroker(EmailMessageBrokerConfig config) {
-        this.config = config;
-        this.session = openSession(config);
-    }
-
-    private Session openSession(EmailMessageBrokerConfig config) {
-        Properties props = new Properties();
-        props.put("mail.smtp.host", config.getHost());
-        props.put("mail.smtp.port", config.getPort());
-        props.put("mail.smtp.auth", "true");
-        props.put("mail.smtp.starttls.enable", "true");
-        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
-
-        Authenticator auth = new Authenticator() {
-            protected PasswordAuthentication getPasswordAuthentication() {
-                return new PasswordAuthentication(config.getUsername(), config.getPass());
-            }
-        };
-        return Session.getInstance(props, auth);
-    }
-
-    private Message createMessage(Report report) throws MessagingException {
-        Message message = new MimeMessage(session);
-        message.setFrom(new InternetAddress(config.getSender()));
-        message.setRecipients(TO, InternetAddress.parse(config.getRecipient()));
-        message.setSubject(config.getSubject());
-
-        MimeBodyPart mimeBodyPart = new MimeBodyPart();
-        StringBuilder content = new StringBuilder();
-
-        TableWriter tableWriter = 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"));
-        final String rowStyle = "border: 1px solid #dddddd; text-align: left; padding: 8px;";
-
-        report.getReports().sort(Comparator.comparing(SimpleReport::getStatus).reversed());
-        for (SimpleReport simpleReport : report.getReports()) {
-            if (simpleReport.getRecord() instanceof ObservationInfo) {
-                ObservationInfo observation = (ObservationInfo)simpleReport.getRecord();
-                Source source = observation.getSource();
-
-                boolean isOk = simpleReport.getStatus().equals(StatusReport.OK);
-                String unitCell = String.format("%s (%s)", source.getUnit().getName(), source.getUnit().getId());
-                String sensorCell = String.format("%s (%s)", source.getSensor().getName(), source.getSensor().getId());
-
-                tableWriter.row(rowStyle + "background-color: " + (isOk ? "#CCFFCC" : "#FFCCCC"))
-                            .cell(unitCell, rowStyle)
-                            .cell(sensorCell, rowStyle)
-                            .cell(observation.getTimestamp().toString(), rowStyle)
-                            .cell(reportedTime, rowStyle)
-                            .cell(simpleReport.getStatus().name(), rowStyle)
-                        .end();
-            }
-        }
-
-        content.append(tableWriter.table()).append(BREAK_LINE);
-        mimeBodyPart.setContent(content.toString(), "text/html");
-
-        Multipart multipart = new MimeMultipart();
-        multipart.addBodyPart(mimeBodyPart);
-        message.setContent(multipart);
-
-        return message;
-    }
-
-    @Override
-    public void send(Report report, MessageBrokerHandler status) {
-        if (report == null) {
-            logger.info("Nothing to send. The receive report is null.");
-            status.handle(new MessageStatus(null, "No report to send.")); return;
-        }
-
-        try {
-            Message message = createMessage(report);
-            logger.info("Sending a message via email.");
-            Transport.send(message);
-            logger.info("The message was send successfully.");
-            status.handle(new MessageStatus(report));
-        } catch (MessagingException e) {
-            logger.catching(e);
-            status.handle(new MessageStatus(report, e.getMessage()));
-        }
-
-      //  vanish();
-    }
-
-    @Override
-    public MessageBrokerType getType() {
-        return MessageBrokerType.EMAIL;
-    }
-
-    public Record[] vanish() {
-        Instant deadline = Instant.now().minusSeconds(dayToSec(config.getVanishPeriodDays()));
-        List<Record> deletedRecords = new ArrayList<>();
-        Iterator<Record> iterator = Collections.emptyListIterator();
-        while(iterator.hasNext()) {
-            Record record = iterator.next();
-            if (record.getTimestamp().isBefore(deadline)) {
-                deletedRecords.add(record);
-                iterator.remove();
-            }
-        }
-        return deletedRecords.toArray(new Record[0]);
-    }
-}

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

@@ -1,22 +1,10 @@
 package cz.senslog.watchdog.provider;
 
-import cz.senslog.watchdog.config.DataProviderConfig;
-import cz.senslog.watchdog.config.DatabaseConfig;
-import cz.senslog.watchdog.config.WebServiceConfig;
-import cz.senslog.watchdog.provider.database.DatabaseDataProvider;
-import cz.senslog.watchdog.provider.ws.WebServiceDataProvider;
-
 import java.util.List;
 
 public interface DataProvider {
 
-    static DataProvider create(DataProviderConfig config) {
-        switch (config.getType()) {
-            case DATABASE: return new DatabaseDataProvider((DatabaseConfig) config);
-            case WEB_SERVICE: return new WebServiceDataProvider((WebServiceConfig) config);
-            default: return null;
-        }
-    }
-
     List<Record> getLastRecords();
+
+    ProvidedData getLastData();
 }

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

@@ -0,0 +1,52 @@
+package cz.senslog.watchdog.provider;
+
+import cz.senslog.watchdog.config.DataProviderConfig;
+import cz.senslog.watchdog.config.DatabaseDataProviderConfig;
+import cz.senslog.watchdog.config.WebServiceDataProviderConfig;
+import cz.senslog.watchdog.provider.database.DatabaseDataProvider;
+import cz.senslog.watchdog.provider.ws.WebServiceDataProvider;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DataProviderManager {
+
+    private final Map<String, DataProvider> providerInstances;
+
+    public static DataProviderManager createInstance(
+            Collection<DataProviderConfig> providerConfigs
+    ) {
+        Map<String, DataProvider> instances = new HashMap<>();
+        for (DataProviderConfig config : providerConfigs) {
+            String id = config.getId();
+            switch (config.getType()) {
+                case WEB_SERVICE: {
+                    WebServiceDataProviderConfig wsConfig = (WebServiceDataProviderConfig) config;
+                    instances.put(id, new WebServiceDataProvider(wsConfig));
+                } break;
+                case DATABASE: {
+                    DatabaseDataProviderConfig dbConfig = (DatabaseDataProviderConfig) config;
+                    instances.put(id, new DatabaseDataProvider(dbConfig));
+                } break;
+                default: throw new IllegalStateException(
+                        "Data provider for the type of '"+config.getType()+"' is not implemented yet"
+                );
+            }
+        }
+
+        return new DataProviderManager(instances);
+    }
+
+    public DataProviderManager(Map<String, DataProvider> providerInstances) {
+        this.providerInstances = providerInstances;
+    }
+
+    public DataProvider getInstance(String dataProviderId) {
+        DataProvider instance = providerInstances.get(dataProviderId);
+        if (instance != null) {
+            return instance;
+        }
+        throw new IllegalStateException("Data provider '"+dataProviderId+"' does not exist.");
+    }
+}

+ 25 - 0
src/main/java/cz/senslog/watchdog/provider/ProvidedData.java

@@ -0,0 +1,25 @@
+package cz.senslog.watchdog.provider;
+
+import cz.senslog.watchdog.core.adt.Graph;
+
+import java.util.List;
+
+public class ProvidedData extends Graph<String, ProvidedObject> {
+
+    private final ProviderInfo info;
+    private final List<String> errorMessages;
+
+    public ProvidedData(ProviderInfo info, List<String> errorMessages) {
+        super(info.getSourceName(), null);
+        this.info = info;
+        this.errorMessages = errorMessages;
+    }
+
+    public ProviderInfo getInfo() {
+        return info;
+    }
+
+    public List<String> getErrorMessages() {
+        return errorMessages;
+    }
+}

+ 28 - 0
src/main/java/cz/senslog/watchdog/provider/ProvidedObject.java

@@ -0,0 +1,28 @@
+package cz.senslog.watchdog.provider;
+
+import java.time.OffsetDateTime;
+
+public class ProvidedObject {
+
+    private final String id;
+    private final String name;
+    private final OffsetDateTime timestamp;
+
+    public ProvidedObject(String id, String name, OffsetDateTime timestamp) {
+        this.id = id;
+        this.name = name;
+        this.timestamp = timestamp;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public OffsetDateTime getTimestamp() {
+        return timestamp;
+    }
+}

+ 28 - 0
src/main/java/cz/senslog/watchdog/provider/ProviderInfo.java

@@ -0,0 +1,28 @@
+package cz.senslog.watchdog.provider;
+
+import cz.senslog.watchdog.config.DataProviderType;
+
+public class ProviderInfo {
+
+    private final String sourceName;
+    private final DataProviderType type;
+    private final boolean alive;
+
+    public ProviderInfo(String sourceName, DataProviderType type, boolean alive) {
+        this.sourceName = sourceName;
+        this.type = type;
+        this.alive = alive;
+    }
+
+    public String getSourceName() {
+        return sourceName;
+    }
+
+    public DataProviderType getType() {
+        return type;
+    }
+
+    public boolean isAlive() {
+        return alive;
+    }
+}

+ 5 - 6
src/main/java/cz/senslog/watchdog/provider/database/Connection.java

@@ -2,7 +2,6 @@ package cz.senslog.watchdog.provider.database;
 
 import com.zaxxer.hikari.HikariConfig;
 import com.zaxxer.hikari.HikariDataSource;
-import cz.senslog.watchdog.config.DatabaseConfig;
 import org.jdbi.v3.core.Jdbi;
 import org.jdbi.v3.jodatime2.JodaTimePlugin;
 import org.jdbi.v3.postgres.PostgresPlugin;
@@ -12,15 +11,15 @@ public class Connection<T> {
 
     private final T connection;
 
-    public static Connection<Jdbi> create(DatabaseConfig config) {
+    public static Connection<Jdbi> create(String url, String username, String password, int poolSize) {
         PGSimpleDataSource ds = new PGSimpleDataSource();
-        ds.setUrl(config.getConnectionUrl());
-        ds.setPassword(config.getPassword());
-        ds.setUser(config.getUsername());
+        ds.setUrl(url);
+        ds.setPassword(password);
+        ds.setUser(username);
         ds.setLoadBalanceHosts(true);
         HikariConfig hc = new HikariConfig();
         hc.setDataSource(ds);
-        hc.setMaximumPoolSize(config.getConnectionPoolSize());
+        hc.setMaximumPoolSize(poolSize);
         Jdbi jdbiConnection = Jdbi
                 .create(new HikariDataSource(hc))
                 .installPlugin(new PostgresPlugin())

+ 14 - 3
src/main/java/cz/senslog/watchdog/provider/database/DatabaseDataProvider.java

@@ -1,21 +1,27 @@
 package cz.senslog.watchdog.provider.database;
 
-import cz.senslog.watchdog.config.DatabaseConfig;
+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 DatabaseDataProvider implements DataProvider {
 
     private final SensLogRepository repository;
     private final String groupName;
 
-    public DatabaseDataProvider(DatabaseConfig config) {
-        Connection<Jdbi> connection = Connection.create(config);
+    public DatabaseDataProvider(DatabaseDataProviderConfig config) {
+        Connection<Jdbi> connection = Connection.create(
+                config.getConnectionUrl(), config.getUsername(), config.getPassword(), config.getConnectionPoolSize()
+        );
         this.repository = new SensLogRepository(connection);
         this.groupName = config.getGroupName();
     }
@@ -25,4 +31,9 @@ public class DatabaseDataProvider implements DataProvider {
         List<ObservationInfo> lastObservations = repository.getLastObservations(groupName);
         return new ArrayList<>(lastObservations);
     }
+
+    @Override
+    public ProvidedData getLastData() {
+        return new ProvidedData(new ProviderInfo("db", DATABASE, false), new ArrayList<>());
+    }
 }

+ 75 - 31
src/main/java/cz/senslog/watchdog/provider/ws/WebServiceDataProvider.java

@@ -1,12 +1,11 @@
 package cz.senslog.watchdog.provider.ws;
 
 import com.google.gson.reflect.TypeToken;
-import cz.senslog.watchdog.config.WebServiceConfig;
+import cz.senslog.watchdog.config.WebServiceDataProviderConfig;
 import cz.senslog.watchdog.domain.Sensor;
 import cz.senslog.watchdog.domain.Unit;
-import cz.senslog.watchdog.provider.DataProvider;
+import cz.senslog.watchdog.provider.*;
 import cz.senslog.watchdog.domain.ObservationInfo;
-import cz.senslog.watchdog.provider.Record;
 import cz.senslog.watchdog.util.Tuple;
 import cz.senslog.watchdog.util.http.HttpClient;
 import cz.senslog.watchdog.util.http.HttpRequest;
@@ -20,35 +19,37 @@ import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
+import static cz.senslog.watchdog.config.DataProviderType.WEB_SERVICE;
 import static cz.senslog.watchdog.util.json.BasicJson.jsonToObject;
 import static java.time.format.DateTimeFormatter.ofPattern;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptyMap;
+import static java.util.Collections.*;
 
 public class WebServiceDataProvider implements DataProvider {
 
     private static final Logger logger = LogManager.getLogger(WebServiceDataProvider.class);
 
+    private static final String ERROR_KEY = "__error";
+
     private static final String DEFAULT_NAME = "unknown";
-    private static final Tuple<String, Map<Long, String>> DEFAULT_UNIT_INFO = Tuple.of(DEFAULT_NAME, emptyMap());
+    private static final Tuple<String, Map<String, String>> DEFAULT_UNIT_INFO = Tuple.of(DEFAULT_NAME, emptyMap());
 
     private static final DateTimeFormatter TIMESTAMP_PATTERN = ofPattern("yyyy-MM-dd HH:mm:ssX");
 
     private final HttpClient httpClient = HttpClient.newHttpClient();
-    private final WebServiceConfig config;
+    private final WebServiceDataProviderConfig config;
 
-    public WebServiceDataProvider(WebServiceConfig config) {
+    public WebServiceDataProvider(WebServiceDataProviderConfig config) {
         this.config = config;
     }
 
 
     /* Map<unitId, Tuple<unitName, Map<sensorId, sensorName>>> */
-    private Map<Long, Tuple<String, Map<Long, String>>> loadUnitsInfo() {
+    private Map<String, Tuple<String, Map<String, String>>> loadUnitsInfo() {
         HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(config.getUrl(), "/DataService")
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), "/DataService")
                         .addParam("Operation", "GetUnits")
-                        .addParam("group", config.getGroup())
-                        .addParam("user", config.getUser())
+                        .addParam("group", config.getGroupName())
+                        .addParam("user", config.getUserName())
                         .build()
                 ).build();
         logger.info("Getting new data from the server: {}.", request.getUrl());
@@ -58,10 +59,12 @@ public class WebServiceDataProvider implements DataProvider {
 
         if (response.isError()) {
             logger.error("code: {}, message: {}", response.getStatus(), response.getBody());
-            return emptyMap();
+            Map<String, Tuple<String, Map<String, String>>> errorMap = new HashMap<>();
+            errorMap.put(ERROR_KEY, Tuple.of(response.getBody(), emptyMap()));
+            return errorMap;
         }
 
-        Map<Long, Tuple<String, Map<Long, String>>> unitsInfo = new HashMap<>();
+        Map<String, Tuple<String, Map<String, String>>> unitsInfo = new HashMap<>();
 
         final Type unitInfoType = new TypeToken<Collection<Map<String, Object>>>() {}.getType();
         List<Map<String, Object>> unitInfoList = jsonToObject(response.getBody(), unitInfoType);
@@ -70,9 +73,9 @@ public class WebServiceDataProvider implements DataProvider {
             if (unitObj instanceof Map) {
                 Map<?, ?> unitMap = (Map<?, ?>)unitObj;
                 String unitName = unitMap.get("description").toString();
-                long unitId = ((Double)unitMap.get("unitId")).longValue();
+                String unitId = String.format("%d", ((Double)unitMap.get("unitId")).longValue());
 
-                Map<Long, String> resultSensorInfo = new HashMap<>();
+                Map<String, String> resultSensorInfo = new HashMap<>();
                 Object sensorsObj = unitInfo.get("sensors");
                 if (sensorsObj instanceof List) {
                     List<?> sensorsList = (List<?>)sensorsObj;
@@ -80,7 +83,8 @@ public class WebServiceDataProvider implements DataProvider {
                         if (sensorInfoObj instanceof Map) {
                             Map<?, ?> sensorMap = (Map<?, ?>)sensorInfoObj;
                             String sensorName = sensorMap.get("sensorName").toString();
-                            long sensorId = ((Double)sensorMap.get("sensorId")).longValue();
+                            String sensorId = String.format("%d", ((Double)sensorMap.get("sensorId")).longValue());
+
                             resultSensorInfo.put(sensorId, sensorName);
                         }
                     }
@@ -88,7 +92,6 @@ public class WebServiceDataProvider implements DataProvider {
 
                 unitsInfo.put(unitId, Tuple.of(unitName, resultSensorInfo));
             }
-
         }
 
         return unitsInfo;
@@ -96,10 +99,10 @@ public class WebServiceDataProvider implements DataProvider {
 
     private List<Map<String, Object>> loadLastObservations() {
         HttpRequest request = HttpRequest.newBuilder().GET()
-                .url(URLBuilder.newBuilder(config.getUrl(), "/SensorService")
+                .url(URLBuilder.newBuilder(config.getBaseUrl(), "/SensorService")
                         .addParam("Operation", "GetLastObservations")
-                        .addParam("group", config.getGroup())
-                        .addParam("user", config.getUser())
+                        .addParam("group", config.getGroupName())
+                        .addParam("user", config.getUserName())
                         .build()
                 ).build();
         logger.info("Getting new data from the server: {}.", request.getUrl());
@@ -109,7 +112,9 @@ public class WebServiceDataProvider implements DataProvider {
 
         if (response.isError()) {
             logger.error("code: {}, message: {}", response.getStatus(), response.getBody());
-            return emptyList();
+            Map<String, Object> errorMap = new HashMap<>();
+            errorMap.put(ERROR_KEY, response.getBody());
+            return singletonList(errorMap);
         }
 
         final Type lastObsType = new TypeToken<Collection<Map<String, Object>>>() {}.getType();
@@ -120,23 +125,62 @@ public class WebServiceDataProvider implements DataProvider {
     public List<Record> getLastRecords() {
 
         // Map<unitId, Tuple<unitName, Map<sensorId, sensorName>>>
-        Map<Long, Tuple<String, Map<Long, String>>> unitsInfo = loadUnitsInfo();
+        Map<String, Tuple<String, Map<String, String>>> unitsInfo = loadUnitsInfo();
         List<Map<String, Object>> lastObservations = loadLastObservations();
 
         List<Record> observations = new ArrayList<>(lastObservations.size());
-        for (Map<String, Object> observationMap : lastObservations) {
+        for (Map<String, Object> obMap : lastObservations) {
 
-            long unitId = ((Double)observationMap.get("unitId")).longValue();
-            long sensorId = ((Double)observationMap.get("sensorId")).longValue();
-            String timestampStr = observationMap.get("timeStamp").toString();
-            OffsetDateTime timestamp = OffsetDateTime.parse(timestampStr, TIMESTAMP_PATTERN);
+            String unitId = obMap.getOrDefault("unitId", "").toString();
+            String sensorId = obMap.getOrDefault("sensorId", "").toString();
+            OffsetDateTime timestamp = OffsetDateTime.parse(obMap.get("timeStamp").toString(), TIMESTAMP_PATTERN);
 
-            Tuple<String, Map<Long, String>> unitInfo = unitsInfo.getOrDefault(unitId, DEFAULT_UNIT_INFO);
-            Unit unit = new Unit(unitId, unitInfo.getItem1());
-            Sensor sensor = new Sensor(sensorId, unitInfo.getItem2().getOrDefault(sensorId, DEFAULT_NAME));
+            Tuple<String, Map<String, String>> unitInfo = unitsInfo.getOrDefault(unitId, DEFAULT_UNIT_INFO);
+            Unit unit = new Unit(Long.parseLong(unitId), unitInfo.getItem1());
+            Sensor sensor = new Sensor(Long.parseLong(sensorId), unitInfo.getItem2().getOrDefault(sensorId, DEFAULT_NAME));
             observations.add(new ObservationInfo(unit, sensor, timestamp));
         }
         return observations;
     }
 
+    @Override
+    public ProvidedData getLastData() {
+
+        // Map<unitId, Tuple<unitName, Map<sensorId, sensorName>>>
+        Map<String, Tuple<String, Map<String, String>>> unitsInfo = loadUnitsInfo();
+        List<Map<String, Object>> lastObservations = loadLastObservations();
+
+        List<String> errorMessages = new ArrayList<>();
+        if (unitsInfo.containsKey(ERROR_KEY)) {
+            errorMessages.add(unitsInfo.get(ERROR_KEY).getItem1());
+        }
+        boolean isServerAlive = true;
+        if (!lastObservations.isEmpty() && lastObservations.get(0).containsKey(ERROR_KEY)) {
+            errorMessages.add((String) lastObservations.get(0).get(ERROR_KEY));
+            isServerAlive = false;
+        }
+
+        String source = String.format("%s: %s", config.getId(), config.getBaseUrl());
+        ProvidedData providedData = new ProvidedData(new ProviderInfo(source, WEB_SERVICE, isServerAlive), errorMessages);
+
+        for (Map<String, Object> obMap : lastObservations) {
+            long unitId = ((Double) obMap.get("unitId")).longValue();
+            long sensorId = ((Double) obMap.get("sensorId")).longValue();
+            OffsetDateTime timestamp = OffsetDateTime.parse(obMap.get("timeStamp").toString(), TIMESTAMP_PATTERN);
+
+            String unitIdStr = String.format("%d", unitId);
+            String sensorIdStr = String.format("%d", sensorId);
+
+            Tuple<String, Map<String, String>> unitInfo = unitsInfo.getOrDefault(unitIdStr, DEFAULT_UNIT_INFO);
+            String unitName = unitInfo.getItem1();
+            String sensorName = unitInfo.getItem2().getOrDefault(sensorIdStr, DEFAULT_NAME);
+
+            providedData.computeIfAbsent(unitIdStr, k -> new ProvidedObject(k, unitName, null))
+                    .addNode(sensorIdStr, new ProvidedObject(sensorIdStr, sensorName, timestamp));
+
+        }
+
+        return providedData;
+    }
+
 }

+ 8 - 0
src/main/java/cz/senslog/watchdog/util/StringUtils.java

@@ -19,4 +19,12 @@ public final class StringUtils {
     public static boolean isNotBlank(String string) {
         return !isBlank(string);
     }
+
+    public static String repeat(String value, int count) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < count; i++) {
+            builder.append(value);
+        }
+        return builder.toString();
+    }
 }

+ 36 - 0
src/main/java/cz/senslog/watchdog/util/schedule/PeriodicalTask.java

@@ -0,0 +1,36 @@
+package cz.senslog.watchdog.util.schedule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+
+public class PeriodicalTask implements Task {
+
+    private final String name;
+    private final Runnable task;
+    private final int repeat;
+
+    private TaskDescription description;
+
+    public PeriodicalTask(String name, Runnable task, int repeat) {
+        this.description = new TaskDescription(name, Status.STOPPED);
+        this.name = name;
+        this.task = task;
+        this.repeat = repeat;
+    }
+
+    @Override
+    public Runnable getRunnable() {
+        return task;
+    }
+
+    @Override
+    public TaskDescription getDescription() {
+        return description;
+    }
+
+    @Override
+    public void schedule(ScheduledExecutorService scheduledService, CountDownLatch latch) {
+        task.run();
+        latch.countDown();
+    }
+}

+ 13 - 7
src/main/java/cz/senslog/watchdog/util/schedule/ScheduleTask.java

@@ -1,7 +1,7 @@
 package cz.senslog.watchdog.util.schedule;
 
 
-import cz.senslog.watchdog.messagebroker.email.EmailMessageBroker;
+import cz.senslog.watchdog.messagebroker.broker.EmailMessageBroker;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -14,7 +14,7 @@ import java.util.concurrent.ScheduledFuture;
 import static cz.senslog.watchdog.util.TimeConverter.secToMillis;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
-public final class ScheduleTask {
+public class ScheduleTask implements Task {
 
     private static final Logger logger = LogManager.getLogger(EmailMessageBroker.class);
 
@@ -47,23 +47,29 @@ public final class ScheduleTask {
         this(name, task, periodSec, null);
     }
 
-    public TaskDescription getDescription() {
-        return description;
+    public long getPeriodMillis() {
+        return periodMillis;
     }
 
-    public Runnable getTask() {
+    @Override
+    public Runnable getRunnable() {
         return task;
     }
 
-    public long getPeriodMillis() {
-        return periodMillis;
+    @Override
+    public TaskDescription getDescription() {
+        return description;
     }
 
+    @Override
     public void schedule(ScheduledExecutorService scheduledService, CountDownLatch latch) {
 
         long delay = DEFAULT_DELAY_MILLIS;
         if (startAt != null) {
             delay = LocalDateTime.now().until(startAt, ChronoUnit.MILLIS);
+            if (delay <= 0) {
+                delay = DEFAULT_DELAY_MILLIS;
+            }
         } else if (delayMillis > 0) {
             delay = delayMillis;
         }

+ 6 - 0
src/main/java/cz/senslog/watchdog/util/schedule/Scheduler.java

@@ -27,6 +27,12 @@ public interface Scheduler {
         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();
     }
 }

+ 30 - 12
src/main/java/cz/senslog/watchdog/util/schedule/SchedulerBuilderImpl.java

@@ -1,13 +1,12 @@
 package cz.senslog.watchdog.util.schedule;
 
 import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.util.HashSet;
 import java.util.Set;
 
 public class SchedulerBuilderImpl implements Scheduler.SchedulerBuilder {
 
-    private final Set<ScheduleTask> tasks;
+    private final Set<Task> tasks;
 
     public SchedulerBuilderImpl() {
         this.tasks = new HashSet<>();
@@ -20,33 +19,52 @@ public class SchedulerBuilderImpl implements Scheduler.SchedulerBuilder {
     }
 
     @Override
-    public Scheduler.SchedulerBuilder addTask(Runnable task, long period) {
-        tasks.add(new ScheduleTask(task.getClass().getSimpleName(), task, period));
+    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, int delaySec) {
-        tasks.add(new ScheduleTask(name, task, period, delaySec));
+    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(Runnable task, long period, int delaySec) {
-        tasks.add(new ScheduleTask(task.getClass().getSimpleName(), task, period, delaySec));
+    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, long period, LocalDateTime startAt) {
-        tasks.add(new ScheduleTask(name, task, period, startAt));
+    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) {
-        tasks.add(new ScheduleTask(task.getClass().getSimpleName(), task, period, startAt));
-        return this;
+        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

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

@@ -9,12 +9,12 @@ import java.util.concurrent.TimeUnit;
 
 public class SchedulerImpl implements Scheduler {
 
-    private final Set<ScheduleTask> tasks;
+    private final Set<Task> tasks;
 
     private ScheduledExecutorService scheduler;
     private CountDownLatch latch;
 
-    public SchedulerImpl(Set<ScheduleTask> tasks) {
+    public SchedulerImpl(Set<Task> tasks) {
         this.tasks = tasks;
     }
 
@@ -54,7 +54,7 @@ public class SchedulerImpl implements Scheduler {
     @Override
     public Set<TaskDescription> getTaskDescriptions() {
         Set<TaskDescription> descriptions = new HashSet<>(tasks.size());
-        for (ScheduleTask task : tasks) {
+        for (Task task : tasks) {
             descriptions.add(task.getDescription());
         }
         return descriptions;

+ 12 - 0
src/main/java/cz/senslog/watchdog/util/schedule/Task.java

@@ -0,0 +1,12 @@
+package cz.senslog.watchdog.util.schedule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+
+public interface Task {
+
+    Runnable getRunnable();
+    TaskDescription getDescription();
+
+    void schedule(ScheduledExecutorService scheduledService, CountDownLatch latch);
+}

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

@@ -0,0 +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());
+
+
+    }
+}

+ 27 - 0
src/test/java/cz/senslog/watchdog/config/graph/GraphTest.java

@@ -0,0 +1,27 @@
+package cz.senslog.watchdog.config.graph;
+
+import cz.senslog.watchdog.config.MonitoredObject;
+import cz.senslog.watchdog.core.adt.Graph;
+import cz.senslog.watchdog.core.adt.Node;
+import org.junit.jupiter.api.Test;
+
+class GraphTest {
+
+    @Test
+    void testGraph() {
+
+        Graph<String, MonitoredObject> graph = new Graph<>("group_name", new MonitoredObject("group_name"));
+
+        graph.addNode("unit_1", new MonitoredObject("unit_1"));
+        graph.addNode("unit_2", new MonitoredObject("unit_2"));
+
+        graph.getNode("unit_1")
+                .addNode("sensor_1", new MonitoredObject("sensor_1"))
+                .addNode("sensor_2", new MonitoredObject("sensor_2"));
+
+        Node<String, MonitoredObject> unit2 = graph.getNode("unit_2");
+        unit2.addNode("sensor_1", new MonitoredObject("sensor_1"));
+
+        System.out.println(graph);
+    }
+}

+ 105 - 0
src/test/resources/configExample.yaml

@@ -0,0 +1,105 @@
+emailServers:
+  exampleEmailServer:
+    smtpHost: "<smtp_server>"
+    smtpPort: 465
+    authUsername: "username"
+    authPassword: "password"
+
+dataProviders:
+  wsServer1:
+    type: WEB_SERVICE
+    config:
+      baseUrl: "<domain_url>"
+  wsServer2:
+    type: WEB_SERVICE
+    config:
+      baseUrl: "<domain_url>"
+  dbServer:
+    type: DATABASE
+    config:
+      url: "<jdbc_url>"
+      username: "<username>"
+      password: "<password>"
+      connectionPoolSize: 6
+      groupName: "<group_name>"
+
+messageBrokers:
+  emailToAll:
+    type: EMAIL
+    config:
+      server: exampleEmailServer
+      senderEmail: "from@example.org"
+      recipientEmail: "to@example.org"
+      subject: "[watchdog] Example Report"
+  emailToTeam:
+    type: EMAIL
+    config:
+      server: exampleEmailServer
+      senderEmail: "from@example.org"
+      recipientEmail: "to@example.org"
+      subject: "[watchdog] Example Report"
+  signalEmergency:
+    type: SIGNAL
+    config:
+      recipientNumber: "+420 123 456 789"
+      senderNumber: "+420 123 456 789"
+      messagePattern: "No actual data from: $unit_id/$sensor_id"  # available props: $unit_id, $sensor_id, $timestamp, $value
+
+groups:
+  group1Checker:
+    dataProvider: wsServer1
+    messageBroker: emailToTeam
+    resultType: ALL
+    period: 81000
+  group2Checker:
+    dataProvider: dbServer
+    messageBroker: emailToTeam
+    resultType: ALL
+    period: 82000
+  groupRealTime:
+    dataProvider: wsServer1
+    messageBroker: signalEmergency
+    resultType: ERROR
+
+senslogGroups:
+  senslogGAdmin:
+    dataProvider: wsServer1
+    messageBroker: emailToAll
+    resultType: ERROR
+    period: 83000
+    groups: [ "admin", "...." ]
+
+superGroups:
+  sumarizeError:
+    messageBroker: emailToAll
+    resultType: ERROR
+    period: 84000
+    groups: [ group1Checker, group2Checker, senslogGAdmin ]
+
+monitoredObjects:
+  unit_id_1:
+    groups: [group1Checker, group2Checker]
+    period: 1440
+
+  unit_id_2:
+    period: 1440
+    groups: [groupRealTime]
+    sensors: [210000000, 220000000]
+
+  unit_id_3:
+    period: 1440
+    groups: [groupRealTime]
+    sensors:
+      310000000:
+        groups: [group1Checker]
+      320000000:
+        period: 2880
+
+  unit_id_4:
+    sensors:
+      410000000:
+        period: 2880
+        groups: [group1Checker]
+      420000000:
+        period: 1440
+        groups: [group1Checker]