Przeglądaj źródła

added persistence in memory database (cache)

Lukas Cerny 5 lat temu
rodzic
commit
e3543bb233
36 zmienionych plików z 603 dodań i 191 usunięć
  1. 1 0
      .gitignore
  2. 2 2
      build.gradle
  3. 7 2
      config/config.yaml
  4. 0 3
      src/main/java/cz/senslog/analyzer/analysis/AnalyzerModule.java
  5. 29 36
      src/main/java/cz/senslog/analyzer/analysis/module/CollectorHandler.java
  6. 13 7
      src/main/java/cz/senslog/analyzer/analysis/module/HandlersModule.java
  7. 6 5
      src/main/java/cz/senslog/analyzer/app/Application.java
  8. 30 19
      src/main/java/cz/senslog/analyzer/app/Configuration.java
  9. 1 1
      src/main/java/cz/senslog/analyzer/app/Parameters.java
  10. 63 0
      src/main/java/cz/senslog/analyzer/core/AbstractWatchableObjects.java
  11. 3 3
      src/main/java/cz/senslog/analyzer/core/EventBusModule.java
  12. 16 0
      src/main/java/cz/senslog/analyzer/core/WatchableObjects.java
  13. 5 0
      src/main/java/cz/senslog/analyzer/core/api/WatchableObject.java
  14. 37 0
      src/main/java/cz/senslog/analyzer/domain/CollectedStatistics.java
  15. 14 3
      src/main/java/cz/senslog/analyzer/domain/Timestamp.java
  16. 0 54
      src/main/java/cz/senslog/analyzer/persistence/ConnectionModule.java
  17. 0 36
      src/main/java/cz/senslog/analyzer/persistence/RepositoryModule.java
  18. 2 2
      src/main/java/cz/senslog/analyzer/provider/ScheduleDatabaseProviderModule.java
  19. 1 1
      src/main/java/cz/senslog/analyzer/provider/ScheduledDatabaseProvider.java
  20. 1 1
      src/main/java/cz/senslog/analyzer/server/handler/GroupsHandler.java
  21. 4 3
      src/main/java/cz/senslog/analyzer/server/handler/StatisticsHandler.java
  22. 1 1
      src/main/java/cz/senslog/analyzer/server/vertx/VertxHandlersModule.java
  23. 1 1
      src/main/java/cz/senslog/analyzer/storage/Connection.java
  24. 79 0
      src/main/java/cz/senslog/analyzer/storage/ConnectionModule.java
  25. 44 0
      src/main/java/cz/senslog/analyzer/storage/RepositoryModule.java
  26. 24 0
      src/main/java/cz/senslog/analyzer/storage/StorageConfig.java
  27. 48 0
      src/main/java/cz/senslog/analyzer/storage/inmemory/CollectedStatisticsStorage.java
  28. 10 0
      src/main/java/cz/senslog/analyzer/storage/inmemory/InMemoryConnection.java
  29. 29 0
      src/main/java/cz/senslog/analyzer/storage/inmemory/InMemoryStorageConfig.java
  30. 96 0
      src/main/java/cz/senslog/analyzer/storage/inmemory/repository/CollectedStatisticsRepository.java
  31. 11 0
      src/main/java/cz/senslog/analyzer/storage/permanent/PermanentConnection.java
  32. 3 3
      src/main/java/cz/senslog/analyzer/storage/permanent/PermanentStorageConfig.java
  33. 2 2
      src/main/java/cz/senslog/analyzer/storage/permanent/repository/SensLogRepository.java
  34. 2 4
      src/main/java/cz/senslog/analyzer/storage/permanent/repository/StatisticsConfigRepository.java
  35. 2 2
      src/main/java/cz/senslog/analyzer/storage/permanent/repository/StatisticsRepository.java
  36. 16 0
      src/test/java/cz/senslog/analyzer/domain/TimestampTest.java

+ 1 - 0
.gitignore

@@ -8,3 +8,4 @@ build
 gradle
 *.log
 .DS_Store
+*.mv.db

+ 2 - 2
build.gradle

