]> git.basschouten.com Git - openhab-addons.git/blob
17f937112d75434298075dc0d38e3af19b82b26c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.velux.internal.bridge.json;
14
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;
20
21 import org.apache.commons.io.IOUtils;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
24 import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
25 import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
26 import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
27 import org.openhab.core.io.net.http.HttpUtil;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import com.google.gson.Gson;
32 import com.google.gson.JsonSyntaxException;
33
34 /**
35  * JSON-based 2nd Level I/O interface towards the <B>Velux</B> bridge.
36  * <P>
37  * It provides methods for pre- and postcommunication
38  * as well as a common method for the real communication.
39  * <P>
40  * The following class access methods exist:
41  * <UL>
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>
45  * </UL>
46  * <P>
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
52  * interaction.
53  *
54  * @author Guenther Schreiner - Initial contribution.
55  */
56 @NonNullByDefault
57 public class JsonVeluxBridge extends VeluxBridge {
58     private final Logger logger = LoggerFactory.getLogger(JsonVeluxBridge.class);
59
60     /**
61      * Timestamp of last communication in milliseconds.
62      *
63      */
64     private long lastCommunicationInMSecs = 0;
65
66     /**
67      * Timestamp of last successful communication in milliseconds.
68      *
69      */
70     private long lastSuccessfulCommunicationInMSecs = 0;
71
72     /**
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}.
75      *
76      */
77     private BridgeAPI bridgeAPI;
78
79     /**
80      * Constructor.
81      * <P>
82      * Inherits the initialization of the binding-wide instance for dealing for common informations and
83      * initializes the Velux bridge connectivity settings.
84      *
85      * @param bridgeInstance refers to the binding-wide instance for dealing for common informations.
86      */
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.");
95     }
96
97     /**
98      * Provides information about the base-level communication method and
99      * any kind of available gateway interactions.
100      * <P>
101      * <B>Note:</B> the implementation within this class {@link JsonVeluxBridge} as inherited from {@link VeluxBridge}
102      * will return the protocol-specific class implementations.
103      * <P>
104      * The information will be initialized by the corresponding API class {@link JsonBridgeAPI}.
105      *
106      * @return <b>bridgeAPI</b> of type {@link BridgeAPI} contains all possible methods.
107      */
108     @Override
109     public BridgeAPI bridgeAPI() {
110         logger.trace("bridgeAPI() called.");
111         return bridgeAPI;
112     }
113
114     /**
115      * <B>Method as implementation of abstract superclass method.</B>
116      * <P>
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).
120      *
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.
125      *
126      */
127     @Override
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);
132     }
133
134     /**
135      * Returns the timestamp in milliseconds since Unix epoch
136      * of last (potentially faulty) communication.
137      *
138      * @return timestamp in milliseconds.
139      */
140     @Override
141     public long lastCommunication() {
142         return lastCommunicationInMSecs;
143     }
144
145     /**
146      * Returns the timestamp in milliseconds since Unix epoch
147      * of last successful communication.
148      *
149      * @return timestamp in milliseconds.
150      */
151     @Override
152     public long lastSuccessfulCommunication() {
153         return lastSuccessfulCommunicationInMSecs;
154     }
155
156     /**
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).
160      *
161      * @param communication Structure of interface type {@link JsonBridgeCommunicationProtocol} describing the
162      *            intended
163      *            communication,
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.
167      */
168     private synchronized boolean bridgeDirectCommunicate(JsonBridgeCommunicationProtocol communication,
169             boolean useAuthentication) {
170         logger.trace("bridgeDirectCommunicate({},{}authenticated) called.", communication.name(),
171                 useAuthentication ? "" : "un");
172
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();
180         Object response;
181
182         try {
183             if (useAuthentication) {
184                 response = ioAuthenticated(sapURL, authenticationToken, getRequest, classOfResponse);
185             } else {
186                 response = ioUnauthenticated(sapURL, getRequest, classOfResponse);
187             }
188             communication.setResponse(response);
189             logger.trace("bridgeCommunicate(): communication result is {}, returning details.",
190                     communication.isCommunicationSuccessful());
191             return true;
192         } catch (IOException ioe) {
193             logger.warn("bridgeCommunicate(): Exception occurred on accessing {}: {}.", sapURL, ioe.getMessage());
194             return false;
195         } catch (JsonSyntaxException jse) {
196             logger.warn("bridgeCommunicate(): Exception occurred on (de-)serialization during accessing {}: {}.",
197                     sapURL, jse.getMessage());
198             return false;
199         }
200     }
201
202     /**
203      * Base level communication with the <b>Velux</b> bridge.
204      *
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
210      *            JSON.
211      * @param classOfResponse as Class representing the expected structure of the message response body to be converted
212      *            from JSON.
213      * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
214      *         Will
215      *         return
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.
219      */
220     private <T, U> T io(String url, String authentication, U request, Class<T> classOfResponse)
221             throws JsonSyntaxException, IOException {
222         /** Local handles */
223         int retryCount = 0;
224
225         lastCommunicationInMSecs = System.currentTimeMillis();
226         do {
227             try {
228                 Gson gson = new Gson();
229                 String jsonRequest = gson.toJson(request);
230                 logger.trace("io() to {} using request {}.", url, jsonRequest);
231
232                 Properties headerItems = new Properties();
233                 if (authentication.length() > 0) {
234                     headerItems.setProperty("Authorization", String.format("Bearer %s", authentication));
235                 }
236                 InputStream content = IOUtils.toInputStream(jsonRequest, StandardCharsets.UTF_8.name());
237
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");
242                 }
243                 logger.trace("io(): wait time {} msecs.", this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
244                 // Give the bridge some time to breathe
245                 try {
246                     Thread.sleep(this.bridgeInstance.veluxBridgeConfiguration().timeoutMsecs);
247                 } catch (InterruptedException ie) {
248                     logger.trace("io() wait interrupted.");
249                 }
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();
255                 return response;
256
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);
263                 try {
264                     Thread.sleep(waitTime);
265                 } catch (InterruptedException ie) {
266                     logger.trace("io() wait interrupted.");
267                 }
268             } catch (JsonSyntaxException jse) {
269                 logger.info("io(): Exception occurred on deserialization: {}, aborting.", jse.getMessage());
270                 throw jse;
271             }
272
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));
276     }
277
278     /**
279      * Initializes an authenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
280      *
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
286      *            JSON.
287      * @param classOfResponse as Class representing the expected structure of the message response body to be converted
288      *            from JSON.
289      * @return <b>response</b> of type T containing all resulting informations, i.e. device status, errors a.s.o. Will
290      *         return
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.
294      */
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);
298     }
299
300     /**
301      * Initializes an unauthenticated communication with the {@link JsonVeluxBridge <b>Velux</b> bridge}.
302      *
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
307      *            JSON.
308      * @param classOfResponse as Class representing the expected structure of the message response body to be converted
309      *            from JSON.
310      * @return <b>response</b> of type Object containing all resulting informations, i.e. device status, errors a.s.o.
311      *         Will
312      *         return
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.
316      */
317     private <T, U> T ioUnauthenticated(String url, U request, Class<T> classOfResponse)
318             throws JsonSyntaxException, IOException {
319         return io(url, "", request, classOfResponse);
320     }
321 }