Skip to content

Commit edb9e62

Browse files
committed
Add optional item state formatters
Signed-off-by: Laurent Garnier <lg.hc@free.fr>
1 parent 73f12a3 commit edb9e62

9 files changed

Lines changed: 87 additions & 28 deletions

File tree

bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/fileformat/FileFormatItemDTO.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class FileFormatItemDTO extends ItemDTO {
2929

3030
public String groupType;
3131
public GroupFunctionDTO function;
32+
public String format;
3233
public Map<String, MetadataDTO> metadata;
3334
public List<FileFormatChannelLinkDTO> channelLinks;
3435

bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/fileformat/FileFormatItemDTOMapper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@ public class FileFormatItemDTOMapper {
4646
*
4747
* @param item the item
4848
* @param metadata some metadata
49+
* @param format the format to be used to format the item state, can be NULL
4950
* @param channelLinks some items channel links
5051
* @return file format item DTO object
5152
*/
52-
public static FileFormatItemDTO map(Item item, Collection<Metadata> metadata,
53+
public static FileFormatItemDTO map(Item item, Collection<Metadata> metadata, @Nullable String format,
5354
Collection<ItemChannelLink> channelLinks) {
5455
ItemDTO itemDto = ItemDTOMapper.map(item);
5556
FileFormatItemDTO dto = new FileFormatItemDTO(itemDto, itemDto instanceof GroupItemDTO);
5657

58+
dto.format = format;
59+
5760
Map<String, MetadataDTO> metadataDTO = new LinkedHashMap<>();
5861
metadata.forEach(md -> {
5962
if (item.getName().equals(md.getUID().getItemName())) {

bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/fileformat/FileFormatResource.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
import org.openhab.core.thing.type.ThingType;
8888
import org.openhab.core.thing.type.ThingTypeRegistry;
8989
import org.openhab.core.thing.util.ThingHelper;
90+
import org.openhab.core.types.StateDescription;
9091
import org.osgi.service.component.annotations.Activate;
9192
import org.osgi.service.component.annotations.Component;
9293
import org.osgi.service.component.annotations.Deactivate;
@@ -315,8 +316,16 @@ public Response createFileFormatForItems(final @Context HttpHeaders httpHeaders,
315316
}
316317
}
317318
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
318-
String genId = GEN_ID_PATTERN.formatted(++counter);
319-
generator.setItemsToBeGenerated(genId, items, getMetadata(items), hideDefaultParameters);
319+
String genId = newIdForGeneration();
320+
Map<String, String> stateFormatters = new HashMap<>();
321+
items.forEach(item -> {
322+
StateDescription stateDescr = item.getStateDescription();
323+
String format = stateDescr == null ? null : stateDescr.getPattern();
324+
if (format != null) {
325+
stateFormatters.put(item.getName(), format);
326+
}
327+
});
328+
generator.setItemsToBeGenerated(genId, items, getMetadata(items), stateFormatters, hideDefaultParameters);
320329
generator.generateFileFormat(genId, outputStream);
321330
return Response.ok(new String(outputStream.toByteArray())).build();
322331
}
@@ -354,7 +363,7 @@ public Response createFileFormatForThings(final @Context HttpHeaders httpHeaders
354363
}
355364
}
356365
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
357-
String genId = GEN_ID_PATTERN.formatted(++counter);
366+
String genId = newIdForGeneration();
358367
generator.setThingsToBeGenerated(genId, things, true, hideDefaultParameters);
359368
generator.generateFileFormat(genId, outputStream);
360369
return Response.ok(new String(outputStream.toByteArray())).build();
@@ -384,15 +393,16 @@ public Response create(final @Context HttpHeaders httpHeaders,
384393
List<Thing> things = new ArrayList<>();
385394
List<Item> items = new ArrayList<>();
386395
List<Metadata> metadata = new ArrayList<>();
396+
Map<String, String> stateFormatters = new HashMap<>();
387397
List<String> errors = new ArrayList<>();
388-
if (!convertFromFileFormatDTO(data, things, items, metadata, errors)) {
398+
if (!convertFromFileFormatDTO(data, things, items, metadata, stateFormatters, errors)) {
389399
return Response.status(Response.Status.BAD_REQUEST).entity(String.join("\n", errors)).build();
390400
}
391401

392402
ThingFileGenerator thingGenerator = getThingFileGenerator(acceptHeader);
393403
ItemFileGenerator itemGenerator = getItemFileGenerator(acceptHeader);
394404
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
395-
String genId = GEN_ID_PATTERN.formatted(++counter);
405+
String genId = newIdForGeneration();
396406
switch (acceptHeader) {
397407
case "text/vnd.openhab.dsl.thing":
398408
if (thingGenerator == null) {
@@ -412,7 +422,7 @@ public Response create(final @Context HttpHeaders httpHeaders,
412422
return Response.status(Response.Status.BAD_REQUEST).entity("No item loaded from input").build();
413423
}
414424
itemGenerator.setItemsToBeGenerated(genId, items, hideChannelLinksAndMetadata ? List.of() : metadata,
415-
hideDefaultParameters);
425+
stateFormatters, hideDefaultParameters);
416426
itemGenerator.generateFileFormat(genId, outputStream);
417427
break;
418428
case "application/yaml":
@@ -421,7 +431,7 @@ public Response create(final @Context HttpHeaders httpHeaders,
421431
}
422432
if (itemGenerator != null) {
423433
itemGenerator.setItemsToBeGenerated(genId, items,
424-
hideChannelLinksAndMetadata ? List.of() : metadata, hideDefaultParameters);
434+
hideChannelLinksAndMetadata ? List.of() : metadata, stateFormatters, hideDefaultParameters);
425435
}
426436
if (thingGenerator != null) {
427437
thingGenerator.generateFileFormat(genId, outputStream);
@@ -459,6 +469,7 @@ public Response parse(final @Context HttpHeaders httpHeaders,
459469
Collection<Item> items = List.of();
460470
Collection<Metadata> metadata = List.of();
461471
Collection<ItemChannelLink> channelLinks = List.of();
472+
Map<String, String> stateFormatters = Map.of();
462473
List<String> errors = new ArrayList<>();
463474
List<String> warnings = new ArrayList<>();
464475
ThingFileParser thingParser = getThingFileParser(contentTypeHeader);
@@ -496,6 +507,7 @@ public Response parse(final @Context HttpHeaders httpHeaders,
496507
return Response.status(Response.Status.BAD_REQUEST).entity("No item loaded from input").build();
497508
}
498509
metadata = itemParser.getParsedMetadata(modelName2);
510+
stateFormatters = itemParser.getParsedStateFormatters(modelName2);
499511
// We need to go through the thing parser to retrieve the items channel links
500512
// But there is no need to parse again the input
501513
if (thingParser != null) {
@@ -524,13 +536,16 @@ public Response parse(final @Context HttpHeaders httpHeaders,
524536
.getParsedItems(modelName != null ? modelName : Objects.requireNonNull(modelName2));
525537
metadata = itemParser
526538
.getParsedMetadata(modelName != null ? modelName : Objects.requireNonNull(modelName2));
539+
stateFormatters = itemParser.getParsedStateFormatters(
540+
modelName != null ? modelName : Objects.requireNonNull(modelName2));
527541
}
528542
break;
529543
default:
530544
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE)
531545
.entity("Unsupported content type '" + contentTypeHeader + "'!").build();
532546
}
533-
ExtendedFileFormatDTO result = convertToFileFormatDTO(things, items, metadata, channelLinks, warnings);
547+
ExtendedFileFormatDTO result = convertToFileFormatDTO(things, items, metadata, stateFormatters, channelLinks,
548+
warnings);
534549
if (modelName != null && thingParser != null) {
535550
thingParser.finishParsingFileFormat(modelName);
536551
}
@@ -540,6 +555,10 @@ public Response parse(final @Context HttpHeaders httpHeaders,
540555
return Response.ok(result).build();
541556
}
542557

558+
private String newIdForGeneration() {
559+
return GEN_ID_PATTERN.formatted(++counter);
560+
}
561+
543562
/*
544563
* Get all the metadata for a list of items including channel links mapped to metadata in the namespace "channel"
545564
*/
@@ -739,7 +758,7 @@ private List<Thing> getThingsOrDiscoveryResult(List<String> thingUIDs) {
739758
}
740759

741760
private boolean convertFromFileFormatDTO(FileFormatDTO data, List<Thing> things, List<Item> items,
742-
List<Metadata> metadata, List<String> errors) {
761+
List<Metadata> metadata, Map<String, String> stateFormatters, List<String> errors) {
743762
boolean ok = true;
744763
if (data.things != null) {
745764
for (ThingDTO thingBean : data.things) {
@@ -823,13 +842,17 @@ private boolean convertFromFileFormatDTO(FileFormatDTO data, List<Thing> things,
823842
}
824843
items.add(item);
825844
metadata.addAll(FileFormatItemDTOMapper.mapMetadata(itemData));
845+
if (itemData.format != null) {
846+
stateFormatters.put(name, itemData.format);
847+
}
826848
}
827849
}
828850
return ok;
829851
}
830852

831853
private ExtendedFileFormatDTO convertToFileFormatDTO(Collection<Thing> things, Collection<Item> items,
832-
Collection<Metadata> metadata, Collection<ItemChannelLink> channelLinks, List<String> warnings) {
854+
Collection<Metadata> metadata, Map<String, String> stateFormatters,
855+
Collection<ItemChannelLink> channelLinks, List<String> warnings) {
833856
ExtendedFileFormatDTO dto = new ExtendedFileFormatDTO();
834857
dto.warnings = warnings.isEmpty() ? null : warnings;
835858
if (!things.isEmpty()) {
@@ -841,7 +864,8 @@ private ExtendedFileFormatDTO convertToFileFormatDTO(Collection<Thing> things, C
841864
if (!items.isEmpty()) {
842865
dto.items = new ArrayList<>();
843866
items.forEach(item -> {
844-
dto.items.add(FileFormatItemDTOMapper.map(item, metadata, channelLinks));
867+
dto.items.add(
868+
FileFormatItemDTOMapper.map(item, metadata, stateFormatters.get(item.getName()), channelLinks));
845869
});
846870
}
847871
return dto;

bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/GenericItemProvider.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public class GenericItemProvider extends AbstractProvider<Item>
8888

8989
private final Collection<ItemFactory> itemFactorys = new ArrayList<>();
9090

91+
private final Map<String, Map<String, String>> stateFormattersMap = new ConcurrentHashMap<>();
9192
private final Map<String, StateDescriptionFragment> stateDescriptionFragments = new ConcurrentHashMap<>();
9293

9394
private Integer rank;
@@ -176,6 +177,10 @@ public Collection<Item> getAllFromModel(String modelName) {
176177
return itemsMap.getOrDefault(modelName, List.of());
177178
}
178179

180+
public Map<String, String> getStateFormattersFromModel(String modelName) {
181+
return stateFormattersMap.getOrDefault(modelName, Map.of());
182+
}
183+
179184
private Collection<Item> getItemsFromModel(String modelName) {
180185
logger.debug("Read items from model '{}'", modelName);
181186

@@ -259,11 +264,21 @@ private void processBindingConfigsFromModel(String modelName, EventType type) {
259264
String format = extractFormat(label);
260265
if (format != null) {
261266
label = label.substring(0, label.indexOf("[")).trim();
267+
Map<String, String> formatters = Objects
268+
.requireNonNull(stateFormattersMap.computeIfAbsent(modelName, k -> new HashMap<>()));
269+
formatters.put(modelItem.getName(), format);
262270
if (!modelRepository.isIsolatedModel(modelName)) {
263271
stateDescriptionFragments.put(modelItem.getName(),
264272
StateDescriptionFragmentBuilder.create().withPattern(format).build());
265273
}
266274
} else {
275+
Map<String, String> formatters = stateFormattersMap.get(modelName);
276+
if (formatters != null) {
277+
formatters.remove(modelItem.getName());
278+
if (formatters.isEmpty()) {
279+
stateFormattersMap.remove(modelName);
280+
}
281+
}
267282
if (!modelRepository.isIsolatedModel(modelName)) {
268283
stateDescriptionFragments.remove(modelItem.getName());
269284
}
@@ -437,6 +452,7 @@ public void modelChanged(String modelName, EventType type) {
437452
processBindingConfigsFromModel(modelName, type);
438453
Collection<Item> itemsFromModel = getItemsFromModel(modelName);
439454
itemsMap.remove(modelName);
455+
stateFormattersMap.remove(modelName);
440456
for (Item item : itemsFromModel) {
441457
notifyAndCleanup(modelName, item);
442458
}
@@ -538,7 +554,6 @@ private Map<String, Item> toItemMap(@Nullable Collection<Item> items) {
538554

539555
@Override
540556
public @Nullable StateDescriptionFragment getStateDescriptionFragment(String itemName, @Nullable Locale locale) {
541-
// FIXME: what to do for isolated models to not override data for items in item registry ?
542557
return stateDescriptionFragments.get(itemName);
543558
}
544559
}

bundles/org.openhab.core.model.item/src/org/openhab/core/model/item/internal/fileconverter/DslItemFileConverter.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import org.openhab.core.model.items.ModelItem;
5151
import org.openhab.core.model.items.ModelProperty;
5252
import org.openhab.core.types.State;
53-
import org.openhab.core.types.StateDescription;
5453
import org.osgi.service.component.annotations.Activate;
5554
import org.osgi.service.component.annotations.Component;
5655
import org.osgi.service.component.annotations.Reference;
@@ -94,14 +93,14 @@ public String getFileFormatGenerator() {
9493

9594
@Override
9695
public void setItemsToBeGenerated(String id, List<Item> items, Collection<Metadata> metadata,
97-
boolean hideDefaultParameters) {
96+
Map<String, String> stateFormatters, boolean hideDefaultParameters) {
9897
if (items.isEmpty()) {
9998
return;
10099
}
101100
ItemModel model = ItemsFactory.eINSTANCE.createItemModel();
102101
for (Item item : items) {
103102
model.getItems().add(buildModelItem(item, getChannelLinks(metadata, item.getName()),
104-
getMetadata(metadata, item.getName()), hideDefaultParameters));
103+
getMetadata(metadata, item.getName()), stateFormatters, hideDefaultParameters));
105104
}
106105
elementsToGenerate.put(id, model);
107106
}
@@ -115,7 +114,7 @@ public void generateFileFormat(String id, OutputStream out) {
115114
}
116115

117116
private ModelItem buildModelItem(Item item, List<Metadata> channelLinks, List<Metadata> metadata,
118-
boolean hideDefaultParameters) {
117+
Map<String, String> stateFormatters, boolean hideDefaultParameters) {
119118
ModelItem model;
120119
if (item instanceof GroupItem groupItem) {
121120
ModelGroupItem modelGroup = ItemsFactory.eINSTANCE.createModelGroupItem();
@@ -144,8 +143,7 @@ private ModelItem buildModelItem(Item item, List<Metadata> channelLinks, List<Me
144143
boolean patternInjected = false;
145144
String defaultPattern = getDefaultStatePattern(item);
146145
if (label != null && !label.isEmpty()) {
147-
StateDescription stateDescr = item.getStateDescription();
148-
String statePattern = stateDescr == null ? null : stateDescr.getPattern();
146+
String statePattern = stateFormatters.get(item.getName());
149147
String patterToInject = statePattern != null && !statePattern.equals(defaultPattern) ? statePattern : null;
150148
if (patterToInject != null) {
151149
// Inject the pattern in the label
@@ -322,6 +320,11 @@ public Collection<Metadata> getParsedMetadata(String modelName) {
322320
return metadataProvider.getAllFromModel(modelName);
323321
}
324322

323+
@Override
324+
public Map<String, String> getParsedStateFormatters(String modelName) {
325+
return itemProvider.getStateFormattersFromModel(modelName);
326+
}
327+
325328
@Override
326329
public void finishParsingFileFormat(String modelName) {
327330
modelRepository.removeModel(modelName);

bundles/org.openhab.core.model.yaml/src/main/java/org/openhab/core/model/yaml/internal/items/YamlChannelLinkProvider.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@
4242
* @author Laurent Garnier - Initial contribution
4343
*/
4444
@NonNullByDefault
45-
@Component(immediate = true, service = { YamlChannelLinkProvider.class, ItemChannelLinkProvider.class,
46-
YamlChannelLinkProvider.class })
45+
@Component(immediate = true, service = { ItemChannelLinkProvider.class, YamlChannelLinkProvider.class })
4746
public class YamlChannelLinkProvider extends AbstractProvider<ItemChannelLink> implements ItemChannelLinkProvider {
4847

4948
private final Logger logger = LoggerFactory.getLogger(YamlChannelLinkProvider.class);

bundles/org.openhab.core.model.yaml/src/main/java/org/openhab/core/model/yaml/internal/items/fileconverter/YamlItemFileConverter.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import org.openhab.core.model.yaml.internal.items.YamlMetadataDTO;
5252
import org.openhab.core.model.yaml.internal.items.YamlMetadataProvider;
5353
import org.openhab.core.types.State;
54-
import org.openhab.core.types.StateDescription;
5554
import org.osgi.service.component.annotations.Activate;
5655
import org.osgi.service.component.annotations.Component;
5756
import org.osgi.service.component.annotations.Reference;
@@ -91,11 +90,11 @@ public String getFileFormatGenerator() {
9190

9291
@Override
9392
public void setItemsToBeGenerated(String id, List<Item> items, Collection<Metadata> metadata,
94-
boolean hideDefaultParameters) {
93+
Map<String, String> stateFormatters, boolean hideDefaultParameters) {
9594
List<YamlElement> elements = new ArrayList<>();
9695
items.forEach(item -> {
9796
elements.add(buildItemDTO(item, getChannelLinks(metadata, item.getName()),
98-
getMetadata(metadata, item.getName()), hideDefaultParameters));
97+
getMetadata(metadata, item.getName()), stateFormatters, hideDefaultParameters));
9998
});
10099
modelRepository.addElementsToBeGenerated(id, elements);
101100
}
@@ -106,7 +105,7 @@ public void generateFileFormat(String id, OutputStream out) {
106105
}
107106

108107
private YamlItemDTO buildItemDTO(Item item, List<Metadata> channelLinks, List<Metadata> metadata,
109-
boolean hideDefaultParameters) {
108+
Map<String, String> stateFormatters, boolean hideDefaultParameters) {
110109
YamlItemDTO dto = new YamlItemDTO();
111110
dto.name = item.getName();
112111

@@ -115,8 +114,7 @@ private YamlItemDTO buildItemDTO(Item item, List<Metadata> channelLinks, List<Me
115114
String defaultPattern = getDefaultStatePattern(item);
116115
if (label != null && !label.isEmpty()) {
117116
dto.label = item.getLabel();
118-
StateDescription stateDescr = item.getStateDescription();
119-
String statePattern = stateDescr == null ? null : stateDescr.getPattern();
117+
String statePattern = stateFormatters.get(item.getName());
120118
String patterToSet = statePattern != null && !statePattern.equals(defaultPattern) ? statePattern : null;
121119
dto.format = patterToSet;
122120
patternSet = patterToSet != null;
@@ -291,6 +289,11 @@ public Collection<Metadata> getParsedMetadata(String modelName) {
291289
return metadataProvider.getAllFromModel(modelName);
292290
}
293291

292+
@Override
293+
public Map<String, String> getParsedStateFormatters(String modelName) {
294+
return Map.of();
295+
}
296+
294297
@Override
295298
public void finishParsingFileFormat(String modelName) {
296299
modelRepository.removeIsolatedModel(modelName);

bundles/org.openhab.core/src/main/java/org/openhab/core/items/fileconverter/ItemFileGenerator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.io.OutputStream;
1616
import java.util.Collection;
1717
import java.util.List;
18+
import java.util.Map;
1819

1920
import org.eclipse.jdt.annotation.NonNullByDefault;
2021
import org.openhab.core.items.Item;
@@ -42,10 +43,11 @@ public interface ItemFileGenerator {
4243
* @param id the identifier of the file format generation
4344
* @param items the items
4445
* @param metadata the provided collection of metadata for these items (including channel links)
46+
* @param stateFormatters the optional state formatter for each item
4547
* @param hideDefaultParameters true to hide the configuration parameters having the default value
4648
*/
4749
void setItemsToBeGenerated(String id, List<Item> items, Collection<Metadata> metadata,
48-
boolean hideDefaultParameters);
50+
Map<String, String> stateFormatters, boolean hideDefaultParameters);
4951

5052
/**
5153
* Generate the file format for all data that were associated to the provided identifier.

0 commit comments

Comments
 (0)