/**
* 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.owserver.internal;
import java.io.IOException;
import java.io.StringReader;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.owserver.OWServerBindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
import org.openhab.io.net.http.HttpUtil;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* An active binding which requests the EDS OWServer data.
*
* This class parses the information from the EDS OW-Server XML file.
*
* @author Chris Jackson
* @since 1.3.0
*/
public class OWServerBinding extends AbstractActiveBinding<OWServerBindingProvider>implements ManagedService {
static final Logger logger = LoggerFactory.getLogger(OWServerBinding.class);
/**
* the timeout to use for connecting to a given host (defaults to 5000
* milliseconds)
*/
private int timeout = 5000;
/**
* the interval to find new refresh candidates (defaults to 1000
* milliseconds)
*/
private int granularity = 1000;
/**
* the maximum duration of data in the cache (defaults to 1500 milliseconds)
*/
private int cacheDuration = 1500;
/**
* RegEx to validate a config <code>'^(.*?)\\.(host|port)$'</code>
*/
private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.(.*?)$");
private Map<String, Long> lastUpdateMap = new HashMap<String, Long>();
private Map<String, OWServerConfig> serverList = new HashMap<String, OWServerConfig>();
public OWServerBinding() {
}
@Override
public void activate() {
logger.debug("OWServer: Activate");
super.activate();
}
/**
* @{inheritDoc
*/
@Override
protected long getRefreshInterval() {
return granularity;
}
@Override
protected String getName() {
return "OWServer Refresh Service";
}
String getVariable(String response, String romId, String name) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Get the DOM Builder
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
logger.error("Error parsing OWServer XML response " + e.getMessage());
}
// Load and Parse the XML document
// document contains the complete XML as a Tree.
Document document = null;
try {
InputSource is = new InputSource(new StringReader(response));
document = builder.parse(is);
} catch (SAXException e) {
logger.error("Error reading OWServer XML response " + e.getMessage());
} catch (IOException e) {
logger.error("Error reading OWServer XML response " + e.getMessage());
}
// Iterating through the nodes and extracting the data.
NodeList nodeList = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeName().startsWith("owd_")) {
boolean romMatch = false;
NodeList childNodes = node.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Node cNode = childNodes.item(j);
// Identifying the child tag of employee encountered.
if (cNode instanceof Element) {
String content = cNode.getLastChild().getTextContent().trim();
if (cNode.getNodeName().equals("ROMId") & content.equals(romId)) {
romMatch = true;
}
String nname = cNode.getNodeName();
if (nname.equals(name) & romMatch == true) {
return content;
}
}
}
}
}
return null;
}
/**
* @{inheritDoc
*/
@Override
public void execute() {
for (OWServerBindingProvider provider : providers) {
for (String itemName : provider.getInBindingItemNames()) {
int refreshInterval = provider.getRefreshInterval(itemName);
Long lastUpdateTimeStamp = lastUpdateMap.get(itemName);
if (lastUpdateTimeStamp == null) {
lastUpdateTimeStamp = 0L;
}
long age = System.currentTimeMillis() - lastUpdateTimeStamp;
boolean needsUpdate = age >= refreshInterval;
if (needsUpdate) {
logger.debug("Item '{}' is about to be refreshed now", itemName);
// Get the unit serverId from the binding, and relate that to the config
String unit = provider.getServerId(itemName);
OWServerConfig server = serverList.get(unit);
needsUpdate = false;
if (server == null) {
needsUpdate = false;
logger.error("Unknown OW server referenced: " + unit);
continue;
} else {
age = System.currentTimeMillis() - server.lastUpdate;
if (age >= cacheDuration) {
needsUpdate = true;
}
}
String response = null;
if (needsUpdate == true) {
String address;
if (server.user == null) {
address = "http://" + server.host + "/details.xml";
} else {
address = "http://" + server.user + ":" + server.password + "@" + server.host
+ "/details.xml";
}
logger.debug("Getting OWSERVER data from " + address);
response = HttpUtil.executeUrl("GET", address, timeout);
server.cache = response;
if (response == null) {
server.lastUpdate = (long) 0;
logger.error("No response received from '{}'", address);
} else {
server.lastUpdate = System.currentTimeMillis();
}
serverList.put(address, server);
} else {
logger.debug("Using OWSERVER cache");
response = server.cache;
}
if (response != null) {
String value = getVariable(response, provider.getRomId(itemName), provider.getName(itemName));
if (value != null) {
Class<? extends Item> itemType = provider.getItemType(itemName);
State state = createState(itemType, value);
eventPublisher.postUpdate(itemName, state);
}
}
lastUpdateMap.put(itemName, System.currentTimeMillis());
}
}
}
}
/**
* 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 itemType
* @param transformedResponse
*
* @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(Class<? extends Item> itemType, String transformedResponse) {
try {
if (itemType.isAssignableFrom(NumberItem.class)) {
return DecimalType.valueOf(transformedResponse);
} else if (itemType.isAssignableFrom(ContactItem.class)) {
return OpenClosedType.valueOf(transformedResponse);
} else if (itemType.isAssignableFrom(SwitchItem.class)) {
return OnOffType.valueOf(transformedResponse);
} else if (itemType.isAssignableFrom(RollershutterItem.class)) {
return PercentType.valueOf(transformedResponse);
} else {
return StringType.valueOf(transformedResponse);
}
} catch (Exception e) {
logger.debug("Couldn't create state of type '{}' for value '{}'", itemType, transformedResponse);
return StringType.valueOf(transformedResponse);
}
}
protected void addBindingProvider(OWServerBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(OWServerBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config != null) {
Enumeration<String> keys = config.keys();
if (serverList == null) {
serverList = new HashMap<String, OWServerConfig>();
}
while (keys.hasMoreElements()) {
String key = keys.nextElement();
// the config-key enumeration contains additional keys that we
// don't want to process here ...
if ("service.pid".equals(key)) {
continue;
}
Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
if (!matcher.matches()) {
continue;
}
matcher.reset();
matcher.find();
String serverId = matcher.group(1);
OWServerConfig deviceConfig = serverList.get(serverId);
if (deviceConfig == null) {
deviceConfig = new OWServerConfig();
serverList.put(serverId, deviceConfig);
}
String configKey = matcher.group(2);
String value = (String) config.get(key);
if ("host".equals(configKey)) {
deviceConfig.host = value;
} else if ("user".equals(configKey)) {
deviceConfig.user = value;
} else if ("password".equals(configKey)) {
deviceConfig.password = value;
} else {
throw new ConfigurationException(configKey,
"The given OWServer configKey '" + configKey + "' is unknown");
}
}
String timeoutString = (String) config.get("timeout");
if (StringUtils.isNotBlank(timeoutString)) {
timeout = Integer.parseInt(timeoutString);
}
String granularityString = (String) config.get("granularity");
if (StringUtils.isNotBlank(granularityString)) {
granularity = Integer.parseInt(granularityString);
}
String cacheString = (String) config.get("cache");
if (StringUtils.isNotBlank(cacheString)) {
cacheDuration = Integer.parseInt(cacheString);
}
setProperlyConfigured(true);
}
}
/**
* This is an internal data structure to store information from the binding
* config strings and use it to answer the requests to the HTTP binding
* provider.
*/
static class OWServerConfig {
public String host;
public String user;
public String password;
public Long lastUpdate;
public String cache;
OWServerConfig() {
lastUpdate = (long) 0;
}
@Override
public String toString() {
return "OWServerCache [host=" + host + " last=" + lastUpdate + ", cache=" + cache + "]";
}
}
}