Persistence no default strategies and persistence configuration health check (#4682)

* no default strategy and persistence health
* upgradetool

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege
2025-12-14 17:28:26 +01:00
committed by GitHub
parent 636c476d3e
commit 361d71433d
20 changed files with 288 additions and 82 deletions
@@ -12,6 +12,7 @@
*/
package org.openhab.core.io.rest.core.internal.persistence;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
@@ -44,11 +45,13 @@ import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.auth.Role;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.rest.JSONResponse;
import org.openhab.core.io.rest.LocaleService;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.core.io.rest.core.config.ConfigurationService;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
@@ -63,12 +66,13 @@ import org.openhab.core.persistence.PersistenceItemConfiguration;
import org.openhab.core.persistence.PersistenceItemInfo;
import org.openhab.core.persistence.PersistenceManager;
import org.openhab.core.persistence.PersistenceService;
import org.openhab.core.persistence.PersistenceServiceProblem;
import org.openhab.core.persistence.PersistenceServiceRegistry;
import org.openhab.core.persistence.QueryablePersistenceService;
import org.openhab.core.persistence.config.PersistenceAllConfig;
import org.openhab.core.persistence.dto.ItemHistoryDTO;
import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO;
import org.openhab.core.persistence.dto.PersistenceServiceDTO;
import org.openhab.core.persistence.dto.PersistenceStrategyDTO;
import org.openhab.core.persistence.registry.ManagedPersistenceServiceConfigurationProvider;
import org.openhab.core.persistence.registry.PersistenceServiceConfiguration;
import org.openhab.core.persistence.registry.PersistenceServiceConfigurationDTOMapper;
@@ -109,6 +113,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
* @author Mark Herwege - Implement aliases
* @author Mark Herwege - Make default strategy to be only a configuration suggestion
*/
@Component
@JaxrsResource
@@ -136,6 +141,7 @@ public class PersistenceResource implements RESTResource {
private final PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry;
private final ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProvider;
private final TimeZoneProvider timeZoneProvider;
private final ConfigurationService configurationService;
@Activate
public PersistenceResource( //
@@ -145,7 +151,8 @@ public class PersistenceResource implements RESTResource {
final @Reference PersistenceManager persistenceManager,
final @Reference PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry,
final @Reference ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProvider,
final @Reference TimeZoneProvider timeZoneProvider) {
final @Reference TimeZoneProvider timeZoneProvider,
final @Reference ConfigurationService configurationService) {
this.itemRegistry = itemRegistry;
this.localeService = localeService;
this.persistenceServiceRegistry = persistenceServiceRegistry;
@@ -153,6 +160,7 @@ public class PersistenceResource implements RESTResource {
this.persistenceServiceConfigurationRegistry = persistenceServiceConfigurationRegistry;
this.managedPersistenceServiceConfigurationProvider = managedPersistenceServiceConfigurationProvider;
this.timeZoneProvider = timeZoneProvider;
this.configurationService = configurationService;
}
@GET
@@ -180,25 +188,11 @@ public class PersistenceResource implements RESTResource {
public Response httpGetPersistenceServiceConfiguration(@Context HttpHeaders headers,
@Parameter(description = "Id of the persistence service.") @PathParam("serviceId") String serviceId) {
PersistenceServiceConfiguration configuration = persistenceServiceConfigurationRegistry.get(serviceId);
boolean editable = managedPersistenceServiceConfigurationProvider.get(serviceId) != null;
if (configuration == null) {
PersistenceService service = persistenceServiceRegistry.get(serviceId);
if (service != null) {
List<PersistenceStrategy> strategies = service.getDefaultStrategies();
List<PersistenceItemConfiguration> configs = List
.of(new PersistenceItemConfiguration(List.of(new PersistenceAllConfig()), strategies, null));
Map<String, String> aliases = Map.of();
configuration = new PersistenceServiceConfiguration(serviceId, configs, aliases, strategies, strategies,
List.of());
editable = true;
}
}
if (configuration != null) {
PersistenceServiceConfigurationDTO configurationDTO = PersistenceServiceConfigurationDTOMapper
.map(configuration);
configurationDTO.editable = editable;
configurationDTO.editable = managedPersistenceServiceConfigurationProvider.get(serviceId) != null;
return JSONResponse.createResponse(Status.OK, configurationDTO, null);
} else {
return Response.status(Status.NOT_FOUND).build();
@@ -345,6 +339,79 @@ public class PersistenceResource implements RESTResource {
return putItemState(serviceId, itemName, value, time);
}
@GET
@RolesAllowed({ Role.ADMIN })
@Produces({ MediaType.APPLICATION_JSON })
@Path("strategysuggestions")
@Operation(operationId = "getPersistenceServiceStrategySuggestions", summary = "Gets a persistence service suggested strategies.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PersistenceStrategyDTO.class), uniqueItems = true))),
@ApiResponse(responseCode = "404", description = "Suggested strategies not found.") })
public Response httpGetPersistenceServiceStrategySuggestions(@Context HttpHeaders headers,
@Parameter(description = "Id of the persistence service.") @QueryParam("serviceId") String serviceId) {
PersistenceService service = persistenceServiceRegistry.get(serviceId);
if (service != null) {
return JSONResponse.createResponse(Status.OK, service.getSuggestedStrategies(), null);
}
return Response.status(Status.NOT_FOUND).build();
}
@GET
@RolesAllowed({ Role.ADMIN })
@Produces({ MediaType.APPLICATION_JSON })
@Path("persistencehealth")
@Operation(operationId = "getPersistenceHealth", summary = "Gets configuration problems with persistence services.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = PersistenceServiceProblem.class), uniqueItems = true))) })
public Response httpGetPersistenceHealth(@Context HttpHeaders headers) {
List<PersistenceServiceProblem> persistenceProblems = new ArrayList<>();
Set<PersistenceService> persistenceServices = persistenceServiceRegistry.getAll();
if (persistenceServices.size() > 1) {
try {
Configuration configuration = configurationService.get("org.openhab.persistence");
if (configuration == null || configuration.get("default") == null) {
persistenceProblems.add(new PersistenceServiceProblem(
PersistenceServiceProblem.PERSISTENCE_NO_DEFAULT, null, null, true));
}
} catch (IOException e) {
logger.warn("Unable to retrieve configuration for 'org.openhab.persistence': {}", e.getMessage());
}
}
for (PersistenceService service : persistenceServices) {
String serviceId = service.getId();
PersistenceServiceConfiguration serviceConfig = persistenceServiceConfigurationRegistry.get(serviceId);
if (serviceConfig == null) {
persistenceProblems.add(new PersistenceServiceProblem(PersistenceServiceProblem.PERSISTENCE_NO_CONFIG,
serviceId, null, true));
} else {
boolean editable = managedPersistenceServiceConfigurationProvider.get(serviceId) != null;
List<PersistenceItemConfiguration> configs = serviceConfig.getConfigs();
if (configs.isEmpty()) {
persistenceProblems.add(new PersistenceServiceProblem(
PersistenceServiceProblem.PERSISTENCE_NO_ITEMS, serviceId, null, editable));
} else {
for (PersistenceItemConfiguration config : configs) {
List<PersistenceStrategy> strategies = config.strategies();
List<String> items = config.items().stream()
.map(PersistenceServiceConfigurationDTOMapper::persistenceConfigToString).toList();
if (strategies.isEmpty()) {
persistenceProblems.add(new PersistenceServiceProblem(
PersistenceServiceProblem.PERSISTENCE_NO_STRATEGY, serviceId, items, editable));
} else if (strategies.size() == 1
&& PersistenceStrategy.Globals.RESTORE.equals(strategies.get(0))) {
persistenceProblems.add(new PersistenceServiceProblem(
PersistenceServiceProblem.PERSISTENCE_NO_STORE_STRATEGY, serviceId, items,
editable));
}
}
}
}
}
return JSONResponse.createResponse(Status.OK, persistenceProblems, null);
}
private ZonedDateTime convertTime(String sTime) {
DateTimeType dateTime = new DateTimeType(sTime);
return dateTime.getZonedDateTime(timeZoneProvider.getTimeZone());
@@ -12,11 +12,12 @@
*/
package org.openhab.core.io.rest.core.internal.persistence;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.time.ZoneId;
@@ -36,6 +37,7 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.rest.LocaleService;
import org.openhab.core.io.rest.core.config.ConfigurationService;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
@@ -77,13 +79,14 @@ public class PersistenceResourceTest {
private @Mock @NonNullByDefault({}) PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistryMock;
private @Mock @NonNullByDefault({}) ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProviderMock;
private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock;
private @Mock @NonNullByDefault({}) ConfigurationService configurationServiceMock;
private @Mock @NonNullByDefault({}) Item itemMock;
@BeforeEach
public void beforeEach() {
pResource = new PersistenceResource(itemRegistryMock, localeServiceMock, persistenceServiceRegistryMock,
persistenceManagerMock, persistenceServiceConfigurationRegistryMock,
managedPersistenceServiceConfigurationProviderMock, timeZoneProviderMock);
managedPersistenceServiceConfigurationProviderMock, timeZoneProviderMock, configurationServiceMock);
int startValue = 2016;
int endValue = 2018;
@@ -6,9 +6,7 @@ generate persistence "https://openhab.org/model/Persistence"
PersistenceModel:
{PersistenceModel}
('Strategies' '{' strategies+=Strategy*
('default' '=' defaults+=[Strategy|ID] (',' defaults+=[Strategy|ID])*)?
'}')?
('Strategies' '{' strategies+=Strategy* '}')?
('Filters' '{' filters+=Filter* '}')?
('Items' '{' configs+=PersistenceConfiguration* '}')?
('Aliases' '{' aliases+=AliasConfiguration* '}')?
@@ -59,9 +57,8 @@ NotIncludeFilter:
PersistenceConfiguration:
items+=(AllConfig | ItemConfig | GroupConfig | ItemExcludeConfig | GroupExcludeConfig)
(',' items+=(AllConfig | ItemConfig | GroupConfig | ItemExcludeConfig | GroupExcludeConfig))*
((':' ('strategy' '=' strategies+=[Strategy|ID] (',' strategies+=[Strategy|ID])*)?
(':' ('strategy' '=' strategies+=[Strategy|ID] (',' strategies+=[Strategy|ID])*)
('filter' '=' filters+=[Filter|ID] (',' filters+=[Filter|ID])*)?)
| ';')
;
AllConfig:
@@ -116,8 +116,7 @@ public class PersistenceModelManager extends AbstractProvider<PersistenceService
if (model != null) {
PersistenceServiceConfiguration newConfiguration = new PersistenceServiceConfiguration(serviceName,
mapConfigs(model.getConfigs()), mapAliases(model.getAliases()),
mapStrategies(model.getDefaults()), mapStrategies(model.getStrategies()),
mapFilters(model.getFilters()));
mapStrategies(model.getStrategies()), mapFilters(model.getFilters()));
PersistenceServiceConfiguration oldConfiguration = configurations.put(serviceName,
newConfiguration);
if (oldConfiguration == null) {
@@ -14,12 +14,11 @@ package org.openhab.core.persistence;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.items.Item;
import org.openhab.core.persistence.internal.PersistenceManagerImpl;
/**
* The {@link PersistenceManager} interface is used to communicate between external components (e.g. REST interface)
* that modify persisted data bypassing the {@link PersistenceManagerImpl}. This is required because forecast jobs might
* need an update when the persisted data changes.
* that modify persisted data bypassing the {@link org.openhab.core.persistence.internal.PersistenceManagerImpl}. This
* is required because forecast jobs might need an update when the persisted data changes.
*
* @author Jan N. Klug - Initial contribution
*/
@@ -27,6 +27,7 @@ import org.openhab.core.persistence.strategy.PersistenceStrategy;
* for sending data to an IoT (Internet of Things) service.
*
* @author Kai Kreuzer - Initial contribution
* @author Mark Herwege - Make default strategy to be only a configuration suggestion
*/
@NonNullByDefault
public interface PersistenceService {
@@ -76,7 +77,22 @@ public interface PersistenceService {
/**
* Provides default persistence strategies that are used for all items if no user defined configuration is found.
*
* @return The default persistence strategies
* This method has been deprecated and {@link #getSuggestedStrategies()} should be used instead. These
* persistence strategies are no longer applied automatically.
*
* @return The suggested persistence strategies
*/
List<PersistenceStrategy> getDefaultStrategies();
@Deprecated
default List<PersistenceStrategy> getDefaultStrategies() {
return List.of();
}
/**
* Provides suggested persistence strategies that can be used in the UI as a suggestion for configuration.
*
* @return The suggested persistence strategies
*/
default List<PersistenceStrategy> getSuggestedStrategies() {
return getDefaultStrategies();
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2025 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;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
/**
* This is a representation of a persistence service configuration problem.
*
* @param reason description for the persistence configuration problem, free text, but one of the public constants can
* be used to allow the consumer (e.g. UI) to tailor the message.
* @param serviceId persistence service
* @param items list of persistence item definitions
* @param editable true if this is a managed service
*
* @author Mark Herwege - Persistence health API endpoint
*/
public record PersistenceServiceProblem(String reason, @Nullable String serviceId, @Nullable List<String> items,
boolean editable) {
// Reasons for persistence configuration problems.
// If one of these constants is used as reason, the UI can use these values to give a more descriptive message.
public static final String PERSISTENCE_NO_DEFAULT = "PERSISTENCE_NO_DEFAULT";
public static final String PERSISTENCE_NO_CONFIG = "PERSISTENCE_SERVICE_NO_CONFIG";
public static final String PERSISTENCE_NO_ITEMS = "PERSISTENCE_SERVICE_NO_ITEMS";
public static final String PERSISTENCE_NO_STRATEGY = "PERSISTENCE_SERVICE_ITEMS_NO_STRATEGY";
public static final String PERSISTENCE_NO_STORE_STRATEGY = "PERSISTENCE_SERVICE_ITEMS_NO_STORE_STRATEGY";
}
@@ -17,18 +17,26 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link PersistenceServiceConfigurationDTO} is used for transferring persistence service configurations
*
* @author Jan N. Klug - Initial contribution
* @author Mark Herwege - Make default strategy to be only a configuration suggestion
*/
@NonNullByDefault
public class PersistenceServiceConfigurationDTO {
public String serviceId = "";
public Collection<PersistenceItemConfigurationDTO> configs = List.of();
public Map<String, String> aliases = Map.of();
public Collection<String> defaults = List.of();
/**
* @deprecated This field is kept to enable migration from previous version storage.
* It should not be removed as this would make automatic upgrading persistence configurations from an
* older version impossible.
*/
@Deprecated
public @Nullable Collection<String> defaults;
public Collection<PersistenceCronStrategyDTO> cronStrategies = List.of();
public Collection<PersistenceFilterDTO> thresholdFilters = List.of();
public Collection<PersistenceFilterDTO> timeFilters = List.of();
@@ -92,6 +92,7 @@ import org.slf4j.LoggerFactory;
* @author Jan N. Klug - Refactored to use service configuration registry
* @author Jan N. Klug - Added time series support
* @author Mark Herwege - Added restoring lastState, lastStateChange and lastStateUpdate
* @author Mark Herwege - Make default strategy to be only a configuration suggestion
*/
@Component(immediate = true, service = PersistenceManager.class)
@NonNullByDefault
@@ -461,7 +462,7 @@ public class PersistenceManagerImpl implements ItemRegistryChangeListener, State
public PersistenceServiceContainer(PersistenceService persistenceService,
@Nullable PersistenceServiceConfiguration configuration) {
this.persistenceService = persistenceService;
this.configuration = Objects.requireNonNullElseGet(configuration, this::getDefaultConfig);
this.configuration = Objects.requireNonNullElseGet(configuration, this::getEmptyConfig);
}
public PersistenceService getPersistenceService() {
@@ -471,13 +472,13 @@ public class PersistenceManagerImpl implements ItemRegistryChangeListener, State
/**
* Set a new configuration for this persistence service (also cancels all cron jobs)
*
* @param configuration the new {@link PersistenceServiceConfiguration}, if {@code null} the default
* configuration of the service is used
* @param configuration the new {@link PersistenceServiceConfiguration}, if {@code null} all configuration will
* be removed
*/
public void setConfiguration(@Nullable PersistenceServiceConfiguration configuration) {
cancelPersistJobs();
cancelForecastJobs();
this.configuration = Objects.requireNonNullElseGet(configuration, this::getDefaultConfig);
this.configuration = Objects.requireNonNullElseGet(configuration, this::getEmptyConfig);
strategyCache.clear();
}
@@ -489,25 +490,18 @@ public class PersistenceManagerImpl implements ItemRegistryChangeListener, State
*/
public Stream<PersistenceItemConfiguration> getMatchingConfigurations(PersistenceStrategy strategy) {
return Objects.requireNonNull(strategyCache.computeIfAbsent(strategy, s -> {
boolean matchesDefaultStrategies = configuration.getDefaults().contains(strategy);
return configuration.getConfigs().stream()
.filter(itemConfig -> itemConfig.strategies().contains(strategy)
|| (itemConfig.strategies().isEmpty() && matchesDefaultStrategies))
.toList();
}).stream());
.filter(itemConfig -> itemConfig.strategies().contains(strategy)).toList();
})).stream();
}
public @Nullable String getAlias(Item item) {
return configuration.getAliases().get(item.getName());
}
private PersistenceServiceConfiguration getDefaultConfig() {
List<PersistenceStrategy> strategies = persistenceService.getDefaultStrategies();
List<PersistenceItemConfiguration> configs = List
.of(new PersistenceItemConfiguration(List.of(new PersistenceAllConfig()), strategies, null));
Map<String, String> aliases = Map.of();
return new PersistenceServiceConfiguration(persistenceService.getId(), configs, aliases, strategies,
strategies, List.of());
private PersistenceServiceConfiguration getEmptyConfig() {
return new PersistenceServiceConfiguration(persistenceService.getId(), List.of(), Map.of(), List.of(),
List.of());
}
/**
@@ -27,23 +27,22 @@ import org.openhab.core.persistence.strategy.PersistenceStrategy;
*
* @author Jan N. Klug - Initial contribution
* @author Mark Herwege - Implement aliases
* @author Mark Herwege - Make default strategy to be only a configuration suggestion
*/
@NonNullByDefault
public class PersistenceServiceConfiguration implements Identifiable<String> {
private final String serviceId;
private final List<PersistenceItemConfiguration> configs;
private final Map<String, String> aliases;
private final List<PersistenceStrategy> defaults;
private final List<PersistenceStrategy> strategies;
private final List<PersistenceFilter> filters;
public PersistenceServiceConfiguration(String serviceId, Collection<PersistenceItemConfiguration> configs,
Map<String, String> aliases, Collection<PersistenceStrategy> defaults,
Collection<PersistenceStrategy> strategies, Collection<PersistenceFilter> filters) {
Map<String, String> aliases, Collection<PersistenceStrategy> strategies,
Collection<PersistenceFilter> filters) {
this.serviceId = serviceId;
this.configs = List.copyOf(configs);
this.aliases = Map.copyOf(aliases);
this.defaults = List.copyOf(defaults);
this.strategies = List.copyOf(strategies);
this.filters = List.copyOf(filters);
}
@@ -71,15 +70,6 @@ public class PersistenceServiceConfiguration implements Identifiable<String> {
return aliases;
}
/**
* Get the default strategies.
*
* @return an unmodifiable list of the default strategies
*/
public List<PersistenceStrategy> getDefaults() {
return defaults;
}
/**
* Get all defined strategies.
*
@@ -47,6 +47,7 @@ import org.openhab.core.persistence.strategy.PersistenceStrategy;
*
* @author Jan N. Klug - Initial contribution
* @author Mark Herwege - Implement aliases
* @author Mark Herwege - Make default strategy to be only a configuration suggestion
*/
@NonNullByDefault
public class PersistenceServiceConfigurationDTOMapper {
@@ -62,8 +63,6 @@ public class PersistenceServiceConfigurationDTOMapper {
dto.configs = persistenceServiceConfiguration.getConfigs().stream()
.map(PersistenceServiceConfigurationDTOMapper::mapPersistenceItemConfig).toList();
dto.aliases = Map.copyOf(persistenceServiceConfiguration.getAliases());
dto.defaults = persistenceServiceConfiguration.getDefaults().stream().map(PersistenceStrategy::getName)
.toList();
dto.cronStrategies = filterList(persistenceServiceConfiguration.getStrategies(), PersistenceCronStrategy.class,
PersistenceServiceConfigurationDTOMapper::mapPersistenceCronStrategy);
dto.thresholdFilters = filterList(persistenceServiceConfiguration.getFilters(),
@@ -92,9 +91,6 @@ public class PersistenceServiceConfigurationDTOMapper {
.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();
List<PersistenceItemConfiguration> configs = dto.configs.stream().map(config -> {
List<PersistenceConfig> items = config.items.stream()
.map(PersistenceServiceConfigurationDTOMapper::stringToPersistenceConfig).toList();
@@ -107,7 +103,7 @@ public class PersistenceServiceConfigurationDTOMapper {
Map<String, String> aliases = Map.copyOf(dto.aliases);
return new PersistenceServiceConfiguration(dto.serviceId, configs, aliases, defaults, strategyMap.values(),
return new PersistenceServiceConfiguration(dto.serviceId, configs, aliases, strategyMap.values(),
filterMap.values());
}
@@ -154,7 +150,7 @@ public class PersistenceServiceConfigurationDTOMapper {
throw new IllegalArgumentException("Filter '" + string + "' unknown for service '" + serviceId + "'");
}
private static String persistenceConfigToString(PersistenceConfig config) {
public static String persistenceConfigToString(PersistenceConfig config) {
if (config instanceof PersistenceAllConfig) {
return "*";
} else if (config instanceof PersistenceGroupConfig persistenceGroupConfig) {
@@ -14,12 +14,11 @@ package org.openhab.core.persistence.registry;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.common.registry.Registry;
import org.openhab.core.persistence.internal.PersistenceManagerImpl;
/**
* The {@link PersistenceServiceConfigurationRegistry} is the central place to store persistence service configurations.
* Configurations are registered through {@link PersistenceServiceConfigurationProvider}.
* Because the {@link PersistenceManagerImpl} implementation needs to listen to
* Because the {@link org.openhab.core.persistence.internal.PersistenceManagerImpl} implementation needs to listen to
* different registries, the {@link PersistenceServiceConfigurationRegistryChangeListener} can be used to add listeners
* to this registry.
*
@@ -124,7 +124,7 @@ public class TestCachedValuesPersistenceService implements ModifiablePersistence
}
@Override
public List<PersistenceStrategy> getDefaultStrategies() {
public List<PersistenceStrategy> getSuggestedStrategies() {
return List.of();
}
@@ -231,7 +231,7 @@ public class TestPersistenceService implements QueryablePersistenceService {
}
@Override
public List<PersistenceStrategy> getDefaultStrategies() {
public List<PersistenceStrategy> getSuggestedStrategies() {
return List.of();
}
@@ -13,8 +13,7 @@
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.hamcrest.Matchers.*;
import static org.mockito.Mockito.when;
import java.util.Collection;
@@ -47,9 +46,8 @@ import org.openhab.core.types.State;
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class PersistenceEqualsFilterTest {
private static final String ITEM_NAME = "itemName";
private @NonNullByDefault({}) @Mock GenericItem item;
private @Mock @NonNullByDefault({}) GenericItem item;
@ParameterizedTest
@MethodSource("argumentProvider")
@@ -13,7 +13,7 @@
package org.openhab.core.persistence.filter;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
@@ -42,9 +42,8 @@ import org.openhab.core.types.State;
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class PersistenceIncludeFilterTest {
private static final String ITEM_NAME = "itemName";
private @NonNullByDefault({}) @Mock GenericItem item;
private @Mock @NonNullByDefault({}) GenericItem item;
@ParameterizedTest
@MethodSource("argumentProvider")
@@ -574,7 +574,7 @@ public class PersistenceManagerTest {
: List.of(strategy);
PersistenceServiceConfiguration serviceConfiguration = new PersistenceServiceConfiguration(serviceId,
List.of(itemConfiguration), Map.of(), List.of(), strategies, filters);
List.of(itemConfiguration), Map.of(), strategies, filters);
manager.added(serviceConfiguration);
return serviceConfiguration;
@@ -625,7 +625,7 @@ public class PersistenceManagerTest {
}
@Override
public List<PersistenceStrategy> getDefaultStrategies() {
public List<PersistenceStrategy> getSuggestedStrategies() {
return List.of();
}
+5
View File
@@ -37,6 +37,11 @@
<artifactId>org.openhab.core.thing</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.storage.json</artifactId>
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
*
* @author Jan N. Klug - Initial contribution
* @author Jimmy Tanagra - Refactor upgraders into individual classes
* @author Mark Herwege - Added persistence strategy upgrader
*/
@NonNullByDefault
public class UpgradeTool {
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2010-2025 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.tools.internal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.persistence.dto.PersistenceItemConfigurationDTO;
import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO;
import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.tools.Upgrader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PersistenceUpgrader} removes the default persistence strategy.
*
* It upgrades the PersistenceServiceConfiguration database, removing default strategies and setting strategies on each
* configuration that has no strategy defined.
* See <a href="https://github.com/openhab/openhab-core/pull/4682">openhab/openhab-core#4682</a>.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class PersistenceUpgrader implements Upgrader {
private final Logger logger = LoggerFactory.getLogger(PersistenceUpgrader.class);
@Override
public String getName() {
return "persistenceCopyDefaultStrategy";
}
@Override
public String getDescription() {
return "Move persistence default strategy configuration to all persistence configuration without strategy defined";
}
@Override
public boolean execute(@Nullable Path userdataPath, @Nullable Path confPath) {
if (userdataPath == null) {
logger.error("{} skipped: no userdata directory found.", getName());
return false;
}
Path persistenceJsonDatabasePath = userdataPath
.resolve(Path.of("jsondb", "org.openhab.core.persistence.PersistenceServiceConfiguration.json"));
logger.info("Setting default strategy on persistence configurations without strategy '{}'",
persistenceJsonDatabasePath);
if (!Files.isWritable(persistenceJsonDatabasePath)) {
logger.error("Cannot access persistence configuration database '{}', check path and access rights.",
persistenceJsonDatabasePath);
return false;
}
JsonStorage<PersistenceServiceConfigurationDTO> persistenceStorage = new JsonStorage<>(
persistenceJsonDatabasePath.toFile(), null, 5, 0, 0, List.of());
List.copyOf(persistenceStorage.getKeys()).forEach(serviceId -> {
PersistenceServiceConfigurationDTO serviceConfigDTO = Objects
.requireNonNull(persistenceStorage.get(serviceId));
Collection<String> defaults = serviceConfigDTO.defaults;
if (defaults != null) {
Collection<PersistenceItemConfigurationDTO> configs = serviceConfigDTO.configs;
configs.forEach(config -> {
Collection<String> strategies = config.strategies;
if (strategies.isEmpty()) {
config.strategies = defaults;
}
});
serviceConfigDTO.defaults = null;
persistenceStorage.put(serviceId, serviceConfigDTO);
logger.info("{}: updated strategy configurations and removed default strategies", serviceId);
}
});
persistenceStorage.flush();
return true;
}
}