From d64bd3b90b98ecbf69ed0cd99beb04ed3c0f450c Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 18 Jun 2023 09:29:49 +0200 Subject: [PATCH] Improve existing and add new persistence filters (#3642) * Improve existing and add new persistence filters * include filter Signed-off-by: Jan N. Klug --- .../core/model/persistence/Persistence.xtext | 23 +++- .../internal/PersistenceModelManager.java | 24 +++- .../persistence/dto/PersistenceFilterDTO.java | 24 +++- .../PersistenceServiceConfigurationDTO.java | 2 + .../filter/PersistenceEqualsFilter.java | 59 +++++++++ .../filter/PersistenceIncludeFilter.java | 106 +++++++++++++++++ .../filter/PersistenceThresholdFilter.java | 17 ++- .../filter/PersistenceTimeFilter.java | 4 +- ...sistenceServiceConfigurationDTOMapper.java | 53 ++++++++- .../filter/PersistenceEqualsFilterTest.java | 84 +++++++++++++ .../filter/PersistenceIncludeFilterTest.java | 112 ++++++++++++++++++ .../PersistenceThresholdFilterTest.java | 33 +++--- .../internal/PersistenceManagerTest.java | 4 +- 13 files changed, 503 insertions(+), 42 deletions(-) create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java create mode 100644 bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext index 4e34511c2e1..7699c6e7405 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/Persistence.xtext @@ -27,17 +27,34 @@ Filter: ; FilterDetails: - ThresholdFilter | TimeFilter + ThresholdFilter | TimeFilter | EqualsFilter | NotEqualsFilter | IncludeFilter | NotIncludeFilter ; ThresholdFilter: - '>' value=DECIMAL unit=STRING + '>' (relative?='%')? value=DECIMAL unit=STRING? ; TimeFilter: - value=INT unit=('s' | 'm' | 'h' | 'd') + 'T' value=INT unit=('s' | 'm' | 'h' | 'd') ; +EqualsFilter: + '=' values+=STRING (',' values+=STRING)* +; + +NotEqualsFilter: + '!' values+=STRING (',' values+=STRING)* +; + +IncludeFilter: + '[]' lower=DECIMAL upper=DECIMAL unit=STRING? +; + +NotIncludeFilter: + '][' lower=DECIMAL upper=DECIMAL unit=STRING? +; + + PersistenceConfiguration: items+=(AllConfig | ItemConfig | GroupConfig) (',' items+=(AllConfig | ItemConfig | GroupConfig))* ('->' alias=STRING)? ((':' ('strategy' '=' strategies+=[Strategy|ID] (',' strategies+=[Strategy|ID])*)? diff --git a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java index 3531eb4976c..9b5d2b24de6 100644 --- a/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java +++ b/bundles/org.openhab.core.model.persistence/src/org/openhab/core/model/persistence/internal/PersistenceModelManager.java @@ -26,9 +26,13 @@ import org.openhab.core.model.core.ModelRepositoryChangeListener; import org.openhab.core.model.persistence.persistence.AllConfig; import org.openhab.core.model.persistence.persistence.CronStrategy; +import org.openhab.core.model.persistence.persistence.EqualsFilter; import org.openhab.core.model.persistence.persistence.Filter; import org.openhab.core.model.persistence.persistence.GroupConfig; +import org.openhab.core.model.persistence.persistence.IncludeFilter; import org.openhab.core.model.persistence.persistence.ItemConfig; +import org.openhab.core.model.persistence.persistence.NotEqualsFilter; +import org.openhab.core.model.persistence.persistence.NotIncludeFilter; import org.openhab.core.model.persistence.persistence.PersistenceConfiguration; import org.openhab.core.model.persistence.persistence.PersistenceModel; import org.openhab.core.model.persistence.persistence.Strategy; @@ -40,7 +44,9 @@ import org.openhab.core.persistence.config.PersistenceConfig; import org.openhab.core.persistence.config.PersistenceGroupConfig; import org.openhab.core.persistence.config.PersistenceItemConfig; +import org.openhab.core.persistence.filter.PersistenceEqualsFilter; import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceIncludeFilter; import org.openhab.core.persistence.filter.PersistenceThresholdFilter; import org.openhab.core.persistence.filter.PersistenceTimeFilter; import org.openhab.core.persistence.registry.PersistenceServiceConfiguration; @@ -175,13 +181,21 @@ private List mapFilters(List filters) { } private PersistenceFilter mapFilter(Filter filter) { - if (filter.getDefinition() instanceof TimeFilter) { - TimeFilter timeFilter = (TimeFilter) filter.getDefinition(); + if (filter.getDefinition() instanceof TimeFilter timeFilter) { return new PersistenceTimeFilter(filter.getName(), timeFilter.getValue(), timeFilter.getUnit()); - } else if (filter.getDefinition() instanceof ThresholdFilter) { - ThresholdFilter thresholdFilter = (ThresholdFilter) filter.getDefinition(); + } else if (filter.getDefinition() instanceof ThresholdFilter thresholdFilter) { return new PersistenceThresholdFilter(filter.getName(), thresholdFilter.getValue(), - thresholdFilter.getUnit()); + thresholdFilter.getUnit(), thresholdFilter.isRelative()); + } else if (filter.getDefinition() instanceof EqualsFilter equalsFilter) { + return new PersistenceEqualsFilter(filter.getName(), equalsFilter.getValues(), false); + } else if (filter.getDefinition() instanceof NotEqualsFilter notEqualsFilter) { + return new PersistenceEqualsFilter(filter.getName(), notEqualsFilter.getValues(), true); + } else if (filter.getDefinition() instanceof IncludeFilter includeFilter) { + return new PersistenceIncludeFilter(filter.getName(), includeFilter.getLower(), includeFilter.getUpper(), + includeFilter.getUnit(), false); + } else if (filter.getDefinition() instanceof NotIncludeFilter notIncludeFilter) { + return new PersistenceIncludeFilter(filter.getName(), notIncludeFilter.getLower(), + notIncludeFilter.getUpper(), notIncludeFilter.getUnit(), true); } throw new IllegalArgumentException("Unknown filter type " + filter.getClass()); } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java index d59bc9a480d..097e92204d7 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceFilterDTO.java @@ -13,6 +13,7 @@ package org.openhab.core.persistence.dto; import java.math.BigDecimal; +import java.util.List; /** * The {@link org.openhab.core.persistence.dto.PersistenceFilterDTO} is used for transferring persistence filter @@ -21,7 +22,24 @@ * @author Jan N. Klug - Initial contribution */ public class PersistenceFilterDTO { - public String name = ""; - public BigDecimal value = BigDecimal.ZERO; - public String unit = ""; + public String name; + + // threshold and time + public BigDecimal value; + + // threshold + public Boolean relative; + + // threshold, include/exclude + public String unit; + + // include/exclude + public BigDecimal lower; + public BigDecimal upper; + + // equals/not equals + public List values; + + // equals/not equals, include/exclude + public Boolean inverted; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java index bc796a36871..e4faa9ed20f 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/dto/PersistenceServiceConfigurationDTO.java @@ -30,6 +30,8 @@ public class PersistenceServiceConfigurationDTO { public Collection cronStrategies = List.of(); public Collection thresholdFilters = List.of(); public Collection timeFilters = List.of(); + public Collection equalsFilters = List.of(); + public Collection includeFilters = List.of(); public boolean editable = false; } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java new file mode 100644 index 00000000000..fdceb2863a8 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceEqualsFilter.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.util.Collection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; + +/** + * The {@link PersistenceEqualsFilter} is a filter that allows only specific values to pass + *

+ * The filter returns {@code false} if the string representation of the item's state is not in the given list + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceEqualsFilter extends PersistenceFilter { + private final Collection values; + private final boolean inverted; + + public PersistenceEqualsFilter(String name, Collection values, boolean inverted) { + super(name); + this.values = values; + this.inverted = inverted; + } + + public Collection getValues() { + return values; + } + + public boolean getInverted() { + return inverted; + } + + @Override + public boolean apply(Item item) { + return values.contains(item.getState().toFullString()) != inverted; + } + + @Override + public void persisted(Item item) { + } + + @Override + public String toString() { + return String.format("%s [name=%s, value=%s, inverted=]", getClass().getSimpleName(), getName(), values); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java new file mode 100644 index 00000000000..105562f1df0 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceIncludeFilter.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PersistenceIncludeFilter} is a filter that allows only specific values to pass + *

+ * The filter returns {@code false} if the string representation of the item's state is not in the given list + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PersistenceIncludeFilter extends PersistenceFilter { + private final Logger logger = LoggerFactory.getLogger(PersistenceIncludeFilter.class); + + private final BigDecimal lower; + private final BigDecimal upper; + private final String unit; + private final boolean inverted; + + public PersistenceIncludeFilter(String name, BigDecimal lower, BigDecimal upper, String unit, boolean inverted) { + super(name); + this.lower = lower; + this.upper = upper; + this.unit = unit; + this.inverted = inverted; + } + + public BigDecimal getLower() { + return lower; + } + + public BigDecimal getUpper() { + return upper; + } + + public String getUnit() { + return unit; + } + + public boolean getInverted() { + return inverted; + } + + @Override + public boolean apply(Item item) { + State state = item.getState(); + BigDecimal compareValue = null; + if (state instanceof DecimalType decimalType) { + compareValue = decimalType.toBigDecimal(); + } else if (state instanceof QuantityType quantityType) { + if (!unit.isBlank()) { + QuantityType convertedQuantity = quantityType.toUnit(unit); + if (convertedQuantity != null) { + compareValue = convertedQuantity.toBigDecimal(); + } + } + } + if (compareValue == null) { + logger.warn("Cannot compare {} to range {}{} - {}{} ", state, lower, unit, upper, unit); + return true; + } + + if (inverted) { + logger.error("Compare {} {} to {} -> {}, {} -> {}", inverted, compareValue, lower, + compareValue.compareTo(lower) <= 0, upper, compareValue.compareTo(upper) >= 0); + + return compareValue.compareTo(lower) <= 0 || compareValue.compareTo(upper) >= 0; + } else { + + logger.error("Compare {} {} to {} -> {}, {} -> {}", inverted, compareValue, lower, + compareValue.compareTo(lower) >= 0, upper, compareValue.compareTo(upper) <= 0); + return compareValue.compareTo(lower) >= 0 && compareValue.compareTo(upper) <= 0; + } + } + + @Override + public void persisted(Item item) { + } + + @Override + public String toString() { + return String.format("%s [name=%s, lower=%s, upper=%s, unit=%s, inverted=%b]", getClass().getSimpleName(), + getName(), lower, upper, unit, inverted); + } +} diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java index efd3d6d968c..269c40db9d8 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceThresholdFilter.java @@ -29,7 +29,7 @@ /** * The {@link PersistenceThresholdFilter} is a filter to prevent persistence based on a threshold. - * + *

* The filter returns {@code false} if the new value deviates by less than {@link #value}. If unit is "%" is * {@code true}, the filter returns {@code false} if the relative deviation is less than {@link #value}. * @@ -43,13 +43,15 @@ public class PersistenceThresholdFilter extends PersistenceFilter { private final BigDecimal value; private final String unit; + private final boolean relative; private final transient Map valueCache = new HashMap<>(); - public PersistenceThresholdFilter(String name, BigDecimal value, String unit) { + public PersistenceThresholdFilter(String name, BigDecimal value, String unit, boolean relative) { super(name); this.value = value; this.unit = unit; + this.relative = relative; } public BigDecimal getValue() { @@ -60,6 +62,10 @@ public String getUnit() { return unit; } + public boolean isRelative() { + return relative; + } + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean apply(Item item) { @@ -78,7 +84,7 @@ public boolean apply(Item item) { if (state instanceof DecimalType) { BigDecimal oldState = ((DecimalType) cachedState).toBigDecimal(); BigDecimal delta = oldState.subtract(((DecimalType) state).toBigDecimal()); - if ("%".equals(unit) && !BigDecimal.ZERO.equals(oldState)) { + if (relative && !BigDecimal.ZERO.equals(oldState)) { delta = delta.multiply(HUNDRED).divide(oldState, 2, RoundingMode.HALF_UP); } return delta.abs().compareTo(value) > 0; @@ -86,7 +92,7 @@ public boolean apply(Item item) { try { QuantityType oldState = (QuantityType) cachedState; QuantityType delta = oldState.subtract((QuantityType) state); - if ("%".equals(unit)) { + if (relative) { if (BigDecimal.ZERO.equals(oldState.toBigDecimal())) { // value is different and old value is 0 -> always above relative threshold return true; @@ -117,6 +123,7 @@ public void persisted(Item item) { @Override public String toString() { - return String.format("%s [name=%s, value=%s, unit=%s]", getClass().getSimpleName(), getName(), value, unit); + return String.format("%s [name=%s, value=%s, unit=%s, relative=%b]", getClass().getSimpleName(), getName(), + value, unit, relative); } } diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java index c3edcfdc6f3..524ef2bc342 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/filter/PersistenceTimeFilter.java @@ -24,8 +24,8 @@ /** * The {@link PersistenceTimeFilter} is a filter to prevent persistence base on intervals. - * - * The filter returns {@link false} if the time between now and the time of the last persisted value is less than + *

+ * The filter returns {@code false} if the time between now and the time of the last persisted value is less than * {@link #duration} {@link #unit} * * @author Jan N. Klug - Initial contribution diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java index 2b9b513ed18..a20dbcc6d39 100644 --- a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/registry/PersistenceServiceConfigurationDTOMapper.java @@ -32,7 +32,9 @@ import org.openhab.core.persistence.dto.PersistenceFilterDTO; import org.openhab.core.persistence.dto.PersistenceItemConfigurationDTO; import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO; +import org.openhab.core.persistence.filter.PersistenceEqualsFilter; import org.openhab.core.persistence.filter.PersistenceFilter; +import org.openhab.core.persistence.filter.PersistenceIncludeFilter; import org.openhab.core.persistence.filter.PersistenceThresholdFilter; import org.openhab.core.persistence.filter.PersistenceTimeFilter; import org.openhab.core.persistence.strategy.PersistenceCronStrategy; @@ -65,6 +67,10 @@ public static PersistenceServiceConfigurationDTO map( PersistenceServiceConfigurationDTOMapper::mapPersistenceThresholdFilter); dto.timeFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceTimeFilter.class, PersistenceServiceConfigurationDTOMapper::mapPersistenceTimeFilter); + dto.equalsFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceEqualsFilter.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceEqualsFilter); + dto.includeFilters = filterList(persistenceServiceConfiguration.getFilters(), PersistenceIncludeFilter.class, + PersistenceServiceConfigurationDTOMapper::mapPersistenceIncludeFilter); return dto; } @@ -73,11 +79,14 @@ public static PersistenceServiceConfiguration map(PersistenceServiceConfiguratio Map strategyMap = dto.cronStrategies.stream() .collect(Collectors.toMap(e -> e.name, e -> new PersistenceCronStrategy(e.name, e.cronExpression))); - Map filterMap = Stream - .concat(dto.thresholdFilters.stream().map(f -> new PersistenceThresholdFilter(f.name, f.value, f.unit)), - dto.timeFilters.stream() - .map(f -> new PersistenceTimeFilter(f.name, f.value.intValue(), f.unit))) - .collect(Collectors.toMap(PersistenceFilter::getName, e -> e)); + Map filterMap = Stream.of( + dto.thresholdFilters.stream() + .map(f -> new PersistenceThresholdFilter(f.name, f.value, f.unit, f.relative)), + dto.timeFilters.stream().map(f -> new PersistenceTimeFilter(f.name, f.value.intValue(), f.unit)), + dto.equalsFilters.stream().map(f -> new PersistenceEqualsFilter(f.name, f.values, f.inverted)), + dto.includeFilters.stream() + .map(f -> new PersistenceIncludeFilter(f.name, f.lower, f.upper, f.unit, f.inverted))) + .flatMap(Function.identity()).collect(Collectors.toMap(PersistenceFilter::getName, e -> e)); List defaults = dto.defaults.stream() .map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList(); @@ -87,7 +96,9 @@ public static PersistenceServiceConfiguration map(PersistenceServiceConfiguratio .map(PersistenceServiceConfigurationDTOMapper::stringToPersistenceConfig).toList(); List strategies = config.strategies.stream() .map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList(); - return new PersistenceItemConfiguration(items, config.alias, strategies, List.of()); + List filters = config.filters.stream() + .map(str -> stringToPersistenceFilter(str, filterMap, dto.serviceId)).toList(); + return new PersistenceItemConfiguration(items, config.alias, strategies, filters); }).toList(); return new PersistenceServiceConfiguration(dto.serviceId, configs, defaults, strategyMap.values(), @@ -121,6 +132,16 @@ private static PersistenceStrategy stringToPersistenceStrategy(String string, throw new IllegalArgumentException("Strategy '" + string + "' unknown for service '" + serviceId + "'"); } + private static PersistenceFilter stringToPersistenceFilter(String string, Map filterMap, + String serviceId) { + PersistenceFilter filter = filterMap.get(string); + if (filter != null) { + return filter; + } + + throw new IllegalArgumentException("Filter '" + string + "' unknown for service '" + serviceId + "'"); + } + private static String persistenceConfigToString(PersistenceConfig config) { if (config instanceof PersistenceAllConfig) { return "*"; @@ -137,6 +158,7 @@ private static PersistenceItemConfigurationDTO mapPersistenceItemConfig(Persiste itemDto.items = config.items().stream().map(PersistenceServiceConfigurationDTOMapper::persistenceConfigToString) .toList(); itemDto.strategies = config.strategies().stream().map(PersistenceStrategy::getName).toList(); + itemDto.filters = config.filters().stream().map(PersistenceFilter::getName).toList(); itemDto.alias = config.alias(); return itemDto; } @@ -153,6 +175,7 @@ private static PersistenceFilterDTO mapPersistenceThresholdFilter(PersistenceThr filterDTO.name = thresholdFilter.getName(); filterDTO.value = thresholdFilter.getValue(); filterDTO.unit = thresholdFilter.getUnit(); + filterDTO.relative = thresholdFilter.isRelative(); return filterDTO; } @@ -163,4 +186,22 @@ private static PersistenceFilterDTO mapPersistenceTimeFilter(PersistenceTimeFilt filterDTO.unit = persistenceTimeFilter.getUnit(); return filterDTO; } + + private static PersistenceFilterDTO mapPersistenceEqualsFilter(PersistenceEqualsFilter persistenceEqualsFilter) { + PersistenceFilterDTO filterDTO = new PersistenceFilterDTO(); + filterDTO.name = persistenceEqualsFilter.getName(); + filterDTO.values = persistenceEqualsFilter.getValues().stream().toList(); + filterDTO.inverted = persistenceEqualsFilter.getInverted(); + return filterDTO; + } + + private static PersistenceFilterDTO mapPersistenceIncludeFilter(PersistenceIncludeFilter persistenceIncludeFilter) { + PersistenceFilterDTO filterDTO = new PersistenceFilterDTO(); + filterDTO.name = persistenceIncludeFilter.getName(); + filterDTO.lower = persistenceIncludeFilter.getLower(); + filterDTO.upper = persistenceIncludeFilter.getUpper(); + filterDTO.unit = persistenceIncludeFilter.getUnit(); + filterDTO.inverted = persistenceIncludeFilter.getInverted(); + return filterDTO; + } } diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java new file mode 100644 index 00000000000..2376cc59b47 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceEqualsFilterTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.items.GenericItem; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.types.State; + +/** + * The {@link PersistenceEqualsFilterTest} contains tests for {@link PersistenceEqualsFilter} + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class PersistenceEqualsFilterTest { + private static final String ITEM_NAME = "itemName"; + + private @NonNullByDefault({}) @Mock GenericItem item; + + @ParameterizedTest + @MethodSource("argumentProvider") + public void equalsFilterTest(State state, Collection values, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceEqualsFilter filter = new PersistenceEqualsFilter("filter", values, false); + assertThat(filter.apply(item), is(expected)); + } + + @ParameterizedTest + @MethodSource("argumentProvider") + public void notEqualsFilterTest(State state, Collection values, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceEqualsFilter filter = new PersistenceEqualsFilter("filter", values, true); + assertThat(filter.apply(item), is(not(expected))); + } + + private static Stream argumentProvider() { + return Stream.of(// + // item state, values, result + Arguments.of(new StringType("value1"), List.of("value1", "value2"), true), + Arguments.of(new StringType("value3"), List.of("value1", "value2"), false), + Arguments.of(new DecimalType(5), List.of("3", "5", "9"), true), + Arguments.of(new DecimalType(7), List.of("3", "5", "9"), false), + Arguments.of(new QuantityType<>(10, SIUnits.CELSIUS), List.of("5 °C", "10 °C", "15 °C"), true), + Arguments.of(new QuantityType<>(20, SIUnits.CELSIUS), List.of("5 °C", "10 °C", "15 °C"), false), + Arguments.of(OnOffType.ON, List.of("ON", "UNDEF", "NULL"), true), + Arguments.of(OnOffType.OFF, List.of("ON", "UNDEF", "NULL"), false)); + } +} diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java new file mode 100644 index 00000000000..a43b85cab41 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceIncludeFilterTest.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.core.items.GenericItem; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; + +/** + * The {@link PersistenceIncludeFilterTest} contains tests for {@link PersistenceIncludeFilter} + * + * @author Jan N. Klug - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class PersistenceIncludeFilterTest { + private static final String ITEM_NAME = "itemName"; + + private @NonNullByDefault({}) @Mock GenericItem item; + + @ParameterizedTest + @MethodSource("argumentProvider") + public void includeFilterTest(State state, BigDecimal lower, BigDecimal upper, String unit, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceIncludeFilter filter = new PersistenceIncludeFilter("filter", lower, upper, unit, false); + assertThat(filter.apply(item), is(expected)); + } + + @ParameterizedTest + @MethodSource("notArgumentProvider") + public void notIncludeFilterTest(State state, BigDecimal lower, BigDecimal upper, String unit, boolean expected) { + when(item.getState()).thenReturn(state); + + PersistenceIncludeFilter filter = new PersistenceIncludeFilter("filter", lower, upper, unit, true); + assertThat(filter.apply(item), is(expected)); + } + + private static Stream argumentProvider() { + return Stream.of(// + // item state, lower, upper, unit, result + // QuantityType + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("10 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", false), + Arguments.of(new QuantityType<>("20 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", false), + Arguments.of(new QuantityType<>("0 °C"), BigDecimal.valueOf(270), BigDecimal.valueOf(275), "K", true), + // invalid or missing units + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + // DecimalType + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "", true), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true), + Arguments.of(new DecimalType("10"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", false), + Arguments.of(new DecimalType("20"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", false)); + } + + private static Stream notArgumentProvider() { + return Stream.of(// + // item state, lower, upper, unit, result + // QuantityType + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", false), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("10 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("20 °C"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "°C", true), + Arguments.of(new QuantityType<>("0 °C"), BigDecimal.valueOf(270), BigDecimal.valueOf(275), "K", false), + // invalid or missing units + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "°C", true), + Arguments.of(new QuantityType<>("5 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + Arguments.of(new QuantityType<>("17 kg"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", true), + // DecimalType + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(19), "", false), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(17), BigDecimal.valueOf(19), "", true), + Arguments.of(new DecimalType("17"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true), + Arguments.of(new DecimalType("10"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true), + Arguments.of(new DecimalType("20"), BigDecimal.valueOf(14), BigDecimal.valueOf(17), "", true)); + } +} diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java index 15cef6acc65..e37e22c5030 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/filter/PersistenceThresholdFilterTest.java @@ -70,42 +70,43 @@ public void setup() { @Test public void differentItemSameValue() { - filterTest(ITEM_NAME_2, DecimalType.ZERO, DecimalType.ZERO, "", true); + filterTest(ITEM_NAME_2, DecimalType.ZERO, DecimalType.ZERO, "", false, true); } @ParameterizedTest @MethodSource("argumentProvider") - public void filterTest(State state1, State state2, String unit, boolean expected) { - filterTest(ITEM_NAME_1, state1, state2, unit, expected); + public void filterTest(State state1, State state2, String unit, boolean relative, boolean expected) { + filterTest(ITEM_NAME_1, state1, state2, unit, relative, expected); } private static Stream argumentProvider() { return Stream.of(// // same item, same value -> false - Arguments.of(DecimalType.ZERO, DecimalType.ZERO, "", false), + Arguments.of(DecimalType.ZERO, DecimalType.ZERO, "", false, false), // plain decimal, below threshold, absolute - Arguments.of(DecimalType.ZERO, DecimalType.valueOf("5"), "", false), + Arguments.of(DecimalType.ZERO, DecimalType.valueOf("5"), "", false, false), // plain decimal, above threshold, absolute - Arguments.of(DecimalType.ZERO, DecimalType.valueOf("15"), "", true), + Arguments.of(DecimalType.ZERO, DecimalType.valueOf("15"), "", false, true), // plain decimal, below threshold, relative - Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("9.5"), "%", false), + Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("9.5"), "", true, false), // plain decimal, above threshold, relative - Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("11.5"), "%", true), + Arguments.of(DecimalType.valueOf("10.0"), DecimalType.valueOf("11.5"), "", true, true), // quantity type, below threshold, relative - Arguments.of(new QuantityType<>("15 A"), new QuantityType<>("14000 mA"), "%", false), + Arguments.of(new QuantityType<>("15 A"), new QuantityType<>("14000 mA"), "", true, false), // quantity type, above threshold, relative - Arguments.of(new QuantityType<>("2000 mbar"), new QuantityType<>("2.6 bar"), "%", true), + Arguments.of(new QuantityType<>("2000 mbar"), new QuantityType<>("2.6 bar"), "", true, true), // quantity type, below threshold, absolute, no unit - Arguments.of(new QuantityType<>("100 K"), new QuantityType<>("105 K"), "", false), + Arguments.of(new QuantityType<>("100 K"), new QuantityType<>("105 K"), "", false, false), // quantity type, above threshold, absolute, no unit - Arguments.of(new QuantityType<>("20 V"), new QuantityType<>("9000 mV"), "", true), + Arguments.of(new QuantityType<>("20 V"), new QuantityType<>("9000 mV"), "", false, true), // quantity type, below threshold, absolute, with unit - Arguments.of(new QuantityType<>("10 m"), new QuantityType<>("10.002 m"), "mm", false), + Arguments.of(new QuantityType<>("10 m"), new QuantityType<>("10.002 m"), "mm", false, false), // quantity type, above threshold, absolute, with unit - Arguments.of(new QuantityType<>("-10 °C"), new QuantityType<>("5 °C"), "K", true)); + Arguments.of(new QuantityType<>("-10 °C"), new QuantityType<>("5 °C"), "K", false, true)); } - private void filterTest(String item2name, State state1, State state2, String unit, boolean expected) { + private void filterTest(String item2name, State state1, State state2, String unit, boolean relative, + boolean expected) { String itemType = "Number"; if (state1 instanceof QuantityType q) { itemType += ":" + UnitUtils.getDimensionName(q.getUnit()); @@ -117,7 +118,7 @@ private void filterTest(String item2name, State state1, State state2, String uni item1.setState(state1); item2.setState(state2); - PersistenceFilter filter = new PersistenceThresholdFilter("test", BigDecimal.TEN, unit); + PersistenceFilter filter = new PersistenceThresholdFilter("test", BigDecimal.TEN, unit, relative); assertThat(filter.apply(item1), is(true)); filter.persisted(item1); diff --git a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java index 9172dd3e83e..b33766fbd4a 100644 --- a/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java +++ b/bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java @@ -308,7 +308,7 @@ public void cronStrategyIsScheduledAndCancelledAndPersistsValue() throws Excepti new PersistenceCronStrategy("withoutFilter", "0 0 * * * ?"), null); addConfiguration(TEST_QUERYABLE_PERSISTENCE_SERVICE_ID, new PersistenceItemConfig(TEST_ITEM3_NAME), new PersistenceCronStrategy("withFilter", "0 * * * * ?"), - new PersistenceThresholdFilter("test", BigDecimal.TEN, "")); + new PersistenceThresholdFilter("test", BigDecimal.TEN, "", false)); manager.onReadyMarkerAdded(new ReadyMarker("", "")); @@ -352,7 +352,7 @@ public void cronStrategyIsProperlyUpdated() { @Test public void filterAppliesOnStateUpdate() { addConfiguration(TEST_PERSISTENCE_SERVICE_ID, new PersistenceAllConfig(), PersistenceStrategy.Globals.UPDATE, - new PersistenceThresholdFilter("test", BigDecimal.TEN, "")); + new PersistenceThresholdFilter("test", BigDecimal.TEN, "", false)); manager.stateUpdated(TEST_ITEM3, DecimalType.ZERO); manager.stateUpdated(TEST_ITEM3, DecimalType.ZERO);