/**
* 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.ihc.ws;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.ihc.ws.datatypes.WSBaseDataType;
import org.openhab.binding.ihc.ws.datatypes.WSBooleanValue;
import org.openhab.binding.ihc.ws.datatypes.WSDateValue;
import org.openhab.binding.ihc.ws.datatypes.WSEnumValue;
import org.openhab.binding.ihc.ws.datatypes.WSFloatingPointValue;
import org.openhab.binding.ihc.ws.datatypes.WSIntegerValue;
import org.openhab.binding.ihc.ws.datatypes.WSResourceValue;
import org.openhab.binding.ihc.ws.datatypes.WSTimeValue;
import org.openhab.binding.ihc.ws.datatypes.WSTimerValue;
import org.openhab.binding.ihc.ws.datatypes.WSWeekdayValue;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Class to handle IHC / ELKO LS Controller's resource interaction service.
*
* Service is used to fetch or update resource values from/to controller.
*
* @author Pauli Anttila
* @since 1.5.0
*/
public class IhcResourceInteractionService extends IhcHttpsClient {
private String url;
private int timeout;
IhcResourceInteractionService(String host, int timeout) {
url = "https://" + host + "/ws/ResourceInteractionService";
this.timeout = timeout;
super.setConnectTimeout(timeout);
}
/**
* Query resource value from controller.
*
*
* @param resoureId
* Resource Identifier.
* @return Resource value.
*/
public WSResourceValue resourceQuery(int resoureId) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<soapenv:Body>"
+ " <ns1:getRuntimeValue1 xmlns:ns1=\"utcs\">%s</ns1:getRuntimeValue1>" + "</soapenv:Body>"
+ "</soapenv:Envelope>";
String query = String.format(soapQuery, String.valueOf(resoureId));
openConnection(url);
String response = sendQuery(query, timeout);
closeConnection();
NodeList nodeList;
try {
nodeList = parseList(response, "/SOAP-ENV:Envelope/SOAP-ENV:Body/ns1:getRuntimeValue2");
if (nodeList.getLength() == 1) {
WSResourceValue val = parseResourceValue(nodeList.item(0), 2);
if (val.getResourceID() == resoureId) {
return val;
} else {
throw new IhcExecption("No resource id found");
}
} else {
throw new IhcExecption("No resource value found");
}
} catch (XPathExpressionException e) {
throw new IhcExecption(e);
} catch (UnsupportedEncodingException e) {
throw new IhcExecption(e);
}
}
private NodeList parseList(String xml, String xpathExpression)
throws XPathExpressionException, UnsupportedEncodingException {
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF8"));
XPath xpath = XPathFactory.newInstance().newXPath();
InputSource inputSource = new InputSource(is);
xpath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new NullPointerException("Null prefix");
} else if ("SOAP-ENV".equals(prefix)) {
return "http://schemas.xmlsoap.org/soap/envelope/";
} else if ("ns1".equals(prefix)) {
return "utcs";
} else if ("ns2".equals(prefix)) {
return "utcs.values";
}
return null;
}
@Override
public String getPrefix(String uri) {
return null;
}
@Override
@SuppressWarnings("rawtypes")
public Iterator getPrefixes(String uri) {
throw new UnsupportedOperationException();
}
});
return (NodeList) xpath.evaluate(xpathExpression, inputSource, XPathConstants.NODESET);
}
private WSResourceValue parseResourceValue(Node n, int index) throws XPathExpressionException {
// parse resource id
String resourceId = getValue(n, "ns1:resourceID");
if (StringUtils.isNotBlank(resourceId)) {
int id = Integer.parseInt(resourceId);
// Parse floating point value
String value = getValue(n, "ns1:value/ns" + index + ":floatingPointValue");
if (StringUtils.isNotBlank(value)) {
WSFloatingPointValue val = new WSFloatingPointValue();
val.setResourceID(id);
val.setFloatingPointValue(Double.valueOf(value));
value = getValue(n, "ns1:value/ns" + index + ":maximumValue");
if (StringUtils.isNotBlank(value)) {
val.setMaximumValue(Double.valueOf(value));
}
value = getValue(n, "ns1:value/ns" + index + ":minimumValue");
if (StringUtils.isNotBlank(value)) {
val.setMinimumValue(Double.valueOf(value));
}
return val;
}
// Parse boolean value
value = getValue(n, "ns1:value/ns" + index + ":value");
if (StringUtils.isNotBlank(value)) {
WSBooleanValue val = new WSBooleanValue();
val.setResourceID(id);
val.setValue(Boolean.valueOf(value));
return val;
}
// Parse integer value
value = getValue(n, "ns1:value/ns" + index + ":integer");
if (StringUtils.isNotBlank(value)) {
WSIntegerValue val = new WSIntegerValue();
val.setResourceID(id);
val.setInteger(Integer.valueOf(value));
value = getValue(n, "ns1:value/ns" + index + ":maximumValue");
if (StringUtils.isNotBlank(value)) {
val.setMaximumValue(Integer.valueOf(value));
}
value = getValue(n, "ns1:value/ns" + index + ":minimumValue");
if (StringUtils.isNotBlank(value)) {
val.setMinimumValue(Integer.valueOf(value));
}
return val;
}
// Parse timer value
value = getValue(n, "ns1:value/ns" + index + ":milliseconds");
if (StringUtils.isNotBlank(value)) {
WSTimerValue val = new WSTimerValue();
val.setResourceID(id);
val.setMilliseconds(Integer.valueOf(value));
return val;
}
// Parse time value
value = getValue(n, "ns1:value/ns" + index + ":hours");
if (StringUtils.isNotBlank(value)) {
WSTimeValue val = new WSTimeValue();
val.setResourceID(id);
val.setHours(Integer.valueOf(value));
value = getValue(n, "ns1:value/ns" + index + ":minutes");
if (StringUtils.isNotBlank(value)) {
val.setMinutes(Integer.valueOf(value));
}
value = getValue(n, "ns1:value/ns" + index + ":seconds");
if (StringUtils.isNotBlank(value)) {
val.setSeconds(Integer.valueOf(value));
}
return val;
}
// Parse date value
value = getValue(n, "ns1:value/ns" + index + ":day");
if (StringUtils.isNotBlank(value)) {
WSDateValue val = new WSDateValue();
val.setResourceID(id);
val.setDay(Byte.valueOf(value));
value = getValue(n, "ns1:value/ns" + index + ":month");
if (StringUtils.isNotBlank(value)) {
val.setMonth(Byte.valueOf(value));
}
value = getValue(n, "ns1:value/ns" + index + ":year");
if (StringUtils.isNotBlank(value)) {
val.setYear(Short.valueOf(value));
}
return val;
}
// Parse enum value
value = getValue(n, "ns1:value/ns" + index + ":definitionTypeID");
if (StringUtils.isNotBlank(value)) {
WSEnumValue val = new WSEnumValue();
val.setResourceID(id);
val.setDefinitionTypeID(Integer.valueOf(value));
value = getValue(n, "ns1:value/ns" + index + ":enumValueID");
if (StringUtils.isNotBlank(value)) {
val.setEnumValueID(Integer.valueOf(value));
}
value = getValue(n, "ns1:value/ns" + index + ":enumName");
if (StringUtils.isNotBlank(value)) {
val.setEnumName(value);
}
return val;
}
// Parse week day value
value = getValue(n, "ns1:value/ns" + index + ":weekdayNumber");
if (StringUtils.isNotBlank(value)) {
WSWeekdayValue val = new WSWeekdayValue();
val.setResourceID(id);
val.setWeekdayNumber(Integer.valueOf(value));
return val;
}
throw new IllegalArgumentException("Unsupported value type");
}
return null;
}
private String getValue(Node n, String expr) throws XPathExpressionException {
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new NullPointerException("Null prefix");
} else if ("SOAP-ENV".equals(prefix)) {
return "http://schemas.xmlsoap.org/soap/envelope/";
} else if ("ns1".equals(prefix)) {
return "utcs";
}
// else if ("ns2".equals(prefix)) return "utcs.values";
return "utcs.values";
// return null;
}
@Override
public String getPrefix(String uri) {
return null;
}
@Override
@SuppressWarnings("rawtypes")
public Iterator getPrefixes(String uri) {
throw new UnsupportedOperationException();
}
});
XPathExpression pathExpr = xpath.compile(expr);
return (String) pathExpr.evaluate(n, XPathConstants.STRING);
}
/**
* Update resource value to controller.
*
*
* @param value
* Resource value.
* @return True if value is successfully updated.
*/
public boolean resourceUpdate(WSResourceValue value) throws IhcExecption {
boolean retval = false;
if (value instanceof WSFloatingPointValue) {
retval = resourceUpdate((WSFloatingPointValue) value);
}
else if (value instanceof WSBooleanValue) {
retval = resourceUpdate((WSBooleanValue) value);
}
else if (value instanceof WSIntegerValue) {
retval = resourceUpdate((WSIntegerValue) value);
}
else if (value instanceof WSTimerValue) {
retval = resourceUpdate((WSTimerValue) value);
}
else if (value instanceof WSWeekdayValue) {
retval = resourceUpdate((WSWeekdayValue) value);
}
else if (value instanceof WSEnumValue) {
retval = resourceUpdate((WSEnumValue) value);
}
else if (value instanceof WSTimeValue) {
retval = resourceUpdate((WSTimeValue) value);
}
else if (value instanceof WSDateValue) {
retval = resourceUpdate((WSDateValue) value);
}
else {
throw new IhcExecption("Unsupported value type " + value.getClass().toString());
}
return retval;
}
public boolean resourceUpdate(WSBooleanValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSBooleanValue\">" + " <q1:value>%s</q1:value>"
+ " </value>" + " <resourceID>%s</resourceID>" + " <isValueRuntime>true</isValueRuntime>"
+ " </setResourceValue1>" + "</soap:Body>" + "</soap:Envelope>";
String query = String.format(soapQuery, value.isValue() ? "true" : "false", value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSFloatingPointValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSFloatingPointValue\">"
+ " <q1:maximumValue>%s</q1:maximumValue>" + " <q1:minimumValue>%s</q1:minimumValue>"
+ " <q1:floatingPointValue>%s</q1:floatingPointValue>" + " </value>"
+ " <resourceID>%s</resourceID>" + " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>"
+ "</soap:Body>" + "</soap:Envelope>";
String query = String.format(soapQuery, value.getMaximumValue(), value.getMinimumValue(),
value.getFloatingPointValue(), value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSIntegerValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSIntegerValue\">"
+ " <q1:maximumValue>%s</q1:maximumValue>" + " <q1:minimumValue>%s</q1:minimumValue>"
+ " <q1:integer>%s</q1:integer>" + " </value>" + " <resourceID>%s</resourceID>"
+ " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>" + "</soap:Body>"
+ "</soap:Envelope>";
String query = String.format(soapQuery, value.getMaximumValue(), value.getMinimumValue(), value.getInteger(),
value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSTimerValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSTimerValue\">"
+ " <q1:milliseconds>%s</q1:milliseconds>" + " </value>" + " <resourceID>%s</resourceID>"
+ " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>" + "</soap:Body>"
+ "</soap:Envelope>";
String query = String.format(soapQuery, value.getMilliseconds(), value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSWeekdayValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSWeekdayValue\">"
+ " <q1:weekdayNumber>%s</q1:weekdayNumber>" + " </value>" + " <resourceID>%s</resourceID>"
+ " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>" + "</soap:Body>"
+ "</soap:Envelope>";
String query = String.format(soapQuery, value.getWeekdayNumber(), value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSEnumValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSEnumValue\">"
+ " <q1:definitionTypeID>%s</q1:definitionTypeID>" + " <q1:enumValueID>%s</q1:enumValueID>"
+ " <q1:enumName>%s</q1:enumName>" + " </value>" + " <resourceID>%s</resourceID>"
+ " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>" + "</soap:Body>"
+ "</soap:Envelope>";
String query = String.format(soapQuery, value.getDefinitionTypeID(), value.getEnumValueID(),
value.getEnumName(), value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSTimeValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSTimeValue\">" + " <q1:hours>%s</q1:hours>"
+ " <q1:minutes>%s</q1:minutes>" + " <q1:seconds>%s</q1:seconds>" + " </value>"
+ " <resourceID>%s</resourceID>" + " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>"
+ "</soap:Body>" + "</soap:Envelope>";
String query = String.format(soapQuery, value.getHours(), value.getMinutes(), value.getSeconds(),
value.getResourceID());
return doResourceUpdate(query);
}
public boolean resourceUpdate(WSDateValue value) throws IhcExecption {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + " <setResourceValue1 xmlns=\"utcs\">"
+ " <value xmlns:q1=\"utcs.values\" xsi:type=\"q1:WSDateValue\">" + " <q1:month>%s</q1:month>"
+ " <q1:year>%s</q1:year>" + " <q1:day>%s</q1:day>" + " </value>" + " <resourceID>%s</resourceID>"
+ " <isValueRuntime>true</isValueRuntime>" + " </setResourceValue1>" + "</soap:Body>"
+ "</soap:Envelope>";
String query = String.format(soapQuery, value.getMonth(), value.getYear(), value.getDay(),
value.getResourceID());
return doResourceUpdate(query);
}
private boolean doResourceUpdate(String query) throws IhcExecption {
openConnection(url);
String response = sendQuery(query, timeout);
closeConnection();
return Boolean.parseBoolean(
WSBaseDataType.parseValue(response, "/SOAP-ENV:Envelope/SOAP-ENV:Body/ns1:setResourceValue2"));
}
/**
* Enable resources runtime value notifications.
*
* @param resourceIdList
* List of resource Identifiers.
* @return True is connection successfully opened.
*/
public void enableRuntimeValueNotifications(List<? extends Integer> resourceIdList) throws IhcExecption {
final String soapQueryPrefix = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body>" + "<enableRuntimeValueNotifications1 xmlns=\"utcs\">";
final String soapQuerySuffix = "</enableRuntimeValueNotifications1>" + "</soap:Body>" + "</soap:Envelope>";
String query = soapQueryPrefix;
for (int i : resourceIdList) {
query += "<xsd:arrayItem>" + i + "</xsd:arrayItem>";
}
query += soapQuerySuffix;
openConnection(url);
@SuppressWarnings("unused")
String response = sendQuery(query, timeout);
closeConnection();
}
/**
* Wait runtime value notifications.
*
* Runtime value notification should firstly be activated by
* enableRuntimeValueNotifications function.
*
* @param timeoutInSeconds
* How many seconds to wait notifications.
* @return List of received runtime value notifications.
* @throws SocketTimeoutException
*/
public List<? extends WSResourceValue> waitResourceValueNotifications(int timeoutInSeconds)
throws IhcExecption, SocketTimeoutException {
final String soapQuery = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:utcs=\"utcs\">"
+ "<soapenv:Header/>" + "<soapenv:Body>"
+ " <utcs:waitForResourceValueChanges1>%s</utcs:waitForResourceValueChanges1>" + "</soapenv:Body>"
+ "</soapenv:Envelope>";
String query = String.format(soapQuery, timeoutInSeconds);
openConnection(url);
String response = sendQuery(query, timeout + timeoutInSeconds * 1000);
closeConnection();
List<WSResourceValue> resourceValueList = new ArrayList<WSResourceValue>();
NodeList nodeList;
try {
nodeList = parseList(response,
"/SOAP-ENV:Envelope/SOAP-ENV:Body/ns1:waitForResourceValueChanges2/ns1:arrayItem");
if (nodeList.getLength() == 1) {
String resourceId = getValue(nodeList.item(0), "ns1:resourceID");
if (resourceId == null || resourceId == "") {
throw new SocketTimeoutException();
}
}
for (int i = 0; i < nodeList.getLength(); i++) {
int index = i + 2;
WSResourceValue newVal = parseResourceValue(nodeList.item(i), index);
resourceValueList.add(newVal);
}
return resourceValueList;
} catch (XPathExpressionException e) {
throw new IhcExecption(e);
} catch (UnsupportedEncodingException e) {
throw new IhcExecption(e);
}
}
}