/** * 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.binding; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.smarthome.config.core.ConfigDescriptionRegistry; import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.config.core.status.ConfigStatusProvider; import org.eclipse.smarthome.core.thing.Bridge; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.binding.firmware.FirmwareUpdateHandler; import org.eclipse.smarthome.core.thing.type.ThingType; import org.eclipse.smarthome.core.thing.type.ThingTypeRegistry; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; import org.osgi.util.tracker.ServiceTracker; import com.google.common.base.Preconditions; /** * The {@link BaseThingHandlerFactory} provides a base implementation for the {@link ThingHandlerFactory} interface. * <p> * It is recommended to extend this abstract base class, because it covers a lot of common logic. * <p> * * @author Dennis Nobel - Initial contribution * @author Benedikt Niehues - fix for Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=445137 considering * default values * @author Thomas Höfer - added config status provider and firmware update handler service registration * @author Stefan Bußweiler - API changes due to bridge/thing life cycle refactoring, removed OSGi service registration * for thing handlers */ public abstract class BaseThingHandlerFactory implements ThingHandlerFactory { protected BundleContext bundleContext; private Map<String, ServiceRegistration<ConfigStatusProvider>> configStatusProviders = new ConcurrentHashMap<>(); private Map<String, ServiceRegistration<FirmwareUpdateHandler>> firmwareUpdateHandlers = new ConcurrentHashMap<>(); private ServiceTracker<ThingTypeRegistry, ThingTypeRegistry> thingTypeRegistryServiceTracker; private ServiceTracker<ConfigDescriptionRegistry, ConfigDescriptionRegistry> configDescriptionRegistryServiceTracker; /** * Initializes the {@link BaseThingHandlerFactory}. If this method is overridden by a sub class, the implementing * method must call <code>super.activate(componentContext)</code> first. * * @param componentContext * component context (must not be null) */ protected void activate(ComponentContext componentContext) { this.bundleContext = componentContext.getBundleContext(); thingTypeRegistryServiceTracker = new ServiceTracker<>(bundleContext, ThingTypeRegistry.class.getName(), null); thingTypeRegistryServiceTracker.open(); configDescriptionRegistryServiceTracker = new ServiceTracker<>(bundleContext, ConfigDescriptionRegistry.class.getName(), null); configDescriptionRegistryServiceTracker.open(); } /** * Disposes the {@link BaseThingHandlerFactory}. If this method is overridden by a sub class, the implementing * method must call <code>super.deactivate(componentContext)</code> first. * * @param componentContext * component context (must not be null) */ protected void deactivate(ComponentContext componentContext) { for (ServiceRegistration<ConfigStatusProvider> serviceRegistration : configStatusProviders.values()) { if (serviceRegistration != null) { serviceRegistration.unregister(); } } for (ServiceRegistration<FirmwareUpdateHandler> serviceRegistration : firmwareUpdateHandlers.values()) { if (serviceRegistration != null) { serviceRegistration.unregister(); } } thingTypeRegistryServiceTracker.close(); configDescriptionRegistryServiceTracker.close(); configStatusProviders.clear(); firmwareUpdateHandlers.clear(); bundleContext = null; } @Override public ThingHandler registerHandler(Thing thing) { Preconditions.checkArgument(thing != null, "The argument 'thing' must not be null."); ThingHandler thingHandler = createHandler(thing); if (thingHandler == null) { throw new IllegalStateException(this.getClass().getSimpleName() + " could not create a handler for the thing '" + thing.getUID() + "'."); } if ((thing instanceof Bridge) && !(thingHandler instanceof BridgeHandler)) { throw new IllegalStateException( "Created handler of bridge '" + thing.getUID() + "' must implement the BridgeHandler interface."); } setHandlerContext(thingHandler); registerConfigStatusProvider(thing, thingHandler); registerFirmwareUpdateHandler(thing, thingHandler); return thingHandler; } /** * Creates a {@link ThingHandler} for the given thing. * * @param thing the thing * @return thing the created handler */ protected abstract ThingHandler createHandler(Thing thing); private void setHandlerContext(ThingHandler thingHandler) { if (thingHandler instanceof BaseThingHandler) { if (bundleContext == null) { throw new IllegalStateException( "Base thing handler factory has not been properly initialized. Did you forget to call super.activate()?"); } ((BaseThingHandler) thingHandler).setBundleContext(bundleContext); } } private void registerConfigStatusProvider(Thing thing, ThingHandler thingHandler) { if (thingHandler instanceof ConfigStatusProvider) { ServiceRegistration<ConfigStatusProvider> serviceRegistration = registerAsService(thingHandler, ConfigStatusProvider.class); configStatusProviders.put(thing.getUID().getAsString(), serviceRegistration); } } private void registerFirmwareUpdateHandler(Thing thing, ThingHandler thingHandler) { if (thingHandler instanceof FirmwareUpdateHandler) { ServiceRegistration<FirmwareUpdateHandler> serviceRegistration = registerAsService(thingHandler, FirmwareUpdateHandler.class); firmwareUpdateHandlers.put(thing.getUID().getAsString(), serviceRegistration); } } private <T> ServiceRegistration<T> registerAsService(ThingHandler thingHandler, Class<T> type) { @SuppressWarnings("unchecked") ServiceRegistration<T> serviceRegistration = (ServiceRegistration<T>) bundleContext .registerService(type.getName(), thingHandler, null); return serviceRegistration; } @Override public void unregisterHandler(Thing thing) { Preconditions.checkArgument(thing != null, "The argument 'thing' must not be null."); ThingHandler thingHandler = thing.getHandler(); if (thingHandler != null) { removeHandler(thingHandler); unsetBundleContext(thingHandler); } unregisterConfigStatusProvider(thing); unregisterFirmwareUpdateHandler(thing); } /** * This method is called when a thing handler should be removed. The * implementing caller can override this method to release specific * resources. * * @param thingHandler * thing handler to be removed */ protected void removeHandler(ThingHandler thingHandler) { // can be overridden } private void unsetBundleContext(ThingHandler thingHandler) { if (thingHandler instanceof BaseThingHandler) { ((BaseThingHandler) thingHandler).unsetBundleContext(bundleContext); } } private void unregisterConfigStatusProvider(Thing thing) { ServiceRegistration<ConfigStatusProvider> serviceRegistration = configStatusProviders .remove(thing.getUID().getAsString()); if (serviceRegistration != null) { serviceRegistration.unregister(); } } private void unregisterFirmwareUpdateHandler(Thing thing) { ServiceRegistration<FirmwareUpdateHandler> serviceRegistration = firmwareUpdateHandlers .remove(thing.getUID().getAsString()); if (serviceRegistration != null) { serviceRegistration.unregister(); } } @Override public void removeThing(ThingUID thingUID) { // can be overridden } /** * Returns the {@link ThingType} which is represented by the given {@link ThingTypeUID}. * * @param thingTypeUID the unique id of the thing type * @return the thing type represented by the given unique id */ protected ThingType getThingTypeByUID(ThingTypeUID thingTypeUID) { if (thingTypeRegistryServiceTracker == null) { throw new IllegalStateException( "Base thing handler factory has not been properly initialized. Did you forget to call super.activate()?"); } ThingTypeRegistry thingTypeRegistry = thingTypeRegistryServiceTracker.getService(); if (thingTypeRegistry != null) { return thingTypeRegistry.getThingType(thingTypeUID); } return null; } /** * Creates a thing based on given thing type uid. * * @param thingTypeUID * thing type uid (can not be null) * @param thingUID * thingUID (can not be null) * @param configuration * (can not be null) * @return thing (can be null, if thing type is unknown) */ protected Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID) { return createThing(thingTypeUID, configuration, thingUID, null); } /** * Creates a thing based on given thing type uid. * * @param thingTypeUID * thing type uid (must not be null) * @param thingUID * thingUID (can be null) * @param configuration * (must not be null) * @param bridgeUID * (can be null) * @return thing (can be null, if thing type is unknown) */ @Override public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID, ThingUID bridgeUID) { if (thingTypeUID == null) { throw new IllegalArgumentException("Thing Type UID must not be null"); } if (thingUID == null) { thingUID = ThingFactory.generateRandomThingUID(thingTypeUID); } ThingType thingType = getThingTypeByUID(thingTypeUID); if (thingType != null) { Thing thing = ThingFactory.createThing(thingType, thingUID, configuration, bridgeUID, getConfigDescriptionRegistry()); return thing; } else { return null; } } protected ConfigDescriptionRegistry getConfigDescriptionRegistry() { if (configDescriptionRegistryServiceTracker == null) { throw new IllegalStateException( "Config Description Registry has not been properly initialized. Did you forget to call super.activate()?"); } return configDescriptionRegistryServiceTracker.getService(); } }