diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ChannelItemProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ChannelItemProvider.java deleted file mode 100644 index 46179ff56..000000000 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ChannelItemProvider.java +++ /dev/null @@ -1,411 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * 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.openhab.core.thing.internal; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.common.registry.ProviderChangeListener; -import org.openhab.core.common.registry.RegistryChangeListener; -import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.items.GenericItem; -import org.openhab.core.items.Item; -import org.openhab.core.items.ItemFactory; -import org.openhab.core.items.ItemProvider; -import org.openhab.core.items.ItemRegistry; -import org.openhab.core.items.RegistryHook; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingRegistry; -import org.openhab.core.thing.link.ItemChannelLink; -import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.thing.type.ChannelKind; -import org.openhab.core.thing.type.ChannelType; -import org.openhab.core.thing.type.ChannelTypeRegistry; -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.Modified; -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 class dynamically provides items for all links that point to non-existing items. - * - * @author Kai Kreuzer - Initial contribution - * @author Markus Rathgeb - Add locale provider support - * @author Thomas Höfer - Added modified operation - */ -@Component(configurationPid = "org.openhab.channelitemprovider", immediate = true) -@NonNullByDefault -public class ChannelItemProvider implements ItemProvider { - - private static final long INITIALIZATION_DELAY_NANOS = TimeUnit.SECONDS.toNanos(2); - - private final Logger logger = LoggerFactory.getLogger(ChannelItemProvider.class); - - private final Set> listeners = new HashSet<>(); - private final Set itemFactories = new HashSet<>(); - private @Nullable Map items; - - private final LocaleProvider localeProvider; - private final ChannelTypeRegistry channelTypeRegistry; - private final ThingRegistry thingRegistry; - private final ItemRegistry itemRegistry; - private final ItemChannelLinkRegistry linkRegistry; - - private boolean enabled = true; - private volatile boolean initialized = false; - private volatile long lastUpdate = System.nanoTime(); - private @Nullable ScheduledExecutorService executor; - - @Activate - public ChannelItemProvider(final @Reference LocaleProvider localeProvider, - final @Reference ChannelTypeRegistry channelTypeRegistry, final @Reference ThingRegistry thingRegistry, - final @Reference ItemRegistry itemRegistry, final @Reference ItemChannelLinkRegistry linkRegistry) { - this.localeProvider = localeProvider; - this.channelTypeRegistry = channelTypeRegistry; - this.thingRegistry = thingRegistry; - this.itemRegistry = itemRegistry; - this.linkRegistry = linkRegistry; - } - - @Override - public Collection getAll() { - if (!enabled || !initialized) { - return Collections.emptySet(); - } else { - initializeItems(); - return new HashSet<>(items.values()); - } - } - - @Override - public void addProviderChangeListener(ProviderChangeListener listener) { - listeners.add(listener); - for (Item item : getAll()) { - listener.added(this, item); - } - } - - @Override - public void removeProviderChangeListener(ProviderChangeListener listener) { - listeners.remove(listener); - } - - @Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policy = ReferencePolicy.DYNAMIC) - protected void addItemFactory(ItemFactory itemFactory) { - this.itemFactories.add(itemFactory); - } - - protected void removeItemFactory(ItemFactory itemFactory) { - this.itemFactories.remove(itemFactory); - } - - @Activate - protected void activate(Map properties) { - modified(properties); - } - - @Modified - protected synchronized void modified(Map properties) { - if (properties != null) { - String enabled = (String) properties.get("enabled"); - if ("false".equalsIgnoreCase(enabled)) { - this.enabled = false; - } else { - this.enabled = true; - } - } - - if (enabled) { - addRegistryChangeListeners(); - - boolean initialDelay = properties == null - || !"false".equalsIgnoreCase((String) properties.get("initialDelay")); - if (initialDelay) { - executor = Executors.newSingleThreadScheduledExecutor(); - delayedInitialize(); - } else { - initialize(); - } - } else { - logger.debug("Disabling channel item provider."); - disableChannelItemProvider(); - } - } - - private synchronized void disableChannelItemProvider() { - if (executor != null) { - executor.shutdownNow(); - executor = null; - } - - for (ProviderChangeListener listener : listeners) { - for (Item item : getAll()) { - listener.removed(this, item); - } - } - removeRegistryChangeListeners(); - - initialized = false; - items = null; - } - - private synchronized void delayedInitialize() { - if (Thread.currentThread().isInterrupted()) { - return; - } - // we wait until no further new links or items are announced in order to avoid creation of - // items which then must be removed again immediately. - final long diff = System.nanoTime() - lastUpdate - INITIALIZATION_DELAY_NANOS; - if (diff < 0) { - executor.schedule(() -> delayedInitialize(), -diff, TimeUnit.NANOSECONDS); - } else { - executor.shutdown(); - executor = null; - - initialize(); - } - } - - private void initialize() { - initializeItems(); - initialized = true; - } - - private synchronized void initializeItems() { - if (items != null) { - return; - } - items = new ConcurrentHashMap<>(); - for (ItemChannelLink link : linkRegistry.getAll()) { - createItemForLink(link); - } - } - - @Deactivate - protected void deactivate() { - disableChannelItemProvider(); - } - - private void addRegistryChangeListeners() { - linkRegistry.addRegistryChangeListener(linkRegistryListener); - itemRegistry.addRegistryHook(itemRegistryListener); - thingRegistry.addRegistryChangeListener(thingRegistryListener); - } - - private void removeRegistryChangeListeners() { - itemRegistry.removeRegistryHook(itemRegistryListener); - linkRegistry.removeRegistryChangeListener(linkRegistryListener); - thingRegistry.removeRegistryChangeListener(thingRegistryListener); - } - - private void createItemForLink(ItemChannelLink link) { - if (!enabled) { - return; - } - if (itemRegistry.get(link.getItemName()) != null) { - // there is already an item, we do not need to create one - return; - } - Channel channel = thingRegistry.getChannel(link.getLinkedUID()); - if (channel != null) { - Item item = null; - // Only create an item for state channels - if (channel.getKind() == ChannelKind.STATE) { - for (ItemFactory itemFactory : itemFactories) { - item = itemFactory.createItem(channel.getAcceptedItemType(), link.getItemName()); - if (item != null) { - break; - } - } - } - if (item instanceof GenericItem) { - GenericItem gItem = (GenericItem) item; - gItem.setLabel(getLabel(channel)); - gItem.setCategory(getCategory(channel)); - gItem.addTags(channel.getDefaultTags()); - } - if (item != null) { - logger.trace("Created virtual item '{}'", item.getName()); - items.put(item.getName(), item); - for (ProviderChangeListener listener : listeners) { - listener.added(this, item); - } - } - } - } - - private @Nullable String getCategory(Channel channel) { - if (channel.getChannelTypeUID() != null) { - ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID(), - localeProvider.getLocale()); - if (channelType != null) { - return channelType.getCategory(); - } - } - return null; - } - - private @Nullable String getLabel(Channel channel) { - if (channel.getLabel() != null) { - return channel.getLabel(); - } else { - if (channel.getChannelTypeUID() != null) { - final ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID(), - localeProvider.getLocale()); - if (channelType != null) { - return channelType.getLabel(); - } - } - } - return null; - } - - private void removeItem(String key) { - if (!enabled) { - return; - } - Item item = items.get(key); - if (item != null) { - for (ProviderChangeListener listener : listeners) { - listener.removed(this, item); - } - items.remove(key); - logger.trace("Removed virtual item '{}'", item.getName()); - } - } - - public boolean isEnabled() { - return enabled; - } - - final RegistryChangeListener thingRegistryListener = new RegistryChangeListener() { - - @Override - public void added(Thing element) { - if (!initialized) { - return; - } - for (Channel channel : element.getChannels()) { - for (ItemChannelLink link : linkRegistry.getLinks(channel.getUID())) { - createItemForLink(link); - } - } - } - - @Override - public void removed(Thing element) { - if (!initialized) { - return; - } - for (Channel channel : element.getChannels()) { - for (ItemChannelLink link : linkRegistry.getLinks(channel.getUID())) { - removeItem(link.getItemName()); - } - } - } - - @Override - public void updated(Thing oldElement, Thing element) { - removed(oldElement); - added(element); - } - }; - - final RegistryChangeListener linkRegistryListener = new RegistryChangeListener() { - - @Override - public void added(ItemChannelLink element) { - if (!initialized) { - lastUpdate = System.nanoTime(); - return; - } - createItemForLink(element); - } - - @Override - public void removed(ItemChannelLink element) { - if (!initialized) { - return; - } - removeItem(element.getItemName()); - } - - @Override - public void updated(ItemChannelLink oldElement, ItemChannelLink element) { - removed(oldElement); - added(element); - } - }; - - final RegistryHook itemRegistryListener = new RegistryHook() { - - @Override - public void beforeAdding(Item element) { - if (!initialized) { - lastUpdate = System.nanoTime(); - return; - } - // check, if it is our own item - for (Item item : items.values()) { - if (item == element) { - return; - } - } - // it is from some other provider, so remove ours, if we have one - Item oldElement = items.get(element.getName()); - if (oldElement != null) { - for (ProviderChangeListener listener : listeners) { - listener.removed(ChannelItemProvider.this, oldElement); - } - items.remove(element.getName()); - } - } - - @Override - public void afterRemoving(Item element) { - if (!initialized) { - return; - } - // check, if it is our own item - for (Item item : items.values()) { - if (item == element) { - return; - } - } - // it is from some other provider, so create one ourselves if needed - for (ChannelUID uid : linkRegistry.getBoundChannels(element.getName())) { - for (ItemChannelLink link : linkRegistry.getLinks(uid)) { - if (itemRegistry.get(link.getItemName()) == null) { - createItemForLink(link); - } - } - } - } - }; -} diff --git a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ChannelItemProviderTest.java b/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ChannelItemProviderTest.java deleted file mode 100644 index 4c21deff0..000000000 --- a/bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/internal/ChannelItemProviderTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * 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.openhab.core.thing.internal; - -import static java.util.Map.entry; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.openhab.core.common.registry.ProviderChangeListener; -import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.items.Item; -import org.openhab.core.items.ItemFactory; -import org.openhab.core.items.ItemRegistry; -import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.library.items.NumberItem; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingRegistry; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.link.ItemChannelLink; -import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.thing.type.ChannelTypeRegistry; - -/** - * @author Simon Kaufmann - Initial contribution - */ -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.WARN) -public class ChannelItemProviderTest { - - private static final ChannelUID CHANNEL_UID = new ChannelUID("test:test:test:test"); - private static final Channel CHANNEL = ChannelBuilder.create(CHANNEL_UID, CoreItemFactory.NUMBER).build(); - - private static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID("test:test"); - private static final Thing THING = ThingBuilder.create(THING_TYPE_UID, "test").withChannel(CHANNEL).build(); - - private static final String ITEM_NAME = "test"; - private static final NumberItem ITEM = new NumberItem(ITEM_NAME); - private static final ItemChannelLink LINK = new ItemChannelLink(ITEM_NAME, CHANNEL_UID); - - private @Mock ItemFactory itemFactoryMock; - private @Mock ItemRegistry itemRegistryMock; - private @Mock ItemChannelLinkRegistry linkRegistryMock; - private @Mock ProviderChangeListener<@NonNull Item> listenerMock; - private @Mock LocaleProvider localeProviderMock; - private @Mock ThingRegistry thingRegistryMock; - - private ChannelItemProvider provider; - - @BeforeEach - public void setup() { - provider = createProvider(); - - provider.activate(Map.ofEntries(entry("enabled", "true"), entry("initialDelay", "false"))); - - when(thingRegistryMock.getChannel(same(CHANNEL_UID))).thenReturn(CHANNEL); - when(itemFactoryMock.createItem(CoreItemFactory.NUMBER, ITEM_NAME)).thenReturn(ITEM); - when(localeProviderMock.getLocale()).thenReturn(Locale.ENGLISH); - } - - @Test - public void testItemCreationFromThingNotThere() { - resetAndPrepareListener(); - - provider.thingRegistryListener.added(THING); - verify(listenerMock, only()).added(same(provider), same(ITEM)); - } - - @Test - public void testItemCreationFromThingAlreadyExists() { - when(itemRegistryMock.get(eq(ITEM_NAME))).thenReturn(ITEM); - - resetAndPrepareListener(); - - provider.thingRegistryListener.added(THING); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - } - - @Test - public void testItemRemovalFromThingLinkRemoved() { - provider.linkRegistryListener.added(LINK); - - resetAndPrepareListener(); - - provider.thingRegistryListener.removed(THING); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - verify(listenerMock, only()).removed(same(provider), same(ITEM)); - } - - @Test - public void testItemCreationFromLinkNotThere() { - provider.linkRegistryListener.added(LINK); - verify(listenerMock, only()).added(same(provider), same(ITEM)); - } - - @Test - public void testItemCreationFromLinkAlreadyExists() { - when(itemRegistryMock.get(eq(ITEM_NAME))).thenReturn(ITEM); - - provider.linkRegistryListener.added(LINK); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - } - - @Test - public void testItemRemovalFromLinkLinkRemoved() { - provider.linkRegistryListener.added(LINK); - - resetAndPrepareListener(); - - provider.linkRegistryListener.removed(LINK); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - verify(listenerMock, only()).removed(same(provider), same(ITEM)); - } - - @Test - public void testItemRemovalItemFromOtherProvider() { - provider.linkRegistryListener.added(LINK); - - resetAndPrepareListener(); - - provider.itemRegistryListener.beforeAdding(new NumberItem(ITEM_NAME)); - verify(listenerMock, only()).removed(same(provider), same(ITEM)); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - } - - @Test - public void testDisableBeforeDelayedInitialization() throws Exception { - provider = createProvider(); - reset(linkRegistryMock); - - // Set the initialization delay to 40ms so we don't have to wait 2000ms to do the assertion - Field field = ChannelItemProvider.class.getDeclaredField("INITIALIZATION_DELAY_NANOS"); - field.setAccessible(true); - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - field.set(provider, TimeUnit.MILLISECONDS.toNanos(40)); - - Map props = new HashMap<>(); - props.put("enabled", "true"); - provider.activate(props); - - provider.linkRegistryListener.added(LINK); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - verify(linkRegistryMock, never()).getAll(); - - props = new HashMap<>(); - props.put("enabled", "false"); - provider.modified(props); - - Thread.sleep(100); - - provider.linkRegistryListener.added(LINK); - verify(listenerMock, never()).added(same(provider), same(ITEM)); - verify(linkRegistryMock, never()).getAll(); - } - - @SuppressWarnings("unchecked") - private void resetAndPrepareListener() { - reset(listenerMock); - doAnswer(invocation -> { - // this is crucial as it mimics the real ItemRegistry's behavior - provider.itemRegistryListener.afterRemoving((Item) invocation.getArguments()[1]); - return null; - }).when(listenerMock).removed(same(provider), any(Item.class)); - doAnswer(invocation -> { - // this is crucial as it mimics the real ItemRegistry's behavior - provider.itemRegistryListener.beforeAdding((Item) invocation.getArguments()[1]); - return null; - }).when(listenerMock).added(same(provider), any(Item.class)); - when(linkRegistryMock.getBoundChannels(eq(ITEM_NAME))).thenReturn(Set.of(CHANNEL_UID)); - when(linkRegistryMock.getLinks(eq(CHANNEL_UID))).thenReturn(Set.of(LINK)); - } - - private ChannelItemProvider createProvider() { - ChannelItemProvider provider = new ChannelItemProvider(localeProviderMock, mock(ChannelTypeRegistry.class), - thingRegistryMock, itemRegistryMock, linkRegistryMock); - provider.addItemFactory(itemFactoryMock); - provider.addProviderChangeListener(listenerMock); - return provider; - } -} diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiJavaTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiJavaTest.java index fab8ecfb3..a084a80ad 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiJavaTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiJavaTest.java @@ -173,13 +173,6 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest { throw new RuntimeException(e); } }); - waitForAssert(() -> { - try { - assertThat(bundleContext.getServiceReferences(ChannelItemProvider.class, null), is(notNullValue())); - } catch (InvalidSyntaxException e) { - throw new RuntimeException(e); - } - }); } @AfterEach diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiTest.java index 5ee62cb75..67a031fb6 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/openhab/core/thing/internal/ThingManagerOSGiTest.java @@ -168,13 +168,6 @@ public class ThingManagerOSGiTest extends JavaOSGiTest { fail("Failed to get service reference: " + e.getMessage()); } }); - waitForAssert(() -> { - try { - assertThat(bundleContext.getServiceReferences(ChannelItemProvider.class, null), is(notNullValue())); - } catch (InvalidSyntaxException e) { - fail("Failed to get service reference: " + e.getMessage()); - } - }); Bundle bundle = mock(Bundle.class); when(bundle.getSymbolicName()).thenReturn("org.openhab.core.thing"); @@ -794,6 +787,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest { managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, CHANNEL_UID)); registerService(thingHandlerFactory); + Item item = new StringItem(itemName); + itemRegistry.add(item); waitForAssert(() -> assertThat(itemRegistry.get(itemName), is(notNullValue()))); state.callback.statusUpdated(thing, ThingStatusInfoBuilder.create(ThingStatus.ONLINE).build()); @@ -1954,11 +1949,13 @@ public class ThingManagerOSGiTest extends JavaOSGiTest { when(thingHandlerFactory.registerHandler(any(Thing.class))).thenReturn(thingHandler); registerService(thingHandlerFactory); + String itemName = "testItem"; + Item item = new StringItem(itemName); + itemRegistry.add(item); + itemChannelLinkRegistry.add(new ItemChannelLink(itemName, new ChannelUID(thing.getUID(), "channel"))); + waitForAssert(() -> assertThat(itemRegistry.get(itemName), is(notNullValue()))); - itemChannelLinkRegistry.add(new ItemChannelLink("testItem", new ChannelUID(thing.getUID(), "channel"))); - waitForAssert(() -> assertThat(itemRegistry.get("testItem"), is(notNullValue()))); - - eventPublisher.post(ItemEventFactory.createCommandEvent("testItem", OnOffType.ON)); + eventPublisher.post(ItemEventFactory.createCommandEvent(itemName, OnOffType.ON)); assertThat(state.handleCommandCalled, is(false)); @@ -1967,7 +1964,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest { state.callback.statusUpdated(thing, unknownNone); assertThat(thing.getStatusInfo(), is(unknownNone)); - eventPublisher.post(ItemEventFactory.createCommandEvent("testItem", OnOffType.ON)); + eventPublisher.post(ItemEventFactory.createCommandEvent(itemName, OnOffType.ON)); waitForAssert(() -> { assertThat(state.handleCommandCalled, is(true));