/**
* 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.internal;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.eclipse.smarthome.core.common.registry.ProviderChangeListener;
import org.eclipse.smarthome.core.common.registry.RegistryChangeListener;
import org.eclipse.smarthome.core.i18n.LocaleProvider;
import org.eclipse.smarthome.core.items.GenericItem;
import org.eclipse.smarthome.core.items.Item;
import org.eclipse.smarthome.core.items.ItemFactory;
import org.eclipse.smarthome.core.items.ItemProvider;
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.Thing;
import org.eclipse.smarthome.core.thing.ThingRegistry;
import org.eclipse.smarthome.core.thing.link.ItemChannelLink;
import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry;
import org.eclipse.smarthome.core.thing.type.ChannelKind;
import org.eclipse.smarthome.core.thing.type.ChannelType;
import org.eclipse.smarthome.core.thing.type.TypeResolver;
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
* @author Markus Rathgeb - Add locale provider support
* @author Thomas Höfer - Added modified operation
*/
public class ChannelItemProvider implements ItemProvider {
private final Logger logger = LoggerFactory.getLogger(ChannelItemProvider.class);
private Set<ProviderChangeListener<Item>> listeners = new HashSet<>();
private LocaleProvider localeProvider;
private ThingRegistry thingRegistry;
private ItemChannelLinkRegistry linkRegistry;
private ItemRegistry itemRegistry;
private Set<ItemFactory> itemFactories = new HashSet<>();
private Map<String, Item> items = null;
private boolean enabled = true;
private boolean initialized = false;
private long lastUpdate = System.nanoTime();
@Override
public Collection<Item> getAll() {
if (!enabled || !initialized) {
return Collections.emptySet();
} else {
synchronized (this) {
if (items == null) {
items = new HashMap<>();
for (ItemChannelLink link : linkRegistry.getAll()) {
createItemForLink(link);
}
}
}
return items.values();
}
}
@Override
public void addProviderChangeListener(ProviderChangeListener<Item> listener) {
listeners.add(listener);
for (Item item : getAll()) {
listener.added(this, item);
}
}
@Override
public void removeProviderChangeListener(ProviderChangeListener<Item> listener) {
listeners.remove(listener);
}
protected void setLocaleProvider(final LocaleProvider localeProvider) {
this.localeProvider = localeProvider;
}
protected void unsetLocaleProvider(final LocaleProvider localeProvider) {
this.localeProvider = null;
}
protected void addItemFactory(ItemFactory itemFactory) {
this.itemFactories.add(itemFactory);
}
protected void removeItemFactory(ItemFactory itemFactory) {
this.itemFactories.remove(itemFactory);
}
protected void setThingRegistry(ThingRegistry thingRegistry) {
this.thingRegistry = thingRegistry;
}
protected void unsetThingRegistry(ThingRegistry thingRegistry) {
this.thingRegistry = null;
}
protected void setItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = itemRegistry;
}
protected void unsetItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = null;
}
protected void setItemChannelLinkRegistry(ItemChannelLinkRegistry linkRegistry) {
this.linkRegistry = linkRegistry;
}
protected void unsetItemChannelLinkRegistry(ItemChannelLinkRegistry linkRegistry) {
this.linkRegistry = null;
}
protected void activate(Map<String, Object> properties) {
modified(properties);
}
protected synchronized void modified(Map<String, Object> properties) {
if (properties != null) {
String enabled = (String) properties.get("enabled");
if ("false".equalsIgnoreCase(enabled)) {
this.enabled = false;
} else {
this.enabled = true;
}
}
if (enabled) {
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
// 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.
while (lastUpdate > System.nanoTime() - TimeUnit.SECONDS.toNanos(2)) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
}
}
logger.debug("Enabling channel item provider.");
initialized = true;
// simply call getAll() will create the items and notify all registered listeners automatically
getAll();
addRegistryChangeListeners();
}
});
} else {
logger.debug("Disabling channel item provider.");
for (ProviderChangeListener<Item> listener : listeners) {
for (Item item : getAll()) {
listener.removed(this, item);
}
}
removeRegistryChangeListeners();
}
}
protected void deactivate() {
removeRegistryChangeListeners();
synchronized (this) {
initialized = false;
items = null;
}
}
private void addRegistryChangeListeners() {
this.linkRegistry.addRegistryChangeListener(linkRegistryListener);
this.itemRegistry.addRegistryChangeListener(itemRegistryListener);
this.thingRegistry.addRegistryChangeListener(thingRegistryListener);
}
private void removeRegistryChangeListeners() {
this.itemRegistry.removeRegistryChangeListener(itemRegistryListener);
this.linkRegistry.removeRegistryChangeListener(linkRegistryListener);
this.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.getUID());
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 != null) {
if (item instanceof GenericItem) {
GenericItem gItem = (GenericItem) item;
gItem.setLabel(getLabel(channel));
gItem.setCategory(getCategory(channel));
gItem.addTags(channel.getDefaultTags());
}
}
if (item != null) {
items.put(item.getName(), item);
for (ProviderChangeListener<Item> listener : listeners) {
listener.added(this, item);
}
}
}
}
private String getCategory(Channel channel) {
if (channel.getChannelTypeUID() != null) {
ChannelType channelType = TypeResolver.resolve(channel.getChannelTypeUID(), localeProvider.getLocale());
if (channelType != null) {
return channelType.getCategory();
}
}
return null;
}
private String getLabel(Channel channel) {
if (channel.getLabel() != null) {
return channel.getLabel();
} else {
final Locale locale = localeProvider.getLocale();
if (channel.getChannelTypeUID() != null) {
final ChannelType channelType = TypeResolver.resolve(channel.getChannelTypeUID(), locale);
if (channelType != null) {
return channelType.getLabel();
}
}
}
return null;
}
private void removeItem(String key) {
if (!enabled) {
return;
}
Item item = items.get(key);
if (item != null) {
items.remove(key);
for (ProviderChangeListener<Item> listener : listeners) {
listener.removed(this, item);
}
items.remove(key);
}
}
public boolean isEnabled() {
return enabled;
}
RegistryChangeListener<Thing> thingRegistryListener = new RegistryChangeListener<Thing>() {
@Override
public void added(Thing element) {
for (Channel channel : element.getChannels()) {
for (ItemChannelLink link : linkRegistry.getLinks(channel.getUID())) {
createItemForLink(link);
}
}
}
@Override
public void removed(Thing element) {
removeItem(element.getUID().toString());
}
@Override
public void updated(Thing oldElement, Thing element) {
removed(oldElement);
added(element);
}
};
RegistryChangeListener<ItemChannelLink> linkRegistryListener = new RegistryChangeListener<ItemChannelLink>() {
@Override
public void added(ItemChannelLink element) {
createItemForLink(element);
lastUpdate = System.nanoTime();
}
@Override
public void removed(ItemChannelLink element) {
removeItem(element.getItemName());
}
@Override
public void updated(ItemChannelLink oldElement, ItemChannelLink element) {
removed(oldElement);
added(element);
}
};
RegistryChangeListener<Item> itemRegistryListener = new RegistryChangeListener<Item>() {
@Override
public void added(Item element) {
// 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.remove(element.getName());
if (oldElement != null) {
for (ProviderChangeListener<Item> listener : listeners) {
listener.removed(ChannelItemProvider.this, oldElement);
}
}
lastUpdate = System.nanoTime();
}
@Override
public void removed(Item element) {
for (ChannelUID uid : linkRegistry.getBoundChannels(element.getName())) {
for (ItemChannelLink link : linkRegistry.getLinks(uid)) {
if (itemRegistry.get(link.getItemName()) == null) {
createItemForLink(link);
}
}
}
}
@Override
public void updated(Item oldElement, Item element) {
}
};
}