@@ -37,12 +37,12 @@ dependencies {
     compile group: 'io.vertx', name: 'vertx-core', version: '3.8.5'
     compile group: 'io.vertx', name: 'vertx-web', version: '3.8.5'
 
+    compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.13.1'
     compile group: 'org.jdbi', name: 'jdbi3-postgres', version: '3.12.2'
     compile group: 'org.jdbi', name: 'jdbi3-jodatime2', version: '3.12.2'
     compile group: 'com.zaxxer', name: 'HikariCP', version: '3.4.2'
     compile group: 'org.postgresql', name: 'postgresql', version: '42.2.10'
-    compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.13.1'
-
+    compile group: 'com.h2database', name: 'h2', version: '1.4.200'
 
     implementation 'com.google.dagger:dagger:2.26'
     annotationProcessor 'com.google.dagger:dagger-compiler:2.26'

+ 7 - 2
config/config.yaml

@@ -1,12 +1,17 @@
-database:
+permanentStorage:
   url: "jdbc:postgresql://localhost:5432/senslog1"
   username: "postgres"
   password: "root"
   connectionPoolSize: 6
 
+inMemoryStorage:
+  path: "./statistics"
+  persistence: true
+  parameters: ""
+
 scheduler:
   initDate: "2020-01-30T14:00:00.00+02:00"
-  period: 100
+  period: 30
 
 server:
   port: 9090

+ 0 - 3
src/main/java/cz/senslog/analyzer/analysis/AnalyzerModule.java

@@ -10,7 +10,6 @@ import cz.senslog.analyzer.core.api.HandlerInvoker;
 import cz.senslog.analyzer.domain.DoubleStatistics;
 import cz.senslog.analyzer.domain.Observation;
 
-import cz.senslog.analyzer.persistence.repository.StatisticsRepository;
 import dagger.Module;
 import dagger.Provides;
 import org.apache.logging.log4j.LogManager;
@@ -18,8 +17,6 @@ import org.apache.logging.log4j.Logger;
 
 import javax.inject.Named;
 
-import java.util.List;
-
 import static cz.senslog.analyzer.core.api.HandlerInvoker.cancelInvoker;
 
 

+ 29 - 36
src/main/java/cz/senslog/analyzer/analysis/module/CollectorHandler.java

@@ -4,67 +4,60 @@ import cz.senslog.analyzer.core.api.BlockingHandler;
 import cz.senslog.analyzer.core.api.DataFinisher;
 import cz.senslog.analyzer.core.api.HandlerContext;
 import cz.senslog.analyzer.domain.*;
+import cz.senslog.analyzer.storage.inmemory.CollectedStatisticsStorage;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.util.*;
-import java.util.function.Consumer;
+import java.util.function.Function;
 
-import static cz.senslog.common.json.BasicJson.objectToJson;
-import static java.util.Collections.singletonList;
 
 public abstract class CollectorHandler<I extends Data<?, ?>> extends BlockingHandler<I, DoubleStatistics> {
 
     private static final Logger logger = LogManager.getLogger(CollectorHandler.class);
 
-    private static class CollectedStatistics {
-        private final Timestamp startTime;
-        private final Timestamp endTime;
-        private final DoubleStatistics summaryStatistics;
-
-        private CollectedStatistics(Group group, Timestamp startTime) {
-            this.startTime = startTime;
-            this.endTime = Timestamp.of(startTime.get().plusSeconds(group.getInterval()));
-            this.summaryStatistics = DoubleStatistics.init(group, startTime);
-        }
-    }
-
-    /** Map of saved statistics group by group_id and interval (Map<group_id, Statistics>).*/
-    private final Map<Group, List<CollectedStatistics>> collectedStatistics;
-
     /** Map of saved groups (Map<group_id, Group>). */
     private Map<Long, Group> groupsGroupById;
 
-    public CollectorHandler() {
-        this.collectedStatistics = new HashMap<>();
+    private Map<Group, List<CollectedStatistics>> collectedStatistics;
+
+    private final CollectedStatisticsStorage storage;
+
+    public CollectorHandler(CollectedStatisticsStorage storage) {
+        this.storage = storage;
     }
 
     protected abstract List<Group> loadGroups();
-    protected abstract Consumer<I> collectData(DoubleStatistics statistics);
+    protected abstract Function<I, Boolean> collectData(DoubleStatistics statistics);
     protected abstract long getGroupId(I data);
 
     @Override
     public void init() {
         List<Group> groups = loadGroups();
         groupsGroupById = new HashMap<>(groups.size());
+        collectedStatistics = new HashMap<>(groups.size());
         for (Group group : groups) {
             groupsGroupById.put(group.getId(), group);
+            collectedStatistics.put(group, storage.restore(group));
         }
     }
 
     @Override
     public void finish(DataFinisher<DoubleStatistics> finisher, Timestamp edgeDateTime) {
         List<DoubleStatistics> finishedData = new ArrayList<>();
-        for (List<CollectedStatistics> statistics : collectedStatistics.values()) {
+        for (Group group : groupsGroupById.values()) {
+            List<CollectedStatistics> statistics = getCollectedStatisticsByGroup(group);
             Iterator<CollectedStatistics> statisticsIterator = statistics.iterator();
             while (statisticsIterator.hasNext()) {
-                CollectedStatistics statistic = statisticsIterator.next();
-                if (statistic.endTime.isBefore(edgeDateTime)) {
-                    finishedData.add(statistic.summaryStatistics);
+                CollectedStatistics st = statisticsIterator.next();
+                if (st.getEndTime().isBefore(edgeDateTime)) {
+                    finishedData.add(st.getStatistics());
                     statisticsIterator.remove();
+                    storage.remove(st);
                 }
             }
         }
+        storage.commit();
         finisher.finish(finishedData);
     }
 
@@ -81,26 +74,26 @@ public abstract class CollectorHandler<I extends Data<?, ?>> extends BlockingHan
 
         boolean newDataAccepted = false;
         for (CollectedStatistics st : groupStatistics) {
-            if (timestamp.isEqual(st.startTime) ||
-                    (timestamp.isAfter(st.startTime) && timestamp.isBefore(st.endTime))
+            if (timestamp.isEqual(st.getStartTime()) ||
+                    (timestamp.isAfter(st.getStartTime()) && timestamp.isBefore(st.getEndTime()))
             ) {
-                collectData(st.summaryStatistics).accept(data);
+                collectData(st.getStatistics()).apply(data);
                 newDataAccepted = true;
             }
         }
 
-        if (!newDataAccepted) {
-            CollectedStatistics st = new CollectedStatistics(group, timestamp);
-            collectData(st.summaryStatistics).accept(data);
-            groupStatistics.add(st);
+        if (!newDataAccepted) { // register a new statistics
+            CollectedStatistics newSt = new CollectedStatistics(group, timestamp);
+            collectData(newSt.getStatistics()).apply(data);
+            groupStatistics.add(storage.watch(newSt));
         }
     }
 
-    private List<CollectedStatistics> getCollectedStatisticsByGroup(Group group) {
-        return collectedStatistics.computeIfAbsent(group, k -> new ArrayList<>());
-    }
-
     private Group getGroupByGroupId(long groupId) {
         return groupsGroupById.getOrDefault(groupId, Group.empty());
     }
+
+    private List<CollectedStatistics> getCollectedStatisticsByGroup(Group group) {
+        return collectedStatistics.computeIfAbsent(group, g -> new ArrayList<>());
+    }
 }

+ 13 - 7
src/main/java/cz/senslog/analyzer/analysis/module/HandlersModule.java

@@ -2,8 +2,10 @@ package cz.senslog.analyzer.analysis.module;
 
 import cz.senslog.analyzer.core.api.BlockingHandler;
 import cz.senslog.analyzer.domain.*;
-import cz.senslog.analyzer.persistence.RepositoryModule;
-import cz.senslog.analyzer.persistence.repository.StatisticsConfigRepository;
+import cz.senslog.analyzer.storage.RepositoryModule;
+import cz.senslog.analyzer.storage.inmemory.CollectedStatisticsStorage;
+import cz.senslog.analyzer.storage.inmemory.repository.CollectedStatisticsRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsConfigRepository;
 import dagger.Module;
 import dagger.Provides;
 import org.apache.logging.log4j.LogManager;
@@ -12,7 +14,7 @@ import org.apache.logging.log4j.Logger;
 import javax.inject.Named;
 import javax.inject.Singleton;
 import java.util.*;
-import java.util.function.Consumer;
+import java.util.function.Function;
 
 @Module(includes = RepositoryModule.class)
 public class HandlersModule {
@@ -49,13 +51,17 @@ public class HandlersModule {
     }
 
     @Provides @Singleton @Named("aggregationCollectorHandler")
-    public BlockingHandler<Observation, DoubleStatistics> provideObservationCollector(StatisticsConfigRepository repository) {
+    public BlockingHandler<Observation, DoubleStatistics> provideObservationCollector(
+            StatisticsConfigRepository statisticsConfigRepository,
+            CollectedStatisticsRepository collectedStatisticsRepository
+    ) {
         logger.info("Creating a new instance for the handler '{}'.", "aggregationCollectorHandler");
-        return new CollectorHandler<Observation>() {
+        CollectedStatisticsStorage storage = CollectedStatisticsStorage.createContext(collectedStatisticsRepository);
+        return new CollectorHandler<Observation>(storage) {
             @Override protected List<Group> loadGroups() {
-                return repository.getGroupInfos();
+                return statisticsConfigRepository.getGroupInfos();
             }
-            @Override protected Consumer<Observation> collectData(DoubleStatistics statistics) {
+            @Override protected Function<Observation, Boolean> collectData(DoubleStatistics statistics) {
                 return val -> statistics.accept(val.getSource(), val.getValue());
             }
             @Override protected long getGroupId(Observation data) { return data.getSource().getGroupId(); }

+ 6 - 5
src/main/java/cz/senslog/analyzer/app/Application.java

@@ -2,8 +2,9 @@ package cz.senslog.analyzer.app;
 
 import cz.senslog.analyzer.analysis.Analyzer;
 import cz.senslog.analyzer.analysis.DaggerAnalyzerComponent;
-import cz.senslog.analyzer.persistence.ConnectionModule;
-import cz.senslog.analyzer.persistence.DatabaseConfig;
+import cz.senslog.analyzer.storage.ConnectionModule;
+import cz.senslog.analyzer.storage.StorageConfig;
+import cz.senslog.analyzer.storage.permanent.PermanentStorageConfig;
 import cz.senslog.analyzer.provider.ProviderConfig;
 import cz.senslog.analyzer.provider.DaggerDataProviderComponent;
 import cz.senslog.analyzer.provider.DataProvider;
@@ -72,19 +73,19 @@ public class Application extends Thread {
     public void run() {
 
         String configFile = params.getConfigFileName();
-        Triple<DatabaseConfig, ProviderConfig, Integer> configs = null;
+        Triple<StorageConfig, ProviderConfig, Integer> configs = null;
         try {
             configs = Configuration.load(configFile);
         } catch (IOException e) {
             System.exit(1);
         }
 
-        DatabaseConfig dbConfig = configs.getItem1();
+        StorageConfig storageConfig = configs.getItem1();
         ProviderConfig config = configs.getItem2();
         int port = configs.getItem3();
 
 
-        ConnectionModule connectionModule = ConnectionModule.create(dbConfig);
+        ConnectionModule connectionModule = ConnectionModule.create(storageConfig);
 
         Analyzer analyzer = DaggerAnalyzerComponent.builder()
                 .connectionModule(connectionModule)

+ 30 - 19
src/main/java/cz/senslog/analyzer/app/Configuration.java

@@ -1,6 +1,8 @@
 package cz.senslog.analyzer.app;
 
-import cz.senslog.analyzer.persistence.DatabaseConfig;
+import cz.senslog.analyzer.storage.StorageConfig;
+import cz.senslog.analyzer.storage.inmemory.InMemoryStorageConfig;
+import cz.senslog.analyzer.storage.permanent.PermanentStorageConfig;
 import cz.senslog.analyzer.provider.ProviderConfig;
 import cz.senslog.common.exception.UnsupportedFileException;
 import cz.senslog.common.util.Triple;
@@ -14,19 +16,13 @@ import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.Date;
 import java.util.Map;
 
-import static java.time.ZoneOffset.UTC;
-import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
-
 public class Configuration {
-    private static Logger logger = LogManager.getLogger(Configuration.class);
+    private static final Logger logger = LogManager.getLogger(Configuration.class);
 
-    public static Triple<DatabaseConfig, ProviderConfig, Integer> load(String fileName) throws IOException {
+    public static Triple<StorageConfig, ProviderConfig, Integer> load(String fileName) throws IOException {
 
         logger.info("Loading '{}' configuration file.", fileName);
 
@@ -54,10 +50,17 @@ public class Configuration {
             ));
         }
 
-        Object dbMap = properties.get("database");
-        if (!(dbMap instanceof Map)) {
+        Object permanentStMap = properties.get("permanentStorage");
+        if (!(permanentStMap instanceof Map)) {
             throw new IOException(String.format(
-                    "Configuration file '%s' contains an error at 'database' attribute.", fileName
+                    "Configuration file '%s' contains an error at 'permanentStorage' attribute.", fileName
+            ));
+        }
+
+        Object inMemoryStMap = properties.get("inMemoryStorage");
+        if (!(inMemoryStMap instanceof Map)) {
+            throw new IOException(String.format(
+                    "Configuration file '%s' contains an error at 'inMemoryStorage' attribute.", fileName
             ));
         }
 
@@ -75,12 +78,19 @@ public class Configuration {
             ));
         }
 
-        Map<String, Object> databaseConfigMap = (Map<String, Object>)dbMap;
-        DatabaseConfig databaseConfig = new DatabaseConfig(
-                (String)databaseConfigMap.get("url"),
-                (String)databaseConfigMap.get("username"),
-                (String)databaseConfigMap.get("password"),
-                (Integer) databaseConfigMap.get("connectionPoolSize")
+        Map<String, Object> permanentStConfigMap = (Map<String, Object>)permanentStMap;
+        PermanentStorageConfig permanentStConfig = new PermanentStorageConfig(
+                (String)permanentStConfigMap.get("url"),
+                (String)permanentStConfigMap.get("username"),
+                (String)permanentStConfigMap.get("password"),
+                (Integer) permanentStConfigMap.get("connectionPoolSize")
+        );
+
+        Map<String, Object> inMemoryStConfigMap = (Map<String, Object>)inMemoryStMap;
+        InMemoryStorageConfig inMemoryStConfig = new InMemoryStorageConfig(
+                (String)inMemoryStConfigMap.get("path"),
+                (Boolean)inMemoryStConfigMap.get("persistence"),
+                (String)inMemoryStConfigMap.get("parameters")
         );
 
         Map<String, Object> schedulerConfigMap = (Map<String, Object>)schedulerMap;
@@ -95,6 +105,7 @@ public class Configuration {
         Map<String, Object> serverConfigMap = (Map<String, Object>)serverMap;
         Integer port = (Integer)serverConfigMap.get("port");
 
-        return Triple.of(databaseConfig, providerConfig, port);
+        StorageConfig storageConfig = new StorageConfig(permanentStConfig, inMemoryStConfig);
+        return Triple.of(storageConfig, providerConfig, port);
     }
 }

+ 1 - 1
src/main/java/cz/senslog/analyzer/app/Parameters.java

@@ -24,7 +24,7 @@ import static java.nio.file.Paths.get;
  */
 public final class Parameters {
 
-    private static Logger logger = LogManager.getLogger(Parameters.class);
+    private static final Logger logger = LogManager.getLogger(Parameters.class);
 
     private JCommander jCommander;
 

+ 63 - 0
src/main/java/cz/senslog/analyzer/core/AbstractWatchableObjects.java

@@ -0,0 +1,63 @@
+package cz.senslog.analyzer.core;
+
+import cz.senslog.analyzer.core.api.WatchableObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AbstractWatchableObjects<T extends WatchableObject> implements WatchableObjects<T> {
+
+    private static class SavedObject<T extends WatchableObject> {
+        private final T value;
+        private final int identity;
+        private final int originalHash;
+        private int currentHash;
+
+        private SavedObject(T value) {
+            this.value = value;
+            this.identity = System.identityHashCode(value);
+            this.originalHash = value.hash();
+            this.currentHash = originalHash;
+        }
+    }
+
+    private final Map<Integer, SavedObject<T>> watchedObjects;
+
+    protected AbstractWatchableObjects() {
+        this.watchedObjects = new HashMap<>();
+    }
+
+    @Override
+    public T watch(T value) {
+        SavedObject<T> savedObject = new SavedObject<>(value);
+        watchedObjects.put(savedObject.identity, savedObject);
+        return value;
+    }
+
+    @Override
+    public T remove(T value) {
+        SavedObject<T> savedObject = watchedObjects.remove(System.identityHashCode(value));
+        return savedObject == null ? null : savedObject.value;
+    }
+
+    @Override
+    public List<T> changes() {
+        List<T> changed = new ArrayList<>(watchedObjects.size());
+        for (SavedObject<T> obj : watchedObjects.values()) {
+            if (obj.currentHash != obj.value.hash()) {
+                changed.add(obj.value);
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    public boolean commit() {
+        for (SavedObject<T> obj : watchedObjects.values()) {
+            obj.currentHash = obj.value.hash();
+        }
+        return true;
+    }
+}

+ 3 - 3
src/main/java/cz/senslog/analyzer/core/EventBusModule.java

@@ -2,9 +2,9 @@ package cz.senslog.analyzer.core;
 
 import cz.senslog.analyzer.domain.Alert;
 import cz.senslog.analyzer.domain.DoubleStatistics;
-import cz.senslog.analyzer.persistence.RepositoryModule;
-import cz.senslog.analyzer.persistence.repository.SensLogRepository;
-import cz.senslog.analyzer.persistence.repository.StatisticsRepository;
+import cz.senslog.analyzer.storage.RepositoryModule;
+import cz.senslog.analyzer.storage.permanent.repository.SensLogRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsRepository;
 import dagger.Module;
 import dagger.Provides;
 

+ 16 - 0
src/main/java/cz/senslog/analyzer/core/WatchableObjects.java

@@ -0,0 +1,16 @@
+package cz.senslog.analyzer.core;
+
+import cz.senslog.analyzer.core.api.WatchableObject;
+
+import java.util.List;
+
+public interface WatchableObjects<T extends WatchableObject> {
+
+    T watch(T value);
+
+    T remove(T value);
+
+    boolean commit();
+
+    List<T> changes();
+}

+ 5 - 0
src/main/java/cz/senslog/analyzer/core/api/WatchableObject.java

@@ -0,0 +1,5 @@
+package cz.senslog.analyzer.core.api;
+
+public interface WatchableObject {
+    int hash();
+}

+ 37 - 0
src/main/java/cz/senslog/analyzer/domain/CollectedStatistics.java

@@ -0,0 +1,37 @@
+package cz.senslog.analyzer.domain;
+
+import cz.senslog.analyzer.core.api.WatchableObject;
+
+public class CollectedStatistics implements WatchableObject {
+
+    private final Timestamp startTime, endTime;
+    private final DoubleStatistics statistics;
+
+    public CollectedStatistics(Group group, Timestamp startTime) {
+        this(DoubleStatistics.init(group, startTime));
+    }
+
+    public CollectedStatistics(DoubleStatistics statistics) {
+        this.startTime = statistics.getTimestamp();
+        long interval = statistics.getSource().getInterval();
+        this.endTime = Timestamp.of(startTime.get().plusSeconds(interval));
+        this.statistics = statistics;
+    }
+
+    public Timestamp getStartTime() {
+        return startTime;
+    }
+
+    public Timestamp getEndTime() {
+        return endTime;
+    }
+
+    public DoubleStatistics getStatistics() {
+        return statistics;
+    }
+
+    @Override
+    public int hash() {
+        return statistics.hashCode();
+    }
+}

+ 14 - 3
src/main/java/cz/senslog/analyzer/domain/Timestamp.java

@@ -1,13 +1,24 @@
 package cz.senslog.analyzer.domain;
 
-import java.time.Instant;
-import java.time.OffsetDateTime;
+import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
 import java.util.Objects;
 
 public class Timestamp {
 
+    private static final DateTimeFormatter INPUT_FORMATTER = new DateTimeFormatterBuilder()
+            .append(DateTimeFormatter.ofPattern("yyyy-MM-dd[ HH:mm:ss]"))
+            .optionalStart()
+            .appendOffset("+HH", "+00")
+            .optionalEnd()
+            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
+            .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
+            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
+            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
+            .toFormatter();
+
     private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
             .append(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
             .optionalStart().appendOffset("+HH", "+00").optionalEnd()
@@ -26,7 +37,7 @@ public class Timestamp {
     private final OffsetDateTime value;
 
     public static Timestamp parse(String value) {
-        return of(OffsetDateTime.parse(value, DATE_TIME_FORMATTER));
+        return of(OffsetDateTime.parse(value, INPUT_FORMATTER));
     }
 
     public static Timestamp now() {

+ 0 - 54
src/main/java/cz/senslog/analyzer/persistence/ConnectionModule.java

@@ -1,54 +0,0 @@
-package cz.senslog.analyzer.persistence;
-
-import com.zaxxer.hikari.HikariConfig;
-import com.zaxxer.hikari.HikariDataSource;
-import dagger.Module;
-import dagger.Provides;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.jdbi.v3.core.Jdbi;
-import org.jdbi.v3.jodatime2.JodaTimePlugin;
-import org.jdbi.v3.postgres.PostgresPlugin;
-import org.postgresql.ds.PGSimpleDataSource;
-
-import javax.inject.Singleton;
-
-@Module
-public class ConnectionModule {
-
-    private static final Logger logger = LogManager.getLogger(ConnectionModule.class);
-
-    private final DatabaseConfig config;
-
-    public static ConnectionModule create(DatabaseConfig dbConfig) {
-        return new ConnectionModule(dbConfig);
-    }
-
-    private ConnectionModule(DatabaseConfig dbConfig) {
-        this.config = dbConfig;
-    }
-
-    private Connection<Jdbi> connection;
-
-    @Provides @Singleton
-    public Connection<Jdbi> provideConnection() {
-        if (connection == null) {
-            logger.info("Creating a connection to the database of the class: {}.", Jdbi.class);
-            PGSimpleDataSource ds = new PGSimpleDataSource();
-            ds.setUrl(config.getConnectionUrl());
-            ds.setPassword(config.getPassword());
-            ds.setUser(config.getUsername());
-            ds.setLoadBalanceHosts(true);
-            HikariConfig hc = new HikariConfig();
-            hc.setDataSource(ds);
-            hc.setMaximumPoolSize(config.getConnectionPoolSize());
-
-            connection = new Connection<>(Jdbi
-                    .create(new HikariDataSource(hc))
-                    .installPlugin(new PostgresPlugin())
-                    .installPlugin(new JodaTimePlugin())
-            );
-        }
-        return connection;
-    }
-}

+ 0 - 36
src/main/java/cz/senslog/analyzer/persistence/RepositoryModule.java

@@ -1,36 +0,0 @@
-package cz.senslog.analyzer.persistence;
-
-import cz.senslog.analyzer.persistence.repository.SensLogRepository;
-import cz.senslog.analyzer.persistence.repository.StatisticsConfigRepository;
-import cz.senslog.analyzer.persistence.repository.StatisticsRepository;
-import dagger.Module;
-import dagger.Provides;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.jdbi.v3.core.Jdbi;
-
-import javax.inject.Singleton;
-
-@Module(includes = ConnectionModule.class)
-public class RepositoryModule {
-
-    private static Logger logger = LogManager.getLogger(RepositoryModule.class);
-
-    @Provides @Singleton
-    public SensLogRepository provideObservationRepository(Connection<Jdbi> connection) {
-        logger.info("Creating a new instance of {}.", SensLogRepository.class);
-        return new SensLogRepository(connection);
-    }
-
-    @Provides @Singleton
-    public StatisticsConfigRepository provideStatisticsConfigRepository(Connection<Jdbi> connection) {
-        logger.info("Creating a new instance of {}.", StatisticsConfigRepository.class);
-        return new StatisticsConfigRepository(connection);
-    }
-
-    @Provides @Singleton
-    public StatisticsRepository provideStatisticsRepository(Connection<Jdbi> connection) {
-        logger.info("Creating a new instance of {}.", StatisticsRepository.class);
-        return new StatisticsRepository(connection);
-    }
-}

+ 2 - 2
src/main/java/cz/senslog/analyzer/provider/ScheduleDatabaseProviderModule.java

@@ -1,7 +1,7 @@
 package cz.senslog.analyzer.provider;
 
-import cz.senslog.analyzer.persistence.RepositoryModule;
-import cz.senslog.analyzer.persistence.repository.SensLogRepository;
+import cz.senslog.analyzer.storage.RepositoryModule;
+import cz.senslog.analyzer.storage.permanent.repository.SensLogRepository;
 import dagger.Module;
 import dagger.Provides;
 

+ 1 - 1
src/main/java/cz/senslog/analyzer/provider/ScheduledDatabaseProvider.java

@@ -3,7 +3,7 @@ package cz.senslog.analyzer.provider;
 import cz.senslog.analyzer.analysis.Analyzer;
 import cz.senslog.analyzer.domain.Observation;
 import cz.senslog.analyzer.domain.Timestamp;
-import cz.senslog.analyzer.persistence.repository.SensLogRepository;
+import cz.senslog.analyzer.storage.permanent.repository.SensLogRepository;
 import cz.senslog.common.util.schedule.Scheduler;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;

+ 1 - 1
src/main/java/cz/senslog/analyzer/server/handler/GroupsHandler.java

@@ -1,6 +1,6 @@
 package cz.senslog.analyzer.server.handler;
 
-import cz.senslog.analyzer.persistence.repository.StatisticsConfigRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsConfigRepository;
 import cz.senslog.analyzer.server.vertx.AbstractRestHandler;
 
 import javax.inject.Inject;

+ 4 - 3
src/main/java/cz/senslog/analyzer/server/handler/StatisticsHandler.java

@@ -4,8 +4,8 @@ import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonSerializer;
 import cz.senslog.analyzer.domain.*;
-import cz.senslog.analyzer.persistence.repository.StatisticsConfigRepository;
-import cz.senslog.analyzer.persistence.repository.StatisticsRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsConfigRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsRepository;
 import cz.senslog.analyzer.server.vertx.AbstractRestHandler;
 import cz.senslog.common.util.TimeRange;
 import cz.senslog.common.util.Tuple;
@@ -19,6 +19,7 @@ import org.apache.logging.log4j.Logger;
 import javax.inject.Inject;
 import java.time.Instant;
 import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 
@@ -84,7 +85,7 @@ public class StatisticsHandler extends AbstractRestHandler {
 
                 groupByParam = GroupBy.parse(params.get("group_by"));
                 intervalParam = Interval.parse(params.get("interval"));
-            } catch (NumberFormatException e) {
+            } catch (NumberFormatException | DateTimeParseException e) {
                 response.setStatusCode(404).end(new JsonObject()
                         .put("message", e.getMessage()).encode()); return;
             }

+ 1 - 1
src/main/java/cz/senslog/analyzer/server/vertx/VertxHandlersModule.java

@@ -1,6 +1,6 @@
 package cz.senslog.analyzer.server.vertx;
 
-import cz.senslog.analyzer.persistence.RepositoryModule;
+import cz.senslog.analyzer.storage.RepositoryModule;
 import cz.senslog.analyzer.server.handler.GroupsHandler;
 import cz.senslog.analyzer.server.handler.InfoHandler;
 import cz.senslog.analyzer.server.handler.StatisticsHandler;

+ 1 - 1
src/main/java/cz/senslog/analyzer/persistence/Connection.java → src/main/java/cz/senslog/analyzer/storage/Connection.java

@@ -1,4 +1,4 @@
-package cz.senslog.analyzer.persistence;
+package cz.senslog.analyzer.storage;
 
 
 public class Connection<T> {

+ 79 - 0
src/main/java/cz/senslog/analyzer/storage/ConnectionModule.java

@@ -0,0 +1,79 @@
+package cz.senslog.analyzer.storage;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import cz.senslog.analyzer.storage.inmemory.InMemoryConnection;
+import cz.senslog.analyzer.storage.inmemory.InMemoryStorageConfig;
+import cz.senslog.analyzer.storage.permanent.PermanentStorageConfig;
+import cz.senslog.analyzer.storage.permanent.PermanentConnection;
+import dagger.Module;
+import dagger.Provides;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdbi.v3.core.Jdbi;
+import org.jdbi.v3.jodatime2.JodaTimePlugin;
+import org.jdbi.v3.postgres.PostgresPlugin;
+import org.postgresql.ds.PGSimpleDataSource;
+
+import javax.inject.Singleton;
+
+import static java.lang.String.format;
+
+@Module
+public class ConnectionModule {
+
+    private static final Logger logger = LogManager.getLogger(ConnectionModule.class);
+
+    private final StorageConfig config;
+
+    public static ConnectionModule create(StorageConfig storageConfig) {
+        return new ConnectionModule(storageConfig);
+    }
+
+    private ConnectionModule(StorageConfig config) {
+        this.config = config;
+    }
+
+    private PermanentConnection permanentConnection;
+    private InMemoryConnection inMemoryConnection;
+
+    @Provides @Singleton
+    public PermanentConnection providePermanentStorageConnection() {
+        if (permanentConnection == null) {
+            PermanentStorageConfig config = this.config.getPermanentStorageConfig();
+            logger.info("Creating a connection to the database of the class: {}.", Jdbi.class);
+            PGSimpleDataSource ds = new PGSimpleDataSource();
+            ds.setUrl(config.getConnectionUrl());
+            ds.setPassword(config.getPassword());
+            ds.setUser(config.getUsername());
+            ds.setLoadBalanceHosts(true);
+            HikariConfig hc = new HikariConfig();
+            hc.setDataSource(ds);
+            hc.setMaximumPoolSize(config.getConnectionPoolSize());
+
+            permanentConnection = new PermanentConnection(Jdbi
+                    .create(new HikariDataSource(hc))
+                    .installPlugin(new PostgresPlugin())
+                    .installPlugin(new JodaTimePlugin())
+            );
+        }
+        return permanentConnection;
+    }
+
+    @Provides @Singleton
+    public InMemoryConnection provideInMemoryStorageConnection() {
+        if (inMemoryConnection == null) {
+            InMemoryStorageConfig config = this.config.getInMemoryStorageConfig();
+
+            StringBuilder url = new StringBuilder("jdbc:h2");
+            url.append(config.isPersistence() ? ":file" : ":mem");
+            url.append(":").append(config.getPath());
+            if (!config.getParameters().isEmpty()) {
+                url.append(":").append(config.getParameters());
+            }
+
+            inMemoryConnection = new InMemoryConnection(Jdbi.create(url.toString()));
+        }
+        return inMemoryConnection;
+    }
+}

+ 44 - 0
src/main/java/cz/senslog/analyzer/storage/RepositoryModule.java

@@ -0,0 +1,44 @@
+package cz.senslog.analyzer.storage;
+
+import cz.senslog.analyzer.storage.inmemory.InMemoryConnection;
+import cz.senslog.analyzer.storage.inmemory.repository.CollectedStatisticsRepository;
+import cz.senslog.analyzer.storage.permanent.PermanentConnection;
+import cz.senslog.analyzer.storage.permanent.repository.SensLogRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsConfigRepository;
+import cz.senslog.analyzer.storage.permanent.repository.StatisticsRepository;
+import dagger.Module;
+import dagger.Provides;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.inject.Singleton;
+
+@Module(includes = ConnectionModule.class)
+public class RepositoryModule {
+
+    private static final Logger logger = LogManager.getLogger(RepositoryModule.class);
+
+    @Provides @Singleton
+    public SensLogRepository provideObservationRepository(PermanentConnection connection) {
+        logger.info("Creating a new instance of {}.", SensLogRepository.class);
+        return new SensLogRepository(connection);
+    }
+
+    @Provides @Singleton
+    public StatisticsConfigRepository provideStatisticsConfigRepository(PermanentConnection connection) {
+        logger.info("Creating a new instance of {}.", StatisticsConfigRepository.class);
+        return new StatisticsConfigRepository(connection);
+    }
+
+    @Provides @Singleton
+    public StatisticsRepository provideStatisticsRepository(PermanentConnection connection) {
+        logger.info("Creating a new instance of {}.", StatisticsRepository.class);
+        return new StatisticsRepository(connection);
+    }
+
+    @Provides @Singleton
+    public CollectedStatisticsRepository provideCollectedStatisticsRepository(InMemoryConnection connection) {
+        logger.info("Creating a new instance of {}.", CollectedStatisticsRepository.class);
+        return new CollectedStatisticsRepository(connection);
+    }
+}

+ 24 - 0
src/main/java/cz/senslog/analyzer/storage/StorageConfig.java

@@ -0,0 +1,24 @@
+package cz.senslog.analyzer.storage;
+
+import cz.senslog.analyzer.storage.inmemory.InMemoryStorageConfig;
+import cz.senslog.analyzer.storage.permanent.PermanentStorageConfig;
+
+public class StorageConfig {
+
+    private final PermanentStorageConfig permanentStorageConfig;
+
+    private final InMemoryStorageConfig inMemoryStorageConfig;
+
+    public StorageConfig(PermanentStorageConfig permanentStorageConfig, InMemoryStorageConfig inMemoryStorageConfig) {
+        this.permanentStorageConfig = permanentStorageConfig;
+        this.inMemoryStorageConfig = inMemoryStorageConfig;
+    }
+
+    public PermanentStorageConfig getPermanentStorageConfig() {
+        return permanentStorageConfig;
+    }
+
+    public InMemoryStorageConfig getInMemoryStorageConfig() {
+        return inMemoryStorageConfig;
+    }
+}

+ 48 - 0
src/main/java/cz/senslog/analyzer/storage/inmemory/CollectedStatisticsStorage.java

@@ -0,0 +1,48 @@
+package cz.senslog.analyzer.storage.inmemory;
+
+import cz.senslog.analyzer.core.AbstractWatchableObjects;
+import cz.senslog.analyzer.domain.CollectedStatistics;
+import cz.senslog.analyzer.domain.Group;
+import cz.senslog.analyzer.storage.inmemory.repository.CollectedStatisticsRepository;
+
+import java.util.List;
+
+public class CollectedStatisticsStorage extends AbstractWatchableObjects<CollectedStatistics> {
+
+    private final CollectedStatisticsRepository repository;
+
+    public static CollectedStatisticsStorage createContext(CollectedStatisticsRepository repository) {
+        return new CollectedStatisticsStorage(repository);
+    }
+
+    private CollectedStatisticsStorage(CollectedStatisticsRepository repository) {
+        this.repository = repository;
+    }
+
+    @Override
+    public CollectedStatistics watch(CollectedStatistics value) {
+        return repository.save(value) ? super.watch(value) : value;
+    }
+
+    @Override
+    public CollectedStatistics remove(CollectedStatistics value) {
+        return repository.delete(value) ? super.remove(value) : value;
+    }
+
+    @Override
+    public boolean commit() {
+        boolean res = true;
+        for (CollectedStatistics change : changes()) {
+            res &= repository.save(change);
+        }
+        return res && super.commit();
+    }
+
+    public List<CollectedStatistics> restore(Group group) {
+        List<CollectedStatistics> saved = repository.selectByGroup(group);
+        for (CollectedStatistics st : saved) {
+            super.watch(st);
+        }
+        return saved;
+    }
+}

+ 10 - 0
src/main/java/cz/senslog/analyzer/storage/inmemory/InMemoryConnection.java

@@ -0,0 +1,10 @@
+package cz.senslog.analyzer.storage.inmemory;
+
+import cz.senslog.analyzer.storage.Connection;
+import org.jdbi.v3.core.Jdbi;
+
+public class InMemoryConnection extends Connection<Jdbi> {
+    public InMemoryConnection(Jdbi connection) {
+        super(connection);
+    }
+}

+ 29 - 0
src/main/java/cz/senslog/analyzer/storage/inmemory/InMemoryStorageConfig.java

@@ -0,0 +1,29 @@
+package cz.senslog.analyzer.storage.inmemory;
+
+public class InMemoryStorageConfig {
+
+    private final String path;
+
+    private final boolean persistence;
+
+    private final String parameters;
+
+
+    public InMemoryStorageConfig(String path, boolean persistence, String parameters) {
+        this.path = path;
+        this.persistence = persistence;
+        this.parameters = parameters;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public boolean isPersistence() {
+        return persistence;
+    }
+
+    public String getParameters() {
+        return parameters;
+    }
+}

+ 96 - 0
src/main/java/cz/senslog/analyzer/storage/inmemory/repository/CollectedStatisticsRepository.java

@@ -0,0 +1,96 @@
+package cz.senslog.analyzer.storage.inmemory.repository;
+
+import cz.senslog.analyzer.domain.CollectedStatistics;
+import cz.senslog.analyzer.domain.DoubleStatistics;
+import cz.senslog.analyzer.domain.Group;
+import cz.senslog.analyzer.domain.Timestamp;
+import cz.senslog.analyzer.storage.Connection;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jdbi.v3.core.Jdbi;
+
+import javax.inject.Inject;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.List;
+
+public class CollectedStatisticsRepository {
+
+    private static final Logger logger = LogManager.getLogger(CollectedStatisticsRepository.class);
+
+    private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
+            .appendPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")
+            .parseLenient()
+            .appendOffset("+HH", "Z")
+            .toFormatter();
+
+    private final Jdbi jdbi;
+
+    @Inject
+    public CollectedStatisticsRepository(Connection<Jdbi> connection) {
+        this.jdbi = connection.get();
+
+        create();
+    }
+
+    private void create() {
+        jdbi.withHandle(h -> h.execute(
+                "CREATE TABLE IF NOT EXISTS double_statistics (" +
+                        "group_id INTEGER NOT NULL , " +
+                        "timestamp TIMESTAMP WITH TIME ZONE NOT NULL, " +
+                        "count INTEGER NOT NULL, " +
+                        "min INTEGER NOT NULL, " +
+                        "max INTEGER NOT NULL, " +
+                        "sum INTEGER NOT NULL," +
+                        "PRIMARY KEY (group_id,timestamp)" +
+                        ")"
+        ));
+    }
+
+    public boolean save(CollectedStatistics statistics) {
+        long count = statistics.getStatistics().getCount();
+        return count > 0 && (count == 1 ? insert(statistics) : update(statistics));
+    }
+
+    private boolean insert(CollectedStatistics statistics) {
+        DoubleStatistics ds = statistics.getStatistics();
+        int affectedRows = jdbi.withHandle(h -> h.execute(
+                "INSERT INTO double_statistics(group_id, timestamp, count, min, max, sum) VALUES (?,?,?,?,?,?)",
+                ds.getSource().getId(), ds.getTimestamp().get(), ds.getCount(), ds.getMin(), ds.getMax(), ds.getSum()
+        ));
+        return affectedRows == 1;
+    }
+
+    private boolean update(CollectedStatistics statistics) {
+        DoubleStatistics ds = statistics.getStatistics();
+        int affectedRows = jdbi.withHandle(h -> h.execute(
+                "UPDATE double_statistics SET count=?, min=?, max=?, sum=? WHERE group_id=? AND timestamp=?",
+                ds.getCount(), ds.getMin(), ds.getMax(), ds.getSum(), ds.getSource().getId(), ds.getTimestamp().get()
+        ));
+        return affectedRows == 1;
+    }
+
+    public boolean delete(CollectedStatistics statistics) {
+        DoubleStatistics ds = statistics.getStatistics();
+        int affectedRows = jdbi.withHandle(h -> h.execute(
+                "DELETE FROM double_statistics WHERE group_id=? AND timestamp=?",
+                ds.getSource().getId(), ds.getTimestamp().get()
+        ));
+        return affectedRows == 1;
+    }
+
+    public List<CollectedStatistics> selectByGroup(Group group) {
+        return jdbi.withHandle(h -> h
+                .createQuery("SELECT timestamp, count, min, max, sum FROM double_statistics WHERE group_id=:group_id")
+                .bind("group_id", group.getId())
+                .map((rs, rv) -> new CollectedStatistics(new DoubleStatistics(
+                        group,
+                        rs.getLong("count"),
+                        rs.getDouble("min"),
+                        rs.getDouble("max"),
+                        rs.getDouble("sum"),
+                        Timestamp.parse(rs.getString("timestamp"))
+                ))).list()
+        );
+    }
+}

+ 11 - 0
src/main/java/cz/senslog/analyzer/storage/permanent/PermanentConnection.java

@@ -0,0 +1,11 @@
+package cz.senslog.analyzer.storage.permanent;
+
+import cz.senslog.analyzer.storage.Connection;
+import org.jdbi.v3.core.Jdbi;
+
+public class PermanentConnection extends Connection<Jdbi> {
+
+    public PermanentConnection(Jdbi connection) {
+        super(connection);
+    }
+}

+ 3 - 3
src/main/java/cz/senslog/analyzer/persistence/DatabaseConfig.java → src/main/java/cz/senslog/analyzer/storage/permanent/PermanentStorageConfig.java

@@ -1,6 +1,6 @@
-package cz.senslog.analyzer.persistence;
+package cz.senslog.analyzer.storage.permanent;
 
-public class DatabaseConfig {
+public class PermanentStorageConfig {
 
     private final String connectionUrl;
 
@@ -10,7 +10,7 @@ public class DatabaseConfig {
 
     private final Integer connectionPoolSize;
 
-    public DatabaseConfig(String connectionUrl, String username, String password, Integer connectionPoolSize) {
+    public PermanentStorageConfig(String connectionUrl, String username, String password, Integer connectionPoolSize) {
         this.connectionUrl = connectionUrl;
         this.username = username;
         this.password = password;

+ 2 - 2
src/main/java/cz/senslog/analyzer/persistence/repository/SensLogRepository.java → src/main/java/cz/senslog/analyzer/storage/permanent/repository/SensLogRepository.java

@@ -1,10 +1,10 @@
-package cz.senslog.analyzer.persistence.repository;
+package cz.senslog.analyzer.storage.permanent.repository;
 
 import cz.senslog.analyzer.domain.Alert;
 import cz.senslog.analyzer.domain.Observation;
 import cz.senslog.analyzer.domain.Sensor;
 import cz.senslog.analyzer.domain.Timestamp;
-import cz.senslog.analyzer.persistence.Connection;
+import cz.senslog.analyzer.storage.Connection;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.jdbi.v3.core.Jdbi;

+ 2 - 4
src/main/java/cz/senslog/analyzer/persistence/repository/StatisticsConfigRepository.java → src/main/java/cz/senslog/analyzer/storage/permanent/repository/StatisticsConfigRepository.java

@@ -1,18 +1,16 @@
-package cz.senslog.analyzer.persistence.repository;
+package cz.senslog.analyzer.storage.permanent.repository;
 
 import cz.senslog.analyzer.domain.*;
-import cz.senslog.analyzer.persistence.Connection;
+import cz.senslog.analyzer.storage.Connection;
 import cz.senslog.common.util.Tuple;
 import org.jdbi.v3.core.Jdbi;
 
 import javax.inject.Inject;
 import java.util.AbstractMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
 import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
 import static java.util.stream.Collectors.*;
 
 public class StatisticsConfigRepository {

+ 2 - 2
src/main/java/cz/senslog/analyzer/persistence/repository/StatisticsRepository.java → src/main/java/cz/senslog/analyzer/storage/permanent/repository/StatisticsRepository.java

@@ -1,7 +1,7 @@
-package cz.senslog.analyzer.persistence.repository;
+package cz.senslog.analyzer.storage.permanent.repository;
 
 import cz.senslog.analyzer.domain.*;
-import cz.senslog.analyzer.persistence.Connection;
+import cz.senslog.analyzer.storage.Connection;
 import cz.senslog.common.util.TimeRange;
 import cz.senslog.common.util.Tuple;
 import org.apache.logging.log4j.LogManager;

+ 16 - 0
src/test/java/cz/senslog/analyzer/domain/TimestampTest.java

@@ -0,0 +1,16 @@
+package cz.senslog.analyzer.domain;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TimestampTest {
+
+    @Test
+    void parse() {
+
+        System.out.println(Timestamp.parse("2020-12-30"));
+        System.out.println(Timestamp.parse("2020-12-30 12:30:50+02"));
+
+    }
+}