Skip to content

Commit 5b0b8a8

Browse files
committed
[rest] Fix GET /rest/persistence/items?serviceid=... returns null entries
When querying Items from InMemory, I noticed that the return is an array of null values. This was caused by a silent serialization failure, where Gson serialized anonymous implementations of PersistenceItemInfo to null due to missing fields. Serialization was error-prone in general, as the serialization results depended on the field names of PersistenceItemInfo implementations from the persistence services. This PR fixes that issue by introducing a proper DTO and mapping to that. It also adds unit tests. Signed-off-by: Florian Hotze <dev@florianhotze.com>
1 parent e065bea commit 5b0b8a8

3 files changed

Lines changed: 116 additions & 31 deletions

File tree

bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import java.time.LocalDateTime;
1818
import java.time.ZonedDateTime;
1919
import java.util.ArrayList;
20-
import java.util.Date;
2120
import java.util.Iterator;
2221
import java.util.List;
2322
import java.util.Locale;
@@ -70,6 +69,7 @@
7069
import org.openhab.core.persistence.PersistenceServiceRegistry;
7170
import org.openhab.core.persistence.QueryablePersistenceService;
7271
import org.openhab.core.persistence.dto.ItemHistoryDTO;
72+
import org.openhab.core.persistence.dto.PersistenceItemInfoDTO;
7373
import org.openhab.core.persistence.dto.PersistenceServiceConfigurationDTO;
7474
import org.openhab.core.persistence.dto.PersistenceServiceDTO;
7575
import org.openhab.core.persistence.dto.PersistenceStrategyDTO;
@@ -620,6 +620,18 @@ private List<PersistenceServiceDTO> getPersistenceServiceList(Locale locale) {
620620
return dtoList;
621621
}
622622

623+
protected Set<PersistenceItemInfoDTO> getServiceItemList(QueryablePersistenceService service,
624+
Map<String, String> aliases) {
625+
return service.getItemInfo().stream().map(info -> {
626+
String alias = aliases.get(info.getName());
627+
if (alias != null) {
628+
return new PersistenceItemInfoDTO(info, alias);
629+
} else {
630+
return new PersistenceItemInfoDTO(info);
631+
}
632+
}).collect(Collectors.toSet());
633+
}
634+
623635
private Response getServiceItemList(@Nullable String serviceId) {
624636
// If serviceId is null, then use the default service
625637
PersistenceService service;
@@ -647,35 +659,7 @@ private Response getServiceItemList(@Nullable String serviceId) {
647659

648660
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry.get(effectiveServiceId);
649661
Map<String, String> aliases = config != null ? config.getAliases() : Map.of();
650-
Set<PersistenceItemInfo> itemInfo = qService.getItemInfo().stream().map(info -> {
651-
String alias = aliases.get(info.getName());
652-
if (alias != null) {
653-
return new PersistenceItemInfo() {
654-
655-
@Override
656-
public String getName() {
657-
return alias;
658-
}
659-
660-
@Override
661-
public @Nullable Integer getCount() {
662-
return info.getCount();
663-
}
664-
665-
@Override
666-
public @Nullable Date getEarliest() {
667-
return info.getEarliest();
668-
}
669-
670-
@Override
671-
public @Nullable Date getLatest() {
672-
return info.getLatest();
673-
}
674-
};
675-
} else {
676-
return info;
677-
}
678-
}).collect(Collectors.toSet());
662+
Set<PersistenceItemInfoDTO> itemInfo = getServiceItemList(qService, aliases);
679663
return JSONResponse.createResponse(Status.OK, itemInfo, "");
680664
}
681665

bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResourceTest.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
import java.time.ZoneId;
2424
import java.time.ZonedDateTime;
2525
import java.util.ArrayList;
26+
import java.util.Date;
2627
import java.util.List;
28+
import java.util.Map;
29+
import java.util.Set;
2730

2831
import javax.ws.rs.core.HttpHeaders;
2932

3033
import org.eclipse.jdt.annotation.NonNullByDefault;
34+
import org.eclipse.jdt.annotation.Nullable;
3135
import org.junit.jupiter.api.BeforeEach;
3236
import org.junit.jupiter.api.Test;
3337
import org.junit.jupiter.api.extension.ExtendWith;
@@ -46,9 +50,11 @@
4650
import org.openhab.core.library.types.OnOffType;
4751
import org.openhab.core.persistence.HistoricItem;
4852
import org.openhab.core.persistence.ModifiablePersistenceService;
53+
import org.openhab.core.persistence.PersistenceItemInfo;
4954
import org.openhab.core.persistence.PersistenceServiceRegistry;
5055
import org.openhab.core.persistence.dto.ItemHistoryDTO;
5156
import org.openhab.core.persistence.dto.ItemHistoryDTO.HistoryDataBean;
57+
import org.openhab.core.persistence.dto.PersistenceItemInfoDTO;
5258
import org.openhab.core.persistence.internal.PersistenceManagerImpl;
5359
import org.openhab.core.persistence.registry.ManagedPersistenceServiceConfigurationProvider;
5460
import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry;
@@ -67,6 +73,7 @@
6773
public class PersistenceResourceTest {
6874

6975
private static final String PERSISTENCE_SERVICE_ID = "TestServiceID";
76+
private static final String ITEM_NAME = "Test";
7077

7178
private @NonNullByDefault({}) PersistenceResource pResource;
7279
private @NonNullByDefault({}) List<HistoricItem> items;
@@ -110,12 +117,33 @@ public State getState() {
110117

111118
@Override
112119
public String getName() {
113-
return "Test";
120+
return ITEM_NAME;
114121
}
115122
});
116123
}
117124

