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} + +