Improve existing and add new persistence filters (#3642)

* Improve existing and add new persistence filters
* include filter

Signed-off-by: Jan N. Klug <github@klug.nrw>
pull/3675/head
J-N-K 2023-06-18 09:29:49 +02:00 committed by GitHub
parent c2e81a13fd
commit d64bd3b90b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 503 additions and 42 deletions

View File

@ -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])*)?

View File

@ -26,9 +26,13 @@ import org.openhab.core.model.core.ModelRepository;
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.PersistenceAllConfig;
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 @@ public class PersistenceModelManager extends AbstractProvider<PersistenceService
}
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());
}

View File

@ -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 @@ import java.math.BigDecimal;
* @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<String> values;
// equals/not equals, include/exclude
public Boolean inverted;
}

View File

@ -30,6 +30,8 @@ public class PersistenceServiceConfigurationDTO {
public Collection<PersistenceCronStrategyDTO> cronStrategies = List.of();
public Collection<PersistenceFilterDTO> thresholdFilters = List.of();
public Collection<PersistenceFilterDTO> timeFilters = List.of();
public Collection<PersistenceFilterDTO> equalsFilters = List.of();
public Collection<PersistenceFilterDTO> includeFilters = List.of();
public boolean editable = false;
}

View File

@ -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
* <p />
* 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<String> values;
private final boolean inverted;
public PersistenceEqualsFilter(String name, Collection<String> values, boolean inverted) {
super(name);
this.values = values;
this.inverted = inverted;
}
public Collection<String> 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);
}
}

View File

@ -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
* <p />
* 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);
}
}

View File

@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
/**
* The {@link PersistenceThresholdFilter} is a filter to prevent persistence based on a threshold.
*
* <p />
* 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<String, State> 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 class PersistenceThresholdFilter extends PersistenceFilter {
return unit;
}
public boolean isRelative() {
return relative;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean apply(Item item) {
@ -78,7 +84,7 @@ public class PersistenceThresholdFilter extends PersistenceFilter {
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 class PersistenceThresholdFilter extends PersistenceFilter {
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 class PersistenceThresholdFilter extends PersistenceFilter {
@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);
}
}

View File

@ -24,8 +24,8 @@ import org.openhab.core.items.Item;
/**
* 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
* <p />
* 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

View File

@ -32,7 +32,9 @@ import org.openhab.core.persistence.dto.PersistenceCronStrategyDTO;
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 class PersistenceServiceConfigurationDTOMapper {
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 class PersistenceServiceConfigurationDTOMapper {
Map<String, PersistenceStrategy> strategyMap = dto.cronStrategies.stream()
.collect(Collectors.toMap(e -> e.name, e -> new PersistenceCronStrategy(e.name, e.cronExpression)));
Map<String, PersistenceFilter> 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<String, PersistenceFilter> 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<PersistenceStrategy> defaults = dto.defaults.stream()
.map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList();
@ -87,7 +96,9 @@ public class PersistenceServiceConfigurationDTOMapper {
.map(PersistenceServiceConfigurationDTOMapper::stringToPersistenceConfig).toList();
List<PersistenceStrategy> strategies = config.strategies.stream()
.map(str -> stringToPersistenceStrategy(str, strategyMap, dto.serviceId)).toList();
return new PersistenceItemConfiguration(items, config.alias, strategies, List.of());
List<PersistenceFilter> 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 @@ public class PersistenceServiceConfigurationDTOMapper {
throw new IllegalArgumentException("Strategy '" + string + "' unknown for service '" + serviceId + "'");
}
private static PersistenceFilter stringToPersistenceFilter(String string, Map<String, PersistenceFilter> 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 @@ public class PersistenceServiceConfigurationDTOMapper {
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 @@ public class PersistenceServiceConfigurationDTOMapper {
filterDTO.name = thresholdFilter.getName();
filterDTO.value = thresholdFilter.getValue();
filterDTO.unit = thresholdFilter.getUnit();
filterDTO.relative = thresholdFilter.isRelative();
return filterDTO;
}
@ -163,4 +186,22 @@ public class PersistenceServiceConfigurationDTOMapper {
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;
}
}

View File

@ -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<String> 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<String> 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<Arguments> 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));
}
}

View File

@ -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<Arguments> 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<Arguments> 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));
}
}

View File

@ -70,42 +70,43 @@ public class PersistenceThresholdFilterTest {
@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<Arguments> 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 @@ public class PersistenceThresholdFilterTest {
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);

View File

@ -308,7 +308,7 @@ public class PersistenceManagerTest {
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 class PersistenceManagerTest {
@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);