118125
when(pServiceMock.query(any(), any())).thenReturn(items);
126+
when(pServiceMock.getItemInfo()).thenReturn(Set.of(new PersistenceItemInfo() {
127+
@Override
128+
public String getName() {
129+
return ITEM_NAME;
130+
}
131+
132+
@Override
133+
public @Nullable Integer getCount() {
134+
return 5;
135+
}
136+
137+
@Override
138+
public @Nullable Date getEarliest() {
139+
return null;
140+
}
141+
142+
@Override
143+
public @Nullable Date getLatest() {
144+
return null;
145+
}
146+
}));
119147

120148
when(persistenceServiceRegistryMock.get(PERSISTENCE_SERVICE_ID)).thenReturn(pServiceMock);
121149
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.systemDefault());
@@ -183,6 +211,28 @@ public void testGetPersistenceItemDataWithItemStateUndefined() throws ItemNotFou
183211
assertThat(dto.data, hasSize(5));
184212
}
185213

214+
@Test
215+
public void testGetPersistenceServiceItems() {
216+
Set<PersistenceItemInfoDTO> info = pResource.getServiceItemList(pServiceMock, Map.of());
217+
218+
assertEquals(1, info.size());
219+
PersistenceItemInfoDTO dto = info.iterator().next();
220+
assertEquals(ITEM_NAME, dto.name);
221+
assertEquals(5, dto.count);
222+
assertNull(dto.earliest);
223+
assertNull(dto.latest);
224+
}
225+
226+
@Test
227+
public void testGetPersistenceServiceItemsWithAlias() {
228+
final String alias = "TestAlias";
229+
Set<PersistenceItemInfoDTO> info = pResource.getServiceItemList(pServiceMock, Map.of(ITEM_NAME, alias));
230+
231+
assertEquals(1, info.size());
232+
PersistenceItemInfoDTO dto = info.iterator().next();
233+
assertEquals(alias, dto.name);
234+
}
235+
186236
@Test
187237
public void testGetPersistenceItemDataWithItemStateNull() throws ItemNotFoundException {
188238
when(itemRegistryMock.getItem("testItem")).thenReturn(itemMock);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.core.persistence.dto;
14+
15+
import java.util.Date;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.eclipse.jdt.annotation.Nullable;
19+
import org.openhab.core.persistence.PersistenceItemInfo;
20+
21+
import io.swagger.v3.oas.annotations.media.Schema;
22+
23+
/**
24+
* The {@link PersistenceItemInfoDTO} is used for transferring {@link org.openhab.core.persistence.PersistenceItemInfo}
25+
* data.
26+
*
27+
* @author Florian Hotze - Initial contribution
28+
*/
29+
@Schema(name = "PersistenceItemInfo")
30+
@NonNullByDefault
31+
public class PersistenceItemInfoDTO {
32+
public @NonNullByDefault({}) String name;
33+
public @Nullable Integer count;
34+
public @Nullable Date earliest;
35+
public @Nullable Date latest;
36+
37+
// do not remove - needed by GSON
38+
public PersistenceItemInfoDTO() {
39+
}
40+
41+
public PersistenceItemInfoDTO(PersistenceItemInfo info) {
42+
this(info, info.getName());
43+
}
44+
45+
public PersistenceItemInfoDTO(PersistenceItemInfo info, String alias) {
46+
this.name = alias;
47+
this.count = info.getCount();
48+
this.earliest = info.getEarliest();
49+
this.latest = info.getLatest();
50+
}
51+
}

0 commit comments

Comments
 (0)