Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

[Core] Added item metadata infrastructure #4390

Merged
merged 26 commits into from
Apr 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ce584a8
added metadata infrastructure
kaikreuzer Aug 2, 2017
59864a6
Introduced AbstractUID
Mar 23, 2018
9f19a43
[metadata] turned MetadataKey into a AbstractUID
Mar 23, 2018
0d60100
[metadata] minor cleanups
Mar 26, 2018
f49460a
[metadata] Cleanup in MetadataRegistry based on ManagedItemProvider only
Mar 26, 2018
173e575
[metadata] implement equals/hashCode in Metadata
Mar 27, 2018
6d6b8e5
[metadata] fix metadata removal in GenericItemProvider
Mar 27, 2018
ba34419
[metadata] made GenericMetadataProvider thread-safe, fixed removal
Mar 27, 2018
a4bd749
added UoM to model.item.test launch config
Mar 27, 2018
107254d
[metadata] optional metadata configuration, immutable
Mar 27, 2018
ec38404
[metadata] handle update and exceptional cases in ItemResource
Mar 27, 2018
fc6a80e
[metadata] added tests for metadata
Mar 27, 2018
07e4b69
[metadata] added a section to the item concept documentation
Mar 28, 2018
2b7738f
[metadata] added missing log statements
Mar 28, 2018
eb742c3
protect AbstractUID segments from external tampering
Mar 28, 2018
4d23ff0
hide managed metadata provider implementation in internal package
Apr 6, 2018
cf8fcf0
allow filtering config descriptions for a specific scheme
Apr 6, 2018
4b365a2
[metadata] added missing @Nullable annotation for the namespace selector
Apr 6, 2018
f59c385
incorporated review feedback
Apr 9, 2018
5924264
[metadata] support config descriptions for meta-data
Apr 9, 2018
5a97015
[metadata] updated license headers
Apr 9, 2018
dbc48fc
[metadata] export config.items package
Apr 9, 2018
51dd700
[metadata] review feedback on config description API
Apr 12, 2018
d478139
fixed illegal list modification in GenericThingProvider
Apr 12, 2018
a37b32b
review feedback: renamed method in ChannelUID
Apr 12, 2018
4d13116
[metadata] pour some magic on items
Apr 12, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.metadata.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.metadata.MetadataConfigDescriptionProvider;
import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder;
import org.eclipse.smarthome.config.core.ParameterOption;
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<ConfigDescription> 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<ConfigDescription> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.metadata,
org.eclipse.smarthome.config.core.status,
org.eclipse.smarthome.config.core.status.events,
org.eclipse.smarthome.config.core.validation
Expand All @@ -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.metadata,
org.eclipse.smarthome.config.core.status,
org.eclipse.smarthome.config.core.status.events,
org.eclipse.smarthome.config.core.validation,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.metadata;

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.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.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.
* <p>
* It exposes the config description for the "main" value under
*
* <pre>
* {@code
* metadata:<namespace>
* }
* </pre>
*
* and the config descriptions for the parameters under
*
* <pre>
* {@code
* metadata:<namespace>:<value>
* }
* </pre>
*
* 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<MetadataConfigDescriptionProvider> providers = new CopyOnWriteArrayList<>();

@Override
public Collection<ConfigDescription> getConfigDescriptions(@Nullable Locale locale) {
List<ConfigDescription> 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<ConfigDescription> getValueConfigDescriptions(@Nullable Locale locale) {
List<ConfigDescription> 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<ParameterOption> 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<ConfigDescriptionParameter> 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);
}

}
Loading