/**
* 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.autoupdate.internal;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArraySet;
import org.eclipse.smarthome.core.autoupdate.AutoUpdateBindingConfigProvider;
import org.eclipse.smarthome.core.events.EventPublisher;
import org.eclipse.smarthome.core.items.GenericItem;
import org.eclipse.smarthome.core.items.ItemNotFoundException;
import org.eclipse.smarthome.core.items.ItemRegistry;
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.types.Command;
import org.eclipse.smarthome.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* The AutoUpdate-Binding is no 'normal' binding as it doesn't connect any hardware to the Eclipse SmartHome system. In
* fact it takes care of updating the State of an item with respect to the received command automatically or not. By
* default the State is getting updated automatically which is desired behavior in most of the cases. However it could
* be useful to disable this default behavior.
*
* <p>
* For example when implementing validation steps before changing a State one needs to control the State update oneself.
*
* @author Thomas.Eichstaedt-Engelen - Initial contribution
* @author Kai Kreuzer - added sending real events
* @author Stefan Bußweiler - Migration to new ESH event concept
*/
public class AutoUpdateBinding extends AbstractItemEventSubscriber {
private final Logger logger = LoggerFactory.getLogger(AutoUpdateBinding.class);
protected volatile ItemRegistry itemRegistry;
/** to keep track of all binding config providers */
protected Collection<AutoUpdateBindingConfigProvider> providers = new CopyOnWriteArraySet<>();
protected EventPublisher eventPublisher = null;
public void addBindingConfigProvider(AutoUpdateBindingConfigProvider provider) {
providers.add(provider);
}
public void removeBindingConfigProvider(AutoUpdateBindingConfigProvider provider) {
providers.remove(provider);
}
public void setEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void unsetEventPublisher(EventPublisher eventPublisher) {
this.eventPublisher = null;
}
public void setItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = itemRegistry;
}
public void unsetItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = null;
}
/**
* Handle the received command event.
*
* <p>
* If the command could be converted to a {@link State} the auto-update configurations are inspected.
* If there is at least one configuration that enable the auto-update, auto-update is applied.
* If there is no configuration provided at all the autoupdate defaults to {@code true} and an update is posted for
* the corresponding {@link State}.
*
* @param commandEvent the command event
*/
@Override
protected void receiveCommand(ItemCommandEvent commandEvent) {
final Command command = commandEvent.getItemCommand();
if (command instanceof State) {
final State state = (State) command;
final String itemName = commandEvent.getItemName();
Boolean autoUpdate = autoUpdate(itemName);
// we didn't find any autoupdate configuration, so apply the default now
if (autoUpdate == null) {
autoUpdate = Boolean.TRUE;
}
if (autoUpdate) {
postUpdate(itemName, state);
} else {
logger.trace("Won't update item '{}' as it is not configured to update its state automatically.",
itemName);
}
}
}
/**
* Check auto update configuration(s) for given item name.
*
* @param itemName the name of the item
* @return true if auto-update is enabled, false if auto-update is disabled, null if there is no explicit
* configuration
*/
private Boolean autoUpdate(final String itemName) {
Boolean autoUpdate = null;
// Check auto update by configuration for item name
for (AutoUpdateBindingConfigProvider provider : providers) {
Boolean au = provider.autoUpdate(itemName);
if (au == null) {
// There is no setting for the item, keep value unchanged.
continue;
} else {
// There is an entry for the item.
if (au) {
// setting is true, we could stop.
return true;
} else {
// setting is false, set it if autoupdate is not set
if (autoUpdate == null) {
autoUpdate = false;
}
}
}
}
return autoUpdate;
}
private void postUpdate(String itemName, State newState) {
if (itemRegistry != null) {
try {
GenericItem item = (GenericItem) itemRegistry.getItem(itemName);
boolean isAccepted = false;
if (item.getAcceptedDataTypes().contains(newState.getClass())) {
isAccepted = true;
} else {
// Look for class hierarchy
for (Class<? extends State> state : item.getAcceptedDataTypes()) {
try {
if (!state.isEnum()
&& state.newInstance().getClass().isAssignableFrom(newState.getClass())) {
isAccepted = true;
break;
}
} catch (InstantiationException e) {
logger.warn("InstantiationException on {}", e.getMessage(), e); // Should never happen
} catch (IllegalAccessException e) {
logger.warn("IllegalAccessException on {}", e.getMessage(), e); // Should never happen
}
}
}
if (isAccepted) {
eventPublisher.post(ItemEventFactory.createStateEvent(itemName, newState,
"org.eclipse.smarthome.core.autoupdate"));
} else {
logger.debug("Received update of a not accepted type ({}) for item {}",
newState.getClass().getSimpleName(), itemName);
}
} catch (ItemNotFoundException e) {
logger.debug("Received update for non-existing item: {}", e.getMessage());
}
}
}
}