Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistence aliases #4363

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
Expand Down Expand Up @@ -104,6 +108,7 @@
* @author Lyubomir Papazov - Change java.util.Date references to be of type java.time.ZonedDateTime
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
* @author Mark Herwege - Implement aliases
*/
@Component
@JaxrsResource
Expand Down Expand Up @@ -181,9 +186,10 @@ public Response httpGetPersistenceServiceConfiguration(@Context HttpHeaders head
PersistenceService service = persistenceServiceRegistry.get(serviceId);
if (service != null) {
List<PersistenceStrategy> strategies = service.getDefaultStrategies();
List<PersistenceItemConfiguration> configs = List.of(
new PersistenceItemConfiguration(List.of(new PersistenceAllConfig()), null, strategies, null));
configuration = new PersistenceServiceConfiguration(serviceId, configs, strategies, strategies,
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;
}
Expand Down Expand Up @@ -368,6 +374,9 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName,
// If serviceId is null, then use the default service
PersistenceService service;
String effectiveServiceId = serviceId != null ? serviceId : persistenceServiceRegistry.getDefaultId();
if (effectiveServiceId == null) {
return null;
}
service = persistenceServiceRegistry.get(effectiveServiceId);

if (service == null) {
Expand Down Expand Up @@ -416,6 +425,8 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName,

ItemHistoryDTO dto = new ItemHistoryDTO();
dto.name = itemName;
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry.get(effectiveServiceId);
String alias = config != null ? config.getAliases().get(itemName) : null;

// If "boundary" is true then we want to get one value before and after the requested period
// This is necessary for values that don't change often otherwise data will start after the start of the graph
Expand All @@ -427,7 +438,7 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName,
filterBeforeStart.setEndDate(dateTimeBegin);
filterBeforeStart.setPageSize(1);
filterBeforeStart.setOrdering(Ordering.DESCENDING);
result = qService.query(filterBeforeStart);
result = qService.query(filterBeforeStart, alias);
if (result.iterator().hasNext()) {
dto.addData(dateTimeBegin.toInstant().toEpochMilli(), result.iterator().next().getState());
quantity++;
Expand All @@ -446,7 +457,7 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName,
filter.setBeginDate(dateTimeBegin);
filter.setEndDate(dateTimeEnd);
filter.setOrdering(Ordering.ASCENDING);
result = qService.query(filter);
result = qService.query(filter, alias);
Iterator<HistoricItem> it = result.iterator();

// Iterate through the data
Expand Down Expand Up @@ -478,7 +489,7 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName,
filterAfterEnd.setBeginDate(dateTimeEnd);
filterAfterEnd.setPageSize(1);
filterAfterEnd.setOrdering(Ordering.ASCENDING);
result = qService.query(filterAfterEnd);
result = qService.query(filterAfterEnd, alias);
if (result.iterator().hasNext()) {
dto.addData(dateTimeEnd.toInstant().toEpochMilli(), result.iterator().next().getState());
quantity++;
Expand Down Expand Up @@ -544,26 +555,60 @@ private List<PersistenceServiceDTO> getPersistenceServiceList(Locale locale) {
private Response getServiceItemList(@Nullable String serviceId) {
// If serviceId is null, then use the default service
PersistenceService service;
if (serviceId == null) {
service = persistenceServiceRegistry.getDefault();
} else {
service = persistenceServiceRegistry.get(serviceId);
String effectiveServiceId = serviceId != null ? serviceId : persistenceServiceRegistry.getDefaultId();
if (effectiveServiceId == null) {
logger.debug("Persistence service not found '{}'.", effectiveServiceId);
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
"Persistence service not found: " + effectiveServiceId);
}
service = persistenceServiceRegistry.get(effectiveServiceId);

if (service == null) {
logger.debug("Persistence service not found '{}'.", serviceId);
return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Persistence service not found: " + serviceId);
logger.debug("Persistence service not found '{}'.", effectiveServiceId);
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
"Persistence service not found: " + effectiveServiceId);
}

if (!(service instanceof QueryablePersistenceService)) {
logger.debug("Persistence service not queryable '{}'.", serviceId);
logger.debug("Persistence service not queryable '{}'.", effectiveServiceId);
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
"Persistence service not queryable: " + serviceId);
"Persistence service not queryable: " + effectiveServiceId);
}

QueryablePersistenceService qService = (QueryablePersistenceService) service;

return JSONResponse.createResponse(Status.OK, qService.getItemInfo(), "");
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry.get(effectiveServiceId);
Map<String, String> aliases = config != null ? config.getAliases() : Map.of();
Set<PersistenceItemInfo> itemInfo = qService.getItemInfo().stream().map(info -> {
String alias = aliases.get(info.getName());
if (alias != null) {
return new PersistenceItemInfo() {

@Override
public String getName() {
return alias;
}

@Override
public @Nullable Integer getCount() {
return info.getCount();
}

@Override
public @Nullable Date getEarliest() {
return info.getEarliest();
}

@Override
public @Nullable Date getLatest() {
return info.getLatest();
}
};
} else {
return info;
}
}).collect(Collectors.toSet());
return JSONResponse.createResponse(Status.OK, itemInfo, "");
}

private Response deletePersistenceItemData(@Nullable String serviceId, String itemName, @Nullable String timeBegin,
Expand Down Expand Up @@ -601,13 +646,15 @@ private Response deletePersistenceItemData(@Nullable String serviceId, String it
// This is necessary for values that don't change often otherwise data will start after the start of the graph
// (or not at all if there's no change during the graph period)
FilterCriteria filter = new FilterCriteria();
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry.get(serviceId);
String alias = config != null ? config.getAliases().get(itemName) : null;
filter.setItemName(itemName);
filter.setBeginDate(dateTimeBegin);
filter.setEndDate(dateTimeEnd);

ModifiablePersistenceService mService = (ModifiablePersistenceService) service;
try {
mService.remove(filter);
mService.remove(filter, alias);
} catch (IllegalArgumentException e) {
return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "Invalid filter parameters.");
}
Expand All @@ -620,7 +667,7 @@ private Response putItemState(@Nullable String serviceId, String itemName, Strin
String effectiveServiceId = serviceId != null ? serviceId : persistenceServiceRegistry.getDefaultId();

PersistenceService service = persistenceServiceRegistry.get(effectiveServiceId);
if (service == null) {
if (effectiveServiceId == null || service == null) {
logger.warn("Persistence service not found '{}'.", effectiveServiceId);
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
"Persistence service not found: " + effectiveServiceId);
Expand Down Expand Up @@ -658,7 +705,9 @@ private Response putItemState(@Nullable String serviceId, String itemName, Strin
}

ModifiablePersistenceService mService = (ModifiablePersistenceService) service;
mService.store(item, dateTime, state);
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry.get(effectiveServiceId);
String alias = config != null ? config.getAliases().get(itemName) : null;
mService.store(item, dateTime, state, alias);

persistenceManager.handleExternalPersistenceDataChange(mService, item);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
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.*;
import static org.mockito.Mockito.*;

import java.time.ZoneId;
Expand Down Expand Up @@ -58,6 +58,7 @@
* Tests for PersistenceItem Restresource
*
* @author Stefan Triller - Initial contribution
* @author Mark Herwege - Implement aliases
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
Expand Down Expand Up @@ -112,7 +113,7 @@ public String getName() {
});
}

when(pServiceMock.query(any())).thenReturn(items);
when(pServiceMock.query(any(), any())).thenReturn(items);

when(persistenceServiceRegistryMock.get(PERSISTENCE_SERVICE_ID)).thenReturn(pServiceMock);
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.systemDefault());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ PersistenceModel:
'}'
('Filters' '{' filters+=Filter* '}')?
('Items' '{' configs+=PersistenceConfiguration* '}')?
('Aliases' '{' aliases+=AliasConfiguration* '}')?
;

Strategy:
Expand Down Expand Up @@ -57,7 +58,7 @@ NotIncludeFilter:

PersistenceConfiguration:
items+=(AllConfig | ItemConfig | GroupConfig | ItemExcludeConfig | GroupExcludeConfig)
(',' items+=(AllConfig | ItemConfig | GroupConfig | ItemExcludeConfig | GroupExcludeConfig))* ('->' alias=STRING)?
(',' items+=(AllConfig | ItemConfig | GroupConfig | ItemExcludeConfig | GroupExcludeConfig))*
((':' ('strategy' '=' strategies+=[Strategy|ID] (',' strategies+=[Strategy|ID])*)?
('filter' '=' filters+=[Filter|ID] (',' filters+=[Filter|ID])*)?)
| ';')
Expand All @@ -84,6 +85,10 @@ GroupExcludeConfig:
'!' groupExclude=ID '*'
;

AliasConfiguration:
item=ID '->' alias=(ID|STRING)
;

DECIMAL returns ecore::EBigDecimal :
'-'? INT ('.' INT)?
;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.core.model.persistence.internal;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand All @@ -24,6 +25,7 @@
import org.openhab.core.model.core.EventType;
import org.openhab.core.model.core.ModelRepository;
import org.openhab.core.model.core.ModelRepositoryChangeListener;
import org.openhab.core.model.persistence.persistence.AliasConfiguration;
import org.openhab.core.model.persistence.persistence.AllConfig;
import org.openhab.core.model.persistence.persistence.CronStrategy;
import org.openhab.core.model.persistence.persistence.EqualsFilter;
Expand Down Expand Up @@ -71,6 +73,7 @@
* @author Kai Kreuzer - Initial contribution
* @author Markus Rathgeb - Move non-model logic to core.persistence
* @author Jan N. Klug - Refactored to {@link PersistenceServiceConfigurationProvider}
* @author Mark Herwege - Separate alias handling
*/
@Component(immediate = true, service = PersistenceServiceConfigurationProvider.class)
@NonNullByDefault
Expand Down Expand Up @@ -112,8 +115,9 @@ public void modelChanged(String modelName, EventType type) {

if (model != null) {
PersistenceServiceConfiguration newConfiguration = new PersistenceServiceConfiguration(serviceName,
mapConfigs(model.getConfigs()), mapStrategies(model.getDefaults()),
mapStrategies(model.getStrategies()), mapFilters(model.getFilters()));
mapConfigs(model.getConfigs()), mapAliases(model.getAliases()),
mapStrategies(model.getDefaults()), mapStrategies(model.getStrategies()),
mapFilters(model.getFilters()));
PersistenceServiceConfiguration oldConfiguration = configurations.put(serviceName,
newConfiguration);
if (oldConfiguration == null) {
Expand Down Expand Up @@ -167,10 +171,18 @@ private PersistenceItemConfiguration mapConfig(PersistenceConfiguration config)
items.add(new PersistenceItemExcludeConfig(itemExcludeConfig.getItemExclude()));
}
}
return new PersistenceItemConfiguration(items, config.getAlias(), mapStrategies(config.getStrategies()),
return new PersistenceItemConfiguration(items, mapStrategies(config.getStrategies()),
mapFilters(config.getFilters()));
}

private Map<String, String> mapAliases(List<AliasConfiguration> aliases) {
final Map<String, String> map = new HashMap<>();
for (final AliasConfiguration alias : aliases) {
map.put(alias.getItem(), alias.getAlias());
}
return map;
}

private List<PersistenceStrategy> mapStrategies(List<Strategy> strategies) {
final List<PersistenceStrategy> lst = new LinkedList<>();
for (final Strategy strategy : strategies) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
* a filter.
*
* @author Kai Kreuzer - Initial contribution
* @author Lyubomir Papazov - Deprecate methods using java.util and add methods
* that use Java8's ZonedDateTime
* @author Lyubomir Papazov - Deprecate methods using java.util and add methods that use Java8's ZonedDateTime
* @author Mark Herwege - Copy constructor
*/
@NonNullByDefault
public class FilterCriteria {
Expand Down Expand Up @@ -91,6 +91,20 @@ public enum Ordering {
/** Filter result to only contain entries that evaluate to true with the given operator and state */
private @Nullable State state;

public FilterCriteria() {
}

public FilterCriteria(FilterCriteria filter) {
this.itemName = filter.itemName;
this.beginDate = filter.beginDate;
this.endDate = filter.endDate;
this.pageNumber = filter.pageNumber;
this.pageSize = filter.pageSize;
this.operator = filter.operator;
this.ordering = filter.ordering;
this.state = filter.state;
}

public @Nullable String getItemName() {
return itemName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* and then periodically provide it to the server to be accommodated.
*
* @author Chris Jackson - Initial contribution
* @author Mark Herwege - Implement aliases
*/
@NonNullByDefault
public interface ModifiablePersistenceService extends QueryablePersistenceService {
Expand Down Expand Up @@ -70,10 +71,35 @@ public interface ModifiablePersistenceService extends QueryablePersistenceServic
* Removes data associated with an item from a persistence service.
* If all data is removed for the specified item, the persistence service should free any resources associated with
* the item (e.g. remove any tables or delete files from the storage).
* If the persistence service implementing this method supports aliases for item names, the default implementation
* of {@link #remove(FilterCriteria, String)} should be overriden as well.
*
* @param filter the filter to apply to the data removal. ItemName can not be null.
* @return true if the query executed successfully
* @throws IllegalArgumentException if item name is null.
*/
boolean remove(FilterCriteria filter) throws IllegalArgumentException;

/**
* Removes data associated with an item from a persistence service.
* If all data is removed for the specified item, the persistence service should free any resources associated with
* the item (e.g. remove any tables or delete files from the storage).
* Persistence services supporting aliases, and relying on lookups in the item registry, should override the default
* implementation from this interface.
*
* @param filter the filter to apply to the data removal. ItemName can not be null.
* @param alias for item name in database
* @return true if the query executed successfully
* @throws IllegalArgumentException if item name is null.
*/
default boolean remove(FilterCriteria filter, @Nullable String alias) throws IllegalArgumentException {
// Default implementation changes the filter to have the alias as itemName.
// This gives correct results as long as the persistence service does not rely on a lookup in the item registry
// (in which case the item will not be found).
if (alias != null) {
FilterCriteria aliasFilter = new FilterCriteria(filter).setItemName(alias);
return remove(aliasFilter);
}
return remove(filter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
* This class holds the configuration of a persistence strategy for specific items.
*
* @author Markus Rathgeb - Initial contribution
* @author Mark Herwege - Extract alias configuration
*/
@NonNullByDefault
public record PersistenceItemConfiguration(List<PersistenceConfig> items, @Nullable String alias,
List<PersistenceStrategy> strategies, List<PersistenceFilter> filters) {
public record PersistenceItemConfiguration(List<PersistenceConfig> items, List<PersistenceStrategy> strategies,
List<PersistenceFilter> filters) {

public PersistenceItemConfiguration(final List<PersistenceConfig> items, @Nullable final String alias,
public PersistenceItemConfiguration(final List<PersistenceConfig> items,
@Nullable final List<PersistenceStrategy> strategies, @Nullable final List<PersistenceFilter> filters) {
this.items = items;
this.alias = alias;
this.strategies = Objects.requireNonNullElse(strategies, List.of());
this.filters = Objects.requireNonNullElse(filters, List.of());
}
Expand Down
Loading