From ce584a8d35bd12c8f632a2561cc3a7b17101b82a Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Wed, 2 Aug 2017 10:00:28 +0200 Subject: [PATCH 01/26] added metadata infrastructure Signed-off-by: Kai Kreuzer --- .../internal/items/MetadataRegistryImpl.java | 124 ++++++++++++++++++ .../core/items/ManagedMetadataProvider.java | 71 ++++++++++ .../smarthome/core/items/Metadata.java | 57 ++++++++ .../smarthome/core/items/MetadataKey.java | 90 +++++++++++++ .../core/items/MetadataPredicates.java | 39 ++++++ .../core/items/MetadataProvider.java | 21 +++ .../core/items/MetadataRegistry.java | 22 ++++ .../smarthome/core/items/dto/MetadataDTO.java | 26 ++++ .../internal/item/ItemResourceOSGiTest.java | 20 +-- .../rest/core/internal/item/ItemResource.java | 111 +++++++++++++++- .../io/rest/core/item/EnrichedItemDTO.java | 4 + .../item/internal/GenericItemProvider.java | 42 ++++-- .../internal/GenericMetadataProvider.java | 69 ++++++++++ 13 files changed, 672 insertions(+), 24 deletions(-) create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java create mode 100644 bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java new file mode 100644 index 00000000000..8aaeffecbef --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.internal.items; + +import java.util.Collection; +import java.util.Map; + +import org.eclipse.smarthome.core.common.registry.AbstractRegistry; +import org.eclipse.smarthome.core.common.registry.Provider; +import org.eclipse.smarthome.core.common.registry.RegistryChangeListener; +import org.eclipse.smarthome.core.events.EventPublisher; +import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.items.ItemRegistry; +import org.eclipse.smarthome.core.items.ManagedMetadataProvider; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataProvider; +import org.eclipse.smarthome.core.items.MetadataRegistry; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the main implementing class of the {@link MetadataRegistry} interface. It + * keeps track of all declared metadata of all metadata providers. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +@Component(immediate = true, service = { MetadataRegistry.class }) +public class MetadataRegistryImpl extends AbstractRegistry + implements MetadataRegistry, RegistryChangeListener { + + private final Logger logger = LoggerFactory.getLogger(MetadataRegistryImpl.class); + private ItemRegistry itemRegistry; + + public MetadataRegistryImpl() { + super(MetadataProvider.class); + } + + @Override + @Activate + protected void activate(BundleContext context) { + super.activate(context); + itemRegistry.addRegistryChangeListener(this); + } + + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + itemRegistry.removeRegistryChangeListener(this); + } + + @Override + public Metadata get(MetadataKey key) { + for (final Map.Entry, Collection> entry : elementMap.entrySet()) { + for (final Metadata item : entry.getValue()) { + if (item.getUID().equals(key)) { + return item; + } + } + } + return null; + } + + @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC) + protected void setItemRegistry(ItemRegistry itemRegistry) { + this.itemRegistry = itemRegistry; + } + + protected void unsetItemRegistry(ItemRegistry itemRegistry) { + this.itemRegistry = null; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + @Override + protected void setEventPublisher(EventPublisher eventPublisher) { + super.setEventPublisher(eventPublisher); + } + + @Override + protected void unsetEventPublisher(EventPublisher eventPublisher) { + super.unsetEventPublisher(eventPublisher); + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, name = "ManagedThingProvider") + protected void setManagedProvider(ManagedMetadataProvider provider) { + super.setManagedProvider(provider); + } + + protected void unsetManagedProvider(ManagedMetadataProvider managedProvider) { + super.removeManagedProvider(managedProvider); + } + + @Override + public void added(Item element) { + // do nothing + } + + @Override + public void removed(Item element) { + if (managedProvider != null) { + // remove our metadata for that item + ((ManagedMetadataProvider) managedProvider).removeItemMetadata(element.getName()); + } + } + + @Override + public void updated(Item oldElement, Item element) { + // do nothing + } +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java new file mode 100644 index 00000000000..5a90222d3a8 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.smarthome.core.common.registry.AbstractManagedProvider; +import org.eclipse.smarthome.core.storage.StorageService; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ManagedMetadataProvider} is an OSGi service, that allows to add or remove + * metadata for items at runtime. Persistence of added metadata is handled by + * a {@link StorageService}. + * + * @author Kai Kreuzer - Initial contribution + */ +@Component(immediate = true, service = { MetadataProvider.class, ManagedMetadataProvider.class }) +public class ManagedMetadataProvider extends AbstractManagedProvider + implements MetadataProvider { + + private final Logger logger = LoggerFactory.getLogger(ManagedMetadataProvider.class); + + @Override + protected String getStorageName() { + return Metadata.class.getName(); + } + + @Override + protected @NonNull String keyToString(@NonNull MetadataKey key) { + return key.toString(); + } + + @Override + protected Metadata toElement(@NonNull String key, @NonNull Metadata persistableElement) { + return persistableElement; + } + + @Override + protected Metadata toPersistableElement(Metadata element) { + return element; + } + + @Override + @Reference + protected void setStorageService(StorageService storageService) { + super.setStorageService(storageService); + } + + @Override + protected void unsetStorageService(StorageService storageService) { + super.unsetStorageService(storageService); + } + + /** + * Removes all metadata of a given item + * + * @param itemname the name of the item for which the meta data is to be removed. + */ + public void removeItemMetadata(@NonNull String name) { + getAll().stream().filter(MetadataPredicates.ofItem(name)).forEach(md -> remove(md.getUID())); + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java new file mode 100644 index 00000000000..72184cfbcb7 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.common.registry.Identifiable; + +/** + * This is a data class for storing meta-data for a given item and namespace. + * It is the entity used for within the {@link MetadataRegistry}. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +@NonNullByDefault +public class Metadata implements Identifiable { + + private final MetadataKey key; + private final String value; + private final Map configuration; + + public Metadata(MetadataKey key, String value, Map configuration) { + this.key = key; + this.value = value; + this.configuration = configuration; + } + + @Override + public MetadataKey getUID() { + return key; + } + + /** + * Provides the configuration meta-data. + * + * @return configuration as a map of key-value pairs + */ + public Map getConfiguration() { + return configuration; + } + + /** + * Provides the main value of the meta-data. + * + * @return the main meta-data as a string + */ + public String getValue() { + return value; + } +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java new file mode 100644 index 00000000000..d7e912a8edd --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * This class represents the key of a {@link Metadata} entity. + * It is a simple combination of a namespace and an item name. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +@NonNullByDefault +public class MetadataKey { + + private final String namespace; + private final String itemName; + + /** + * Creates a new instance. + * + * @param namespace + * @param itemName + */ + public MetadataKey(String namespace, String itemName) { + this.namespace = namespace; + this.itemName = itemName; + } + + /** + * Provides the item name of this key + * + * @return the item name + */ + public String getItemName() { + return itemName; + } + + /** + * Provides the namespace of this key + * + * @return the namespace + */ + public String getNamespace() { + return namespace; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((itemName == null) ? 0 : itemName.hashCode()); + result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MetadataKey other = (MetadataKey) obj; + if (!itemName.equals(other.itemName)) { + return false; + } + if (!namespace.equals(other.namespace)) { + return false; + } + return true; + } + + @Override + public @NonNull String toString() { + return namespace + ":" + itemName; + } +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java new file mode 100644 index 00000000000..230d4140f35 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items; + +import java.util.function.Predicate; + +/** + * Provides some default predicates that are helpful when working with metadata. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +public class MetadataPredicates { + + /** + * Creates a {@link Predicate} which can be used to filter {@link Metadata} for a given namespace. + * + * @param namespace to filter + * @return created {@link Predicate} + */ + public static Predicate hasNamespace(String namespace) { + return md -> md.getUID().getNamespace().equals(namespace); + } + + /** + * Creates a {@link Predicate} which can be used to filter {@link Metadata} of a given item. + * + * @param itemname to filter + * @return created {@link Predicate} + */ + public static Predicate ofItem(String itemname) { + return md -> md.getUID().getItemName().equals(itemname); + } +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java new file mode 100644 index 00000000000..5cc92275259 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items; + +import org.eclipse.smarthome.core.common.registry.Provider; + +/** + * This is a marker interface for metadata provider implementations that should be used to register those as an OSGi + * service. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +public interface MetadataProvider extends Provider { + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java new file mode 100644 index 00000000000..bd2ad74ad09 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items; + +import org.eclipse.smarthome.core.common.registry.Registry; + +/** + * The MetadataRegistry is the central place, where additional information about items is kept. + * + * Metadata can be supplied by {@link MetadataProvider}s, which can provision them from any source + * they like and also dynamically remove or add data. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +public interface MetadataRegistry extends Registry { +} \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java new file mode 100644 index 00000000000..4998de14a79 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.items.dto; + +import java.util.Map; + +/** + * This is a data transfer object that is used to serialize metadata for a certain namespace and item. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +public class MetadataDTO { + + public String value; + public Map config; + + public MetadataDTO() { + } + +} diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java b/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java index 851d7dc7a9f..2e9e2778958 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java @@ -87,25 +87,25 @@ public void shouldFilterItemsByTag() throws Exception { item2.addTag("Tag2"); item3.addTag("Tag2"); - Response response = itemResource.getItems(null, null, "Tag1", false, null); + Response response = itemResource.getItems(null, null, "Tag1", null, false, null); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2)); - response = itemResource.getItems(null, null, "Tag2", false, null); + response = itemResource.getItems(null, null, "Tag2", null, false, null); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME2, ITEM_NAME3)); - response = itemResource.getItems(null, null, "NotExistingTag", false, null); + response = itemResource.getItems(null, null, "NotExistingTag", null, false, null); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @Test public void shouldFilterItemsByType() throws Exception { - Response response = itemResource.getItems(null, "Switch", null, false, null); + Response response = itemResource.getItems(null, "Switch", null, null, false, null); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2)); - response = itemResource.getItems(null, "Dimmer", null, false, null); + response = itemResource.getItems(null, "Dimmer", null, null, false, null); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME3)); - response = itemResource.getItems(null, "Color", null, false, null); + response = itemResource.getItems(null, "Color", null, null, false, null); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @@ -113,15 +113,15 @@ public void shouldFilterItemsByType() throws Exception { public void shouldAddAndRemoveTags() throws Exception { managedItemProvider.add(new SwitchItem("Switch")); - Response response = itemResource.getItems(null, null, "MyTag", false, null); + Response response = itemResource.getItems(null, null, "MyTag", null, false, null); assertThat(readItemNamesFromResponse(response), hasSize(0)); itemResource.addTag("Switch", "MyTag"); - response = itemResource.getItems(null, null, "MyTag", false, null); + response = itemResource.getItems(null, null, "MyTag", null, false, null); assertThat(readItemNamesFromResponse(response), hasSize(1)); itemResource.removeTag("Switch", "MyTag"); - response = itemResource.getItems(null, null, "MyTag", false, null); + response = itemResource.getItems(null, null, "MyTag", null, false, null); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @@ -130,7 +130,7 @@ public void shouldIncludeRequestedFieldsOnly() throws Exception { JsonParser parser = new JsonParser(); managedItemProvider.add(new SwitchItem("Switch")); itemResource.addTag("Switch", "MyTag"); - Response response = itemResource.getItems(null, null, "MyTag", false, "type,name"); + Response response = itemResource.getItems(null, null, "MyTag", null, false, "type,name"); JsonElement result = parser.parse(IOUtils.toString((InputStream) response.getEntity())); JsonElement expected = parser.parse("[{type: \"Switch\", name: \"Switch\"}]"); diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java index 4ac6b872a7e..ff7274879b5 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java @@ -13,11 +13,17 @@ package org.eclipse.smarthome.io.rest.core.internal.item; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.security.RolesAllowed; @@ -53,8 +59,12 @@ import org.eclipse.smarthome.core.items.ItemNotFoundException; import org.eclipse.smarthome.core.items.ItemRegistry; import org.eclipse.smarthome.core.items.ManagedItemProvider; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataRegistry; import org.eclipse.smarthome.core.items.dto.GroupItemDTO; import org.eclipse.smarthome.core.items.dto.ItemDTOMapper; +import org.eclipse.smarthome.core.items.dto.MetadataDTO; import org.eclipse.smarthome.core.items.events.ItemEventFactory; import org.eclipse.smarthome.core.library.items.RollershutterItem; import org.eclipse.smarthome.core.library.items.SwitchItem; @@ -68,6 +78,7 @@ import org.eclipse.smarthome.io.rest.LocaleUtil; import org.eclipse.smarthome.io.rest.RESTResource; import org.eclipse.smarthome.io.rest.Stream2JSONInputStream; +import org.eclipse.smarthome.io.rest.core.item.EnrichedGroupItemDTO; import org.eclipse.smarthome.io.rest.core.item.EnrichedItemDTO; import org.eclipse.smarthome.io.rest.core.item.EnrichedItemDTOMapper; import org.osgi.service.component.annotations.Component; @@ -124,6 +135,8 @@ public class ItemResource implements RESTResource { @NonNullByDefault({}) private ItemRegistry itemRegistry; @NonNullByDefault({}) + private MetadataRegistry metadataRegistry; + @NonNullByDefault({}) private EventPublisher eventPublisher; @NonNullByDefault({}) private ManagedItemProvider managedItemProvider; @@ -140,6 +153,15 @@ protected void unsetItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = null; } + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + protected void setMetadataRegistry(MetadataRegistry metadataRegistry) { + this.metadataRegistry = metadataRegistry; + } + + protected void unsetMetadataRegistry(MetadataRegistry metadataRegistry) { + this.metadataRegistry = null; + } + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) protected void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; @@ -186,13 +208,17 @@ public Response getItems( @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, @QueryParam("type") @ApiParam(value = "item type filter", required = false) @Nullable String type, @QueryParam("tags") @ApiParam(value = "item tag filter", required = false) @Nullable String tags, - @DefaultValue("false") @QueryParam("recursive") @ApiParam(value = "get member items recursivly", required = false) boolean recursive, + @QueryParam("metadata") @ApiParam(value = "metadata selector", required = false) @Nullable String namespaceSelector, + @DefaultValue("false") @QueryParam("recursive") @ApiParam(value = "get member items recursively", required = false) boolean recursive, @QueryParam("fields") @ApiParam(value = "limit output to the given fields (comma separated)", required = false) @Nullable String fields) { final Locale locale = LocaleUtil.getLocale(language); + final Set namespaces = namespaceSelector == null ? Collections.emptySet() + : Arrays.stream(namespaceSelector.split(",")).collect(Collectors.toSet()); logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Stream itemStream = getItems(type, tags).stream() - .map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriInfo.getBaseUri(), locale)); + .map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriInfo.getBaseUri(), locale)) + .peek(dto -> addMetadata(dto, namespaces, null)); itemStream = dtoMapper.limitToFields(itemStream, fields); return Response.ok(new Stream2JSONInputStream(itemStream)).build(); } @@ -205,8 +231,12 @@ public Response getItems( @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class), @ApiResponse(code = 404, message = "Item not found") }) public Response getItemData(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, + @QueryParam("metadata") @ApiParam(value = "meta data selector", required = false) String namespaceSelector, @PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname) { + final Locale locale = LocaleUtil.getLocale(language); + final Set namespaces = namespaceSelector == null ? Collections.emptySet() + : Arrays.stream(namespaceSelector.split(",")).collect(Collectors.toSet()); logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); // get item @@ -215,7 +245,9 @@ public Response getItemData(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam( // if it exists if (item != null) { logger.debug("Received HTTP GET request at '{}'.", uriInfo.getPath()); - return getItemResponse(Status.OK, item, locale, null); + EnrichedItemDTO dto = EnrichedItemDTOMapper.map(item, true, null, uriInfo.getBaseUri(), locale); + addMetadata(dto, namespaces, null); + return JSONResponse.createResponse(Status.OK, dto, null); } else { logger.info("Received HTTP GET request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname); return getItemNotFoundResponse(itemname); @@ -482,6 +514,56 @@ public Response removeTag(@PathParam("itemname") @ApiParam(value = "item name", return Response.ok(null, MediaType.TEXT_PLAIN).build(); } + @PUT + @RolesAllowed({ Role.ADMIN }) + @Path("/{itemname: [a-zA-Z_0-9]*}/metadata/{namespace}") + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Adds meta data to an item.") + @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 404, message = "Item not found."), + @ApiResponse(code = 405, message = "Metadata not editable.") }) + public Response addMetaData(@PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname, + @PathParam("namespace") @ApiParam(value = "namespace", required = true) String namespace, + @ApiParam(value = "meta data", required = true) MetadataDTO metadata) { + + Item item = getItem(itemname); + + if (item == null) { + logger.info("Received HTTP PUT request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname); + return Response.status(Status.NOT_FOUND).build(); + } + + MetadataKey key = new MetadataKey(namespace, itemname); + Metadata md = new Metadata(key, metadata.value, metadata.config); + metadataRegistry.add(md); + + return Response.ok().build(); + } + + @DELETE + @RolesAllowed({ Role.ADMIN }) + @Path("/{itemname: [a-zA-Z_0-9]*}/metadata/{namespace}") + @ApiOperation(value = "Removes meta data from an item.") + @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), + @ApiResponse(code = 404, message = "Item not found."), + @ApiResponse(code = 405, message = "Meta data not editable.") }) + public Response removeMetaData( + @PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname, + @PathParam("namespace") @ApiParam(value = "namespace", required = true) String namespace) { + + Item item = getItem(itemname); + + if (item == null) { + logger.info("Received HTTP DELETE request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname); + return Response.status(Status.NOT_FOUND).build(); + } + + MetadataKey key = new MetadataKey(namespace, itemname); + metadataRegistry.remove(key); + + return Response.ok().build(); + } + /** * Create or Update an item by supplying an item bean. * @@ -685,6 +767,29 @@ private Collection getItems(@Nullable String type, @Nullable String tags) return items; } + private void addMetadata(EnrichedItemDTO dto, Set namespaces, @Nullable Predicate filter) { + Map metadata = new HashMap<>(); + for (String namespace : namespaces) { + MetadataKey key = new MetadataKey(namespace, dto.name); + Metadata md = metadataRegistry.get(key); + if (md != null && (filter == null || filter.test(md))) { + MetadataDTO mdDto = new MetadataDTO(); + mdDto.value = md.getValue(); + mdDto.config = md.getConfiguration().isEmpty() ? null : md.getConfiguration(); + metadata.put(namespace, mdDto); + } + } + if (dto instanceof EnrichedGroupItemDTO) { + for (EnrichedItemDTO member : ((EnrichedGroupItemDTO) dto).members) { + addMetadata(member, namespaces, filter); + } + } + if (!metadata.isEmpty()) { + // we only set it in the dto if there is really data available + dto.metadata = metadata; + } + } + @Override public boolean isSatisfied() { return itemRegistry != null && managedItemProvider != null && eventPublisher != null && !itemFactories.isEmpty() diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java index 4e6570cb988..637c1dfd79f 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java @@ -12,6 +12,8 @@ */ package org.eclipse.smarthome.io.rest.core.item; +import java.util.Map; + import org.eclipse.smarthome.core.items.dto.ItemDTO; import org.eclipse.smarthome.core.types.StateDescription; @@ -20,6 +22,7 @@ * description and the link. * * @author Dennis Nobel - Initial contribution + * @author Kai Kreuzer - Added metadata * */ public class EnrichedItemDTO extends ItemDTO { @@ -28,6 +31,7 @@ public class EnrichedItemDTO extends ItemDTO { public String state; public String transformedState; public StateDescription stateDescription; + public Map metadata; public EnrichedItemDTO(ItemDTO itemDTO, String link, String state, String transformedState, StateDescription stateDescription) { diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java index ca358500186..8d64195e911 100644 --- a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java @@ -74,6 +74,8 @@ public class GenericItemProvider extends AbstractProvider private ModelRepository modelRepository = null; + private GenericMetadataProvider genericMetaDataProvider = null; + private final Map> itemsMap = new ConcurrentHashMap<>(); private final Collection itemFactorys = new ArrayList(); @@ -81,6 +83,7 @@ public class GenericItemProvider extends AbstractProvider private final Map stateDescriptions = new ConcurrentHashMap<>(); private Integer rank; + private boolean active = false; protected void activate(Map properties) { Object serviceRanking = properties.get(Constants.SERVICE_RANKING); @@ -89,6 +92,21 @@ protected void activate(Map properties) { } else { rank = 0; } + + itemFactorys.forEach(itemFactory -> dispatchBindingsPerItemType(null, itemFactory.getSupportedItemTypes())); + + // process models which are already parsed by modelRepository: + for (String modelName : modelRepository.getAllModelNamesOfType("items")) { + modelChanged(modelName, EventType.ADDED); + } + modelRepository.addModelRepositoryChangeListener(this); + + active = true; + } + + protected void deactivate() { + active = false; + modelRepository.removeModelRepositoryChangeListener(this); } @Override @@ -99,20 +117,20 @@ public Integer getRank() { @Reference() public void setModelRepository(ModelRepository modelRepository) { this.modelRepository = modelRepository; - - // process models which are already parsed by modelRepository: - for (String modelName : modelRepository.getAllModelNamesOfType("items")) { - modelChanged(modelName, EventType.ADDED); - } - - modelRepository.addModelRepositoryChangeListener(this); } public void unsetModelRepository(ModelRepository modelRepository) { - modelRepository.removeModelRepositoryChangeListener(this); this.modelRepository = null; } + protected void setGenericMetaDataProvider(GenericMetadataProvider genericMetaDataProvider) { + this.genericMetaDataProvider = genericMetaDataProvider; + } + + protected void unsetGenericMetaDataProvider(GenericMetadataProvider genericMetaDataProvider) { + this.genericMetaDataProvider = null; + } + /** * Add another instance of an {@link ItemFactory}. Used by Declarative Services. * @@ -121,7 +139,9 @@ public void unsetModelRepository(ModelRepository modelRepository) { @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addItemFactory(ItemFactory factory) { itemFactorys.add(factory); - dispatchBindingsPerItemType(null, factory.getSupportedItemTypes()); + if (active) { + dispatchBindingsPerItemType(null, factory.getSupportedItemTypes()); + } } /** @@ -369,8 +389,7 @@ private void internalDispatchBindings(BindingConfigReader reader, String modelNa bindingType, item.getName(), e); } } else { - logger.trace("Couldn't find config reader for binding type '{}' > " - + "parsing binding configuration of Item '{}' aborted!", bindingType, item); + genericMetaDataProvider.addMetadata(bindingType, item.getName(), config, configuration.getProperties()); } } } @@ -399,6 +418,7 @@ public void modelChanged(String modelName, EventType type) { if (!newItems.containsKey(oldItem.getName())) { notifyListenersAboutRemovedElement(oldItem); this.stateDescriptions.remove(oldItem.getName()); + genericMetaDataProvider.removeMetadata(oldItem.getName()); } } break; diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java new file mode 100644 index 00000000000..9a3ba0060d0 --- /dev/null +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.model.item.internal; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.emf.codegen.ecore.templates.edit.ItemProvider; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.common.registry.AbstractProvider; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataProvider; +import org.osgi.service.component.annotations.Component; + +/** + * This class serves as a provider for all metadata that is found within item files. + * It is filled with content by the {@link GenericItemProvider}, which cannot itself implement the + * {@link MetadataProvider} interface as it already implements {@link ItemProvider}, which would lead to duplicate + * methods. + * + * @author Kai Kreuzer - Initial contribution and API + * + */ +@NonNullByDefault +@Component(immediate = true, service = { MetadataProvider.class, GenericMetadataProvider.class }) +public class GenericMetadataProvider extends AbstractProvider implements MetadataProvider { + + Set metadata = new HashSet<>(); + + /** + * Adds metadata to this provider + * + * @param bindingType + * @param itemName + * @param configuration + */ + public void addMetadata(String bindingType, String itemName, String value, Map configuration) { + MetadataKey key = new MetadataKey(bindingType, itemName); + Metadata md = new Metadata(key, value, configuration); + metadata.add(md); + notifyListenersAboutAddedElement(md); + } + + /** + * Removes all meta-data for a given item name + * + * @param itemName + */ + public void removeMetadata(String itemName) { + metadata = metadata.stream().filter(md -> md.getUID().getItemName().equals(itemName)) + .collect(Collectors.toSet()); + } + + @Override + public Collection getAll() { + return Collections.unmodifiableSet(metadata); + } + +} From 59864a682ab8fff82e5c58b25af3b54e16268357 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Fri, 23 Mar 2018 16:22:40 +0100 Subject: [PATCH 02/26] Introduced AbstractUID ...as a common base class for unique, name-spaces IDs. In contrast to the UID class, the AbsstractUID class is bindings agnostic, i.e. it can be used for namespaces other than the binding ID. Signed-off-by: Simon Kaufmann --- .../smarthome/core/thing/ThingUIDTest.java | 10 ++ .../smarthome/core/thing/ChannelUID.java | 42 +++--- .../smarthome/core/thing/ThingUID.java | 12 +- .../org/eclipse/smarthome/core/thing/UID.java | 111 ++++++--------- .../smarthome/core/common/AbstractUID.java | 129 ++++++++++++++++++ 5 files changed, 209 insertions(+), 95 deletions(-) create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java diff --git a/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/java/org/eclipse/smarthome/core/thing/ThingUIDTest.java b/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/java/org/eclipse/smarthome/core/thing/ThingUIDTest.java index 88240daff83..b98b24338d3 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/java/org/eclipse/smarthome/core/thing/ThingUIDTest.java +++ b/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/java/org/eclipse/smarthome/core/thing/ThingUIDTest.java @@ -36,4 +36,14 @@ public void testTwoSegments() { assertEquals("gaga", t.getId()); assertEquals("fake::gaga", t.getAsString()); } + + @Test + public void testGetBridgeIds() { + ThingTypeUID thingType = new ThingTypeUID("fake", "type"); + ThingUID t = new ThingUID(thingType, new ThingUID("fake", "something", "bridge"), "thing"); + + assertEquals("fake:type:bridge:thing", t.getAsString()); + assertEquals(1, t.getBridgeIds().size()); + assertEquals("bridge", t.getBridgeIds().get(0)); + } } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java index cccedd59c34..325964f2a42 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java @@ -12,7 +12,12 @@ */ package org.eclipse.smarthome.core.thing; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * {@link ChannelUID} represents a unique identifier for channels. @@ -22,6 +27,7 @@ * @author Dennis Nobel - Added channel group id * @author Kai Kreuzer - Changed creation of channels to not require a thing type */ +@NonNullByDefault public class ChannelUID extends UID { private static final String CHANNEL_GROUP_SEPERATOR = "#"; @@ -98,17 +104,13 @@ public ChannelUID(String bindingId, String thingTypeId, String thingId, String g super(bindingId, thingTypeId, thingId, getChannelId(groupId, id)); } - private static String[] getArray(ThingUID thingUID, String groupId, String id) { - String[] result = new String[thingUID.getSegments().length + 1]; - for (int i = 0; i < thingUID.getSegments().length; i++) { - result[i] = thingUID.getSegments()[i]; - } - result[result.length - 1] = getChannelId(groupId, id); - - return result; + private static List getArray(ThingUID thingUID, @Nullable String groupId, String id) { + List ret = new ArrayList<>(thingUID.getAllSegments()); + ret.add(getChannelId(groupId, id)); + return ret; } - private static String getChannelId(String groupId, String id) { + private static String getChannelId(@Nullable String groupId, String id) { return groupId != null ? groupId + CHANNEL_GROUP_SEPERATOR + id : id; } @@ -118,8 +120,8 @@ private static String getChannelId(String groupId, String id) { * @return id */ public String getId() { - String[] segments = getSegments(); - return segments[segments.length - 1]; + List segments = getAllSegments(); + return segments.get(segments.size() - 1); } /** @@ -128,17 +130,15 @@ public String getId() { * @return id id without group id */ public String getIdWithoutGroup() { - String[] segments = getSegments(); if (!isInGroup()) { - return segments[segments.length - 1]; + return getId(); } else { - return segments[segments.length - 1].split(CHANNEL_GROUP_SEPERATOR)[1]; + return getId().split(CHANNEL_GROUP_SEPERATOR)[1]; } } public boolean isInGroup() { - String[] segments = getSegments(); - return segments[segments.length - 1].contains(CHANNEL_GROUP_SEPERATOR); + return getId().contains(CHANNEL_GROUP_SEPERATOR); } /** @@ -146,9 +146,8 @@ public boolean isInGroup() { * * @return group id or null if channel is not in a group */ - public String getGroupId() { - String[] segments = getSegments(); - return isInGroup() ? segments[segments.length - 1].split(CHANNEL_GROUP_SEPERATOR)[0] : null; + public @Nullable String getGroupId() { + return isInGroup() ? getId().split(CHANNEL_GROUP_SEPERATOR)[0] : null; } @Override @@ -174,7 +173,8 @@ protected void validateSegment(String segment, int index, int length) { * @return the thing UID */ public ThingUID getThingUID() { - return new ThingUID(Arrays.copyOfRange(getSegments(), 0, getSegments().length - 1)); + List<@NonNull String> allSegments = getAllSegments(); + return new ThingUID(allSegments.subList(0, allSegments.size() - 1).toArray(new String[allSegments.size() - 1])); } } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ThingUID.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ThingUID.java index 2a4d5abdab7..6ecb99be6bd 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ThingUID.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ThingUID.java @@ -173,12 +173,8 @@ public ThingTypeUID getThingTypeUID() { * @return list of bridge ids */ public List getBridgeIds() { - List bridgeIds = new ArrayList<>(); - String[] segments = getSegments(); - for (int i = 2; i < segments.length - 1; i++) { - bridgeIds.add(segments[i]); - } - return bridgeIds; + List allSegments = getAllSegments(); + return allSegments.subList(2, allSegments.size() - 1); } /** @@ -187,8 +183,8 @@ public List getBridgeIds() { * @return id the id */ public String getId() { - String[] segments = getSegments(); - return segments[segments.length - 1]; + List segments = getAllSegments(); + return segments.get(segments.size() - 1); } @Override diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/UID.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/UID.java index 4420531b741..0e6632b1f08 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/UID.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/UID.java @@ -12,28 +12,30 @@ */ package org.eclipse.smarthome.core.thing; -import java.util.Arrays; -import java.util.stream.Collectors; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.common.AbstractUID; /** - * {@link UID} is the base class for unique identifiers within the SmartHome - * framework. A UID must always start with a binding ID. + * Base class for binding related unique identifiers within the SmartHome framework. + *

+ * A UID must always start with a binding ID. * * @author Dennis Nobel - Initial contribution * @author Oliver Libutzki - Added possibility to define UIDs with variable amount of segments * @author Jochen Hiller - Bugfix 455434: added default constructor, object is now mutable */ -public abstract class UID { - - public static final String SEGMENT_PATTERN = "[A-Za-z0-9_-]*"; - public static final String SEPARATOR = ":"; - private final String[] segments; +@NonNullByDefault +public abstract class UID extends AbstractUID { /** - * Constructor must be public, otherwise it can not be called by subclasses from another package. + * For reflection only. + * Constructor must be public, otherwise it cannot be called by subclasses from another package. */ public UID() { - this.segments = null; + super(); } /** @@ -43,14 +45,7 @@ public UID() { * @param uid uid in form a string (must not be null) */ public UID(String uid) { - this(splitToSegments(uid)); - } - - private static String[] splitToSegments(String uid) { - if (uid == null) { - throw new IllegalArgumentException("Given uid must not be null."); - } - return uid.split(SEPARATOR); + super(uid); } /** @@ -59,40 +54,16 @@ private static String[] splitToSegments(String uid) { * @param segments segments (must not be null) */ public UID(String... segments) { - if (segments == null) { - throw new IllegalArgumentException("Given segments argument must not be null."); - } - int numberOfSegments = getMinimalNumberOfSegments(); - if (segments.length < numberOfSegments) { - throw new IllegalArgumentException("UID must have at least " + numberOfSegments + " segments."); - } - for (int i = 0; i < segments.length; i++) { - String segment = segments[i]; - validateSegment(segment, i, segments.length); - } - this.segments = segments; + super(segments); } /** - * Specifies how many segments the UID has to have at least. + * Creates a UID for list of segments. * - * @return + * @param segments segments (must not be null) */ - protected abstract int getMinimalNumberOfSegments(); - - protected String[] getSegments() { - return this.segments; - } - - protected String getSegment(int segment) { - return this.segments[segment]; - } - - protected void validateSegment(String segment, int index, int length) { - if (!segment.matches(SEGMENT_PATTERN)) { - throw new IllegalArgumentException("UID segment '" + segment - + "' contains invalid characters. Each segment of the UID must match the pattern [A-Za-z0-9_-]*."); - } + protected UID(List segments) { + super(segments); } /** @@ -101,38 +72,46 @@ protected void validateSegment(String segment, int index, int length) { * @return binding id */ public String getBindingId() { - return segments[0]; + return getSegment(0); + } + + /** + * @deprecated use {@link #getAllSegments()} instead + */ + @Deprecated + protected String[] getSegments() { + final List segments = super.getAllSegments(); + return segments.toArray(new String[segments.size()]); } @Override + // Avoid subclasses to require importing the o.e.sh.core.common package + protected List getAllSegments() { + return super.getAllSegments(); + } + + @Override + // Avoid bindings to require importing the o.e.sh.core.common package public String toString() { - return getAsString(); + return super.toString(); } + @Override + // Avoid bindings to require importing the o.e.sh.core.common package public String getAsString() { - return Arrays.stream(segments).collect(Collectors.joining(SEPARATOR)); + return super.getAsString(); } @Override + // Avoid bindings to require importing the o.e.sh.core.common package public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(segments); - return result; + return super.hashCode(); } @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - UID other = (UID) obj; - if (!Arrays.equals(segments, other.segments)) - return false; - return true; + // Avoid bindings to require importing the o.e.sh.core.common package + public boolean equals(@Nullable Object obj) { + return super.equals(obj); } } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java new file mode 100644 index 00000000000..bb10f4bbce4 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java @@ -0,0 +1,129 @@ +package org.eclipse.smarthome.core.common; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * A non specific base class for unique identifiers within the SmartHome framework. + * + * @author Markus Rathgeb - Splitted from the Thing's UID class + */ +@NonNullByDefault +public abstract class AbstractUID { + + public static final String SEGMENT_PATTERN = "[A-Za-z0-9_-]*"; + public static final String SEPARATOR = ":"; + private final List segments; + + /** + * Constructor must be public, otherwise it can not be called by subclasses from another package. + */ + public AbstractUID() { + this.segments = Collections.emptyList(); + } + + /** + * Parses a UID for a given string. The UID must be in the format + * 'bindingId:segment:segment:...'. + * + * @param uid uid in form a string (must not be null) + */ + public AbstractUID(String uid) { + this(splitToSegments(uid)); + } + + /** + * Creates a AbstractUID for a list of segments. + * + * @param segments the id segments + */ + public AbstractUID(final String... segments) { + this(Arrays.asList(segments)); + } + + /** + * Creates a UID for list of segments. + * + * @param segments segments (must not be null) + */ + public AbstractUID(List segments) { + int minNumberOfSegments = getMinimalNumberOfSegments(); + int numberOfSegments = segments.size(); + if (numberOfSegments < minNumberOfSegments) { + throw new IllegalArgumentException( + String.format("UID must have at least %d segments.", minNumberOfSegments)); + } + for (int i = 0; i < numberOfSegments; i++) { + String segment = segments.get(i); + validateSegment(segment, i, numberOfSegments); + } + this.segments = segments; + } + + /** + * Specifies how many segments the UID has to have at least. + * + * @return + */ + protected abstract int getMinimalNumberOfSegments(); + + protected List getAllSegments() { + return segments; + } + + protected String getSegment(int segment) { + return segments.get(segment); + } + + protected void validateSegment(String segment, int index, int length) { + if (!segment.matches(SEGMENT_PATTERN)) { + throw new IllegalArgumentException(String.format( + "ID segment '%s' contains invalid characters. Each segment of the ID must match the pattern %s.", + segment, SEGMENT_PATTERN)); + } + } + + @Override + public String toString() { + return getAsString(); + } + + public String getAsString() { + return String.join(SEPARATOR, segments); + } + + private static List splitToSegments(final String id) { + return Arrays.asList(id.split(SEPARATOR)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + segments.hashCode(); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AbstractUID other = (AbstractUID) obj; + if (!segments.equals(other.segments)) { + return false; + } + return true; + } + +} From 9f19a439b3f1d375c67ca45835653f015a743877 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Fri, 23 Mar 2018 16:24:43 +0100 Subject: [PATCH 03/26] [metadata] turned MetadataKey into a AbstractUID Signed-off-by: Simon Kaufmann --- .../smarthome/core/items/MetadataKey.java | 51 +++---------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java index d7e912a8edd..241df966f03 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java @@ -7,9 +7,8 @@ */ package org.eclipse.smarthome.core.items; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.common.AbstractUID; /** * This class represents the key of a {@link Metadata} entity. @@ -19,10 +18,7 @@ * */ @NonNullByDefault -public class MetadataKey { - - private final String namespace; - private final String itemName; +public class MetadataKey extends AbstractUID { /** * Creates a new instance. @@ -31,8 +27,7 @@ public class MetadataKey { * @param itemName */ public MetadataKey(String namespace, String itemName) { - this.namespace = namespace; - this.itemName = itemName; + super(namespace, itemName); } /** @@ -41,50 +36,20 @@ public MetadataKey(String namespace, String itemName) { * @return the item name */ public String getItemName() { - return itemName; + return getSegment(1); } /** * Provides the namespace of this key - * + * * @return the namespace */ public String getNamespace() { - return namespace; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((itemName == null) ? 0 : itemName.hashCode()); - result = prime * result + ((namespace == null) ? 0 : namespace.hashCode()); - return result; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - MetadataKey other = (MetadataKey) obj; - if (!itemName.equals(other.itemName)) { - return false; - } - if (!namespace.equals(other.namespace)) { - return false; - } - return true; + return getSegment(0); } @Override - public @NonNull String toString() { - return namespace + ":" + itemName; + protected int getMinimalNumberOfSegments() { + return 2; } } From 0d601008609445dae4ea84d73c9375ca271cf857 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Mon, 26 Mar 2018 14:19:13 +0200 Subject: [PATCH 04/26] [metadata] minor cleanups Signed-off-by: Simon Kaufmann --- .../smarthome/core/items/ManagedMetadataProvider.java | 2 +- .../model/item/internal/GenericItemProvider.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java index 5a90222d3a8..aec7cc41197 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java @@ -65,7 +65,7 @@ protected void unsetStorageService(StorageService storageService) { * @param itemname the name of the item for which the meta data is to be removed. */ public void removeItemMetadata(@NonNull String name) { - getAll().stream().filter(MetadataPredicates.ofItem(name)).forEach(md -> remove(md.getUID())); + getAll().stream().filter(MetadataPredicates.ofItem(name)).map(Metadata::getUID).forEach(this::remove); } } diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java index 8d64195e911..2a9d9fe69e0 100644 --- a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java @@ -114,7 +114,7 @@ public Integer getRank() { return rank; } - @Reference() + @Reference public void setModelRepository(ModelRepository modelRepository) { this.modelRepository = modelRepository; } @@ -123,11 +123,12 @@ public void unsetModelRepository(ModelRepository modelRepository) { this.modelRepository = null; } - protected void setGenericMetaDataProvider(GenericMetadataProvider genericMetaDataProvider) { - this.genericMetaDataProvider = genericMetaDataProvider; + @Reference + protected void setGenericMetadataProvider(GenericMetadataProvider genericMetadataProvider) { + this.genericMetaDataProvider = genericMetadataProvider; } - protected void unsetGenericMetaDataProvider(GenericMetadataProvider genericMetaDataProvider) { + protected void unsetGenericMetadataProvider(GenericMetadataProvider genericMetadataProvider) { this.genericMetaDataProvider = null; } From f49460a90e391a753537497116dcff309a14c733 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Mon, 26 Mar 2018 14:20:34 +0200 Subject: [PATCH 05/26] [metadata] Cleanup in MetadataRegistry based on ManagedItemProvider only Signed-off-by: Simon Kaufmann --- .../internal/items/MetadataRegistryImpl.java | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java index 8aaeffecbef..fb1070c029d 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java @@ -12,10 +12,10 @@ import org.eclipse.smarthome.core.common.registry.AbstractRegistry; import org.eclipse.smarthome.core.common.registry.Provider; -import org.eclipse.smarthome.core.common.registry.RegistryChangeListener; +import org.eclipse.smarthome.core.common.registry.ProviderChangeListener; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.items.Item; -import org.eclipse.smarthome.core.items.ItemRegistry; +import org.eclipse.smarthome.core.items.ManagedItemProvider; import org.eclipse.smarthome.core.items.ManagedMetadataProvider; import org.eclipse.smarthome.core.items.Metadata; import org.eclipse.smarthome.core.items.MetadataKey; @@ -40,10 +40,27 @@ */ @Component(immediate = true, service = { MetadataRegistry.class }) public class MetadataRegistryImpl extends AbstractRegistry - implements MetadataRegistry, RegistryChangeListener { + implements MetadataRegistry { private final Logger logger = LoggerFactory.getLogger(MetadataRegistryImpl.class); - private ItemRegistry itemRegistry; + + private final ProviderChangeListener itemProviderChangeListener = new ProviderChangeListener() { + @Override + public void added(Provider provider, Item element) { + } + + @Override + public void removed(Provider provider, Item element) { + if (managedProvider != null) { + // remove our metadata for that item + ((ManagedMetadataProvider) managedProvider).removeItemMetadata(element.getName()); + } + } + + @Override + public void updated(Provider provider, Item oldelement, Item element) { + } + }; public MetadataRegistryImpl() { super(MetadataProvider.class); @@ -53,35 +70,33 @@ public MetadataRegistryImpl() { @Activate protected void activate(BundleContext context) { super.activate(context); - itemRegistry.addRegistryChangeListener(this); } @Override @Deactivate protected void deactivate() { super.deactivate(); - itemRegistry.removeRegistryChangeListener(this); } @Override public Metadata get(MetadataKey key) { for (final Map.Entry, Collection> entry : elementMap.entrySet()) { - for (final Metadata item : entry.getValue()) { - if (item.getUID().equals(key)) { - return item; + for (final Metadata metadata : entry.getValue()) { + if (metadata.getUID().equals(key)) { + return metadata; } } } return null; } - @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC) - protected void setItemRegistry(ItemRegistry itemRegistry) { - this.itemRegistry = itemRegistry; + @Reference + protected void setManagedItemProvider(ManagedItemProvider managedItemProvider) { + managedItemProvider.addProviderChangeListener(itemProviderChangeListener); } - protected void unsetItemRegistry(ItemRegistry itemRegistry) { - this.itemRegistry = null; + protected void unsetManagedItemProvider(ManagedItemProvider managedItemProvider) { + managedItemProvider.removeProviderChangeListener(itemProviderChangeListener); } @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) @@ -95,7 +110,7 @@ protected void unsetEventPublisher(EventPublisher eventPublisher) { super.unsetEventPublisher(eventPublisher); } - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, name = "ManagedThingProvider") + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) protected void setManagedProvider(ManagedMetadataProvider provider) { super.setManagedProvider(provider); } @@ -104,21 +119,4 @@ protected void unsetManagedProvider(ManagedMetadataProvider managedProvider) { super.removeManagedProvider(managedProvider); } - @Override - public void added(Item element) { - // do nothing - } - - @Override - public void removed(Item element) { - if (managedProvider != null) { - // remove our metadata for that item - ((ManagedMetadataProvider) managedProvider).removeItemMetadata(element.getName()); - } - } - - @Override - public void updated(Item oldElement, Item element) { - // do nothing - } } From 173e5754c85625721009d92cc0140fceb6d7e5c3 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 16:03:14 +0200 Subject: [PATCH 06/26] [metadata] implement equals/hashCode in Metadata Signed-off-by: Simon Kaufmann --- .../internal/items/MetadataRegistryImpl.java | 15 ---------- .../smarthome/core/items/Metadata.java | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java index fb1070c029d..13f97d995e3 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java @@ -7,9 +7,6 @@ */ package org.eclipse.smarthome.core.internal.items; -import java.util.Collection; -import java.util.Map; - import org.eclipse.smarthome.core.common.registry.AbstractRegistry; import org.eclipse.smarthome.core.common.registry.Provider; import org.eclipse.smarthome.core.common.registry.ProviderChangeListener; @@ -78,18 +75,6 @@ protected void deactivate() { super.deactivate(); } - @Override - public Metadata get(MetadataKey key) { - for (final Map.Entry, Collection> entry : elementMap.entrySet()) { - for (final Metadata metadata : entry.getValue()) { - if (metadata.getUID().equals(key)) { - return metadata; - } - } - } - return null; - } - @Reference protected void setManagedItemProvider(ManagedItemProvider managedItemProvider) { managedItemProvider.addProviderChangeListener(itemProviderChangeListener); diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java index 72184cfbcb7..2312f391d7b 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java @@ -10,6 +10,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.common.registry.Identifiable; /** @@ -54,4 +55,31 @@ public Map getConfiguration() { public String getValue() { return value; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + key.hashCode(); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Metadata other = (Metadata) obj; + if (!key.equals(other.key)) { + return false; + } + return true; + } + } From 6d6b8e5ee01dd914f475795440d85f00c77de728 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 16:03:47 +0200 Subject: [PATCH 07/26] [metadata] fix metadata removal in GenericItemProvider Signed-off-by: Simon Kaufmann --- .../model/item/internal/GenericItemProvider.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java index 2a9d9fe69e0..966a945eccb 100644 --- a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider.java @@ -417,9 +417,7 @@ public void modelChanged(String modelName, EventType type) { processBindingConfigsFromModel(modelName, type); for (Item oldItem : oldItems.values()) { if (!newItems.containsKey(oldItem.getName())) { - notifyListenersAboutRemovedElement(oldItem); - this.stateDescriptions.remove(oldItem.getName()); - genericMetaDataProvider.removeMetadata(oldItem.getName()); + notifyAndCleanup(oldItem); } } break; @@ -428,13 +426,19 @@ public void modelChanged(String modelName, EventType type) { Collection itemsFromModel = getItemsFromModel(modelName); itemsMap.remove(modelName); for (Item item : itemsFromModel) { - notifyListenersAboutRemovedElement(item); + notifyAndCleanup(item); } break; } } } + private void notifyAndCleanup(Item oldItem) { + notifyListenersAboutRemovedElement(oldItem); + this.stateDescriptions.remove(oldItem.getName()); + genericMetaDataProvider.removeMetadata(oldItem.getName()); + } + protected boolean hasItemChanged(Item item1, Item item2) { return !Objects.equals(item1.getClass(), item2.getClass()) || // !Objects.equals(item1.getName(), item2.getName()) || // From ba34419684115e59217f7e81bcfe9c4a4f932d86 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 16:09:59 +0200 Subject: [PATCH 08/26] [metadata] made GenericMetadataProvider thread-safe, fixed removal Signed-off-by: Simon Kaufmann --- .../internal/GenericMetadataProvider.java | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java index 9a3ba0060d0..e57a3619f41 100644 --- a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java @@ -7,18 +7,22 @@ */ package org.eclipse.smarthome.model.item.internal; +import static java.util.stream.Collectors.toSet; + import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.eclipse.emf.codegen.ecore.templates.edit.ItemProvider; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.core.common.registry.AbstractProvider; import org.eclipse.smarthome.core.items.Metadata; import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataPredicates; import org.eclipse.smarthome.core.items.MetadataProvider; import org.osgi.service.component.annotations.Component; @@ -35,7 +39,8 @@ @Component(immediate = true, service = { MetadataProvider.class, GenericMetadataProvider.class }) public class GenericMetadataProvider extends AbstractProvider implements MetadataProvider { - Set metadata = new HashSet<>(); + private final Set metadata = new HashSet<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(true); /** * Adds metadata to this provider @@ -47,7 +52,12 @@ public class GenericMetadataProvider extends AbstractProvider implemen public void addMetadata(String bindingType, String itemName, String value, Map configuration) { MetadataKey key = new MetadataKey(bindingType, itemName); Metadata md = new Metadata(key, value, configuration); - metadata.add(md); + try { + lock.writeLock().lock(); + metadata.add(md); + } finally { + lock.writeLock().unlock(); + } notifyListenersAboutAddedElement(md); } @@ -56,14 +66,28 @@ public void addMetadata(String bindingType, String itemName, String value, Map md.getUID().getItemName().equals(itemName)) - .collect(Collectors.toSet()); + public void removeMetaData(String itemName) { + Set toBeRemoved; + try { + lock.writeLock().lock(); + toBeRemoved = metadata.stream().filter(MetadataPredicates.ofItem(itemName)).collect(toSet()); + metadata.removeAll(toBeRemoved); + } finally { + lock.writeLock().unlock(); + } + for (Metadata m : toBeRemoved) { + notifyListenersAboutRemovedElement(m); + } } @Override public Collection getAll() { - return Collections.unmodifiableSet(metadata); + try { + lock.readLock().lock(); + return Collections.unmodifiableSet(metadata); + } finally { + lock.readLock().unlock(); + } } } From a4bd749d16efdbc53768b1ae73a26f0523b2e8b3 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 16:10:49 +0200 Subject: [PATCH 09/26] added UoM to model.item.test launch config Signed-off-by: Simon Kaufmann --- .../org.eclipse.smarthome.model.item.tests.launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/model/org.eclipse.smarthome.model.item.tests/org.eclipse.smarthome.model.item.tests.launch b/bundles/model/org.eclipse.smarthome.model.item.tests/org.eclipse.smarthome.model.item.tests.launch index 40f79519ed1..9b642ab6935 100644 --- a/bundles/model/org.eclipse.smarthome.model.item.tests/org.eclipse.smarthome.model.item.tests.launch +++ b/bundles/model/org.eclipse.smarthome.model.item.tests/org.eclipse.smarthome.model.item.tests.launch @@ -34,7 +34,7 @@ - + From 107254dbf20297978fbcacc4f98de95dfbcf4d32 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 16:23:16 +0200 Subject: [PATCH 10/26] [metadata] optional metadata configuration, immutable Signed-off-by: Simon Kaufmann --- .../java/org/eclipse/smarthome/core/items/Metadata.java | 8 +++++--- .../model/item/internal/GenericMetadataProvider.java | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java index 2312f391d7b..c7df46537cf 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java @@ -7,6 +7,8 @@ */ package org.eclipse.smarthome.core.items; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -27,10 +29,10 @@ public class Metadata implements Identifiable { private final String value; private final Map configuration; - public Metadata(MetadataKey key, String value, Map configuration) { + public Metadata(MetadataKey key, String value, @Nullable Map configuration) { this.key = key; this.value = value; - this.configuration = configuration; + this.configuration = configuration != null ? new HashMap<>(configuration) : Collections.emptyMap(); } @Override @@ -44,7 +46,7 @@ public MetadataKey getUID() { * @return configuration as a map of key-value pairs */ public Map getConfiguration() { - return configuration; + return Collections.unmodifiableMap(configuration); } /** diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java index e57a3619f41..fd7a62f66b5 100644 --- a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java @@ -19,6 +19,7 @@ import org.eclipse.emf.codegen.ecore.templates.edit.ItemProvider; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.common.registry.AbstractProvider; import org.eclipse.smarthome.core.items.Metadata; import org.eclipse.smarthome.core.items.MetadataKey; @@ -49,7 +50,8 @@ public class GenericMetadataProvider extends AbstractProvider implemen * @param itemName * @param configuration */ - public void addMetadata(String bindingType, String itemName, String value, Map configuration) { + public void addMetadata(String bindingType, String itemName, String value, + @Nullable Map configuration) { MetadataKey key = new MetadataKey(bindingType, itemName); Metadata md = new Metadata(key, value, configuration); try { @@ -66,7 +68,7 @@ public void addMetadata(String bindingType, String itemName, String value, Map toBeRemoved; try { lock.writeLock().lock(); From ec384043c4b740c72018a4de8d88e993f310006e Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 17:01:10 +0200 Subject: [PATCH 11/26] [metadata] handle update and exceptional cases in ItemResource Signed-off-by: Simon Kaufmann --- .../rest/core/internal/item/ItemResource.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java index ff7274879b5..8e0e5198fe2 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java @@ -231,7 +231,7 @@ public Response getItems( @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class), @ApiResponse(code = 404, message = "Item not found") }) public Response getItemData(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, - @QueryParam("metadata") @ApiParam(value = "meta data selector", required = false) String namespaceSelector, + @QueryParam("metadata") @ApiParam(value = "metadata selector", required = false) String namespaceSelector, @PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname) { final Locale locale = LocaleUtil.getLocale(language); @@ -518,13 +518,15 @@ public Response removeTag(@PathParam("itemname") @ApiParam(value = "item name", @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]*}/metadata/{namespace}") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Adds meta data to an item.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item not found."), + @ApiOperation(value = "Adds metadata to an item.") + @ApiResponses(value = { // + @ApiResponse(code = 200, message = "OK"), // + @ApiResponse(code = 201, message = "Created"), // + @ApiResponse(code = 404, message = "Item not found."), // @ApiResponse(code = 405, message = "Metadata not editable.") }) - public Response addMetaData(@PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname, + public Response addMetadata(@PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname, @PathParam("namespace") @ApiParam(value = "namespace", required = true) String namespace, - @ApiParam(value = "meta data", required = true) MetadataDTO metadata) { + @ApiParam(value = "metadata", required = true) MetadataDTO metadata) { Item item = getItem(itemname); @@ -535,19 +537,24 @@ public Response addMetaData(@PathParam("itemname") @ApiParam(value = "item name" MetadataKey key = new MetadataKey(namespace, itemname); Metadata md = new Metadata(key, metadata.value, metadata.config); - metadataRegistry.add(md); + if (metadataRegistry.get(key) == null) { + metadataRegistry.add(md); + return Response.status(Status.CREATED).type(MediaType.TEXT_PLAIN).build(); + } else { + metadataRegistry.update(md); + return Response.ok(null, MediaType.TEXT_PLAIN).build(); + } - return Response.ok().build(); } @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]*}/metadata/{namespace}") - @ApiOperation(value = "Removes meta data from an item.") + @ApiOperation(value = "Removes metadata from an item.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Item not found."), @ApiResponse(code = 405, message = "Meta data not editable.") }) - public Response removeMetaData( + public Response removeMetadata( @PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname, @PathParam("namespace") @ApiParam(value = "namespace", required = true) String namespace) { @@ -559,9 +566,19 @@ public Response removeMetaData( } MetadataKey key = new MetadataKey(namespace, itemname); - metadataRegistry.remove(key); + if (metadataRegistry.get(key) != null) { + if (metadataRegistry.remove(key) == null) { + logger.info("Received HTTP DELETE request at '{}' for unmanaged item meta-data '{}'.", + uriInfo.getPath(), key); + return Response.status(Status.CONFLICT).build(); + } + } else { + logger.info("Received HTTP DELETE request at '{}' for unknown item meta-data '{}'.", uriInfo.getPath(), + key); + return Response.status(Status.NOT_FOUND).build(); + } - return Response.ok().build(); + return Response.ok(null, MediaType.TEXT_PLAIN).build(); } /** From fc6a80ee33d8a45e157eba4c8bbbf775fdac9374 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Tue, 27 Mar 2018 16:11:22 +0200 Subject: [PATCH 12/26] [metadata] added tests for metadata Signed-off-by: Simon Kaufmann --- .../items/MetadataRegistryImplTest.java | 123 ++++++++++++++++++ .../smarthome/core/items/MetadataKeyTest.java | 34 +++++ .../internal/item/ItemResourceOSGiTest.java | 54 ++++++++ .../internal/GenericItemProvider2Test.java | 62 +++++++-- .../internal/GenericMetadataProviderTest.java | 62 +++++++++ 5 files changed, 325 insertions(+), 10 deletions(-) create mode 100644 bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImplTest.java create mode 100644 bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/items/MetadataKeyTest.java create mode 100644 bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProviderTest.java diff --git a/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImplTest.java b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImplTest.java new file mode 100644 index 00000000000..9aba28de985 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImplTest.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.internal.items; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.util.Collections; + +import org.eclipse.smarthome.core.common.registry.ProviderChangeListener; +import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.items.ManagedItemProvider; +import org.eclipse.smarthome.core.items.ManagedMetadataProvider; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; + +/** + * @author Simon Kaufmann - initial contribution and API + */ +public class MetadataRegistryImplTest { + + private static final String ITEM_NAME = "itemName"; + + @SuppressWarnings("rawtypes") + private @Mock ServiceReference managedProviderRef; + private @Mock BundleContext bundleContext; + private @Mock ManagedItemProvider itemProvider; + private @Mock ManagedMetadataProvider managedProvider; + private @Mock Item item; + + private ServiceListener providerTracker; + + private MetadataRegistryImpl registry; + + private ProviderChangeListener providerChangeListener; + + @Before + @SuppressWarnings("unchecked") + public void setup() throws Exception { + initMocks(this); + + when(bundleContext.getService(same(managedProviderRef))).thenReturn(managedProvider); + + when(item.getName()).thenReturn(ITEM_NAME); + + registry = new MetadataRegistryImpl(); + + registry.setManagedItemProvider(itemProvider); + registry.setManagedProvider(managedProvider); + registry.activate(bundleContext); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ServiceListener.class); + verify(bundleContext).addServiceListener(captor.capture(), any()); + providerTracker = captor.getValue(); + providerTracker.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, managedProviderRef)); + + ArgumentCaptor> captorChangeListener = ArgumentCaptor + .forClass(ProviderChangeListener.class); + verify(itemProvider).addProviderChangeListener(captorChangeListener.capture()); + providerChangeListener = captorChangeListener.getValue(); + } + + @Test + public void testManagedItemProviderChangeListenerRegistration() { + verify(itemProvider).addProviderChangeListener(any()); + verifyNoMoreInteractions(itemProvider); + + registry.unsetManagedItemProvider(itemProvider); + verify(itemProvider).removeProviderChangeListener(any()); + verifyNoMoreInteractions(itemProvider); + } + + @Test + public void testRemoved() { + providerChangeListener.removed(itemProvider, item); + verify(managedProvider).removeItemMetadata(eq(ITEM_NAME)); + } + + @Test + public void testGet_empty() throws Exception { + MetadataKey key = new MetadataKey("namespace", "itemName"); + + Metadata res = registry.get(key); + assertNull(res); + } + + @Test + public void testGet() throws Exception { + MetadataKey key = new MetadataKey("namespace", "itemName"); + registry.added(managedProvider, new Metadata(key, "value", Collections.emptyMap())); + registry.added(managedProvider, + new Metadata(new MetadataKey("other", "itemName"), "other", Collections.emptyMap())); + registry.added(managedProvider, + new Metadata(new MetadataKey("namespace", "other"), "other", Collections.emptyMap())); + + Metadata res = registry.get(key); + assertNotNull(res); + assertEquals("value", res.getValue()); + assertEquals("namespace", res.getUID().getNamespace()); + assertEquals("itemName", res.getUID().getItemName()); + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/items/MetadataKeyTest.java b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/items/MetadataKeyTest.java new file mode 100644 index 00000000000..f36d7ebb0b5 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/items/MetadataKeyTest.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * @author Simon Kaufmann - initial contribution and API + */ +public class MetadataKeyTest { + + @Test + public void testGetNamespace() { + assertEquals("namespace", new MetadataKey("namespace", "itemName").getNamespace()); + } + + @Test + public void testGetItemName() { + assertEquals("itemName", new MetadataKey("namespace", "itemName").getItemName()); + } + +} diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java b/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java index 2e9e2778958..17df095dee2 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core.test/src/test/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResourceOSGiTest.java @@ -34,7 +34,11 @@ import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.ItemProvider; import org.eclipse.smarthome.core.items.ManagedItemProvider; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataProvider; import org.eclipse.smarthome.core.items.dto.GroupItemDTO; +import org.eclipse.smarthome.core.items.dto.MetadataDTO; import org.eclipse.smarthome.core.library.items.DimmerItem; import org.eclipse.smarthome.core.library.items.SwitchItem; import org.eclipse.smarthome.test.java.JavaOSGiTest; @@ -206,4 +210,54 @@ public void addMultipleItems() throws IOException { assertThat(statusCodes.get(1), is("updated")); } + @Test + public void testMetadata() { + MetadataDTO dto = new MetadataDTO(); + dto.value = "some value"; + assertEquals(201, itemResource.addMetadata(ITEM_NAME1, "namespace", dto).getStatus()); + assertEquals(200, itemResource.removeMetadata(ITEM_NAME1, "namespace").getStatus()); + assertEquals(404, itemResource.removeMetadata(ITEM_NAME1, "namespace").getStatus()); + } + + @Test + public void testAddMetadata_nonExistingItem() { + MetadataDTO dto = new MetadataDTO(); + dto.value = "some value"; + Response response = itemResource.addMetadata("nonExisting", "foo", dto); + assertEquals(404, response.getStatus()); + } + + @Test + public void testAddMetadata_update() { + MetadataDTO dto = new MetadataDTO(); + dto.value = "some value"; + assertEquals(201, itemResource.addMetadata(ITEM_NAME1, "namespace", dto).getStatus()); + MetadataDTO dto2 = new MetadataDTO(); + dto2.value = "new value"; + assertEquals(200, itemResource.addMetadata(ITEM_NAME1, "namespace", dto2).getStatus()); + } + + @Test + public void testRemoveMetadata_nonExistingItem() { + Response response = itemResource.removeMetadata("nonExisting", "anything"); + assertEquals(404, response.getStatus()); + } + + @Test + public void testRemoveMetadata_nonExistingNamespace() { + Response response = itemResource.removeMetadata(ITEM_NAME1, "anything"); + assertEquals(404, response.getStatus()); + } + + @Test + public void testRemoveMetadata_unmanagedMetadata() { + MetadataProvider provider = mock(MetadataProvider.class); + when(provider.getAll()).thenReturn( + Collections.singleton(new Metadata(new MetadataKey("namespace", ITEM_NAME1), "some value", null))); + registerService(provider); + + Response response = itemResource.removeMetadata(ITEM_NAME1, "namespace"); + assertEquals(409, response.getStatus()); + } + } diff --git a/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider2Test.java b/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider2Test.java index f13e1d91706..835a44350a0 100644 --- a/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider2Test.java +++ b/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericItemProvider2Test.java @@ -12,16 +12,19 @@ */ package org.eclipse.smarthome.model.item.internal; -import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; +import java.math.BigDecimal; import java.util.Iterator; import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.GroupItem; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemRegistry; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataRegistry; import org.eclipse.smarthome.core.library.items.NumberItem; import org.eclipse.smarthome.core.library.items.SwitchItem; import org.eclipse.smarthome.core.library.types.ArithmeticGroupFunction; @@ -45,15 +48,25 @@ public class GenericItemProvider2Test extends JavaOSGiTest { private ModelRepository modelRepository; private ItemRegistry itemRegistry; + private MetadataRegistry metadataRegistry; @Before public void setUp() { + registerVolatileStorageService(); + itemRegistry = getService(ItemRegistry.class); - assertThat(itemRegistry, is(notNullValue())); + assertNotNull(itemRegistry); + modelRepository = getService(ModelRepository.class); - assertThat(modelRepository, is(notNullValue())); + assertNotNull(modelRepository); + + metadataRegistry = getService(MetadataRegistry.class); + assertNotNull(metadataRegistry); + modelRepository.removeModel(TESTMODEL_NAME); modelRepository.removeModel(TESTMODEL_NAME2); + + assertEquals(0, itemRegistry.getAll().size()); } @After @@ -64,8 +77,6 @@ public void tearDown() { @Test public void testStableOrder() { - assertThat(itemRegistry.getAll().size(), is(0)); - String model = "Group testGroup " + // "Number number1 (testGroup) " + // "Number number2 (testGroup) " + // @@ -91,8 +102,6 @@ public void testStableOrder() { @Test public void testStableReloadOrder() { - assertThat(itemRegistry.getAll().size(), is(0)); - String model = "Group testGroup " + // "Number number1 (testGroup) " + // "Number number2 (testGroup) " + // @@ -105,7 +114,7 @@ public void testStableReloadOrder() { "Number number9 (testGroup) "; modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes())); - assertThat(itemRegistry.getAll().size(), is(10)); + assertEquals(10, itemRegistry.getAll().size()); model = "Group testGroup " + // "Number number1 (testGroup) " + // @@ -135,8 +144,6 @@ public void testStableReloadOrder() { @Test public void testGroupAssignmentsAreConsidered() { - assertThat(itemRegistry.getAll().size(), is(0)); - String model = "Group testGroup " + // "Number number1 (testGroup) " + // "Number number2 "; @@ -201,4 +208,39 @@ public void testGroupItemChangesBaseItemAndFunction() { assertTrue(gip.hasItemChanged(g1, g2)); } + + @Test + public void testMetadata_simple() { + String model = "Switch simple { namespace=\"value\" } "; + + modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes())); + Item item = itemRegistry.get("simple"); + assertNotNull(item); + + Metadata res = metadataRegistry.get(new MetadataKey("namespace", "simple")); + assertNotNull(res); + assertEquals("value", res.getValue()); + assertNotNull(res.getConfiguration()); + } + + @Test + public void testMetadata_configured() { + String model = "Switch simple { namespace=\"value\" } " + // + "Switch configured { foo=\"bar\" [ answer=42 ] } "; + + modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes())); + Item item = itemRegistry.get("configured"); + assertNotNull(item); + + Metadata res = metadataRegistry.get(new MetadataKey("foo", "configured")); + assertNotNull(res); + assertEquals("bar", res.getValue()); + assertEquals(new BigDecimal(42), res.getConfiguration().get("answer")); + + modelRepository.removeModel(TESTMODEL_NAME); + + res = metadataRegistry.get(new MetadataKey("foo", "configured")); + assertNull(res); + } + } diff --git a/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProviderTest.java b/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProviderTest.java new file mode 100644 index 00000000000..7e7e2724dcf --- /dev/null +++ b/bundles/model/org.eclipse.smarthome.model.item.tests/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProviderTest.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.model.item.internal; + +import static org.junit.Assert.*; + +import java.util.Collection; + +import org.eclipse.smarthome.core.items.Metadata; +import org.junit.Test; + +/** + * @author Simon Kaufmann - initial contribution and API + */ +public class GenericMetadataProviderTest { + + @Test + public void testGetAll_empty() { + GenericMetadataProvider provider = new GenericMetadataProvider(); + Collection res = provider.getAll(); + assertNotNull(res); + assertEquals(0, res.size()); + } + + @Test + public void testAddMetadata() { + GenericMetadataProvider provider = new GenericMetadataProvider(); + provider.addMetadata("binding", "item", "value", null); + Collection res = provider.getAll(); + assertEquals(1, res.size()); + assertEquals("value", res.iterator().next().getValue()); + } + + @Test + public void testRemoveMetadata_nonExistentItem() { + GenericMetadataProvider provider = new GenericMetadataProvider(); + provider.removeMetadata("nonExistentItem"); + } + + @Test + public void testRemoveMetadata() { + GenericMetadataProvider provider = new GenericMetadataProvider(); + provider.addMetadata("other", "item", "value", null); + provider.addMetadata("binding", "item", "value", null); + provider.addMetadata("binding", "other", "value", null); + assertEquals(3, provider.getAll().size()); + + provider.removeMetadata("item"); + assertEquals(1, provider.getAll().size()); + } + +} From 07e4b69ab4c97c1a051cae215aa70575b38f4f51 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Wed, 28 Mar 2018 11:53:25 +0200 Subject: [PATCH 13/26] [metadata] added a section to the item concept documentation Signed-off-by: Simon Kaufmann --- docs/documentation/concepts/items.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/documentation/concepts/items.md b/docs/documentation/concepts/items.md index 2cc20854c47..13c592a791f 100644 --- a/docs/documentation/concepts/items.md +++ b/docs/documentation/concepts/items.md @@ -137,3 +137,24 @@ Here is a short table demonstrating conversions for the examples above: | Color | `HSBType` | • `OnOffType` - `OFF` if the brightness level in the `HSBType` equals 0, `ON` otherwise
• `PercentType` - the value for the brightness level in the `HSBType` | | Dimmer | `PercentType` | `OnOffType` - `OFF` if the brightness level indicated by the percent type equals 0, `ON` otherwise | | Rollershutter | `PercentType` | `UpDownType` - `UP` if the shutter level indicated by the percent type equals 0, `DOWN` if it equals 100, and `UnDefType.UNDEF` for any other value| + +## Item Metadata + +Sometimes additional information is required to be attached to items for certain use-cases. +This could be e.g. an application which needs some hints in order to render the items in a generic way or an integration with voice controlled assistants or any other services which access the items and need to understand their "meaning". + +For this purpose, such meta-information can be attached to items using disjunct namespaces so they won't conflict with each other. +Each metadata entry has a main value and optionally additional key/value pairs. +There can be metadata for as many namespaces attached to an item as desired. +The following example show how `something` is configured for the `example` namespace, together with `bar` for the `foo` property in an `*.items`-file: + + Switch light { channel="...", example="something" [ foo="bar" ] } + +The metadata can alternatively maintained via a dedicated REST endpoint and is included in the `EnrichedItemDTO` responses. + +Extensions which can infer some metadata automatically need to implement an register a `MetadataProvider` service in order to make them available to the system. +They may provision them from any source they like and also dynamically remove or add data. +They are also not restricted to a single namespace. + +The `MetadataRegistry` provides access for all extensions which need to read the item metadata programmatically. +It is the central place where additional information about items is kept. From 2b7738f0d5ac9605475d2124741d42590cbfea6c Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Wed, 28 Mar 2018 13:32:47 +0200 Subject: [PATCH 14/26] [metadata] added missing log statements Signed-off-by: Simon Kaufmann --- .../smarthome/core/internal/items/MetadataRegistryImpl.java | 1 + .../eclipse/smarthome/core/items/ManagedMetadataProvider.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java index 13f97d995e3..c006731eb65 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java @@ -50,6 +50,7 @@ public void added(Provider provider, Item element) { public void removed(Provider provider, Item element) { if (managedProvider != null) { // remove our metadata for that item + logger.debug("Item {} was removed, trying to clean up corresponding metadata", element.getUID()); ((ManagedMetadataProvider) managedProvider).removeItemMetadata(element.getName()); } } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java index aec7cc41197..13da5e5b582 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java @@ -62,9 +62,10 @@ protected void unsetStorageService(StorageService storageService) { /** * Removes all metadata of a given item * - * @param itemname the name of the item for which the meta data is to be removed. + * @param itemname the name of the item for which the metadata is to be removed. */ public void removeItemMetadata(@NonNull String name) { + logger.debug("Removing all metadata for item {}", name); getAll().stream().filter(MetadataPredicates.ofItem(name)).map(Metadata::getUID).forEach(this::remove); } From eb742c31f907d13cf6956215e5954e23c29e398c Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Wed, 28 Mar 2018 14:58:42 +0200 Subject: [PATCH 15/26] protect AbstractUID segments from external tampering Signed-off-by: Simon Kaufmann --- .../java/org/eclipse/smarthome/core/common/AbstractUID.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java index bb10f4bbce4..5595efbf4b5 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java @@ -1,5 +1,6 @@ package org.eclipse.smarthome.core.common; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -61,7 +62,7 @@ public AbstractUID(List segments) { String segment = segments.get(i); validateSegment(segment, i, numberOfSegments); } - this.segments = segments; + this.segments = Collections.unmodifiableList(new ArrayList<>(segments)); } /** From 4d23ff080372659b51603fb3e85d4f4a6364bffe Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Fri, 6 Apr 2018 14:22:48 +0200 Subject: [PATCH 16/26] hide managed metadata provider implementation in internal package Signed-off-by: Simon Kaufmann --- .../items/ManagedMetadataProviderImpl.java | 78 +++++++++++++++++++ .../core/items/ManagedMetadataProvider.java | 50 +----------- .../smarthome/core/items/Metadata.java | 2 +- .../smarthome/core/items/MetadataKey.java | 2 +- .../core/items/MetadataPredicates.java | 2 +- .../core/items/MetadataRegistry.java | 3 +- 6 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java new file mode 100644 index 00000000000..35effcd1d58 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.internal.items; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.smarthome.core.common.registry.AbstractManagedProvider; +import org.eclipse.smarthome.core.items.ManagedMetadataProvider; +import org.eclipse.smarthome.core.items.Metadata; +import org.eclipse.smarthome.core.items.MetadataKey; +import org.eclipse.smarthome.core.items.MetadataPredicates; +import org.eclipse.smarthome.core.items.MetadataProvider; +import org.eclipse.smarthome.core.storage.StorageService; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ManagedMetadataProviderImpl} is an OSGi service, that allows to add or remove + * metadata for items at runtime. Persistence of added metadata is handled by + * a {@link StorageService}. + * + * @author Kai Kreuzer - Initial contribution + */ +@Component(immediate = true, service = { MetadataProvider.class, ManagedMetadataProvider.class }) +public class ManagedMetadataProviderImpl extends AbstractManagedProvider + implements ManagedMetadataProvider { + + private final Logger logger = LoggerFactory.getLogger(ManagedMetadataProviderImpl.class); + + @Override + protected String getStorageName() { + return Metadata.class.getName(); + } + + @Override + protected @NonNull String keyToString(@NonNull MetadataKey key) { + return key.toString(); + } + + @Override + protected Metadata toElement(@NonNull String key, @NonNull Metadata persistableElement) { + return persistableElement; + } + + @Override + protected Metadata toPersistableElement(Metadata element) { + return element; + } + + @Override + @Reference + protected void setStorageService(StorageService storageService) { + super.setStorageService(storageService); + } + + @Override + protected void unsetStorageService(StorageService storageService) { + super.unsetStorageService(storageService); + } + + /** + * Removes all metadata of a given item + * + * @param itemname the name of the item for which the metadata is to be removed. + */ + @Override + public void removeItemMetadata(@NonNull String name) { + logger.debug("Removing all metadata for item {}", name); + getAll().stream().filter(MetadataPredicates.ofItem(name)).map(Metadata::getUID).forEach(this::remove); + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java index 13da5e5b582..95569a9ab6a 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java @@ -8,65 +8,23 @@ package org.eclipse.smarthome.core.items; import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.smarthome.core.common.registry.AbstractManagedProvider; +import org.eclipse.smarthome.core.common.registry.ManagedProvider; import org.eclipse.smarthome.core.storage.StorageService; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * {@link ManagedMetadataProvider} is an OSGi service, that allows to add or remove + * {@link ManagedMetadataProvider} is an OSGi service interface that allows to add or remove * metadata for items at runtime. Persistence of added metadata is handled by * a {@link StorageService}. * * @author Kai Kreuzer - Initial contribution */ -@Component(immediate = true, service = { MetadataProvider.class, ManagedMetadataProvider.class }) -public class ManagedMetadataProvider extends AbstractManagedProvider - implements MetadataProvider { - - private final Logger logger = LoggerFactory.getLogger(ManagedMetadataProvider.class); - - @Override - protected String getStorageName() { - return Metadata.class.getName(); - } - - @Override - protected @NonNull String keyToString(@NonNull MetadataKey key) { - return key.toString(); - } - - @Override - protected Metadata toElement(@NonNull String key, @NonNull Metadata persistableElement) { - return persistableElement; - } - - @Override - protected Metadata toPersistableElement(Metadata element) { - return element; - } - - @Override - @Reference - protected void setStorageService(StorageService storageService) { - super.setStorageService(storageService); - } - - @Override - protected void unsetStorageService(StorageService storageService) { - super.unsetStorageService(storageService); - } +public interface ManagedMetadataProvider extends ManagedProvider, MetadataProvider { /** * Removes all metadata of a given item * * @param itemname the name of the item for which the metadata is to be removed. */ - public void removeItemMetadata(@NonNull String name) { - logger.debug("Removing all metadata for item {}", name); - getAll().stream().filter(MetadataPredicates.ofItem(name)).map(Metadata::getUID).forEach(this::remove); - } + void removeItemMetadata(@NonNull String name); } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java index c7df46537cf..42f80b5fcd3 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java @@ -23,7 +23,7 @@ * */ @NonNullByDefault -public class Metadata implements Identifiable { +public final class Metadata implements Identifiable { private final MetadataKey key; private final String value; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java index 241df966f03..c50db8fe91b 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java @@ -18,7 +18,7 @@ * */ @NonNullByDefault -public class MetadataKey extends AbstractUID { +public final class MetadataKey extends AbstractUID { /** * Creates a new instance. diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java index 230d4140f35..79a39b728a9 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java @@ -15,7 +15,7 @@ * @author Kai Kreuzer - Initial contribution and API * */ -public class MetadataPredicates { +public final class MetadataPredicates { /** * Creates a {@link Predicate} which can be used to filter {@link Metadata} for a given namespace. diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java index bd2ad74ad09..a99ce67f5cd 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java @@ -19,4 +19,5 @@ * */ public interface MetadataRegistry extends Registry { -} \ No newline at end of file + +} From cf8fcf0e318ef5f73db80f741a27e67cae552c6b Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Fri, 6 Apr 2018 14:24:30 +0200 Subject: [PATCH 17/26] allow filtering config descriptions for a specific scheme Signed-off-by: Simon Kaufmann --- .../internal/config/ConfigDescriptionResource.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/config/ConfigDescriptionResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/config/ConfigDescriptionResource.java index 4624a90bef5..e4964e93f7a 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/config/ConfigDescriptionResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/config/ConfigDescriptionResource.java @@ -22,11 +22,13 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionRegistry; import org.eclipse.smarthome.config.core.dto.ConfigDescriptionDTO; @@ -69,12 +71,14 @@ public class ConfigDescriptionResource implements RESTResource { @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets all available config descriptions.", response = ConfigDescriptionDTO.class, responseContainer = "List") @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = ConfigDescriptionDTO.class, responseContainer = "List")) - public Response getAll(@HeaderParam("Accept-Language") @ApiParam(value = "Accept-Language") String language) { + public Response getAll(@HeaderParam("Accept-Language") @ApiParam(value = "Accept-Language") String language, // + @QueryParam("scheme") @ApiParam(value = "scheme filter", required = false) @Nullable String scheme) { Locale locale = LocaleUtil.getLocale(language); Collection configDescriptions = configDescriptionRegistry.getConfigDescriptions(locale); - return Response.ok(new Stream2JSONInputStream(configDescriptions.stream().map(ConfigDescriptionDTOMapper::map))) - .build(); + return Response.ok(new Stream2JSONInputStream(configDescriptions.stream().filter(configDescription -> { + return scheme == null || scheme.equals(configDescription.getUID().getScheme()); + }).map(ConfigDescriptionDTOMapper::map))).build(); } @GET @@ -105,4 +109,5 @@ protected void unsetConfigDescriptionRegistry(ConfigDescriptionRegistry configDe public boolean isSatisfied() { return configDescriptionRegistry != null; } + } From 4b365a2cb284bcac5dfef59932eeeee805acefc3 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Fri, 6 Apr 2018 16:03:23 +0200 Subject: [PATCH 18/26] [metadata] added missing @Nullable annotation for the namespace selector Signed-off-by: Simon Kaufmann --- .../smarthome/io/rest/core/internal/item/ItemResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java index 8e0e5198fe2..6d408fabee4 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/item/ItemResource.java @@ -231,7 +231,7 @@ public Response getItems( @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class), @ApiResponse(code = 404, message = "Item not found") }) public Response getItemData(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, - @QueryParam("metadata") @ApiParam(value = "metadata selector", required = false) String namespaceSelector, + @QueryParam("metadata") @ApiParam(value = "metadata selector", required = false) @Nullable String namespaceSelector, @PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname) { final Locale locale = LocaleUtil.getLocale(language); From f59c3853c737a5299e04ff9be4d055ce0f3f64b2 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Mon, 9 Apr 2018 14:34:20 +0200 Subject: [PATCH 19/26] incorporated review feedback Signed-off-by: Simon Kaufmann --- .../main/java/org/eclipse/smarthome/core/items/Metadata.java | 5 +++-- .../org/eclipse/smarthome/core/items/MetadataPredicates.java | 3 +++ .../org/eclipse/smarthome/core/items/MetadataProvider.java | 2 ++ .../org/eclipse/smarthome/core/items/MetadataRegistry.java | 2 ++ .../model/item/internal/GenericMetadataProvider.java | 2 +- docs/documentation/concepts/items.md | 5 ++--- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java index 42f80b5fcd3..092918927d4 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java @@ -32,7 +32,8 @@ public final class Metadata implements Identifiable { public Metadata(MetadataKey key, String value, @Nullable Map configuration) { this.key = key; this.value = value; - this.configuration = configuration != null ? new HashMap<>(configuration) : Collections.emptyMap(); + this.configuration = configuration != null ? Collections.unmodifiableMap(new HashMap<>(configuration)) + : Collections.emptyMap(); } @Override @@ -46,7 +47,7 @@ public MetadataKey getUID() { * @return configuration as a map of key-value pairs */ public Map getConfiguration() { - return Collections.unmodifiableMap(configuration); + return configuration; } /** diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java index 79a39b728a9..78b8e561b6b 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java @@ -9,12 +9,15 @@ import java.util.function.Predicate; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Provides some default predicates that are helpful when working with metadata. * * @author Kai Kreuzer - Initial contribution and API * */ +@NonNullByDefault public final class MetadataPredicates { /** diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java index 5cc92275259..cdb2d427719 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java @@ -7,6 +7,7 @@ */ package org.eclipse.smarthome.core.items; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.core.common.registry.Provider; /** @@ -16,6 +17,7 @@ * @author Kai Kreuzer - Initial contribution and API * */ +@NonNullByDefault public interface MetadataProvider extends Provider { } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java index a99ce67f5cd..983f19bdc6d 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java @@ -7,6 +7,7 @@ */ package org.eclipse.smarthome.core.items; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.core.common.registry.Registry; /** @@ -18,6 +19,7 @@ * @author Kai Kreuzer - Initial contribution and API * */ +@NonNullByDefault public interface MetadataRegistry extends Registry { } diff --git a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java index fd7a62f66b5..f87c9beefa7 100644 --- a/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java +++ b/bundles/model/org.eclipse.smarthome.model.item/src/org/eclipse/smarthome/model/item/internal/GenericMetadataProvider.java @@ -37,7 +37,7 @@ * */ @NonNullByDefault -@Component(immediate = true, service = { MetadataProvider.class, GenericMetadataProvider.class }) +@Component(service = { MetadataProvider.class, GenericMetadataProvider.class }) public class GenericMetadataProvider extends AbstractProvider implements MetadataProvider { private final Set metadata = new HashSet<>(); diff --git a/docs/documentation/concepts/items.md b/docs/documentation/concepts/items.md index 13c592a791f..9c29c3543f7 100644 --- a/docs/documentation/concepts/items.md +++ b/docs/documentation/concepts/items.md @@ -145,10 +145,9 @@ This could be e.g. an application which needs some hints in order to render the For this purpose, such meta-information can be attached to items using disjunct namespaces so they won't conflict with each other. Each metadata entry has a main value and optionally additional key/value pairs. -There can be metadata for as many namespaces attached to an item as desired. -The following example show how `something` is configured for the `example` namespace, together with `bar` for the `foo` property in an `*.items`-file: +There can be metadata attached to an item for as many namespaces as desired, like in the following example: - Switch light { channel="...", example="something" [ foo="bar" ] } + Switch "My Fan" { homekit="Fan.v2", alexa="Fan" [ type="oscillating", speedSteps=3 ] } The metadata can alternatively maintained via a dedicated REST endpoint and is included in the `EnrichedItemDTO` responses. From 5924264500e248d386d29a2802a4cb3db88e17c4 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Mon, 9 Apr 2018 14:51:34 +0200 Subject: [PATCH 20/26] [metadata] support config descriptions for meta-data Signed-off-by: Simon Kaufmann --- ...dataConfigDescriptionProviderImplTest.java | 184 ++++++++++++++++++ ...MetadataConfigDescriptionProviderImpl.java | 150 ++++++++++++++ .../MetadataConfigDescriptionProvider.java | 69 +++++++ 3 files changed, 403 insertions(+) create mode 100644 bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java create mode 100644 bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java create mode 100644 bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java diff --git a/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java b/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java new file mode 100644 index 00000000000..7e7bdf43798 --- /dev/null +++ b/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.config.core.internal.items; + +import static org.eclipse.smarthome.config.core.internal.items.MetadataConfigDescriptionProviderImpl.*; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; + +import org.eclipse.smarthome.config.core.ConfigDescription; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder; +import org.eclipse.smarthome.config.core.ParameterOption; +import org.eclipse.smarthome.config.core.items.MetadataConfigDescriptionProvider; +import org.eclipse.smarthome.test.java.JavaTest; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +/** + * + * @author Simon Kaufmann - initial contribution and API + * + */ +public class MetadataConfigDescriptionProviderImplTest extends JavaTest { + + private static final String LIBERAL = "liberal"; + private static final String RESTRICTED = "restricted"; + + private static final URI URI_RESTRICTED = URI.create(SCHEME + SEPARATOR + RESTRICTED); + private static final URI URI_LIBERAL = URI.create(SCHEME + SEPARATOR + LIBERAL); + + private static final URI URI_RESTRICTED_DIMMER = URI.create(SCHEME + SEPARATOR + RESTRICTED + SEPARATOR + "dimmer"); + + private @Mock MetadataConfigDescriptionProvider mockProviderRestricted; + private @Mock MetadataConfigDescriptionProvider mockProviderLiberal; + + private MetadataConfigDescriptionProviderImpl service; + + @Before + public void setup() { + initMocks(this); + service = new MetadataConfigDescriptionProviderImpl(); + + when(mockProviderRestricted.getNamespace()).thenReturn(RESTRICTED); + when(mockProviderRestricted.getDescription(any())).thenReturn("Restricted"); + when(mockProviderRestricted.getParameterOptions(any())).thenReturn(Arrays.asList( // + new ParameterOption("dimmer", "Dimmer"), // + new ParameterOption("switch", "Switch") // + )); + when(mockProviderRestricted.getParameters(eq("dimmer"), any())).thenReturn(Arrays.asList( // + ConfigDescriptionParameterBuilder.create("width", Type.INTEGER).build(), // + ConfigDescriptionParameterBuilder.create("height", Type.INTEGER).build() // + )); + + when(mockProviderLiberal.getNamespace()).thenReturn(LIBERAL); + when(mockProviderLiberal.getDescription(any())).thenReturn("Liberal"); + when(mockProviderLiberal.getParameterOptions(any())).thenReturn(null); + } + + @Test + public void testGetConfigDescriptions_noOptions() { + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + Collection res = service.getConfigDescriptions(Locale.ENGLISH); + assertNotNull(res); + assertEquals(1, res.size()); + + ConfigDescription desc = res.iterator().next(); + assertEquals(URI_LIBERAL, desc.getUID()); + assertEquals(1, desc.getParameters().size()); + + ConfigDescriptionParameter param = desc.getParameters().get(0); + assertEquals("value", param.getName()); + assertEquals("Liberal", param.getDescription()); + assertFalse(param.getLimitToOptions()); + } + + @Test + public void testGetConfigDescriptions_withOptions() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + + Collection res = service.getConfigDescriptions(Locale.ENGLISH); + assertNotNull(res); + assertEquals(1, res.size()); + + ConfigDescription desc = res.iterator().next(); + assertEquals(URI_RESTRICTED, desc.getUID()); + assertEquals(1, desc.getParameters().size()); + + ConfigDescriptionParameter param = desc.getParameters().get(0); + assertEquals("value", param.getName()); + assertEquals("Restricted", param.getDescription()); + assertTrue(param.getLimitToOptions()); + assertEquals("dimmer", param.getOptions().get(0).getValue()); + assertEquals("switch", param.getOptions().get(1).getValue()); + } + + @Test + public void testGetConfigDescription_wrongScheme() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + assertNull(service.getConfigDescription(URI.create("some:nonsense"), null)); + } + + @Test + public void testGetConfigDescription_valueDescription() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + ConfigDescription desc = service.getConfigDescription(URI_LIBERAL, null); + assertNotNull(desc); + assertEquals(URI_LIBERAL, desc.getUID()); + assertEquals(1, desc.getParameters().size()); + + ConfigDescriptionParameter param = desc.getParameters().get(0); + assertEquals("value", param.getName()); + assertEquals("Liberal", param.getDescription()); + assertFalse(param.getLimitToOptions()); + } + + @Test + public void testGetConfigDescription_valueDescriptionNonExistingNamespace() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + ConfigDescription desc = service.getConfigDescription(URI.create("metadata:nonsense"), null); + assertNull(desc); + } + + @Test + public void testGetConfigDescription_propertiesDescription() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + ConfigDescription desc = service.getConfigDescription(URI_RESTRICTED_DIMMER, null); + assertNotNull(desc); + assertEquals(URI_RESTRICTED_DIMMER, desc.getUID()); + assertEquals(2, desc.getParameters().size()); + + ConfigDescriptionParameter paramWidth = desc.getParameters().get(0); + assertEquals("width", paramWidth.getName()); + + ConfigDescriptionParameter paramHeight = desc.getParameters().get(1); + assertEquals("height", paramHeight.getName()); + } + + @Test + public void testGetConfigDescription_propertiesDescriptionNonExistingNamespace() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + ConfigDescription desc = service.getConfigDescription(URI.create("metadata:nonsense:nonsense"), null); + assertNull(desc); + } + + @Test + public void testGetConfigDescription_propertiesDescriptionNonExistingValue() { + service.addMetadataConfigDescriptionProvider(mockProviderRestricted); + service.addMetadataConfigDescriptionProvider(mockProviderLiberal); + + ConfigDescription desc = service.getConfigDescription(URI.create("metadata:foo:nonsense"), null); + assertNull(desc); + } + +} diff --git a/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java new file mode 100644 index 00000000000..cc6e665fd3f --- /dev/null +++ b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.config.core.internal.items; + +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.core.ConfigDescription; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder; +import org.eclipse.smarthome.config.core.ConfigDescriptionProvider; +import org.eclipse.smarthome.config.core.ParameterOption; +import org.eclipse.smarthome.config.core.items.MetadataConfigDescriptionProvider; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +/** + * A {@link ConfigDescriptionProvider} which translated the information of {@link MetadataConfigDescriptionProvider} + * implementations to normal {@link ConfigDescription}s. + *

+ * It exposes the config description for the "main" value under + * + *

+ * {@code
+ *     metadata:
+ * }
+ * 
+ * + * and the config descriptions for the parameters under + * + *
+ * {@code
+ *     metadata::
+ * }
+ * 
+ * + * so that it becomes dependent of the main value and extensions can request different parameters from the user + * depending on which main value was chosen. Implementations of course are free to ignore the {@code value} parameter + * and always return the same set of config descriptions. + * + * @author Simon Kaufmann - initial contribution and API + * + */ +@Component +@NonNullByDefault +public class MetadataConfigDescriptionProviderImpl implements ConfigDescriptionProvider { + + static final String SCHEME = "metadata"; + static final String SEPARATOR = ":"; + + private final List providers = new CopyOnWriteArrayList<>(); + + @Override + public Collection getConfigDescriptions(@Nullable Locale locale) { + List ret = new LinkedList<>(); + ret.addAll(getValueConfigDescriptions(locale)); + return ret; + } + + @Override + public @Nullable ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale) { + if (!SCHEME.equals(uri.getScheme())) { + return null; + } + String part = uri.getSchemeSpecificPart(); + String namespace = part.contains(SEPARATOR) ? part.substring(0, part.indexOf(SEPARATOR)) : part; + String value = part.contains(SEPARATOR) ? part.substring(part.indexOf(SEPARATOR) + 1) : null; + for (MetadataConfigDescriptionProvider provider : providers) { + if (namespace.equals(provider.getNamespace())) { + if (value == null) { + return createValueConfigDescription(provider, locale); + } else { + return createParamConfigDescription(provider, value, locale); + } + } + } + return null; + } + + private List getValueConfigDescriptions(@Nullable Locale locale) { + List ret = new LinkedList<>(); + for (MetadataConfigDescriptionProvider provider : providers) { + ret.add(createValueConfigDescription(provider, locale)); + } + return ret; + } + + private ConfigDescription createValueConfigDescription(MetadataConfigDescriptionProvider provider, + @Nullable Locale locale) { + String namespace = provider.getNamespace(); + String description = provider.getDescription(locale); + List options = provider.getParameterOptions(locale); + URI uri = URI.create(SCHEME + SEPARATOR + namespace); + + ConfigDescriptionParameterBuilder builder = ConfigDescriptionParameterBuilder.create("value", Type.TEXT); + if (options != null && !options.isEmpty()) { + builder.withOptions(options); + builder.withLimitToOptions(true); + } else { + builder.withLimitToOptions(false); + } + builder.withDescription(description != null ? description : namespace); + ConfigDescriptionParameter parameter = builder.build(); + + return new ConfigDescription(uri, Collections.singletonList(parameter)); + } + + private @Nullable ConfigDescription createParamConfigDescription(MetadataConfigDescriptionProvider provider, + String value, @Nullable Locale locale) { + String namespace = provider.getNamespace(); + URI uri = URI.create(SCHEME + SEPARATOR + namespace + SEPARATOR + value); + List parameters = provider.getParameters(value, locale); + if (parameters == null || parameters.isEmpty()) { + return null; + } + return new ConfigDescription(uri, parameters); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addMetadataConfigDescriptionProvider( + MetadataConfigDescriptionProvider metadataConfigDescriptionProvider) { + providers.add(metadataConfigDescriptionProvider); + } + + protected void removeMetadataConfigDescriptionProvider( + MetadataConfigDescriptionProvider metadataConfigDescriptionProvider) { + providers.remove(metadataConfigDescriptionProvider); + } + +} diff --git a/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java new file mode 100644 index 00000000000..10a43eb9dc1 --- /dev/null +++ b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.config.core.items; + +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; +import org.eclipse.smarthome.config.core.ParameterOption; + +/** + * + * @author Simon Kaufmann - initial contribution and API + * + */ +public interface MetadataConfigDescriptionProvider { + + /** + * Get the identifier of the metadata namespace + * + * @return the metadata namespace + */ + String getNamespace(); + + /** + * Get the human-readable description of the metadata namespace + *

+ * Overriding this method is optional - it will default to the namespace identifier. + * + * @param locale a locale, if available + * @return the metadata namespace description + */ + default String getDescription(@Nullable Locale locale) { + return getNamespace(); + } + + /** + * Get all valid options if the main metadata value should be restricted to certain values. + * + * @param locale + * @return + */ + @Nullable + List getParameterOptions(@Nullable Locale locale); + + /** + * Get the config descriptions for all expected parameters. + *

+ * This list may depend on the current "main" value + * + * @param value + * @param locale + * @return + */ + @Nullable + List getParameters(String value, @Nullable Locale locale); + +} From 5a970154b8e0d9f61379647213552b730f51e593 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Mon, 9 Apr 2018 15:16:03 +0200 Subject: [PATCH 21/26] [metadata] updated license headers Signed-off-by: Simon Kaufmann --- .../smarthome/core/common/AbstractUID.java | 12 ++++++++++++ .../items/ManagedMetadataProviderImpl.java | 15 ++++++++++----- .../core/internal/items/MetadataRegistryImpl.java | 15 ++++++++++----- .../core/items/ManagedMetadataProvider.java | 15 ++++++++++----- .../eclipse/smarthome/core/items/Metadata.java | 15 ++++++++++----- .../eclipse/smarthome/core/items/MetadataKey.java | 15 ++++++++++----- .../smarthome/core/items/MetadataPredicates.java | 15 ++++++++++----- .../smarthome/core/items/MetadataProvider.java | 15 ++++++++++----- .../smarthome/core/items/MetadataRegistry.java | 15 ++++++++++----- .../smarthome/core/items/dto/MetadataDTO.java | 15 ++++++++++----- 10 files changed, 102 insertions(+), 45 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java index 5595efbf4b5..5ef4ea2661c 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/AbstractUID.java @@ -1,3 +1,15 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.common; import java.util.ArrayList; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java index 35effcd1d58..5bb673af4f5 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/ManagedMetadataProviderImpl.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.internal.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java index c006731eb65..3c0766f2a3d 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/items/MetadataRegistryImpl.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.internal.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java index 95569a9ab6a..4b7c4ae07a9 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/ManagedMetadataProvider.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java index 092918927d4..ad751ec9d5c 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/Metadata.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java index c50db8fe91b..2132a96340d 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataKey.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java index 78b8e561b6b..a49509968c5 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataPredicates.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java index cdb2d427719..4f9b97104ca 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataProvider.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java index 983f19bdc6d..6854e1560c9 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/MetadataRegistry.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items; diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java index 4998de14a79..e0875176d12 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/items/dto/MetadataDTO.java @@ -1,9 +1,14 @@ /** - * Copyright (c) 2014-2017 by the respective copyright holders. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.items.dto; From dbc48fc8ca00c91b93949599d3f9672cfaab986a Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Mon, 9 Apr 2018 15:31:33 +0200 Subject: [PATCH 22/26] [metadata] export config.items package Signed-off-by: Simon Kaufmann --- .../org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF b/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF index a684cc9e863..b5bfe0110b2 100644 --- a/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF +++ b/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Export-Package: org.eclipse.smarthome.config.core, org.eclipse.smarthome.config.core.dto, org.eclipse.smarthome.config.core.i18n, + org.eclipse.smarthome.config.core.items, org.eclipse.smarthome.config.core.status, org.eclipse.smarthome.config.core.status.events, org.eclipse.smarthome.config.core.validation @@ -21,6 +22,7 @@ Import-Package: org.eclipse.smarthome.config.core, org.eclipse.smarthome.config.core.dto, org.eclipse.smarthome.config.core.i18n, + org.eclipse.smarthome.config.core.items, org.eclipse.smarthome.config.core.status, org.eclipse.smarthome.config.core.status.events, org.eclipse.smarthome.config.core.validation, From 51dd7005079c3b4fb6db7512d2cbc964b8fe7785 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Thu, 12 Apr 2018 13:11:40 +0200 Subject: [PATCH 23/26] [metadata] review feedback on config description API Signed-off-by: Simon Kaufmann --- ...dataConfigDescriptionProviderImplTest.java | 4 +-- .../META-INF/MANIFEST.MF | 4 +-- ...MetadataConfigDescriptionProviderImpl.java | 4 +-- .../MetadataConfigDescriptionProvider.java | 27 ++++++++++++------- 4 files changed, 23 insertions(+), 16 deletions(-) rename bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/{items => metadata}/MetadataConfigDescriptionProviderImpl.java (97%) rename bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/{items => metadata}/MetadataConfigDescriptionProvider.java (63%) diff --git a/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java b/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java index 7e7bdf43798..4cebefc603b 100644 --- a/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java +++ b/bundles/config/org.eclipse.smarthome.config.core.test/src/test/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImplTest.java @@ -12,7 +12,7 @@ */ package org.eclipse.smarthome.config.core.internal.items; -import static org.eclipse.smarthome.config.core.internal.items.MetadataConfigDescriptionProviderImpl.*; +import static org.eclipse.smarthome.config.core.internal.metadata.MetadataConfigDescriptionProviderImpl.*; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; @@ -26,9 +26,9 @@ import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; +import org.eclipse.smarthome.config.core.metadata.MetadataConfigDescriptionProvider; import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder; import org.eclipse.smarthome.config.core.ParameterOption; -import org.eclipse.smarthome.config.core.items.MetadataConfigDescriptionProvider; import org.eclipse.smarthome.test.java.JavaTest; import org.junit.Before; import org.junit.Test; diff --git a/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF b/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF index b5bfe0110b2..96bcdd90e68 100644 --- a/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF +++ b/bundles/config/org.eclipse.smarthome.config.core/META-INF/MANIFEST.MF @@ -9,7 +9,7 @@ Export-Package: org.eclipse.smarthome.config.core, org.eclipse.smarthome.config.core.dto, org.eclipse.smarthome.config.core.i18n, - org.eclipse.smarthome.config.core.items, + org.eclipse.smarthome.config.core.metadata, org.eclipse.smarthome.config.core.status, org.eclipse.smarthome.config.core.status.events, org.eclipse.smarthome.config.core.validation @@ -22,7 +22,7 @@ Import-Package: org.eclipse.smarthome.config.core, org.eclipse.smarthome.config.core.dto, org.eclipse.smarthome.config.core.i18n, - org.eclipse.smarthome.config.core.items, + org.eclipse.smarthome.config.core.metadata, org.eclipse.smarthome.config.core.status, org.eclipse.smarthome.config.core.status.events, org.eclipse.smarthome.config.core.validation, diff --git a/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/metadata/MetadataConfigDescriptionProviderImpl.java similarity index 97% rename from bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java rename to bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/metadata/MetadataConfigDescriptionProviderImpl.java index cc6e665fd3f..ea747c57b3f 100644 --- a/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/items/MetadataConfigDescriptionProviderImpl.java +++ b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/internal/metadata/MetadataConfigDescriptionProviderImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.smarthome.config.core.internal.items; +package org.eclipse.smarthome.config.core.internal.metadata; import java.net.URI; import java.util.Collection; @@ -25,10 +25,10 @@ import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; +import org.eclipse.smarthome.config.core.metadata.MetadataConfigDescriptionProvider; import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder; import org.eclipse.smarthome.config.core.ConfigDescriptionProvider; import org.eclipse.smarthome.config.core.ParameterOption; -import org.eclipse.smarthome.config.core.items.MetadataConfigDescriptionProvider; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; diff --git a/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/metadata/MetadataConfigDescriptionProvider.java similarity index 63% rename from bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java rename to bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/metadata/MetadataConfigDescriptionProvider.java index 10a43eb9dc1..7be343dc6c4 100644 --- a/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/items/MetadataConfigDescriptionProvider.java +++ b/bundles/config/org.eclipse.smarthome.config.core/src/main/java/org/eclipse/smarthome/config/core/metadata/MetadataConfigDescriptionProvider.java @@ -10,20 +10,28 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.smarthome.config.core.items; +package org.eclipse.smarthome.config.core.metadata; import java.util.List; import java.util.Locale; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; import org.eclipse.smarthome.config.core.ParameterOption; /** + * A {@link MetadataConfigDescriptionProvider} implementation can be registered as an OSGi service in order to give + * guidance to UIs what metadata namespaces should be available and what metadata properties are expected. + *

+ * It will be tracked by the framework and the given information will be translated into config descriptions. + *

+ * Every extension which deals with specific metadata (in its own namespace) is expected to provide an implementation of + * this interface. * * @author Simon Kaufmann - initial contribution and API - * */ +@NonNullByDefault public interface MetadataConfigDescriptionProvider { /** @@ -41,15 +49,14 @@ public interface MetadataConfigDescriptionProvider { * @param locale a locale, if available * @return the metadata namespace description */ - default String getDescription(@Nullable Locale locale) { - return getNamespace(); - } + @Nullable + String getDescription(@Nullable Locale locale); /** * Get all valid options if the main metadata value should be restricted to certain values. * - * @param locale - * @return + * @param locale a locale, if available + * @return a list of parameter options or {@code null} */ @Nullable List getParameterOptions(@Nullable Locale locale); @@ -59,9 +66,9 @@ default String getDescription(@Nullable Locale locale) { *

* This list may depend on the current "main" value * - * @param value - * @param locale - * @return + * @param value the current "main" value + * @param locale a locale, if available + * @return a list of config description parameters or {@code null} */ @Nullable List getParameters(String value, @Nullable Locale locale); From d478139d57dc3bf476e05bffc15cc83858b99c72 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Thu, 12 Apr 2018 13:15:39 +0200 Subject: [PATCH 24/26] fixed illegal list modification in GenericThingProvider Signed-off-by: Simon Kaufmann --- .../smarthome/model/thing/internal/GenericThingProvider.xtend | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend index d4ccc5c13ba..7ead6ed0743 100644 --- a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend +++ b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend @@ -319,7 +319,8 @@ class GenericThingProvider extends AbstractProvider implements ThingProvi } def private getParentPath(ThingUID bridgeUID) { - var bridgeIds = bridgeUID.bridgeIds + val bridgeIds = newArrayList + bridgeIds.addAll(bridgeUID.bridgeIds) bridgeIds.add(bridgeUID.id) return bridgeIds } From a37b32b9463ebb6b200820541e2c2f4c2d5c18e2 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Thu, 12 Apr 2018 14:02:53 +0200 Subject: [PATCH 25/26] review feedback: renamed method in ChannelUID Signed-off-by: Simon Kaufmann --- .../org/eclipse/smarthome/core/thing/ChannelUID.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java index 325964f2a42..024118ae49d 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/ChannelUID.java @@ -49,12 +49,12 @@ public ChannelUID(String channelUid) { * @param id the channel's id */ public ChannelUID(ThingUID thingUID, String id) { - super(getArray(thingUID, null, id)); + super(toSegments(thingUID, null, id)); } @Deprecated public ChannelUID(ThingTypeUID thingTypeUID, ThingUID thingUID, String id) { - super(getArray(thingUID, null, id)); + super(toSegments(thingUID, null, id)); } /** @@ -63,12 +63,12 @@ public ChannelUID(ThingTypeUID thingTypeUID, ThingUID thingUID, String id) { * @param id the channel's id */ public ChannelUID(ThingUID thingUID, String groupId, String id) { - super(getArray(thingUID, groupId, id)); + super(toSegments(thingUID, groupId, id)); } @Deprecated public ChannelUID(ThingTypeUID thingTypeUID, ThingUID thingUID, String groupId, String id) { - super(getArray(thingUID, groupId, id)); + super(toSegments(thingUID, groupId, id)); } /** @@ -104,7 +104,7 @@ public ChannelUID(String bindingId, String thingTypeId, String thingId, String g super(bindingId, thingTypeId, thingId, getChannelId(groupId, id)); } - private static List getArray(ThingUID thingUID, @Nullable String groupId, String id) { + private static List toSegments(ThingUID thingUID, @Nullable String groupId, String id) { List ret = new ArrayList<>(thingUID.getAllSegments()); ret.add(getChannelId(groupId, id)); return ret; From 4d13116fb1fadd6b5760fb0ad0e9d1b790b8a804 Mon Sep 17 00:00:00 2001 From: Simon Kaufmann Date: Thu, 12 Apr 2018 15:05:24 +0200 Subject: [PATCH 26/26] [metadata] pour some magic on items Signed-off-by: Simon Kaufmann --- .../META-INF/MANIFEST.MF | 4 + .../metadata/MagicMetadataProvider.java | 82 +++++++++++++++++++ .../metadata/MagicMetadataUsingService.java | 77 +++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataProvider.java create mode 100644 bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataUsingService.java diff --git a/bundles/test/org.eclipse.smarthome.magic/META-INF/MANIFEST.MF b/bundles/test/org.eclipse.smarthome.magic/META-INF/MANIFEST.MF index 592e4fcbabf..38f1e2295d8 100644 --- a/bundles/test/org.eclipse.smarthome.magic/META-INF/MANIFEST.MF +++ b/bundles/test/org.eclipse.smarthome.magic/META-INF/MANIFEST.MF @@ -17,7 +17,11 @@ Import-Package: javax.servlet.http, org.eclipse.jdt.annotation;resolution:=optional, org.eclipse.smarthome.config.core, + org.eclipse.smarthome.config.core.metadata, org.eclipse.smarthome.config.discovery, + org.eclipse.smarthome.core.common, + org.eclipse.smarthome.core.common.registry, + org.eclipse.smarthome.core.items, org.eclipse.smarthome.core.library.types, org.eclipse.smarthome.core.thing, org.eclipse.smarthome.core.thing.binding, diff --git a/bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataProvider.java b/bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataProvider.java new file mode 100644 index 00000000000..b2351ff0a30 --- /dev/null +++ b/bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataProvider.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.magic.internal.metadata; + +import static java.util.stream.Collectors.toList; +import static org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder.create; + +import java.util.List; +import java.util.Locale; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; +import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; +import org.eclipse.smarthome.config.core.ParameterOption; +import org.eclipse.smarthome.config.core.metadata.MetadataConfigDescriptionProvider; +import org.osgi.service.component.annotations.Component; + +/** + * Describes the metadata for the "magic" namespace. + * + * @author Simon Kaufmann - initial contribution and API + * + */ +@Component +@NonNullByDefault +public class MagicMetadataProvider implements MetadataConfigDescriptionProvider { + + @Override + public String getNamespace() { + return "magic"; + } + + @Override + public @Nullable String getDescription(@Nullable Locale locale) { + return "Make items magic"; + } + + @Override + public @Nullable List getParameterOptions(@Nullable Locale locale) { + return Stream.of( // + new ParameterOption("just", "Just Magic"), // + new ParameterOption("pure", "Pure Magic") // + ).collect(toList()); + } + + @Override + public @Nullable List getParameters(String value, @Nullable Locale locale) { + switch (value) { + case "just": + return Stream.of( // + create("electric", Type.BOOLEAN).withLabel("Use Electricity").build() // + ).collect(toList()); + case "pure": + return Stream.of( // + create("spell", Type.TEXT).withLabel("Spell").withDescription("The exact spell to use").build(), // + create("price", Type.DECIMAL).withLabel("Price") + .withDescription("...because magic always comes with a price").build(), // + create("power", Type.INTEGER).withLabel("Power").withLimitToOptions(true).withOptions( // + Stream.of( // + new ParameterOption("0", "Very High"), // + new ParameterOption("1", "Incredible"), // + new ParameterOption("2", "Insane"), // + new ParameterOption("3", "Ludicrous") // + ).collect(toList())).build() // + ).collect(toList()); + } + return null; + } + +} diff --git a/bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataUsingService.java b/bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataUsingService.java new file mode 100644 index 00000000000..b9affe97610 --- /dev/null +++ b/bundles/test/org.eclipse.smarthome.magic/src/main/java/org/eclipse/smarthome/magic/internal/metadata/MagicMetadataUsingService.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.magic.internal.metadata; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.common.ThreadPoolManager; +import org.eclipse.smarthome.core.items.MetadataPredicates; +import org.eclipse.smarthome.core.items.MetadataRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Example service which makes use of the metadata of the "magic" namespace. + * + * @author Simon Kaufmann - initial contribution and API + * + */ +@NonNullByDefault +@Component(immediate = true) +public class MagicMetadataUsingService { + + private final Logger logger = LoggerFactory.getLogger(MagicMetadataUsingService.class); + private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("magic"); + + private @NonNullByDefault({}) MetadataRegistry metadataRegistry; + + private @Nullable ScheduledFuture job; + + @Activate + public void activate() { + job = scheduler.scheduleWithFixedDelay(() -> run(), 30, 30, TimeUnit.SECONDS); + } + + @Deactivate + public void deactivate() { + if (job != null) { + job.cancel(false); + job = null; + } + } + + private void run() { + metadataRegistry.stream().filter(MetadataPredicates.hasNamespace("magic")).forEach(metadata -> { + logger.info("Item {} is {} with {}", metadata.getUID().getItemName(), metadata.getValue(), + metadata.getConfiguration()); + }); + } + + @Reference + protected void setMetadataRegistry(MetadataRegistry metadataRegistry) { + this.metadataRegistry = metadataRegistry; + } + + protected void unsetMetadataRegistry(MetadataRegistry metadataRegistry) { + this.metadataRegistry = null; + } + +}