Java tutorial
/** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ package org.openhab.binding.ihc.internal.ws; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; import org.apache.commons.io.IOUtils; import org.openhab.binding.ihc.internal.ws.datatypes.WSControllerState; import org.openhab.binding.ihc.internal.ws.datatypes.WSFile; import org.openhab.binding.ihc.internal.ws.datatypes.WSLoginResult; import org.openhab.binding.ihc.internal.ws.datatypes.WSProjectInfo; import org.openhab.binding.ihc.internal.ws.datatypes.WSRFDevice; import org.openhab.binding.ihc.internal.ws.datatypes.WSSystemInfo; import org.openhab.binding.ihc.internal.ws.datatypes.WSTimeManagerSettings; import org.openhab.binding.ihc.internal.ws.exeptions.IhcExecption; import org.openhab.binding.ihc.internal.ws.http.IhcConnectionPool; import org.openhab.binding.ihc.internal.ws.resourcevalues.WSResourceValue; import org.openhab.binding.ihc.internal.ws.services.IhcAirlinkManagementService; import org.openhab.binding.ihc.internal.ws.services.IhcAuthenticationService; import org.openhab.binding.ihc.internal.ws.services.IhcConfigurationService; import org.openhab.binding.ihc.internal.ws.services.IhcControllerService; import org.openhab.binding.ihc.internal.ws.services.IhcResourceInteractionService; import org.openhab.binding.ihc.internal.ws.services.IhcTimeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * IhcClient provides interface to communicate IHC / ELKO LS Controller. * * @author Pauli Anttila - Initial contribution */ public class IhcClient { /** Current state of the connection */ public enum ConnectionState { DISCONNECTED, CONNECTING, CONNECTED } public static final String CONTROLLER_STATE_READY = "text.ctrl.state.ready"; public static final String CONTROLLER_STATE_INITIALIZE = "text.ctrl.state.initialize"; private static final int NOTIFICATION_WAIT_TIMEOUT_IN_SEC = 5; private final Logger logger = LoggerFactory.getLogger(IhcClient.class); private ConnectionState connState = ConnectionState.DISCONNECTED; private IhcConnectionPool ihcConnectionPool; /** Controller services */ private IhcAuthenticationService authenticationService; private IhcResourceInteractionService resourceInteractionService; private IhcControllerService controllerService; private IhcConfigurationService configurationService; private IhcAirlinkManagementService airlinkManagementService; private IhcTimeService timeService; /** Thread to handle resource value notifications from the controller */ private IhcResourceValueNotificationListener resourceValueNotificationListener; /** Thread to handle controller's state change notifications */ private IhcControllerStateListener controllerStateListener; private String username; private String password; private String host; /** Timeout in milliseconds */ private int timeout; private Map<Integer, WSResourceValue> resourceValues = new HashMap<Integer, WSResourceValue>(); private List<IhcEventListener> eventListeners = new ArrayList<IhcEventListener>(); public IhcClient(String host, String username, String password) { this(host, username, password, 5000); } public IhcClient(String host, String username, String password, int timeout) { this.host = host; this.username = username; this.password = password; this.timeout = timeout; } public synchronized ConnectionState getConnectionState() { return connState; } private synchronized void setConnectionState(ConnectionState newState) { connState = newState; } public void addEventListener(IhcEventListener listener) { eventListeners.add(listener); } public void removeEventListener(IhcEventListener listener) { eventListeners.remove(listener); } /** * Open connection and authenticate session to IHC / ELKO LS controller. * * @throws IhcExecption */ public void closeConnection() throws IhcExecption { logger.debug("Closing connection"); // interrupt if (resourceValueNotificationListener != null) { resourceValueNotificationListener.setInterrupted(true); } if (controllerStateListener != null) { controllerStateListener.setInterrupted(true); } // wait to stop if (resourceValueNotificationListener != null) { logger.debug("Waiting resource value notification listener to stop"); try { resourceValueNotificationListener.join(NOTIFICATION_WAIT_TIMEOUT_IN_SEC * 1000); } catch (InterruptedException e) { // do nothing } } if (controllerStateListener != null) { logger.debug("Waiting controller state listener to stop"); try { controllerStateListener.join(NOTIFICATION_WAIT_TIMEOUT_IN_SEC * 1000); } catch (InterruptedException e) { // do nothing } } logger.debug("Connection closed"); setConnectionState(ConnectionState.DISCONNECTED); } /** * Open connection and authenticate session to IHC / ELKO LS controller. * * @throws IhcExecption */ public void openConnection() throws IhcExecption { logger.debug("Opening connection"); setConnectionState(ConnectionState.CONNECTING); ihcConnectionPool = new IhcConnectionPool(); authenticationService = new IhcAuthenticationService(host, timeout, ihcConnectionPool); WSLoginResult loginResult = authenticationService.authenticate(username, password, "treeview"); if (!loginResult.isLoginWasSuccessful()) { // Login failed setConnectionState(ConnectionState.DISCONNECTED); if (loginResult.isLoginFailedDueToAccountInvalid()) { throw new IhcExecption("login failed because of invalid account"); } if (loginResult.isLoginFailedDueToConnectionRestrictions()) { throw new IhcExecption("login failed because of connection restrictions"); } if (loginResult.isLoginFailedDueToInsufficientUserRights()) { throw new IhcExecption("login failed because of insufficient user rights"); } throw new IhcExecption("login failed because of unknown reason"); } logger.debug("Connection successfully opened"); resourceInteractionService = new IhcResourceInteractionService(host, timeout, ihcConnectionPool); controllerService = new IhcControllerService(host, timeout, ihcConnectionPool); configurationService = new IhcConfigurationService(host, timeout, ihcConnectionPool); airlinkManagementService = new IhcAirlinkManagementService(host, timeout, ihcConnectionPool); timeService = new IhcTimeService(host, timeout, ihcConnectionPool); setConnectionState(ConnectionState.CONNECTED); } /** * Start event listener to get notifications from IHC / ELKO LS controller. * * @throws IhcExecption * */ public void startControllerEventListeners() throws IhcExecption { if (getConnectionState() == ConnectionState.CONNECTED) { logger.debug("Start IHC / ELKO listeners"); resourceValueNotificationListener = new IhcResourceValueNotificationListener(); resourceValueNotificationListener.start(); controllerStateListener = new IhcControllerStateListener(); controllerStateListener.start(); } else { throw new IhcExecption("Connection to controller not open"); } } /** * Query project information from the controller. * * @return project information. * @throws IhcExecption */ public synchronized WSProjectInfo getProjectInfo() throws IhcExecption { return controllerService.getProjectInfo(); } /** * Query system information from the controller. * * @return system information. * @throws IhcExecption */ public synchronized WSSystemInfo getSystemInfo() throws IhcExecption { return configurationService.getSystemInfo(); } /** * Query time settings from the controller. * * @return time settings. * @throws IhcExecption */ public synchronized WSTimeManagerSettings getTimeSettings() throws IhcExecption { return timeService.getTimeSettings(); } /** * Query detected RF devices from the controller. * * @return List of RF devices. * @throws IhcExecption */ public synchronized List<WSRFDevice> getDetectedRFDevices() throws IhcExecption { return airlinkManagementService.getDetectedDeviceList(); } /** * Query controller current state. * * @return controller's current state. */ public WSControllerState getControllerState() throws IhcExecption { return controllerService.getControllerState(); } /** * Query project number of segments. * * @return number of segments. */ public int getProjectNumberOfSegments() throws IhcExecption { return controllerService.getProjectNumberOfSegments(); } /** * Query project segmentation size. * * @return segmentation size in bytes. */ public int getProjectSegmentationSize() throws IhcExecption { return controllerService.getProjectSegmentationSize(); } /** * Query project segments data. * * @return segments data. */ public WSFile getProjectSegment(int index, int major, int minor) throws IhcExecption { return controllerService.getProjectSegment(index, major, minor); } /** * Fetch project file from controller. * * @return project file. */ public byte[] getProjectFileFromController() throws IhcExecption { try { WSProjectInfo projectInfo = getProjectInfo(); int numberOfSegments = getProjectNumberOfSegments(); int segmentationSize = getProjectSegmentationSize(); logger.debug("Number of segments: {}", numberOfSegments); logger.debug("Segmentation size: {}", segmentationSize); try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) { for (int i = 0; i < numberOfSegments; i++) { logger.debug("Downloading segment {}", i); WSFile data = getProjectSegment(i, projectInfo.getProjectMajorRevision(), projectInfo.getProjectMinorRevision()); byteStream.write(data.getData()); } if (logger.isDebugEnabled()) { logger.debug("File size before base64 encoding: {} bytes", byteStream.size()); } byte[] decodedBytes = Base64.getDecoder().decode(byteStream.toString()); logger.debug("File size after base64 encoding: {} bytes", decodedBytes.length); try (GZIPInputStream gzis = new GZIPInputStream(new ByteArrayInputStream(decodedBytes))) { try (InputStreamReader in = new InputStreamReader(gzis, "ISO-8859-1")) { return IOUtils.toByteArray(in, "ISO-8859-1"); } } } } catch (IOException | IllegalArgumentException e) { throw new IhcExecption(e); } } /** * Wait controller state change notification. * * @param previousState Previous controller state. * @param timeoutInSecondscHow many seconds to wait notifications. * @return current controller state. */ private WSControllerState waitStateChangeNotifications(WSControllerState previousState, int timeoutInSeconds) throws IhcExecption { return controllerService.waitStateChangeNotifications(previousState, timeoutInSeconds); } /** * Enable resources runtime value notifications. * * @param resourceIdList List of resource Identifiers. * @return True is connection successfully opened. */ public synchronized void enableRuntimeValueNotifications(Set<Integer> resourceIdList) throws IhcExecption { resourceInteractionService.enableRuntimeValueNotifications(resourceIdList); } /** * 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 */ private List<WSResourceValue> waitResourceValueNotifications(int timeoutInSeconds) throws IhcExecption { List<WSResourceValue> list = resourceInteractionService.waitResourceValueNotifications(timeoutInSeconds); for (WSResourceValue val : list) { resourceValues.put(val.resourceID, val); } return list; } /** * Query resource value from controller. * * * @param resoureId Resource Identifier. * @return Resource value. */ public WSResourceValue resourceQuery(int resoureId) throws IhcExecption { return resourceInteractionService.resourceQuery(resoureId); } /** * Get resource value information. * * Function return resource value from internal memory, if data is not * available information is read from the controller. * * Resource value's value field (e.g. floatingPointValue) could be old * information. * * @param resoureId Resource Identifier. * @return Resource value. */ public WSResourceValue getResourceValueInformation(int resourceId) throws IhcExecption { if (resourceId != 0) { WSResourceValue data = resourceValues.get(resourceId); if (data == null) { // data is not available, read it from the controller data = resourceInteractionService.resourceQuery(resourceId); } return data; } else { return null; } } /** * Update resource value to controller. * * * @param value Resource value. * @return True if value is successfully updated. */ public boolean resourceUpdate(WSResourceValue value) throws IhcExecption { return resourceInteractionService.resourceUpdate(value); } /** * The IhcReader runs as a separate thread. * * Thread listen resource value notifications from IHC / ELKO LS controller. */ private class IhcResourceValueNotificationListener extends Thread { private volatile boolean interrupted = false; public void setInterrupted(boolean interrupted) { this.interrupted = interrupted; this.interrupt(); } @Override public void run() { logger.debug("IHC resource value listener started"); // as long as no interrupt is requested, continue running while (!interrupted) { waitResourceNotifications(); } logger.debug("IHC resource value listener stopped"); } private void waitResourceNotifications() { try { logger.trace("Wait new resource value notifications from controller"); List<WSResourceValue> resourceValueList = waitResourceValueNotifications( NOTIFICATION_WAIT_TIMEOUT_IN_SEC); logger.debug("{} new notifications received from controller", resourceValueList.size()); for (WSResourceValue value : resourceValueList) { sendResourceValueUpdateEvent(value); } } catch (IhcExecption e) { if (!interrupted) { logger.warn("New notifications wait failed...", e); sendErrorEvent(e); mysleep(1000L); } } } private void mysleep(long milli) { try { sleep(milli); } catch (InterruptedException e) { interrupted = true; } } } /** * The IhcReader runs as a separate thread. * * Thread listen controller state change notifications from IHC / ELKO LS * controller. * */ private class IhcControllerStateListener extends Thread { private volatile boolean interrupted = false; public void setInterrupted(boolean interrupted) { this.interrupted = interrupted; this.interrupt(); } @Override public void run() { logger.debug("IHC controller state listener started"); WSControllerState previousState = null; // as long as no interrupt is requested, continue running while (!interrupted) { try { if (previousState == null) { // initialize previous state previousState = controllerService.getControllerState(); logger.debug("Controller initial state {}", previousState.getState()); } logger.trace("Wait new state change notification from controller"); WSControllerState currentState = waitStateChangeNotifications(previousState, NOTIFICATION_WAIT_TIMEOUT_IN_SEC); logger.trace("Controller state {}", currentState.getState()); if (!previousState.getState().equals(currentState.getState())) { logger.debug("Controller state change detected ({} -> {})", previousState.getState(), currentState.getState()); sendControllerStateUpdateEvent(currentState); previousState.setState(currentState.getState()); } } catch (IhcExecption e) { if (!interrupted) { logger.error("New controller state change notification wait failed...", e); sendErrorEvent(e); mysleep(1000L); } } } logger.debug("IHC controller state listener stopped"); } private void mysleep(long milli) { try { sleep(milli); } catch (InterruptedException e) { interrupted = true; } } } private void sendErrorEvent(IhcExecption err) { eventListeners.forEach(listener -> { try { listener.errorOccured(err); } catch (RuntimeException e) { logger.debug("Event listener invoking error.", e); } }); } private void sendControllerStateUpdateEvent(WSControllerState state) { eventListeners.forEach(listener -> { try { listener.statusUpdateReceived(state); } catch (RuntimeException e) { logger.debug("Event listener invoking error.", e); } }); } private void sendResourceValueUpdateEvent(WSResourceValue value) { eventListeners.forEach(listener -> { try { listener.resourceValueUpdateReceived(value); } catch (RuntimeException e) { logger.debug("Event listener invoking error.", e); } }); } }