/**
* 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.rwesmarthome.internal.communicator;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.ClientProtocolException;
import org.openhab.binding.rwesmarthome.internal.communicator.client.RWEClient;
import org.openhab.binding.rwesmarthome.internal.communicator.exceptions.LoginFailedException;
import org.openhab.binding.rwesmarthome.internal.communicator.exceptions.RWESmarthomeSessionExpiredException;
import org.openhab.binding.rwesmarthome.internal.communicator.exceptions.SHTechnicalException;
import org.openhab.binding.rwesmarthome.internal.communicator.util.HttpComponentsHelper;
import org.openhab.binding.rwesmarthome.internal.communicator.util.XMLUtil;
import org.openhab.binding.rwesmarthome.internal.model.Location;
import org.openhab.binding.rwesmarthome.internal.model.LogicalDevice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
/**
* The RWESmarthomeSession holds the session of the connection
* with the RWE Smarthome Central.
*
* @author ollie-dev
*
*/
public class RWESmarthomeSession {
private static final Logger logger = LoggerFactory.getLogger(RWESmarthomeSession.class);
/** The firmware version of RWE Smarthome */
public static final String FIRMWARE_VERSION = "1.70";
/** The hostname. */
private String hostname = "";
/** The password encrypted. */
private String passwordEncrypted = "";
/** The client id. */
private String clientId = "";
/** The session id. */
private String sessionId = "";
/** The request id. */
private String requestId = "";
/** The http helper. */
HttpComponentsHelper httpHelper = new HttpComponentsHelper();
/** The current configuration version. */
private String currentConfigurationVersion = "";
/** The locations. */
private ConcurrentHashMap<String, Location> locations = null;
/** The logicalDevices */
private ConcurrentHashMap<String, LogicalDevice> logicalDevices = null;
private RWEClient client;
/**
* Constructor with the client implementation.
*
* @param client
*/
public RWESmarthomeSession(RWEClient client) {
super();
this.client = client;
}
/**
* Returns true, if there is a valid session id.
*
* @return boolean
*/
public boolean isValid() {
if (sessionId == null || "".equals(sessionId)) {
return false;
} else {
return true;
}
}
/**
* Gets the session id.
*
* @return the sessionId
*/
public String getSessionId() {
return sessionId;
}
/**
* Returns the firmware version
*
* @return the firmware version
*/
public static String getFirmwareVersion() {
return FIRMWARE_VERSION;
}
/**
* Return the current request id
*
* @return the request id
*/
public String getRequestId() {
return requestId;
}
/**
* Returns a list of locations.
*
* @return the locations
*/
public ConcurrentHashMap<String, Location> getLocations() {
return locations;
}
/**
* Returns the list of logical devices.
*
* @return the logicalDevices
*/
public ConcurrentHashMap<String, LogicalDevice> getLogicalDevices() {
return logicalDevices;
}
/**
* Sets the current configuration version.
*
* @param currentConfigurationVersion
*/
public void setCurrentConfigurationVersion(String currentConfigurationVersion) {
this.currentConfigurationVersion = currentConfigurationVersion;
}
/**
* Sets the locations.
*
* @param locations
*/
public void setLocations(ConcurrentHashMap<String, Location> locations) {
this.locations = locations;
}
/**
* Sets the logical devices.
*
* @param logicalDevices
*/
public void setLogicalDevices(ConcurrentHashMap<String, LogicalDevice> logicalDevices) {
this.logicalDevices = logicalDevices;
}
/**
* Destroys the session.
*/
public void destroy() {
final String LOGOUT_REQUEST = String.format(
"<BaseRequest xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"LogoutRequest\" Version=\"%s\" RequestId=\"%s\" SessionId=\"%s\" />",
FIRMWARE_VERSION, requestId, getSessionId());
try {
logger.debug("Destroying session...");
executeRequest(LOGOUT_REQUEST, "/cmd");
logger.debug("Session destroyed.");
} catch (RWESmarthomeSessionExpiredException e) {
// Ignore expired session for logout
}
sessionId = "";
}
/**
* Executes a request with the given command.
*
* @param request
* @param sCmd
* @return
* @throws RWESmarthomeSessionExpiredException
*/
public String executeRequest(String request, String sCmd) throws RWESmarthomeSessionExpiredException {
return executeRequest(request, sCmd, false);
}
/**
* Executes a request with the given command.
* Set login to true to execute a login request.
*
* @param request
* the login request
* @param command
* the s cmd
* @return the string
* @throws SmartHomeSessionExpiredException
* the smart home session expired exception
*/
private String executeRequest(String request, String command, boolean login)
throws RWESmarthomeSessionExpiredException {
// If there is no sessionId and no login wanted, session is expired
if (!login && "".equals(sessionId)) {
throw new RWESmarthomeSessionExpiredException();
}
String sReturn = "";
try {
// execute the request
sReturn = client.execute(hostname, clientId, request, command);
// return may contain an IllegalSessionId -> session expired.
if (sReturn.contains("IllegalSessionId")) {
logger.info("Session expired!");
sessionId = "";
throw new RWESmarthomeSessionExpiredException(sReturn);
}
logger.trace("XMLResponse:" + sReturn);
} catch (ClientProtocolException ex) {
logger.error(ex.getClass().getSimpleName(), ex);
} catch (IOException ex) {
logger.error(ex.getClass().getSimpleName(), ex);
}
return sReturn;
}
/**
* Logon and initialize a session.
*
* @param username
* the user name
* @param password
* the pass word
* @param hostname
* the host name
* @throws SHTechnicalException
* the sH technical exception
* @throws LoginFailedException
* the login failed exception
*/
public void logon(String username, String password, String hostname)
throws SHTechnicalException, LoginFailedException {
this.hostname = hostname;
clientId = UUID.randomUUID().toString();
requestId = generateRequestId();
passwordEncrypted = generateHashFromPassword(password);
final String LOGIN_REQUEST = String.format(
"<BaseRequest xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"LoginRequest\" Version=\"%s\" RequestId=\"%s\" UserName=\"%s\" Password=\"%s\" />",
FIRMWARE_VERSION, requestId, username, passwordEncrypted);
String sResponse = "";
try {
sResponse = executeRequest(LOGIN_REQUEST, "/cmd", true);
sessionId = XMLUtil.XPathValueFromString(sResponse, "/BaseResponse/@SessionId");
if (sessionId == null || "".equals(sessionId)) {
throw new LoginFailedException(String.format(
"LoginFailed: Authentication with user '%s' was not possible. Session ID is empty.", username));
}
currentConfigurationVersion = XMLUtil.XPathValueFromString(sResponse,
"/BaseResponse/@CurrentConfigurationVersion");
} catch (ParserConfigurationException ex) {
throw new SHTechnicalException("ParserConfigurationException:" + ex.getMessage(), ex);
} catch (SAXException ex) {
throw new SHTechnicalException("SAXException:" + ex.getMessage(), ex);
} catch (XPathExpressionException ex) {
throw new SHTechnicalException("XPathExpressionException:" + ex.getMessage(), ex);
} catch (IOException ex) {
throw new SHTechnicalException(
String.format("IOException. Communication with host '%s' was not possible or interrupted: %s",
hostname, ex.getMessage()),
ex);
} catch (RWESmarthomeSessionExpiredException e) {
logger.error("SessionExpiredException while login?!? Should never exist...");
throw new SHTechnicalException("SessionExpiredException while login?!? Should never exist...");
}
}
/**
* Generate hash from password.
*
* @param plainPassword
* the plain password
* @return the string
*/
private String generateHashFromPassword(String plainPassword) {
String sReturn = "";
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(plainPassword.getBytes());
byte byteData[] = md.digest();
sReturn = new String(Base64.encodeBase64(byteData));
} catch (NoSuchAlgorithmException ex) {
// ignore
}
return sReturn;
}
/**
* Generate request id.
*
* @return the string
*/
private String generateRequestId() {
return UUID.randomUUID().toString();
}
/**
* Returns the current configuration version.
*
* @return
*/
public String getCurrentConfigurationVersion() {
return currentConfigurationVersion;
}
}