/** * 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.thing.link; import java.util.List; import org.eclipse.smarthome.core.common.registry.Provider; import org.eclipse.smarthome.core.common.registry.ProviderChangeListener; import org.eclipse.smarthome.core.common.registry.RegistryChangeListener; import org.eclipse.smarthome.core.events.AbstractTypedEventSubscriber; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemRegistry; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.ManagedThingProvider; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingRegistry; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.binding.ThingHandler; import org.eclipse.smarthome.core.thing.events.ThingStatusInfoChangedEvent; import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.TypeResolver; import org.eclipse.smarthome.core.thing.util.ThingHandlerHelper; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link ThingLinkManager} manages links for channels. * <p> * If a Thing is created, it can automatically create links for its non-advanced channels. * Upon a Thing deletion, it removes all links of this Thing. * * @author Dennis Nobel - Initial contribution * @author Markus Rathgeb - Handle item registry's all items changed notification * @author Kai Kreuzer - Refactored to make it a service and introduced the auto-linking (as a replacement for the * ThingSetupManager) * @author Markus Rathgeb - Send link notification if item and link exists and unlink on the first removal */ public class ThingLinkManager extends AbstractTypedEventSubscriber<ThingStatusInfoChangedEvent> { public ThingLinkManager() { super(ThingStatusInfoChangedEvent.TYPE); } private Logger logger = LoggerFactory.getLogger(ThingLinkManager.class); private ThingRegistry thingRegistry; private ManagedThingProvider managedThingProvider; private ItemRegistry itemRegistry; private ItemChannelLinkRegistry itemChannelLinkRegistry; private boolean autoLinks = true; protected void activate(ComponentContext context) { modified(context); itemRegistry.addRegistryChangeListener(itemRegistryChangeListener); itemChannelLinkRegistry.addRegistryChangeListener(itemChannelLinkRegistryChangeListener); managedThingProvider.addProviderChangeListener(managedThingProviderListener); } protected void modified(ComponentContext context) { // check whether we want to enable the automatic link creation or not if (context != null) { Object value = context.getProperties().get("autoLinks"); autoLinks = value == null || !value.toString().equals("false"); } } protected void deactivate() { itemRegistry.removeRegistryChangeListener(itemRegistryChangeListener); itemChannelLinkRegistry.removeRegistryChangeListener(itemChannelLinkRegistryChangeListener); managedThingProvider.removeProviderChangeListener(managedThingProviderListener); } protected void setItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = itemRegistry; } protected void unsetItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = null; } protected void setItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = itemChannelLinkRegistry; } protected void unsetItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = null; } protected void setThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = thingRegistry; } protected void unsetThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = null; } protected void setManagedThingProvider(ManagedThingProvider managedThingProvider) { this.managedThingProvider = managedThingProvider; } protected void unsetManagedThingProvider(ManagedThingProvider managedThingProvider) { this.managedThingProvider = null; } public boolean isAutoLinksEnabled() { return autoLinks; } private final RegistryChangeListener<Item> itemRegistryChangeListener = new RegistryChangeListener<Item>() { @Override public void added(Item element) { for (final ChannelUID channelUID : itemChannelLinkRegistry.getBoundChannels(element.getName())) { final Thing thing = thingRegistry.get(channelUID.getThingUID()); if (thing != null) { final Channel channel = thing.getChannel(channelUID.getId()); if (channel != null) { ThingLinkManager.this.informHandlerAboutLinkedChannel(thing, channel); } } } } @Override public void removed(Item element) { for (final ChannelUID channelUID : itemChannelLinkRegistry.getBoundChannels(element.getName())) { final Thing thing = thingRegistry.get(channelUID.getThingUID()); if (thing != null) { final Channel channel = thing.getChannel(channelUID.getId()); if (channel != null) { ThingLinkManager.this.informHandlerAboutUnlinkedChannel(thing, channel); } } } } @Override public void updated(Item oldElement, Item element) { if (!oldElement.equals(element)) { this.removed(oldElement); this.added(element); } } }; private final RegistryChangeListener<ItemChannelLink> itemChannelLinkRegistryChangeListener = new RegistryChangeListener<ItemChannelLink>() { @Override public void added(ItemChannelLink itemChannelLink) { if (itemRegistry.get(itemChannelLink.getItemName()) == null) { // Don't inform about the link if the item does not exist. // The handler will be informed on item creation. return; } ChannelUID channelUID = itemChannelLink.getUID(); Thing thing = thingRegistry.get(channelUID.getThingUID()); if (thing != null) { Channel channel = thing.getChannel(channelUID.getId()); if (channel != null) { ThingLinkManager.this.informHandlerAboutLinkedChannel(thing, channel); } } } @Override public void removed(ItemChannelLink itemChannelLink) { /* * Don't check for item existence here. * If an item and its link are removed before the registry change listener methods are called, * a check for the item could prevent that the handler is informed about the unlink at all. */ ChannelUID channelUID = itemChannelLink.getUID(); Thing thing = thingRegistry.get(channelUID.getThingUID()); if (thing != null) { Channel channel = thing.getChannel(channelUID.getId()); if (channel != null) { ThingLinkManager.this.informHandlerAboutUnlinkedChannel(thing, channel); } } } @Override public void updated(ItemChannelLink oldElement, ItemChannelLink element) { if (!oldElement.equals(element)) { this.removed(oldElement); this.added(element); } } }; private final ProviderChangeListener<Thing> managedThingProviderListener = new ProviderChangeListener<Thing>() { @Override public void added(Provider<Thing> provider, Thing thing) { List<Channel> channels = thing.getChannels(); for (Channel channel : channels) { createLinkIfNotAdvanced(channel); } } private void createLinkIfNotAdvanced(Channel channel) { if (autoLinks) { if (channel.getChannelTypeUID() != null) { ChannelType type = TypeResolver.resolve(channel.getChannelTypeUID()); if (type != null && type.isAdvanced()) { return; } } ItemChannelLink link = new ItemChannelLink(deriveItemName(channel.getUID()), channel.getUID()); itemChannelLinkRegistry.add(link); } } @Override public void removed(Provider<Thing> provider, Thing thing) { List<Channel> channels = thing.getChannels(); for (Channel channel : channels) { ItemChannelLink link = new ItemChannelLink(deriveItemName(channel.getUID()), channel.getUID()); itemChannelLinkRegistry.remove(link.getID()); } } @Override public void updated(Provider<Thing> provider, Thing oldThing, Thing newThing) { for (Channel channel : oldThing.getChannels()) { if (newThing.getChannel(channel.getUID().getId()) == null) { // this channel does not exist anymore, so remove outdated links ItemChannelLink link = new ItemChannelLink(deriveItemName(channel.getUID()), channel.getUID()); itemChannelLinkRegistry.remove(link.getID()); } } for (Channel channel : newThing.getChannels()) { if (oldThing.getChannel(channel.getUID().getId()) == null) { // this channel did not exist before, so add a link createLinkIfNotAdvanced(channel); } } } private String deriveItemName(ChannelUID uid) { return uid.getAsString().replaceAll("[^a-zA-Z0-9_]", "_"); } }; private void informHandlerAboutLinkedChannel(Thing thing, Channel channel) { // Don't notify the thing if the thing isn't initialised if (!ThingHandlerHelper.isHandlerInitialized(thing)) { return; } ThingHandler handler = thing.getHandler(); if (handler != null) { try { handler.channelLinked(channel.getUID()); } catch (Exception ex) { logger.error("Exception occurred while informing handler:" + ex.getMessage(), ex); } } else { logger.trace("Can not inform handler about linked channel, because no handler is assigned to the thing {}.", thing.getUID()); } } private void informHandlerAboutUnlinkedChannel(Thing thing, Channel channel) { // Don't notify the thing if the thing isn't initialised if (!ThingHandlerHelper.isHandlerInitialized(thing)) { return; } ThingHandler handler = thing.getHandler(); if (handler != null) { try { handler.channelUnlinked(channel.getUID()); } catch (Exception ex) { logger.error("Exception occurred while informing handler:" + ex.getMessage(), ex); } } else { logger.trace( "Can not inform handler about unlinked channel, because no handler is assigned to the thing {}.", thing.getUID()); } } @Override protected void receiveTypedEvent(ThingStatusInfoChangedEvent event) { // when a thing handler is successfully initialized (i.e. it goes from INITIALIZING to UNKNOWN, ONLINE or // OFFLINE), we need to make sure that channelLinked() is called for all existing links if (ThingStatus.INITIALIZING.equals(event.getOldStatusInfo().getStatus())) { if (ThingHandlerHelper.isHandlerInitialized(event.getStatusInfo().getStatus())) { Thing thing = thingRegistry.get(event.getThingUID()); if (thing != null) { for (Channel channel : thing.getChannels()) { if (itemChannelLinkRegistry.getLinkedItemNames(channel.getUID()).size() > 0) { informHandlerAboutLinkedChannel(thing, channel); } } } } } } }