/**
* Copyright (c) 2010-2016 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.openhab.binding.configadmin.internal;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.configadmin.ConfigAdminBindingProvider;
import org.openhab.binding.configadmin.internal.ConfigAdminGenericBindingProvider.ConfigAdminBindingConfig;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* The {@link ConfigAdminBinding} provides access to the openHAB system
* configuration through items. The system configuration is done through
* property files (one key-value-pair per line) with the extension '*.cfg'.
* <p>
* This Binding is also registered as {@link ConfigurationListener} at the
* {@link ConfigurationAdmin} so all changes to configured items are posted to
* the openHAB event bus as well.
*
* @author Thomas.Eichstaedt-Engelen
* @since 1.0.0
*/
public class ConfigAdminBinding extends AbstractBinding<ConfigAdminBindingProvider>implements ConfigurationListener {
private static final Logger logger = LoggerFactory.getLogger(ConfigAdminBinding.class);
private ConfigurationAdmin configAdmin;
private DelayedExecutor delayedExecutor = new DelayedExecutor();
public void addConfigurationAdmin(ConfigurationAdmin configAdmin) {
this.configAdmin = configAdmin;
}
public void removeConfigurationAdmin(ConfigurationAdmin configAdmin) {
this.configAdmin = null;
}
/**
* Returns a {@link State} which is inherited from the {@link Item}s
* accepted DataTypes. The call is delegated to the {@link TypeParser}. If
* <code>item</code> is <code>null</code> the {@link StringType} is used.
*
* @param item
* @param stateAsString
*
* @return a {@link State} which type is inherited by the {@link TypeParser}
* or a {@link StringType} if <code>item</code> is <code>null</code>
*/
private State createState(Item item, String stateAsString) {
if (item != null) {
return TypeParser.parseState(item.getAcceptedDataTypes(), stateAsString);
} else {
return StringType.valueOf(stateAsString);
}
}
/**
* <p>
* Returns the {@link Configuration} with the given pid from the
* {@link ConfigurationAdmin}-Service or null if <code>bindingConfig</code>
* is null.
* </p>
* <p>
* <b>Note:</b>If there are Configuration items configured for the given pid
* an empty {@link Configuration} is returned.
*
* @param bindingConfig
* @return a Configuration (which could be empty if there are no entries for
* the given pid) or <code>null</code> if the given
* <code>bindingConfig</code> is null.
*/
private Configuration getConfiguration(ConfigAdminBindingConfig bindingConfig) {
Configuration result = null;
if (bindingConfig != null) {
try {
result = configAdmin.getConfiguration(bindingConfig.normalizedPid);
} catch (IOException ioe) {
logger.warn("Fetching configuration for pid '" + bindingConfig.normalizedPid + "' failed", ioe);
}
}
return result;
}
/**
* Gets the given configParameter from the given <code>config</code>
* transforms the value to a {@link State} and posts this State to openHAB
* event bus.
*
* @param config
* the {@link Configuration} which contains the data to post
* @param bindingConfig
* contains the name of the configParameter which is the key for
* the data to post an update for.
*/
private void postUpdate(Configuration config, ConfigAdminBindingConfig bindingConfig) {
if (config != null) {
String stateAsString = (String) config.getProperties().get(bindingConfig.configParameter);
if (StringUtils.isNotBlank(stateAsString)) {
State state = createState(bindingConfig.item, stateAsString);
eventPublisher.postUpdate(bindingConfig.item.getName(), state);
} else {
logger.debug("config parameter '{}:{}' has value 'null'. It won't be posted to the event bus hence.",
bindingConfig.normalizedPid, bindingConfig.configParameter);
}
}
}
protected void addBindingProvider(ConfigAdminBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(ConfigAdminBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void allBindingsChanged(BindingProvider provider) {
initializeBus();
super.allBindingsChanged(provider);
}
/**
* {@inheritDoc}
*/
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
initializeBus();
super.bindingChanged(provider, itemName);
}
/**
* Publishes the items with the configuration values.
*/
private void initializeBus() {
delayedExecutor.cancel();
delayedExecutor.schedule(new TimerTask() {
@Override
public void run() {
for (ConfigAdminBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
ConfigAdminBindingConfig bindingConfig = provider.getBindingConfig(itemName);
Configuration config = getConfiguration(bindingConfig);
postUpdate(config, bindingConfig);
}
}
}
}, 3000);
}
/**
* @{inheritDoc
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected void internalReceiveCommand(String itemName, Command command) {
if (configAdmin != null) {
for (ConfigAdminBindingProvider provider : this.providers) {
ConfigAdminBindingConfig bindingConfig = provider.getBindingConfig(itemName);
Configuration config = getConfiguration(bindingConfig);
if (config != null) {
Dictionary props = config.getProperties();
props.put(bindingConfig.configParameter, command.toString());
try {
config.update(props);
} catch (IOException ioe) {
logger.error("updating Configuration '{}' with '{}' failed", bindingConfig.normalizedPid,
command.toString());
}
logger.debug("successfully updated configuration (pid={}, value={})", bindingConfig.normalizedPid,
command.toString());
} else {
logger.info("There is no configuration found for pid '{}'", bindingConfig.normalizedPid);
}
}
}
}
/**
* @{inheritDoc
*
* Whenever a {@link Configuration} is updated all items for
* the given <code>pid</code> are queried and updated. Since
* the {@link ConfigurationEvent} contains no information which
* key changed we have to post updates for all configured
* items.
*/
@Override
public void configurationEvent(ConfigurationEvent event) {
// we do only care for updates of existing configs!
if (ConfigurationEvent.CM_UPDATED == event.getType()) {
try {
Configuration config = configAdmin.getConfiguration(event.getPid());
for (ConfigAdminBindingProvider provider : this.providers) {
for (ConfigAdminBindingConfig bindingConfig : provider.getBindingConfigByPid(event.getPid())) {
postUpdate(config, bindingConfig);
}
}
} catch (IOException ioe) {
logger.warn("Fetching configuration for pid '" + event.getPid() + "' failed", ioe);
}
}
}
/**
* Schedules a task for later execution with the possibility to cancel it.
*
* @author Gerhard Riegler
* @since 1.5.0
*/
public class DelayedExecutor {
private Timer timer;
private TimerTask task;
public void cancel() {
if (task != null) {
task.cancel();
task = null;
}
if (timer != null) {
timer.cancel();
timer = null;
}
}
public void schedule(TimerTask task, long delay) {
this.task = task;
timer = new Timer();
timer.schedule(task, delay);
}
}
}