diff --git a/bundles/org.connectorio.addons.persistence.memory/pom.xml b/bundles/org.connectorio.addons.persistence.memory/pom.xml
new file mode 100644
index 00000000..b48b5127
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+
+ 4.0.0
+
+
+ org.connectorio.addons
+ bundles
+ 3.0.0-SNAPSHOT
+
+
+ org.connectorio.addons.persistence.memory
+ bundle
+
+ ConnectorIO - Addons - Persistence - Memory
+ A memory persistence service.
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core.persistence
+
+
+ org.openhab.core.bundles
+ org.openhab.core.config.xml
+
+
+ com.thoughtworks.xstream
+ xstream
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.osgi
+ osgi.core
+
+
+ org.osgi
+ org.osgi.service.component.annotations
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+
+
+ org.connectorio.addons
+ org.connectorio.addons.test
+
+
+
+
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryBucket.java b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryBucket.java
new file mode 100644
index 00000000..47748eab
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryBucket.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.connectorio.addons.persistence.memory.internal;
+
+import java.util.Comparator;
+import java.util.Date;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.TreeSet;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MemoryBucket {
+
+ private final Logger logger = LoggerFactory.getLogger(MemoryBucket.class);
+
+ private final NavigableSet entries = new TreeSet<>(
+ Comparator.comparing(MemoryEntry::getTimestamp)
+ );
+
+ private final Lock lock = new ReentrantLock();
+ private int limit;
+
+ public MemoryBucket(int limit) {
+ this.limit = limit;
+ }
+
+ public void append(MemoryEntry entry) {
+ apply(myself -> {
+ logger.trace("Inserted entry {}. Stored entries {}, limit {}", entry, entries.size(), limit);
+ myself.entries.add(entry);
+ });
+ }
+
+ public void truncate(int limit) {
+ this.limit = limit;
+
+ apply(myself -> {
+ while (myself.entries.size() > limit) {
+ MemoryEntry entry = myself.entries.pollFirst();
+ logger.trace("Removed bucket entry {} as it exceeds limit {}", entry, limit);
+ }
+ });
+ }
+
+ public Stream entries() {
+ return entries.stream();
+ }
+
+ public void remove(MemoryEntry entry) {
+ apply(myself -> myself.entries.remove(entry));
+ }
+
+ public Integer getSize() {
+ return entries.size();
+ }
+
+ public Date getEarliest() {
+ return entries.isEmpty() ? null : Date.from(entries.first().getTimestamp().toInstant());
+ }
+
+ public Date getOldest() {
+ return entries.isEmpty() ? null : Date.from(entries.last().getTimestamp().toInstant());
+ }
+
+ public X process(Function function) {
+ lock.lock();
+ try {
+ return function.apply(this);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void apply(Consumer consumer) {
+ lock.lock();
+ try {
+ consumer.accept(this);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof MemoryBucket)) {
+ return false;
+ }
+ MemoryBucket bucket = (MemoryBucket) o;
+ return limit == bucket.limit && Objects.equals(entries, bucket.entries);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(entries, limit);
+ }
+
+}
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryBucketInfo.java b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryBucketInfo.java
new file mode 100644
index 00000000..e486f554
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryBucketInfo.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.connectorio.addons.persistence.memory.internal;
+
+import java.util.Date;
+import org.openhab.core.persistence.PersistenceItemInfo;
+
+public class MemoryBucketInfo implements PersistenceItemInfo {
+
+ private final String name;
+ private final Integer count;
+ private final Date earliest;
+ private final Date oldest;
+
+ public MemoryBucketInfo(String name, Integer count, Date earliest, Date oldest) {
+ this.name = name;
+ this.count = count;
+ this.earliest = earliest;
+ this.oldest = oldest;
+ }
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Integer getCount() {
+ return count;
+ }
+
+ @Override
+ public Date getEarliest() {
+ return earliest;
+ }
+
+ @Override
+ public Date getLatest() {
+ return oldest;
+ }
+}
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryEntry.java b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryEntry.java
new file mode 100644
index 00000000..5ba396ff
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryEntry.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.connectorio.addons.persistence.memory.internal;
+
+import java.time.ZonedDateTime;
+import java.util.Objects;
+import org.openhab.core.types.State;
+
+public class MemoryEntry {
+
+ private final ZonedDateTime timestamp;
+ private final State state;
+
+ public MemoryEntry(ZonedDateTime timestamp, State state) {
+ this.timestamp = timestamp;
+ this.state = state;
+ }
+
+ public ZonedDateTime getTimestamp() {
+ return timestamp;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof MemoryEntry)) {
+ return false;
+ }
+ MemoryEntry that = (MemoryEntry) o;
+ return Objects.equals(getTimestamp(), that.getTimestamp()) && Objects.equals(
+ getState(), that.getState());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getTimestamp(), getState());
+ }
+
+ public String toString() {
+ return "MemoryEntry [" + timestamp + "=" + state + "]";
+ }
+}
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryHistoricItem.java b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryHistoricItem.java
new file mode 100644
index 00000000..aaeb56c1
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryHistoricItem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.connectorio.addons.persistence.memory.internal;
+
+import java.time.ZonedDateTime;
+import org.openhab.core.persistence.HistoricItem;
+import org.openhab.core.types.State;
+
+public class MemoryHistoricItem implements HistoricItem {
+ private final String name;
+ private final ZonedDateTime timestamp;
+ private final State state;
+
+ public MemoryHistoricItem(String name, ZonedDateTime timestamp, State state) {
+ this.name = name;
+ this.timestamp = timestamp;
+ this.state = state;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public ZonedDateTime getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public State getState() {
+ return state;
+ }
+
+}
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryPersistenceService.java b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryPersistenceService.java
new file mode 100644
index 00000000..0555b086
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/main/java/org/connectorio/addons/persistence/memory/internal/MemoryPersistenceService.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.connectorio.addons.persistence.memory.internal;
+
+import java.time.ZonedDateTime;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.items.Item;
+import org.openhab.core.persistence.FilterCriteria;
+import org.openhab.core.persistence.FilterCriteria.Ordering;
+import org.openhab.core.persistence.HistoricItem;
+import org.openhab.core.persistence.ModifiablePersistenceService;
+import org.openhab.core.persistence.PersistenceItemInfo;
+import org.openhab.core.persistence.PersistenceService;
+import org.openhab.core.persistence.QueryablePersistenceService;
+import org.openhab.core.persistence.strategy.PersistenceStrategy;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(property = {"id=memory",
+ "service.config.label=Memory Persistence Configuration",
+ "service.config.category=ConnectorIO Gateway",
+ "service.config.description.uri=connectorio:memory-persistence-service",
+ Constants.SERVICE_PID + "=" + MemoryPersistenceService.SERVICE_ID
+}, service = {PersistenceService.class, QueryablePersistenceService.class, ModifiablePersistenceService.class})
+public class MemoryPersistenceService implements ModifiablePersistenceService {
+
+ public final static String SERVICE_ID = "org.connectorio.addons.persistence.memory";
+ private static final String ID = "memory";
+
+ private final Logger logger = LoggerFactory.getLogger(ModifiablePersistenceService.class);
+ private final Map buckets = new ConcurrentHashMap<>();
+
+ private final TimeZoneProvider timeZoneProvider;
+
+ // number of entries for each bucket
+ private int limit = 100;
+
+ @Activate
+ public MemoryPersistenceService(@Reference TimeZoneProvider timeZoneProvider) {
+ this.timeZoneProvider = timeZoneProvider;
+ }
+
+ @Modified
+ public void update(Map config) {
+ if (config.containsKey("limit")) {
+ try {
+ int newLimit = Integer.parseInt("" + config.get("limit"));
+ this.limit = newLimit > 0 ? newLimit : 1;
+
+ for (MemoryBucket bucket : buckets.values()) {
+ bucket.truncate(limit);
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid value for limit");
+ }
+ }
+ }
+
+ @Override
+ public Iterable query(FilterCriteria criteria) {
+ logger.debug("Received call for item {} and date range {}-{}", criteria.getItemName(), criteria.getBeginDate(), criteria.getEndDate());
+ if (criteria.getItemName() == null || !buckets.containsKey(criteria.getItemName())) {
+ logger.debug("Received call can not be handled - unknown bucket or missing data");
+ return Collections.emptyList();
+ }
+
+ logger.debug("Querying item {} with date range {}-{}", criteria.getItemName(), criteria.getBeginDate(), criteria.getEndDate());
+ return buckets.get(criteria.getItemName()).process(bucket -> {
+ Stream entries = filtering(bucket.entries(), criteria);
+ entries = sorting(entries, criteria);
+ entries = paging(entries, criteria);
+
+ return entries.map(entry -> toHistoricItem(criteria.getItemName(), entry))
+ .collect(Collectors.toList());
+ });
+ }
+
+ @Override
+ public Set getItemInfo() {
+ Set infos = new TreeSet<>(Comparator.comparing(PersistenceItemInfo::getName));
+ for (Entry entry : buckets.entrySet()) {
+ MemoryBucketInfo info = entry.getValue().process(bucket -> new MemoryBucketInfo(entry.getKey(), bucket.getSize(), bucket.getEarliest(), bucket.getOldest()));
+ infos.add(info);
+ }
+ return infos;
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public String getLabel(Locale locale) {
+ return "Memory";
+ }
+
+ @Override
+ public void store(Item item) {
+ ZonedDateTime now = ZonedDateTime.now(timeZoneProvider.getTimeZone());
+
+ memorize(item.getName(), now, item.getState());
+ }
+
+ @Override
+ public void store(Item item, String alias) {
+ ZonedDateTime now = ZonedDateTime.now(timeZoneProvider.getTimeZone());
+
+ String name = alias != null ? alias : item.getName();
+ memorize(name, now, item.getState());
+ }
+
+ @Override
+ public void store(Item item, Date date, State state) {
+ ZonedDateTime time = ZonedDateTime.ofInstant(date.toInstant(), timeZoneProvider.getTimeZone());
+ memorize(item.getName(), time, state);
+ }
+
+ @Override
+ public boolean remove(FilterCriteria criteria) throws IllegalArgumentException {
+ if (criteria.getItemName() == null || !buckets.containsKey(criteria.getItemName())) {
+ return false;
+ }
+
+ logger.trace("Removing entries from bucket {}", criteria.getItemName());
+ return buckets.get(criteria.getItemName()).process(bucket -> {
+ Stream entries = filtering(bucket.entries(), criteria);
+
+ entries = sorting(entries, criteria);
+ entries = paging(entries, criteria);
+ entries.forEach(bucket::remove);
+ return true;
+ });
+ }
+
+ @Override
+ public List getDefaultStrategies() {
+ return Collections.emptyList();
+ }
+
+ private void memorize(String name, ZonedDateTime time, State state) {
+ if (state instanceof UnDefType) {
+ return;
+ }
+
+ MemoryBucket bucket = buckets.computeIfAbsent(name, key -> new MemoryBucket(limit));
+ MemoryEntry entry = new MemoryEntry(time, state);
+ logger.trace("Storing entry {} in bucket {} for item {}", entry, bucket, name);
+ bucket.append(entry);
+ }
+ private HistoricItem toHistoricItem(String name, MemoryEntry entry) {
+ return new MemoryHistoricItem(name, entry.getTimestamp(), entry.getState());
+ }
+
+ private static Stream filtering(Stream entries, FilterCriteria criteria) {
+ return entries.filter(entry -> evaluate(entry, criteria));
+ }
+
+ private static Stream sorting(Stream entries, FilterCriteria criteria) {
+ if (criteria.getOrdering() == Ordering.DESCENDING) {
+ entries = entries.sorted(Comparator.comparing(MemoryEntry::getTimestamp).reversed());
+ }
+ return entries;
+ }
+
+ private static Stream paging(Stream stream, FilterCriteria criteria) {
+ int offset = criteria.getPageNumber() * criteria.getPageSize();
+ return stream.skip(offset)
+ .limit(criteria.getPageSize());
+ }
+
+ private static boolean evaluate(MemoryEntry entry, FilterCriteria criteria) {
+ ZonedDateTime beginDate = criteria.getBeginDate();
+ if (beginDate != null && entry.getTimestamp().isBefore(beginDate)) {
+ return false;
+ }
+
+ ZonedDateTime endDate = criteria.getEndDate();
+ if (endDate != null && entry.getTimestamp().isAfter(endDate)) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+}
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/main/resources/OH-INF/config/memory.xml b/bundles/org.connectorio.addons.persistence.memory/src/main/resources/OH-INF/config/memory.xml
new file mode 100644
index 00000000..77c152d4
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/main/resources/OH-INF/config/memory.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ Number of entries kept in memory for each configured item.
+ 100
+
+
+
+
diff --git a/bundles/org.connectorio.addons.persistence.memory/src/test/java/org/connectorio/addons/persistence/memory/internal/MemoryPersistenceServiceTest.java b/bundles/org.connectorio.addons.persistence.memory/src/test/java/org/connectorio/addons/persistence/memory/internal/MemoryPersistenceServiceTest.java
new file mode 100644
index 00000000..08b3f1fe
--- /dev/null
+++ b/bundles/org.connectorio.addons.persistence.memory/src/test/java/org/connectorio/addons/persistence/memory/internal/MemoryPersistenceServiceTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.connectorio.addons.persistence.memory.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+import java.time.ZoneId;
+import org.assertj.core.api.IterableAssert;
+import org.connectorio.addons.test.ItemMutation;
+import org.connectorio.addons.test.StubItemBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.items.Item;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.persistence.FilterCriteria;
+import org.openhab.core.persistence.FilterCriteria.Ordering;
+import org.openhab.core.persistence.HistoricItem;
+
+@ExtendWith(MockitoExtension.class)
+class MemoryPersistenceServiceTest {
+
+ public static final String TEST_1 = "test1";
+ public static final String TEST_2 = "test2";
+ @Mock
+ TimeZoneProvider tz;
+
+ Item item1 = StubItemBuilder.createNumber(TEST_1).build();
+ Item item2 = StubItemBuilder.createNumber(TEST_2).build();
+
+ @BeforeEach
+ void setup() {
+ when(tz.getTimeZone()).thenReturn(ZoneId.of("GMT"));
+ }
+
+ @Test
+ void testWriteAndQuery() {
+ MemoryPersistenceService service = new MemoryPersistenceService(tz);
+ new ItemMutation(item1).accept(new DecimalType(10));
+ service.store(item1);
+
+ assertThat(service.getItemInfo())
+ .hasSize(1)
+ .element(0).matches(info -> info.getCount() == 1);
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1)))
+ .hasSize(1);
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_2)))
+ .isEmpty();
+ }
+
+ @Test
+ void testWriteAndSortingQuery() {
+ MemoryPersistenceService service = new MemoryPersistenceService(tz);
+ new ItemMutation(item1).accept(new DecimalType(10));
+ service.store(item1);
+ new ItemMutation(item1).accept(new DecimalType(20));
+ service.store(item1);
+
+ assertThat(service.getItemInfo())
+ .hasSize(1)
+ .element(0).matches(info -> info.getCount() == 2);
+
+ IterableAssert itemAssert = assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setOrdering(Ordering.DESCENDING)))
+ .hasSize(2);
+ itemAssert.element(0).matches(state -> state.getState().equals(new DecimalType(20)));
+ itemAssert.element(1).matches(state -> state.getState().equals(new DecimalType(10)));
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_2)))
+ .isEmpty();
+ }
+
+ @Test
+ void testPaging() {
+ MemoryPersistenceService service = new MemoryPersistenceService(tz);
+ new ItemMutation(item1).accept(new DecimalType(10));
+ service.store(item1);
+ new ItemMutation(item1).accept(new DecimalType(20));
+ service.store(item1);
+ new ItemMutation(item1).accept(new DecimalType(30));
+ service.store(item1);
+ new ItemMutation(item1).accept(new DecimalType(40));
+ service.store(item1);
+
+ assertThat(service.getItemInfo())
+ .hasSize(1)
+ .element(0).matches(info -> info.getCount() == 4);
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageSize(1)))
+ .hasSize(1)
+ .element(0).matches(state -> state.getState().equals(new DecimalType(40)));
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(1)))
+ .hasSize(1)
+ .element(0).matches(state -> state.getState().equals(new DecimalType(30)));
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(2).setPageSize(1)))
+ .hasSize(1)
+ .element(0).matches(state -> state.getState().equals(new DecimalType(20)));
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageSize(3).setOrdering(Ordering.ASCENDING)))
+ .hasSize(3)
+ .extracting(HistoricItem::getState)
+ .containsExactly(new DecimalType(10), new DecimalType(20), new DecimalType(30));
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(2).setOrdering(Ordering.ASCENDING)))
+ .hasSize(2)
+ .extracting(HistoricItem::getState)
+ .containsExactly(new DecimalType(30), new DecimalType(40));
+
+ assertThat(service.query(new FilterCriteria().setItemName(TEST_1).setPageNumber(1).setPageSize(2)))
+ .hasSize(2)
+ .extracting(HistoricItem::getState)
+ .containsExactly(new DecimalType(20), new DecimalType(10));
+ }
+
+}
\ No newline at end of file
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 85d458a1..45f44392 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -74,6 +74,7 @@
org.connectorio.addons.network.transmitter.ip
org.connectorio.addons.norule
org.connectorio.addons.norule.shell
+ org.connectorio.addons.persistence.memory
org.connectorio.addons.persistence.manager
org.connectorio.addons.persistence.migrator
org.connectorio.addons.persistence.migrator.shell
diff --git a/features/org.connectorio.addons.feature.persistence/src/main/feature/feature.xml b/features/org.connectorio.addons.feature.persistence/src/main/feature/feature.xml
index 2ed3c49b..7c47ce8b 100644
--- a/features/org.connectorio.addons.feature.persistence/src/main/feature/feature.xml
+++ b/features/org.connectorio.addons.feature.persistence/src/main/feature/feature.xml
@@ -50,4 +50,9 @@
mvn:org.connectorio.addons/org.connectorio.addons.persistence.shell/${project.version}
+
+ openhab-runtime-base
+ mvn:org.connectorio.addons/org.connectorio.addons.persistence.memory/${project.version}
+
+