2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.velux.internal.bridge.json;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.nio.charset.StandardCharsets;
18 import java.util.Properties;
19 import java.util.TreeSet;
21 import org.apache.commons.io.IOUtils;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.core.io.net.http.HttpUtil;
24 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
25 import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
26 import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
27 import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import com.google.gson.Gson;
32 import com.google.gson.JsonSyntaxException;
35 * JSON-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
37 * It provides methods for pre- and postcommunication
38 * as well as a common method for the real communication.
40 * The following class access methods exist:
42 * <LI>{@link VeluxBridge#bridgeLogin} for pre-communication,</LI>
43 * <LI>{@link VeluxBridge#bridgeLogout} for post-communication,</LI>
44 * <LI>{@link VeluxBridge#bridgeCommunicate} as method for the common communication.</LI>
47 * As root of several inheritance levels it predefines an
48 * interfacing method {@link VeluxBridge#bridgeAPI} which
49 * has to be implemented by any kind of protocol-specific
50 * communication returning the appropriate base (1st) level
51 * communication method as well as any other gateway
54 * @author Guenther Schreiner - Initial contribution.
57 public class JsonVeluxBridge extends VeluxBridge {
58 private final Logger logger = LoggerFactory.getLogger(JsonVeluxBridge.class);
61 * Timestamp of last communication in milliseconds.
64 private long lastCommunicationInMSecs = 0;
67 * Timestamp of last successful communication in milliseconds.
70 private long lastSuccessfulCommunicationInMSecs = 0;
73 * Handler passing the interface methods to other classes.
74 * Can be accessed via method {@link org.openhab.binding.velux.internal.bridge.common.BridgeAPI BridgeAPI}.
77 private BridgeAPI bridgeAPI;
82 * Inherits the initialization of the binding-wide instance for dealing for common informations and
83 * initializes the Velux bridge connectivity settings.
85 * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
87 public JsonVeluxBridge(VeluxBridgeInstance bridgeInstance) {
88 super(bridgeInstance);
89 logger.trace("JsonVeluxBridge(constructor) called.");
90 bridgeAPI = new JsonBridgeAPI(bridgeInstance);
91 supportedProtocols = new TreeSet<>();
92 supportedProtocols.add("http");
93 supportedProtocols.add("https");
94 logger.trace("JsonVeluxBridge(constructor) done.");
98 * Provides information about the base-level communication method and
99 * any kind of available gateway interactions.
101 * <B>Note:</B> the implementation within this class {@link JsonVeluxBridge} as inherited from {@link VeluxBridge}
102 * will return the protocol-specific class implementations.
104 * The information will be initialized by the corresponding API class {@link JsonBridgeAPI}.
106 * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
109 public BridgeAPI bridgeAPI() {
110 logger.trace("bridgeAPI() called.");
115 * <B>Method as implementation of abstract superclass method.</B>
117 * Initializes a client/server communication towards <b>Velux</b> veluxBridge
118 * based on the Basic I/O interface {@link #io} and parameters
119 * passed as arguments (see below).
121 * @param communication Structure of interface type {@link JsonBridgeCommunicationProtocol} describing the intended
122 * communication, that is request and response interactions as well as appropriate URL definition.
123 * @param useAuthentication boolean flag to decide whether to use authenticated communication.
124 * @return <b>success</b> of type boolean which signals the success of the communication.
128 protected boolean bridgeDirectCommunicate(BridgeCommunicationProtocol communication, boolean useAuthentication) {
129 logger.trace("bridgeDirectCommunicate(BCP: {},{}authenticated) called.", communication.name(),
130 useAuthentication ? "" : "un");
131 return bridgeDirectCommunicate((JsonBridgeCommunicationProtocol) communication, useAuthentication);
135 * Returns the timestamp in milliseconds since Unix epoch
136 * of last (potentially faulty) communication.
138 * @return timestamp in milliseconds.
141 public long lastCommunication() {
142 return lastCommunicationInMSecs;
146 * Returns the timestamp in milliseconds since Unix epoch
147 * of last successful communication.
149 * @return timestamp in milliseconds.
152 public long lastSuccessfulCommunication() {
153 return lastSuccessfulCommunicationInMSecs;
157 * Initializes a client/server communication towards <b>Velux</b> veluxBridge
158 * based on the Basic I/O interface {@link VeluxBridge} and parameters
159 * passed as arguments (see below).
161 * @param communication Structure of interface type {@link JsonBridgeCommunicationProtocol} describing the
164 * that is request and response interactions as well as appropriate URL definition.
165 * @param useAuthentication boolean flag to decide whether to use authenticated communication.
166 * @return <b>response</b> of type boolean will indicate the success of the communication.
168 private synchronized boolean bridgeDirectCommunicate(JsonBridgeCommunicationProtocol communication,
169 boolean useAuthentication) {
170 logger.trace("bridgeDirectCommunicate({},{}authenticated) called.", communication.name(),
171 useAuthentication ? "" : "un");
173 String sapURL = this.bridgeInstance.veluxBridgeConfiguration().protocol.concat("://")
174 .concat(this.bridgeInstance.veluxBridgeConfiguration().ipAddress).concat(":")
175 .concat(Integer.toString(this.bridgeInstance.veluxBridgeConfiguration().tcpPort))
176 .concat(communication.getURL());
177 logger.trace("bridgeCommunicate(): using SAP {}.", sapURL);
178 Object getRequest = communication.getObjectOfRequest();
179 Class<?> classOfResponse = communication.getClassOfResponse();
183 if (useAuthentication) {
184 response = ioAuthenticated(sapURL, authenticationToken, getRequest, classOfResponse);
186 response = ioUnauthenticated(sapURL, getRequest, classOfResponse);
188 communication.setResponse(response);
189 logger.trace("bridgeCommunicate(): communication result is {}, returning details.",
190 communication.isCommunicationSuccessful());
192 } catch (IOException ioe) {
193 logger.warn("bridgeCommunicate(): Exception occurred on accessing {}: {}.", sapURL, ioe.getMessage());
195 } catch (JsonSyntaxException jse) {
196 logger.warn("bridgeCommunicate(): Exception occurred on (de-)serialization during accessing {}: {}.",
197 sapURL, jse.getMessage());
203 * Base level communication with the <b>Velux</b> bridge.
205 * @param <T> This describes the request type parameter.
206 * @param <U> This describes the response type parameter.
207 * @param url as String describing the Service Access Point location i.e. http://localhost/api .
208 * @param authentication as String providing the Authentication token to be passed with the request header.
209 * @param request as Object representing the structure of the message request body to be converted into
211 * @param classOfResponse as Class representing the expected structure of the message response body to be converted
213 * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
216 * <B>null</B> in case of communication or decoding error.
217 * @throws java.io.IOException in case of continuous communication I/O failures.
218 * @throws JsonSyntaxException in case of unusual communication failures.
220 private <T, U> T io(String url, String authentication, U request, Class<T> classOfResponse)
221 throws JsonSyntaxException, IOException {
225 lastCommunicationInMSecs = System.currentTimeMillis();
228 Gson gson = new Gson();
229 String jsonRequest = gson.toJson(request);
230 logger.trace("io() to {} using request {}.", url, jsonRequest);
232 Properties headerItems = new Properties();
233 if (authentication.length() > 0) {
234 headerItems.setProperty("Authorization", String.format("Bearer %s", authentication));
236 InputStream content = IOUtils.toInputStream(jsonRequest, StandardCharsets.UTF_8.name());
238 String jsonResponse = HttpUtil.executeUrl("PUT", url, headerItems, content, "application/json",
239 this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
240 if (jsonResponse == null) {
241 throw new IOException("transport error");
243 logger.trace("io(): wait time {} msecs.", this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
244 // Give the bridge some time to breathe
246 Thread.sleep(this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
247 } catch (InterruptedException ie) {
248 logger.trace("io() wait interrupted.");
250 logger.trace("io() got response {}.", jsonResponse.replaceAll("\\p{C}", "."));
251 jsonResponse = jsonResponse.replaceAll("^.+,\n", "");
252 logger.trace("io() cleaned response {}.", jsonResponse);
253 T response = gson.fromJson(jsonResponse, classOfResponse);
254 lastCommunicationInMSecs = lastSuccessfulCommunicationInMSecs = System.currentTimeMillis();
257 } catch (IOException ioe) {
258 logger.trace("io(): Exception occurred during I/O: {}.", ioe.getMessage());
259 // Error Retries with Exponential Backoff
260 long waitTime = ((long) Math.pow(2, retryCount)
261 * this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
262 logger.trace("io(): wait time {} msecs.", waitTime);
264 Thread.sleep(waitTime);
265 } catch (InterruptedException ie) {
266 logger.trace("io() wait interrupted.");
268 } catch (JsonSyntaxException jse) {
269 logger.info("io(): Exception occurred on deserialization: {}, aborting.", jse.getMessage());
273 } while (retryCount++ < this.bridgeInstance.veluxBridgeConfiguration().retries);
274 throw new IOException(String.format("io(): socket I/O failed (%d times).",
275 this.bridgeInstance.veluxBridgeConfiguration().retries));
279 * Initializes an authenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
281 * @param <T> This describes the request type parameter.
282 * @param <U> This describes the response type parameter.
283 * @param url as String describing the Service Access Point location i.e. http://localhost/api .
284 * @param authentication as String providing the Authentication token to be passed with the request header.
285 * @param request as Object representing the structure of the message request body to be converted into
287 * @param classOfResponse as Class representing the expected structure of the message response body to be converted
289 * @return <b>response</b> of type T containing all resulting informations, i.e. device status, errors a.s.o. Will
291 * <B>null</B> in case of communication or decoding error.
292 * @throws java.io.IOException in case of continuous communication I/O failures.
293 * @throws JsonSyntaxException in case of unusual communication failures.
295 private <T, U> T ioAuthenticated(String url, String authentication, U request, Class<T> classOfResponse)
296 throws JsonSyntaxException, IOException {
297 return io(url, authentication, request, classOfResponse);
301 * Initializes an unauthenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
303 * @param <T> This describes the request type parameter.
304 * @param <U> This describes the response type parameter.
305 * @param url as String describing the Service Access Point location i.e. http://localhost/api .
306 * @param request as Object representing the structure of the message request body to be converted into
308 * @param classOfResponse as Class representing the expected structure of the message response body to be converted
310 * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
313 * <B>null</B> in case of communication or decoding error.
314 * @throws java.io.IOException in case of continuous communication I/O failures.
315 * @throws JsonSyntaxException in case of unusual communication failures.
317 private <T, U> T ioUnauthenticated(String url, U request, Class<T> classOfResponse)
318 throws JsonSyntaxException, IOException {
319 return io(url, "", request, classOfResponse);