Java tutorial
/** * 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.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.NotImplementedException; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.smarthome.core.common.registry.RegistryChangeListener; import org.eclipse.smarthome.core.events.Event; import org.eclipse.smarthome.core.events.EventFilter; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.events.EventSubscriber; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemRegistry; import org.eclipse.smarthome.core.items.events.ItemCommandEvent; import org.eclipse.smarthome.core.items.events.ItemStateEvent; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.DefaultSystemChannelTypeProvider; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingRegistry; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.events.ChannelTriggeredEvent; import org.eclipse.smarthome.core.thing.events.ThingEventFactory; import org.eclipse.smarthome.core.thing.internal.profiles.DefaultMasterProfile; import org.eclipse.smarthome.core.thing.internal.profiles.RawButtonToggleProfile; import org.eclipse.smarthome.core.thing.link.ItemChannelLink; import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry; import org.eclipse.smarthome.core.thing.profiles.Profile; import org.eclipse.smarthome.core.thing.profiles.ProfileFactory; import org.eclipse.smarthome.core.thing.profiles.StateProfile; import org.eclipse.smarthome.core.thing.profiles.TriggerProfile; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class manages the state related communication between bindings and the framework. * * It mainly mediates commands, state updates and triggers from ThingHandlers to the framework and vice versa. * * @author Simon Kaufmann - initial contribution and API, factored out of ThingManger * */ @Component(service = { EventSubscriber.class, CommunicationManager.class }, immediate = true) public class CommunicationManager implements EventSubscriber, RegistryChangeListener<ItemChannelLink> { private final Logger logger = LoggerFactory.getLogger(CommunicationManager.class); private final Set<String> subscribedEventTypes = Collections.unmodifiableSet( new HashSet<>(Arrays.asList(ItemStateEvent.TYPE, ItemCommandEvent.TYPE, ChannelTriggeredEvent.TYPE))); private ItemChannelLinkRegistry itemChannelLinkRegistry; private ThingRegistry thingRegistry; private ItemRegistry itemRegistry; private EventPublisher eventPublisher; private final Map<ChannelUID, Profile> profiles = new ConcurrentHashMap<>(); private final Map<ProfileFactory, Set<ChannelUID>> profileFactories = new ConcurrentHashMap<>(); @Override public Set<String> getSubscribedEventTypes() { return subscribedEventTypes; } @Override public EventFilter getEventFilter() { return null; } @Override public void receive(Event event) { if (event instanceof ItemStateEvent) { receiveUpdate((ItemStateEvent) event); } else if (event instanceof ItemCommandEvent) { receiveCommand((ItemCommandEvent) event); } else if (event instanceof ChannelTriggeredEvent) { receiveTrigger((ChannelTriggeredEvent) event); } } private Thing getThing(ThingUID thingUID) { return thingRegistry.get(thingUID); } @NonNull private Profile getProfile(@NonNull ItemChannelLink link, @NonNull Item item, Thing thing) { if (thing == null) { return new NoOpProfile(); } Channel channel = thing.getChannel(link.getLinkedUID().getId()); if (channel == null) { return new NoOpProfile(); } Profile profile = null; synchronized (profiles) { profile = profiles.get(link.getLinkedUID()); if (profile == null) { profile = getProfileFromFactories(link, item, channel); if (profile == null) { logger.trace("No profile factory found for link {}, falling back to the defaults", link); profile = createDefaultProfile(channel); } if (profile != null) { profiles.put(link.getLinkedUID(), profile); } } } return profile != null ? profile : new NoOpProfile(); } private Profile getProfileFromFactories(@NonNull ItemChannelLink link, @NonNull Item item, @NonNull Channel channel) { for (Entry<ProfileFactory, Set<ChannelUID>> entry : profileFactories.entrySet()) { Profile profile = entry.getKey().createProfile(link, item, channel); if (profile != null) { entry.getValue().add(link.getLinkedUID()); logger.trace("Going to use profile {} for link {}", profile, link); return profile; } } return null; } private Profile createDefaultProfile(@NonNull Channel channel) { switch (channel.getKind()) { case STATE: return new DefaultMasterProfile(); case TRIGGER: if (DefaultSystemChannelTypeProvider.SYSTEM_RAWBUTTON.getUID().equals(channel.getChannelTypeUID())) { return new RawButtonToggleProfile(); } break; default: throw new NotImplementedException(); } return null; } private void receiveCommand(@NonNull ItemCommandEvent commandEvent) { final String itemName = commandEvent.getItemName(); final Command command = commandEvent.getItemCommand(); final Item item = itemRegistry.get(itemName); if (item == null) { logger.debug("Received an ItemCommandEvent for item {} which does not exist", itemName); return; } itemChannelLinkRegistry.stream().filter(link -> { // all links for the item return link.getItemName().equals(itemName); }).filter(link -> { // make sure the command event is not sent back to its source return !link.getLinkedUID().toString().equals(commandEvent.getSource()); }).forEach(link -> { ChannelUID channelUID = link.getLinkedUID(); Thing thing = getThing(channelUID.getThingUID()); Profile profile = getProfile(link, item, thing); if (profile instanceof StateProfile) { ((StateProfile) profile).onCommand(link, thing, command); } }); } private void receiveUpdate(ItemStateEvent updateEvent) { final String itemName = updateEvent.getItemName(); final State newState = updateEvent.getItemState(); final Item item = itemRegistry.get(itemName); if (item == null) { logger.debug("Received an ItemStateEvent for item {} which does not exist", itemName); return; } itemChannelLinkRegistry.stream().filter(link -> { // all links for the item return link.getItemName().equals(itemName); }).filter(link -> { // make sure the update event is not sent back to its source return !link.getLinkedUID().toString().equals(updateEvent.getSource()); }).forEach(link -> { ChannelUID channelUID = link.getLinkedUID(); Thing thing = getThing(channelUID.getThingUID()); Profile profile = getProfile(link, item, thing); if (profile instanceof StateProfile) { ((StateProfile) profile).onUpdate(link, thing, newState); } }); } private void receiveTrigger(@NonNull ChannelTriggeredEvent channelTriggeredEvent) { final ChannelUID channelUID = channelTriggeredEvent.getChannel(); final String event = channelTriggeredEvent.getEvent(); final Thing thing = getThing(channelUID.getThingUID()); itemChannelLinkRegistry.stream().filter(link -> { // all links for the channel return link.getLinkedUID().equals(channelUID); }).forEach(link -> { Item item = itemRegistry.get(link.getItemName()); if (item != null) { Profile profile = getProfile(link, item, thing); if (profile instanceof TriggerProfile) { ((TriggerProfile) profile).onTrigger(eventPublisher, link, event, item); } } }); } public void stateUpdated(@NonNull ChannelUID channelUID, State state) { final Thing thing = getThing(channelUID.getThingUID()); itemChannelLinkRegistry.stream().filter(link -> { // all links for the channel return link.getLinkedUID().equals(channelUID); }).forEach(link -> { Item item = itemRegistry.get(link.getItemName()); if (item != null) { Profile profile = getProfile(link, item, thing); if (profile instanceof StateProfile) { ((StateProfile) profile).stateUpdated(eventPublisher, link, state, item); } } }); } public void postCommand(@NonNull ChannelUID channelUID, Command command) { final Thing thing = getThing(channelUID.getThingUID()); itemChannelLinkRegistry.stream().filter(link -> { // all links for the channel return link.getLinkedUID().equals(channelUID); }).forEach(link -> { Item item = itemRegistry.get(link.getItemName()); if (item != null) { Profile profile = getProfile(link, item, thing); if (profile instanceof StateProfile) { ((StateProfile) profile).postCommand(eventPublisher, link, command, item); } } }); } public void channelTriggered(Thing thing, ChannelUID channelUID, String event) { eventPublisher.post(ThingEventFactory.createTriggerEvent(event, channelUID)); } private void cleanup(ChannelUID channelUID) { synchronized (profiles) { profiles.remove(channelUID); } profileFactories.values().forEach(list -> list.remove(channelUID)); } @Override public void added(ItemChannelLink element) { // nothing to do } @Override public void removed(ItemChannelLink element) { cleanup(element.getLinkedUID()); } @Override public void updated(ItemChannelLink oldElement, ItemChannelLink element) { cleanup(oldElement.getLinkedUID()); } @Reference protected void setItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = itemChannelLinkRegistry; itemChannelLinkRegistry.addRegistryChangeListener(this); } protected void unsetItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = null; } @Reference protected void setThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = thingRegistry; } protected void unsetThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = null; } @Reference protected void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } protected void unsetEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = null; } @Reference protected void setItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = itemRegistry; } protected void unsetItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = null; } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) protected void addProfileFactory(ProfileFactory profileFactory) { this.profileFactories.put(profileFactory, ConcurrentHashMap.newKeySet()); } protected void removeProfileFactory(ProfileFactory profileFactory) { Set<ChannelUID> channelUIDs = this.profileFactories.remove(profileFactory); synchronized (profiles) { channelUIDs.forEach(channelUID -> profiles.remove(channelUID)); } } private static class NoOpProfile implements Profile { } }