/** * 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.net.URI; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.eclipse.smarthome.config.core.BundleProcessor; import org.eclipse.smarthome.config.core.BundleProcessorVetoManager; import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; import org.eclipse.smarthome.config.core.ConfigDescriptionRegistry; import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.common.SafeMethodCaller; import org.eclipse.smarthome.core.common.ThreadPoolManager; import org.eclipse.smarthome.core.common.registry.ManagedProvider; import org.eclipse.smarthome.core.common.registry.Provider; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemUtil; import org.eclipse.smarthome.core.items.events.AbstractItemEventSubscriber; import org.eclipse.smarthome.core.items.events.ItemCommandEvent; import org.eclipse.smarthome.core.items.events.ItemEventFactory; import org.eclipse.smarthome.core.items.events.ItemStateEvent; 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.ThingRegistry; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.ThingStatusInfo; import org.eclipse.smarthome.core.thing.ThingTypeMigrationService; import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.binding.BridgeHandler; import org.eclipse.smarthome.core.thing.binding.ThingHandler; import org.eclipse.smarthome.core.thing.binding.ThingHandlerCallback; import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; import org.eclipse.smarthome.core.thing.binding.builder.ThingStatusInfoBuilder; import org.eclipse.smarthome.core.thing.events.ThingEventFactory; import org.eclipse.smarthome.core.thing.i18n.ThingStatusInfoI18nLocalizationService; import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry; import org.eclipse.smarthome.core.thing.type.ThingType; import org.eclipse.smarthome.core.thing.type.ThingTypeRegistry; import org.eclipse.smarthome.core.thing.util.ThingHandlerHelper; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; /** * {@link ThingManager} tracks all things in the {@link ThingRegistry} and * mediates the communication between the {@link Thing} and the {@link ThingHandler} from the binding. Therefore it * tracks {@link ThingHandlerFactory}s and calls {@link ThingHandlerFactory#registerHandler(Thing)} for each thing, that * was added to the {@link ThingRegistry}. In addition the {@link ThingManager} acts * as an {@link EventHandler} and subscribes to smarthome update and command * events. Finally the {@link ThingManager} implement the {@link ThingTypeMigrationService} to offer * a way to change the thing type of a {@link Thing}. * * @author Dennis Nobel - Initial contribution * @author Michael Grammling - Added dynamic configuration update * @author Stefan Bußweiler - Added new thing status handling, migration to new event mechanism, * refactorings due to thing/bridge life cycle * @author Simon Kaufmann - Added remove handling, type conversion * @author Kai Kreuzer - Removed usage of itemRegistry and thingLinkRegistry, fixed vetoing mechanism * @author Andre Fuechsel - Added the {@link ThingTypeMigrationService}  * @author Thomas Höfer - Added localization of thing status info */ public class ThingManager extends AbstractItemEventSubscriber implements ThingTracker, ThingTypeMigrationService { private static final String FORCEREMOVE_THREADPOOL_NAME = "forceRemove"; private static final String THING_MANAGER_THREADPOOL_NAME = "thingManager"; private Logger logger = LoggerFactory.getLogger(ThingManager.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("thingManager"); private EventPublisher eventPublisher; private ItemChannelLinkRegistry itemChannelLinkRegistry; private List<ThingHandlerFactory> thingHandlerFactories = new CopyOnWriteArrayList<>(); private Map<ThingUID, ThingHandler> thingHandlers = new ConcurrentHashMap<>(); private final SetMultimap<ThingHandlerFactory, ThingHandler> thingHandlersByFactory = Multimaps .synchronizedSetMultimap(HashMultimap.<ThingHandlerFactory, ThingHandler> create()); private ThingTypeRegistry thingTypeRegistry; private ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService; private ThingHandlerCallback thingHandlerCallback = new ThingHandlerCallback() { @Override public void stateUpdated(ChannelUID channelUID, State state) { Set<Item> items = itemChannelLinkRegistry.getLinkedItems(channelUID); for (Item item : items) { State acceptedState = ItemUtil.convertToAcceptedState(state, item); eventPublisher .post(ItemEventFactory.createStateEvent(item.getName(), acceptedState, channelUID.toString())); } } @Override public void postCommand(ChannelUID channelUID, Command command) { Set<String> items = itemChannelLinkRegistry.getLinkedItemNames(channelUID); for (String item : items) { eventPublisher.post(ItemEventFactory.createCommandEvent(item, command, channelUID.toString())); } } @Override public void statusUpdated(Thing thing, ThingStatusInfo statusInfo) { // note: all provoked operations based on a status update should be executed asynchronously! ThingStatusInfo oldStatusInfo = thing.getStatusInfo(); ensureValidStatus(oldStatusInfo.getStatus(), statusInfo.getStatus()); if (ThingStatus.REMOVING.equals(oldStatusInfo.getStatus()) && !ThingStatus.REMOVED.equals(statusInfo.getStatus())) { // only allow REMOVING -> REMOVED transition, all others are illegal throw new IllegalArgumentException(MessageFormat.format( "Illegal status transition from REMOVING to {0}, only REMOVED would have been allowed.", statusInfo.getStatus())); } // update thing status and send event about new status setThingStatus(thing, statusInfo); // if thing is a bridge if (isBridge(thing)) { handleBridgeStatusUpdate((Bridge) thing, statusInfo, oldStatusInfo); } // if thing has a bridge if (hasBridge(thing)) { handleBridgeChildStatusUpdate(thing, oldStatusInfo); } // notify thing registry about thing removal if (ThingStatus.REMOVED.equals(thing.getStatus())) { notifyRegistryAboutForceRemove(thing); } } private void ensureValidStatus(ThingStatus oldStatus, ThingStatus newStatus) { if (!(ThingStatus.UNKNOWN.equals(newStatus) || ThingStatus.ONLINE.equals(newStatus) || ThingStatus.OFFLINE.equals(newStatus) || ThingStatus.REMOVED.equals(newStatus))) { throw new IllegalArgumentException(MessageFormat.format( "Illegal status {0}. Bindings only may set {1}, {2}, {3} or {4}.", newStatus, ThingStatus.UNKNOWN, ThingStatus.ONLINE, ThingStatus.OFFLINE, ThingStatus.REMOVED)); } if (ThingStatus.REMOVED.equals(newStatus) && !ThingStatus.REMOVING.equals(oldStatus)) { throw new IllegalArgumentException( MessageFormat.format("Illegal status {0}. The thing was in state {1} and not in {2}", newStatus, oldStatus, ThingStatus.REMOVING)); } } private void handleBridgeStatusUpdate(Bridge bridge, ThingStatusInfo statusInfo, ThingStatusInfo oldStatusInfo) { if (ThingHandlerHelper.isHandlerInitialized(bridge) && (ThingStatus.INITIALIZING.equals(oldStatusInfo.getStatus()))) { // bridge has just been initialized: initialize child things as well registerChildHandlers(bridge); } else if (!statusInfo.equals(oldStatusInfo)) { // bridge status has been changed: notify child things about status change notifyThingsAboutBridgeStatusChange(bridge, statusInfo); } } private void handleBridgeChildStatusUpdate(Thing thing, ThingStatusInfo oldStatusInfo) { if (ThingHandlerHelper.isHandlerInitialized(thing) && ThingStatus.INITIALIZING.equals(oldStatusInfo.getStatus())) { // child thing has just been initialized: notify bridge about it notifyBridgeAboutChildHandlerInitialization(thing); } } @Override public void thingUpdated(final Thing thing) { thingUpdatedLock.add(thing.getUID()); AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { Provider<Thing> provider = thingRegistry.getProvider(thing); if (provider == null) { throw new IllegalArgumentException(MessageFormat.format( "Provider for thing {0} cannot be determined because it is not known to the registry", thing.getUID().getAsString())); } if (provider instanceof ManagedProvider) { @SuppressWarnings("unchecked") ManagedProvider<Thing, ThingUID> managedProvider = (ManagedProvider<Thing, ThingUID>) provider; managedProvider.update(thing); } else { logger.debug("Only updating thing {} in the registry because provider {} is not managed.", thing.getUID().getAsString(), provider); thingRegistry.updated(provider, thingRegistry.get(thing.getUID()), thing); } return null; } }); thingUpdatedLock.remove(thing.getUID()); } @Override public void configurationUpdated(Thing thing) { initializeHandler(thing); } @Override public void migrateThingType(final Thing thing, final ThingTypeUID thingTypeUID, final Configuration configuration) { ThingManager.this.migrateThingType(thing, thingTypeUID, configuration); } @Override public void channelTriggered(Thing thing, ChannelUID channelUID, String event) { eventPublisher.post(ThingEventFactory.createTriggerEvent(event, channelUID)); } }; private ThingRegistryImpl thingRegistry; private ConfigDescriptionRegistry configDescriptionRegistry; private Set<Thing> things = new CopyOnWriteArraySet<>(); private Set<ThingUID> registerHandlerLock = new HashSet<>(); private Set<ThingUID> thingUpdatedLock = new HashSet<>(); @Override public void migrateThingType(final Thing thing, final ThingTypeUID thingTypeUID, final Configuration configuration) { final ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID); if (thingType == null) { throw new RuntimeException( MessageFormat.format("No thing type {0} registered, cannot change thing type for thing {1}", thingTypeUID.getAsString(), thing.getUID().getAsString())); } scheduler.schedule(new Runnable() { @Override public void run() { ThingUID thingUID = thing.getUID(); waitForRunningHandlerRegistrations(thingUID); // Remove the ThingHandler, if any final ThingHandlerFactory oldThingHandlerFactory = findThingHandlerFactory(thing.getThingTypeUID()); if (oldThingHandlerFactory != null) { ThingHandler thingHandler = thing.getHandler(); unregisterAndDisposeHandler(oldThingHandlerFactory, thing, thingHandler); waitUntilHandlerUnregistered(thing, 60 * 1000); } else { logger.debug("No ThingHandlerFactory available that can handle {}", thing.getThingTypeUID()); } // Set the new channels List<Channel> channels = ThingFactoryHelper.createChannels(thingType, thingUID, configDescriptionRegistry); ((ThingImpl) thing).setChannels(channels); // Set the given configuration ThingFactoryHelper.applyDefaultConfiguration(configuration, thingType, configDescriptionRegistry); ((ThingImpl) thing).setConfiguration(configuration); // Change the ThingType ((ThingImpl) thing).setThingTypeUID(thingTypeUID); // Register the new Handler - ThingManager.updateThing() is going to take care of that thingRegistry.update(thing); logger.debug("Changed ThingType of Thing {} to {}. New ThingHandler is {}.", thing.getUID().toString(), thing.getThingTypeUID(), thing.getHandler().toString()); } private void waitUntilHandlerUnregistered(final Thing thing, int timeout) { for (int i = 0; i < timeout / 100; i++) { if (thing.getHandler() == null && thingHandlers.get(thing.getUID()) == null) { return; } try { Thread.sleep(100); logger.debug("Waiting for handler deregistration to complete for thing {}. Took already {}ms.", thing.getUID().getAsString(), (i + 1) * 100); } catch (InterruptedException e) { return; } } String message = MessageFormat.format( "Thing type migration failed for {0}. The handler deregistration did not complete within {1}ms.", thing.getUID().getAsString(), timeout); logger.error(message); throw new RuntimeException(message); } private void waitForRunningHandlerRegistrations(ThingUID thingUID) { for (int i = 0; i < 10 * 10; i++) { if (!registerHandlerLock.contains(thingUID)) { return; } try { // Wait a little to give running handler registrations a chance to complete... Thread.sleep(100); } catch (InterruptedException e) { return; } } String message = MessageFormat.format( "Thing type migration failed for {0}. Could not obtain lock for hander registration.", thingUID.getAsString()); logger.error(message); throw new RuntimeException(message); } }, 0, TimeUnit.MILLISECONDS); } @Override protected void receiveCommand(ItemCommandEvent commandEvent) { String itemName = commandEvent.getItemName(); final Command command = commandEvent.getItemCommand(); Set<ChannelUID> boundChannels = this.itemChannelLinkRegistry.getBoundChannels(itemName); for (final ChannelUID channelUID : boundChannels) { // make sure a command event is not sent back to its source if (!channelUID.toString().equals(commandEvent.getSource())) { Thing thing = getThing(channelUID.getThingUID()); if (thing != null) { final ThingHandler handler = thing.getHandler(); if (handler != null) { if (ThingHandlerHelper.isHandlerInitialized(thing)) { logger.debug("Delegating command '{}' for item '{}' to handler for channel '{}'", command, itemName, channelUID); try { SafeMethodCaller.call(new SafeMethodCaller.ActionWithException<Void>() { @Override public Void call() throws Exception { handler.handleCommand(channelUID, command); return null; } }); } catch (TimeoutException ex) { logger.warn("Handler for thing '{}' takes more than {}ms for handling a command", handler.getThing().getUID(), SafeMethodCaller.DEFAULT_TIMEOUT); } catch (Exception ex) { logger.error("Exception occurred while calling handler: {}", ex.getMessage(), ex); } } else { logger.info( "Not delegating command '{}' for item '{}' to handler for channel '{}', " + "because handler is not initialized (thing must be in status UNKNOWN, ONLINE or OFFLINE).", command, itemName, channelUID); } } else { logger.warn("Cannot delegate command '{}' for item '{}' to handler for channel '{}', " + "because no handler is assigned. Maybe the binding is not installed or not " + "propertly initialized.", command, itemName, channelUID); } } else { logger.warn( "Cannot delegate command '{}' for item '{}' to handler for channel '{}', " + "because no thing with the UID '{}' could be found.", command, itemName, channelUID, channelUID.getThingUID()); } } } } @Override protected void receiveUpdate(ItemStateEvent updateEvent) { String itemName = updateEvent.getItemName(); final State newState = updateEvent.getItemState(); Set<ChannelUID> boundChannels = this.itemChannelLinkRegistry.getBoundChannels(itemName); for (final ChannelUID channelUID : boundChannels) { // make sure an update event is not sent back to its source if (!channelUID.toString().equals(updateEvent.getSource())) { Thing thing = getThing(channelUID.getThingUID()); if (thing != null) { final ThingHandler handler = thing.getHandler(); if (handler != null) { if (ThingHandlerHelper.isHandlerInitialized(thing)) { logger.debug("Delegating update '{}' for item '{}' to handler for channel '{}'", newState, itemName, channelUID); try { SafeMethodCaller.call(new SafeMethodCaller.ActionWithException<Void>() { @Override public Void call() throws Exception { handler.handleUpdate(channelUID, newState); return null; } }); } catch (TimeoutException ex) { logger.warn("Handler for thing {} takes more than {}ms for handling an update", handler.getThing().getUID(), SafeMethodCaller.DEFAULT_TIMEOUT); } catch (Exception ex) { logger.error("Exception occurred while calling handler: {}", ex.getMessage(), ex); } } else { logger.info( "Not delegating update '{}' for item '{}' to handler for channel '{}', " + "because handler is not initialized (thing must be in status UNKNOWN, ONLINE or OFFLINE).", newState, itemName, channelUID); } } else { logger.warn("Cannot delegate update '{}' for item '{}' to handler for channel '{}', " + "because no handler is assigned. Maybe the binding is not installed or not " + "propertly initialized.", newState, itemName, channelUID); } } else { logger.warn( "Cannot delegate update '{}' for item '{}' to handler for channel '{}', " + "because no thing with the UID '{}' could be found.", newState, itemName, channelUID, channelUID.getThingUID()); } } } } @Override public void thingAdded(Thing thing, ThingTrackerEvent thingTrackerEvent) { this.things.add(thing); logger.debug("Thing '{}' is tracked by ThingManager.", thing.getUID()); if (!isHandlerRegistered(thing)) { registerAndInitializeHandler(thing, getThingHandlerFactory(thing)); } else { logger.debug("Handler of tracked thing '{}' already registered.", thing.getUID()); } } @Override public void thingRemoving(Thing thing, ThingTrackerEvent thingTrackerEvent) { setThingStatus(thing, ThingStatusInfoBuilder.create(ThingStatus.REMOVING).build()); notifyThingHandlerAboutRemoval(thing); } @Override public void thingRemoved(final Thing thing, ThingTrackerEvent thingTrackerEvent) { logger.debug("Thing '{}' is no longer tracked by ThingManager.", thing.getUID()); ThingHandler thingHandler = thingHandlers.get(thing.getUID()); if (thingHandler != null) { final ThingHandlerFactory thingHandlerFactory = findThingHandlerFactory(thing.getThingTypeUID()); if (thingHandlerFactory != null) { unregisterAndDisposeHandler(thingHandlerFactory, thing, thingHandler); if (thingTrackerEvent == ThingTrackerEvent.THING_REMOVED) { SafeMethodCaller.call(new SafeMethodCaller.Action<Void>() { @Override public Void call() throws Exception { thingHandlerFactory.removeThing(thing.getUID()); return null; } }); } } else { logger.warn("Cannot unregister handler. No handler factory for thing '{}' found.", thing.getUID()); } } this.things.remove(thing); } @Override public void thingUpdated(final Thing thing, ThingTrackerEvent thingTrackerEvent) { ThingUID thingUID = thing.getUID(); Thing oldThing = getThing(thingUID); if (oldThing != thing) { this.things.remove(oldThing); this.things.add(thing); } final ThingHandler thingHandler = thingHandlers.get(thingUID); if (thingHandler != null) { if (oldThing != thing) { thing.setHandler(thingHandler); } if (ThingHandlerHelper.isHandlerInitialized(thing) || isInitializing(thing)) { try { // prevent infinite loops by not informing handler about self-initiated update if (!thingUpdatedLock.contains(thingUID)) { SafeMethodCaller.call(new SafeMethodCaller.ActionWithException<Void>() { @Override public Void call() throws Exception { thingHandler.thingUpdated(thing); return null; } }); } } catch (Exception ex) { logger.error("Exception occurred while calling thing updated at ThingHandler '{}': {}", thingHandler, ex.getMessage(), ex); } } else { logger.debug( "Cannot notify handler about updated thing '{}', because handler is not initialized (thing must be in status UNKNOWN, ONLINE or OFFLINE). Starting handler initialization instead.", thing.getThingTypeUID()); initializeHandler(thing); } } else { registerAndInitializeHandler(thing, getThingHandlerFactory(thing)); } if (oldThing != thing) { oldThing.setHandler(null); } } private Thing getThing(ThingUID id) { for (Thing thing : this.things) { if (thing.getUID().equals(id)) { return thing; } } return null; } private ThingType getThingType(Thing thing) { return thingTypeRegistry.getThingType(thing.getThingTypeUID()); } private ThingHandlerFactory findThingHandlerFactory(ThingTypeUID thingTypeUID) { for (ThingHandlerFactory factory : thingHandlerFactories) { if (factory.supportsThingType(thingTypeUID)) { return factory; } } return null; } private void registerHandler(Thing thing, ThingHandlerFactory thingHandlerFactory) { synchronized (thing) { if (!isHandlerRegistered(thing)) { if (!hasBridge(thing)) { doRegisterHandler(thing, thingHandlerFactory); } else { Bridge bridge = getBridge(thing.getBridgeUID()); if (bridge != null && ThingHandlerHelper.isHandlerInitialized(bridge)) { doRegisterHandler(thing, thingHandlerFactory); } else { setThingStatus(thing, buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_MISSING_ERROR)); } } } else { logger.debug("Attempt to register a handler twice for thing {} at the same time will be ignored.", thing.getUID()); } } } private void doRegisterHandler(final Thing thing, final ThingHandlerFactory thingHandlerFactory) { logger.debug("Calling '{}.registerHandler()' for thing '{}'.", thingHandlerFactory.getClass().getSimpleName(), thing.getUID()); try { ThingHandler thingHandler = thingHandlerFactory.registerHandler(thing); thingHandler.setCallback(ThingManager.this.thingHandlerCallback); thing.setHandler(thingHandler); thingHandlers.put(thing.getUID(), thingHandler); thingHandlersByFactory.put(thingHandlerFactory, thingHandler); } catch (Exception ex) { ThingStatusInfo statusInfo = buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_REGISTERING_ERROR, ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage()); setThingStatus(thing, statusInfo); logger.error("Exception occurred while calling thing handler factory '{}': {}", thingHandlerFactory, ex.getMessage(), ex); } } private void registerChildHandlers(final Bridge bridge) { for (final Thing child : bridge.getThings()) { logger.debug("Register and initialize child '{}' of bridge '{}'.", child.getUID(), bridge.getUID()); ThreadPoolManager.getPool(THING_MANAGER_THREADPOOL_NAME).execute(new Runnable() { @Override public void run() { try { registerAndInitializeHandler(child, getThingHandlerFactory(child)); } catch (Exception ex) { logger.error( "Registration resp. initialization of child '{}' of bridge '{}' has been failed: {}", child.getUID(), bridge.getUID(), ex.getMessage(), ex); } } }); } } private void initializeHandler(Thing thing) { if (!isHandlerRegistered(thing)) { return; } synchronized (thing) { if (ThingHandlerHelper.isHandlerInitialized(thing)) { logger.debug("Attempt to initialize the already initialized thing '{}' will be ignored.", thing.getUID()); return; } if (isInitializing(thing)) { logger.debug("Attempt to initialize a handler twice for thing '{}' at the same time will be ignored.", thing.getUID()); return; } if (thing.getHandler().getThing() != thing) { logger.debug("The model of {} is inconsistent [thing.getHandler().getThing() != thing]", thing.getUID()); } ThingType thingType = getThingType(thing); applyDefaultConfiguration(thing, thingType); if (isInitializable(thing, thingType)) { setThingStatus(thing, buildStatusInfo(ThingStatus.INITIALIZING, ThingStatusDetail.NONE)); doInitializeHandler(thing.getHandler()); } else { logger.debug("Thing '{}' not initializable, check required configuration parameters.", thing.getUID()); setThingStatus(thing, buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_CONFIGURATION_PENDING)); } } } private final BundleProcessorVetoManager<Thing> initializationVetoManager = new BundleProcessorVetoManager<>( new BundleProcessorVetoManager.Action<Thing>() { @Override public void apply(final Thing thing) { final ThingHandlerFactory thingHandlerFactory = getThingHandlerFactory(thing); registerHandler(thing, thingHandlerFactory); initializeHandler(thing); } }); private void applyDefaultConfiguration(Thing thing, ThingType thingType) { if (thingType != null) { ThingFactoryHelper.applyDefaultConfiguration(thing.getConfiguration(), thingType, configDescriptionRegistry); } } private boolean isInitializable(Thing thing, ThingType thingType) { // determines if all 'required' configuration parameters are available in the configuration if (thingType == null) { return true; } ConfigDescription description = resolve(thingType.getConfigDescriptionURI(), null); if (description == null) { return true; } List<String> requiredParameters = getRequiredParameters(description); Map<String, Object> properties = thing.getConfiguration().getProperties(); return properties.keySet().containsAll(requiredParameters); } private ConfigDescription resolve(URI configDescriptionURI, Locale locale) { if (configDescriptionURI == null) { return null; } return configDescriptionRegistry != null ? configDescriptionRegistry.getConfigDescription(configDescriptionURI, locale) : null; } private List<String> getRequiredParameters(ConfigDescription description) { List<String> requiredParameters = new ArrayList<>(); for (ConfigDescriptionParameter param : description.getParameters()) { if (param.isRequired()) { requiredParameters.add(param.getName()); } } return requiredParameters; } private void doInitializeHandler(final ThingHandler thingHandler) { scheduler.schedule(new Runnable() { @Override public void run() { logger.debug("Calling initialize handler for thing '{}' at '{}'.", thingHandler.getThing().getUID(), thingHandler); try { SafeMethodCaller.call(new SafeMethodCaller.ActionWithException<Void>() { @Override public Void call() throws Exception { thingHandler.initialize(); return null; } }); } catch (TimeoutException ex) { logger.warn("Initializing handler for thing '{}' takes more than {}ms.", thingHandler.getThing().getUID(), SafeMethodCaller.DEFAULT_TIMEOUT); } catch (Exception ex) { ThingStatusInfo statusInfo = buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage()); setThingStatus(thingHandler.getThing(), statusInfo); logger.error("Exception occurred while initializing handler of thing '{}': {}", thingHandler.getThing().getUID(), ex.getMessage(), ex); } } }, 0, TimeUnit.NANOSECONDS); } private boolean isInitializing(Thing thing) { return thing.getStatus() == ThingStatus.INITIALIZING; } private boolean isHandlerRegistered(Thing thing) { ThingHandler handler = thingHandlers.get(thing.getUID()); return handler != null && handler == thing.getHandler(); } private boolean isBridge(Thing thing) { return thing instanceof Bridge; } private boolean hasBridge(final Thing thing) { return thing.getBridgeUID() != null; } private Bridge getBridge(ThingUID bridgeUID) { Thing bridge = thingRegistry.get(bridgeUID); return isBridge(bridge) ? (Bridge) bridge : null; } private void unregisterHandler(Thing thing, ThingHandlerFactory thingHandlerFactory) { synchronized (thing) { if (isHandlerRegistered(thing)) { doUnregisterHandler(thing, thingHandlerFactory); } } } private void doUnregisterHandler(final Thing thing, final ThingHandlerFactory thingHandlerFactory) { logger.debug("Calling unregisterHandler handler for thing '{}' at '{}'.", thing.getUID(), thingHandlerFactory); try { SafeMethodCaller.call(new SafeMethodCaller.ActionWithException<Void>() { @Override public Void call() throws Exception { ThingHandler thingHandler = thing.getHandler(); thingHandlerFactory.unregisterHandler(thing); thingHandler.setCallback(null); thing.setHandler(null); setThingStatus(thing, buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_MISSING_ERROR)); thingHandlers.remove(thing.getUID()); thingHandlersByFactory.remove(thingHandlerFactory, thingHandler); return null; } }); } catch (Exception ex) { logger.error("Exception occurred while calling thing handler factory '{}' ", thingHandlerFactory, ex.getMessage(), ex); } } private void disposeHandler(Thing thing, ThingHandler thingHandler) { synchronized (thing) { doDisposeHandler(thingHandler); if (hasBridge(thing)) { notifyBridgeAboutChildHandlerDisposal(thing, thingHandler); } } } private void doDisposeHandler(final ThingHandler thingHandler) { logger.debug("Calling dispose handler for thing '{}' at '{}'.", thingHandler.getThing().getUID(), thingHandler); try { SafeMethodCaller.call(new SafeMethodCaller.ActionWithException<Void>() { @Override public Void call() throws Exception { setThingStatus(thingHandler.getThing(), buildStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE)); thingHandler.dispose(); return null; } }); } catch (TimeoutException ex) { logger.warn("Disposing handler for thing '{}' takes more than {}ms.", thingHandler.getThing().getUID(), SafeMethodCaller.DEFAULT_TIMEOUT); } catch (Exception e) { logger.error("Exception occurred while disposing handler of thing '{}': {}", thingHandler.getThing().getUID(), e.getMessage(), e); } } private void unregisterAndDisposeChildHandlers(Bridge bridge, ThingHandlerFactory thingHandlerFactory) { addThingsToBridge(bridge); for (Thing child : bridge.getThings()) { ThingHandler handler = child.getHandler(); if (handler != null) { logger.debug("Unregister and dispose child '{}' of bridge '{}'.", child.getUID(), bridge.getUID()); unregisterAndDisposeHandler(thingHandlerFactory, child, handler); } } } private void unregisterAndDisposeHandler(ThingHandlerFactory thingHandlerFactory, Thing thing, ThingHandler handler) { if (isBridge(thing)) { unregisterAndDisposeChildHandlers((Bridge) thing, thingHandlerFactory); } disposeHandler(thing, handler); unregisterHandler(thing, thingHandlerFactory); } private void addThingsToBridge(Bridge bridge) { Collection<Thing> things = thingRegistry.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 notifyThingsAboutBridgeStatusChange(final Bridge bridge, final ThingStatusInfo bridgeStatus) { if (ThingHandlerHelper.isHandlerInitialized(bridge)) { for (final Thing child : bridge.getThings()) { ThreadPoolManager.getPool(THING_MANAGER_THREADPOOL_NAME).execute(new Runnable() { @Override public void run() { try { ThingHandler handler = child.getHandler(); if (handler != null && ThingHandlerHelper.isHandlerInitialized(child)) { handler.bridgeStatusChanged(bridgeStatus); } } catch (Exception e) { logger.error( "Exception occurred during notification about bridge status change on thing '{}': {}", child.getUID(), e.getMessage(), e); } } }); } } } private void notifyBridgeAboutChildHandlerInitialization(final Thing thing) { final Bridge bridge = getBridge(thing.getBridgeUID()); if (bridge != null) { ThreadPoolManager.getPool(THING_MANAGER_THREADPOOL_NAME).execute(new Runnable() { @Override public void run() { try { BridgeHandler bridgeHandler = bridge.getHandler(); if (bridgeHandler != null) { bridgeHandler.childHandlerInitialized(thing.getHandler(), thing); } } catch (Exception e) { logger.error( "Exception occurred during bridge handler ('{}') notification about handler initialization of child '{}': {}", bridge.getUID(), thing.getUID(), e.getMessage(), e); } } }); } } private void notifyBridgeAboutChildHandlerDisposal(final Thing thing, final ThingHandler thingHandler) { final Bridge bridge = getBridge(thing.getBridgeUID()); if (bridge != null) { ThreadPoolManager.getPool(THING_MANAGER_THREADPOOL_NAME).execute(new Runnable() { @Override public void run() { try { BridgeHandler bridgeHandler = bridge.getHandler(); if (bridgeHandler != null) { bridgeHandler.childHandlerDisposed(thingHandler, thing); } } catch (Exception ex) { logger.error( "Exception occurred during bridge handler ('{}') notification about handler disposal of child '{}': {}", bridge.getUID(), thing.getUID(), ex.getMessage(), ex); } } }); } } private void notifyThingHandlerAboutRemoval(final Thing thing) { logger.trace("Asking handler of thing '{}' to handle its removal.", thing.getUID()); ThreadPoolManager.getPool(THING_MANAGER_THREADPOOL_NAME).execute(new Runnable() { @Override public void run() { try { ThingHandler handler = thing.getHandler(); if (handler != null) { handler.handleRemoval(); logger.trace("Handler of thing '{}' returned from handling its removal.", thing.getUID()); } else { logger.trace("No handler of thing '{}' available, so deferring the removal call.", thing.getUID()); } } catch (Exception ex) { logger.error("The ThingHandler caused an exception while handling the removal of its thing", ex); } } }); } private void notifyRegistryAboutForceRemove(final Thing thing) { logger.debug("Removal handling of thing '{}' completed. Going to remove it now.", thing.getUID()); // call asynchronous to avoid deadlocks in thing handler ThreadPoolManager.getPool(FORCEREMOVE_THREADPOOL_NAME).execute(new Runnable() { @Override public void run() { try { AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { thingRegistry.forceRemove(thing.getUID()); return null; } }); } catch (IllegalStateException ex) { logger.debug("Could not remove thing {}. Most likely because it is not managed.", thing.getUID(), ex); } catch (Exception ex) { logger.error( "Could not remove thing {}, because an unknwon Exception occurred. Most likely because it is not managed.", thing.getUID(), ex); } } }); } protected void activate(ComponentContext componentContext) { this.thingRegistry.addThingTracker(this); } protected void addThingHandlerFactory(ThingHandlerFactory thingHandlerFactory) { logger.debug("Thing handler factory '{}' added", thingHandlerFactory.getClass().getSimpleName()); thingHandlerFactories.add(thingHandlerFactory); for (Thing thing : things) { if (thingHandlerFactory.supportsThingType(thing.getThingTypeUID())) { if (!isHandlerRegistered(thing)) { registerAndInitializeHandler(thing, thingHandlerFactory); } else { logger.debug("Thing handler for thing '{}' already registered", thing.getUID()); } } } } private void registerAndInitializeHandler(final Thing thing, final ThingHandlerFactory thingHandlerFactory) { if (thingHandlerFactory != null) { initializationVetoManager.applyActionFor(thingHandlerFactory.getClass(), thing); } else { logger.debug("Not registering a handler at this point since no handler factory for thing '{}' found.", thing.getUID()); } } private ThingHandlerFactory getThingHandlerFactory(Thing thing) { ThingHandlerFactory thingHandlerFactory = findThingHandlerFactory(thing.getThingTypeUID()); if (thingHandlerFactory != null) { return thingHandlerFactory; } logger.debug("Not registering a handler at this point since no handler factory for thing '{}' found.", thing.getUID()); return null; } protected void deactivate(ComponentContext componentContext) { this.thingRegistry.removeThingTracker(this); } protected void removeThingHandlerFactory(ThingHandlerFactory thingHandlerFactory) { logger.debug("Thing handler factory '{}' removed", thingHandlerFactory.getClass().getSimpleName()); Set<ThingHandler> handlers = ImmutableSet.copyOf(thingHandlersByFactory.get(thingHandlerFactory)); for (ThingHandler thingHandler : handlers) { Thing thing = thingHandler.getThing(); if (thing != null && isHandlerRegistered(thing)) { unregisterAndDisposeHandler(thingHandlerFactory, thing, thingHandler); } } thingHandlersByFactory.removeAll(thingHandlerFactory); thingHandlerFactories.remove(thingHandlerFactory); } protected void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } protected void setItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = itemChannelLinkRegistry; } protected void setThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = (ThingRegistryImpl) thingRegistry; } protected void unsetEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = null; } protected void unsetItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = null; } protected void unsetThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = null; } protected void setConfigDescriptionRegistry(ConfigDescriptionRegistry configDescriptionRegistry) { this.configDescriptionRegistry = configDescriptionRegistry; } protected void unsetConfigDescriptionRegistry(ConfigDescriptionRegistry configDescriptionRegistry) { this.configDescriptionRegistry = null; } private ThingStatusInfo buildStatusInfo(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, String description) { ThingStatusInfoBuilder statusInfoBuilder = ThingStatusInfoBuilder.create(thingStatus, thingStatusDetail); statusInfoBuilder.withDescription(description); return statusInfoBuilder.build(); } private ThingStatusInfo buildStatusInfo(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail) { return buildStatusInfo(thingStatus, thingStatusDetail, null); } private void setThingStatus(Thing thing, ThingStatusInfo thingStatusInfo) { ThingStatusInfo oldStatusInfo = thingStatusInfoI18nLocalizationService.getLocalizedThingStatusInfo(thing, null); thing.setStatusInfo(thingStatusInfo); ThingStatusInfo newStatusInfo = thingStatusInfoI18nLocalizationService.getLocalizedThingStatusInfo(thing, null); try { eventPublisher.post(ThingEventFactory.createStatusInfoEvent(thing.getUID(), newStatusInfo)); if (!oldStatusInfo.equals(newStatusInfo)) { eventPublisher.post( ThingEventFactory.createStatusInfoChangedEvent(thing.getUID(), newStatusInfo, oldStatusInfo)); } } catch (Exception ex) { logger.error("Could not post 'ThingStatusInfoEvent' event: " + ex.getMessage(), ex); } } protected void setThingTypeRegistry(ThingTypeRegistry thingTypeRegistry) { this.thingTypeRegistry = thingTypeRegistry; } protected void unsetThingTypeRegistry(ThingTypeRegistry thingTypeRegistry) { this.thingTypeRegistry = null; } protected void setBundleProcessor(BundleProcessor bundleProcessor) { initializationVetoManager.addBundleProcessor(bundleProcessor); } protected void unsetBundleProcessor(BundleProcessor bundleProcessor) { initializationVetoManager.removeBundleProcessor(bundleProcessor); } protected void setThingStatusInfoI18nLocalizationService( ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService) { this.thingStatusInfoI18nLocalizationService = thingStatusInfoI18nLocalizationService; } protected void unsetThingStatusInfoI18nLocalizationService( ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService) { this.thingStatusInfoI18nLocalizationService = null; } }