/**
* 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.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.core.common.registry.AbstractRegistry;
import org.eclipse.smarthome.core.common.registry.Provider;
import org.eclipse.smarthome.core.thing.Bridge;
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.ThingProvider;
import org.eclipse.smarthome.core.thing.ThingRegistry;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
import org.eclipse.smarthome.core.thing.events.ThingEventFactory;
import org.eclipse.smarthome.core.thing.internal.ThingTracker.ThingTrackerEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of {@link ThingRegistry}.
*
* @author Michael Grammling - Added dynamic configuration update
* @author Simon Kaufmann - Added forceRemove
* @author Chris Jackson - ensure thing added event is sent before linked events
* @auther Thomas Höfer - Added config description validation exception to updateConfiguration operation
*/
public class ThingRegistryImpl extends AbstractRegistry<Thing, ThingUID, ThingProvider> implements ThingRegistry {
private Logger logger = LoggerFactory.getLogger(ThingRegistryImpl.class.getName());
private List<ThingTracker> thingTrackers = new CopyOnWriteArrayList<>();
private List<ThingHandlerFactory> thingHandlerFactories = new CopyOnWriteArrayList<>();
public ThingRegistryImpl() {
super(ThingProvider.class);
}
/**
* Adds a thing tracker.
*
* @param thingTracker
* the thing tracker
*/
public void addThingTracker(ThingTracker thingTracker) {
notifyTrackerAboutAllThingsAdded(thingTracker);
thingTrackers.add(thingTracker);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.smarthome.core.thing.ThingRegistry#getByUID(java.lang.String)
*/
@Override
public Thing get(final ThingUID uid) {
for (final Map.Entry<Provider<Thing>, Collection<Thing>> entry : elementMap.entrySet()) {
for (final Thing thing : entry.getValue()) {
if (uid.equals(thing.getUID())) {
return thing;
}
}
}
return null;
}
@Override
public Channel getChannel(ChannelUID channelUID) {
ThingUID thingUID = channelUID.getThingUID();
Thing thing = get(thingUID);
if (thing != null) {
return thing.getChannel(channelUID.getId());
}
return null;
}
@Override
public void updateConfiguration(ThingUID thingUID, Map<String, Object> configurationParameters) {
Thing thing = get(thingUID);
if (thing != null) {
ThingHandler thingHandler = thing.getHandler();
if (thingHandler != null) {
thingHandler.handleConfigurationUpdate(configurationParameters);
} else {
throw new IllegalStateException("Thing with UID " + thingUID + " has no handler attached.");
}
} else {
throw new IllegalArgumentException("Thing with UID " + thingUID + " does not exists.");
}
}
@Override
public Thing forceRemove(ThingUID thingUID) {
return super.remove(thingUID);
}
@Override
public Thing remove(ThingUID thingUID) {
Thing thing = get(thingUID);
if (thing != null) {
notifyTrackers(thing, ThingTrackerEvent.THING_REMOVING);
}
return thing;
}
/**
* Removes a thing tracker.
*
* @param thingTracker
* the thing tracker
*/
public void removeThingTracker(ThingTracker thingTracker) {
notifyTrackerAboutAllThingsRemoved(thingTracker);
thingTrackers.remove(thingTracker);
}
@Override
protected void notifyListenersAboutAddedElement(Thing element) {
super.notifyListenersAboutAddedElement(element);
postEvent(ThingEventFactory.createAddedEvent(element));
notifyTrackers(element, ThingTrackerEvent.THING_ADDED);
}
@Override
protected void notifyListenersAboutRemovedElement(Thing element) {
super.notifyListenersAboutRemovedElement(element);
notifyTrackers(element, ThingTrackerEvent.THING_REMOVED);
postEvent(ThingEventFactory.createRemovedEvent(element));
}
@Override
protected void notifyListenersAboutUpdatedElement(Thing oldElement, Thing element) {
super.notifyListenersAboutUpdatedElement(oldElement, element);
notifyTrackers(element, ThingTrackerEvent.THING_UPDATED);
postEvent(ThingEventFactory.createUpdateEvent(element, oldElement));
}
@Override
protected void onAddElement(Thing thing) throws IllegalArgumentException {
addThingToBridge(thing);
if (thing instanceof Bridge) {
addThingsToBridge((Bridge) thing);
}
}
@Override
protected void onRemoveElement(Thing thing) {
// needed because the removed element was taken from the storage and lost its dynamic state
preserveDynamicState(thing);
ThingUID bridgeUID = thing.getBridgeUID();
if (bridgeUID != null) {
Thing bridge = this.get(bridgeUID);
if (bridge instanceof BridgeImpl) {
((BridgeImpl) bridge).removeThing(thing);
}
}
}
@Override
protected void onUpdateElement(Thing oldThing, Thing thing) {
// better call it explicitly here, even if it is called in onRemoveElement
preserveDynamicState(thing);
onRemoveElement(thing);
onAddElement(thing);
}
private void preserveDynamicState(Thing thing) {
final Thing existingThing = get(thing.getUID());
if (existingThing != null) {
thing.setHandler(existingThing.getHandler());
thing.setStatusInfo(existingThing.getStatusInfo());
}
}
private void addThingsToBridge(Bridge bridge) {
Collection<Thing> things = getAll();
for (Thing thing : things) {
ThingUID bridgeUID = thing.getBridgeUID();
if (bridgeUID != null && bridgeUID.equals(bridge.getUID())) {
if (bridge instanceof BridgeImpl && !bridge.getThings().contains(thing)) {
((BridgeImpl) bridge).addThing(thing);
}
}
}
}
private void addThingToBridge(Thing thing) {
ThingUID bridgeUID = thing.getBridgeUID();
if (bridgeUID != null) {
Thing bridge = this.get(bridgeUID);
if (bridge instanceof BridgeImpl && !((Bridge) bridge).getThings().contains(thing)) {
((BridgeImpl) bridge).addThing(thing);
}
}
}
private void notifyTrackers(Thing thing, ThingTrackerEvent event) {
for (ThingTracker thingTracker : thingTrackers) {
try {
switch (event) {
case THING_ADDED:
thingTracker.thingAdded(thing, ThingTrackerEvent.THING_ADDED);
break;
case THING_REMOVING:
thingTracker.thingRemoving(thing, ThingTrackerEvent.THING_REMOVING);
break;
case THING_REMOVED:
thingTracker.thingRemoved(thing, ThingTrackerEvent.THING_REMOVED);
break;
case THING_UPDATED:
thingTracker.thingUpdated(thing, ThingTrackerEvent.THING_UPDATED);
break;
default:
break;
}
} catch (Exception ex) {
logger.error("Could not inform the ThingTracker '" + thingTracker + "' about the '" + event.name()
+ "' event!", ex);
}
}
}
private void notifyTrackerAboutAllThingsAdded(ThingTracker thingTracker) {
for (Thing thing : getAll()) {
thingTracker.thingAdded(thing, ThingTrackerEvent.TRACKER_ADDED);
}
}
private void notifyTrackerAboutAllThingsRemoved(ThingTracker thingTracker) {
for (Thing thing : getAll()) {
thingTracker.thingRemoved(thing, ThingTrackerEvent.TRACKER_REMOVED);
}
}
@Override
public Thing createThingOfType(ThingTypeUID thingTypeUID, ThingUID thingUID, ThingUID bridgeUID, String label,
Configuration configuration) {
logger.debug("Creating thing for type '{}'.", thingTypeUID);
for (ThingHandlerFactory thingHandlerFactory : thingHandlerFactories) {
if (thingHandlerFactory.supportsThingType(thingTypeUID)) {
Thing thing = thingHandlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
thing.setLabel(label);
return thing;
}
}
logger.warn("Cannot create thing. No binding found that supports creating a thing" + " of type {}.",
thingTypeUID);
return null;
}
protected void addThingHandlerFactory(ThingHandlerFactory thingHandlerFactory) {
this.thingHandlerFactories.add(thingHandlerFactory);
}
protected void removeThingHandlerFactory(ThingHandlerFactory thingHandlerFactory) {
this.thingHandlerFactories.remove(thingHandlerFactory);
}
public Provider<Thing> getProvider(Thing thing) {
for (Entry<Provider<Thing>, Collection<Thing>> entry : elementMap.entrySet()) {
if (entry.getValue().contains(thing)) {
return entry.getKey();
}
}
return null;
}
}