]> git.basschouten.com Git - openhab-addons.git/blob
9dab3306c7718cfb887b0e0ba6a8cca8fcbe9919
[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.foobot.internal;
14
15 import static org.openhab.binding.foobot.internal.FoobotBindingConstants.*;
16
17 import java.io.UnsupportedEncodingException;
18 import java.lang.reflect.Type;
19 import java.net.URLEncoder;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27
28 import org.apache.commons.lang.StringUtils;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.eclipse.jetty.client.api.ContentResponse;
33 import org.eclipse.jetty.client.api.Request;
34 import org.eclipse.jetty.http.HttpField;
35 import org.eclipse.jetty.http.HttpHeader;
36 import org.eclipse.jetty.http.HttpStatus;
37 import org.openhab.binding.foobot.internal.json.FoobotDevice;
38 import org.openhab.binding.foobot.internal.json.FoobotJsonData;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.gson.Gson;
43 import com.google.gson.JsonParseException;
44 import com.google.gson.reflect.TypeToken;
45
46 /**
47  * Connector class communicating with Foobot api and parsing returned json.
48  *
49  * @author Hilbrand Bouwkamp - Initial contribution
50  */
51 @NonNullByDefault
52 public class FoobotApiConnector {
53
54     public static final String API_RATE_LIMIT_EXCEEDED_MESSAGE = "Api rate limit exceeded";
55     public static final int API_RATE_LIMIT_EXCEEDED = -2;
56
57     private static final int UNKNOWN_REMAINING = -1;
58     private static final String HEADER_X_API_KEY_TOKEN = "X-API-KEY-TOKEN";
59     private static final String HEADER_X_API_KEY_LIMIT_REMAINING = "x-api-key-limit-remaining";
60     private static final int REQUEST_TIMEOUT_SECONDS = 3;
61     private static final Gson GSON = new Gson();
62     private static final Type FOOTBOT_DEVICE_LIST_TYPE = new TypeToken<ArrayList<FoobotDevice>>() {
63     }.getType();
64
65     private final Logger logger = LoggerFactory.getLogger(FoobotApiConnector.class);
66
67     private @Nullable HttpClient httpClient;
68     private String apiKey = "";
69     private int apiKeyLimitRemaining = UNKNOWN_REMAINING;
70
71     public void setHttpClient(@Nullable HttpClient httpClient) {
72         this.httpClient = httpClient;
73     }
74
75     public void setApiKey(String apiKey) {
76         this.apiKey = apiKey;
77     }
78
79     /**
80      * @return Returns the last known api remaining limit or -1 if not known.
81      */
82     public int getApiKeyLimitRemaining() {
83         return apiKeyLimitRemaining;
84     }
85
86     /**
87      * Retrieves the list of associated devices with the given username from the foobot api.
88      *
89      * @param username to get the associated devices for
90      * @return List of devices
91      * @throws FoobotApiException in case there was a problem communicating or parsing the response
92      */
93     public synchronized List<FoobotDevice> getAssociatedDevices(String username) throws FoobotApiException {
94         try {
95             final String url = URL_TO_FETCH_DEVICES.replace("%username%",
96                     URLEncoder.encode(username, StandardCharsets.UTF_8.toString()));
97             logger.debug("URL = {}", url);
98
99             List<FoobotDevice> foobotDevices = GSON.fromJson(request(url, apiKey), FOOTBOT_DEVICE_LIST_TYPE);
100             return Objects.requireNonNull(foobotDevices);
101         } catch (JsonParseException | UnsupportedEncodingException e) {
102             throw new FoobotApiException(0, e.getMessage());
103         }
104     }
105
106     /**
107      * Retrieves the sensor data for the device with the given uuid from the foobot api.
108      *
109      * @param uuid of the device to get the sensor data for
110      * @return sensor data of the device
111      * @throws FoobotApiException in case there was a problem communicating or parsing the response
112      */
113     public synchronized @Nullable FoobotJsonData getSensorData(String uuid) throws FoobotApiException {
114         try {
115             final String url = URL_TO_FETCH_SENSOR_DATA.replace("%uuid%",
116                     URLEncoder.encode(uuid, StandardCharsets.UTF_8.toString()));
117             logger.debug("URL = {}", url);
118
119             return GSON.fromJson(request(url, apiKey), FoobotJsonData.class);
120         } catch (JsonParseException | UnsupportedEncodingException e) {
121             throw new FoobotApiException(0, e.getMessage());
122         }
123     }
124
125     protected String request(String url, String apiKey) throws FoobotApiException {
126         apiKeyLimitRemaining = UNKNOWN_REMAINING;
127         if (httpClient == null) {
128             logger.debug("No http connection possible: httpClient == null");
129             throw new FoobotApiException(0, "No http connection possible");
130         }
131         final Request request = httpClient.newRequest(url).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
132
133         request.header(HttpHeader.ACCEPT, "application/json");
134         request.header(HttpHeader.ACCEPT_ENCODING, StandardCharsets.UTF_8.name());
135         request.header(HEADER_X_API_KEY_TOKEN, apiKey);
136         final ContentResponse response;
137
138         try {
139             response = request.send();
140         } catch (InterruptedException e) {
141             Thread.currentThread().interrupt();
142             throw new FoobotApiException(0, e.getMessage());
143         } catch (TimeoutException | ExecutionException e) {
144             throw new FoobotApiException(0, e.getMessage());
145         }
146         final String content = response.getContentAsString();
147
148         logger.trace("Foobot content = {}", content);
149         logger.debug("Foobot response = {}", response);
150         setApiKeyLimitRemaining(response);
151         switch (response.getStatus()) {
152             case HttpStatus.FORBIDDEN_403:
153                 throw new FoobotApiException(response.getStatus(),
154                         "Access denied. Did you set the correct api-key and/or username?");
155             case HttpStatus.TOO_MANY_REQUESTS_429:
156                 apiKeyLimitRemaining = API_RATE_LIMIT_EXCEEDED;
157                 throw new FoobotApiException(response.getStatus(), API_RATE_LIMIT_EXCEEDED_MESSAGE);
158             case HttpStatus.OK_200:
159                 if (StringUtils.trimToNull(content) == null) {
160                     throw new FoobotApiException(0, "No data returned");
161                 }
162                 return content;
163             default:
164                 logger.trace("Foobot returned status '{}', reason: {}, content = {}", response.getStatus(),
165                         response.getReason(), content);
166                 throw new FoobotApiException(response.getStatus(), response.getReason());
167         }
168     }
169
170     private void setApiKeyLimitRemaining(ContentResponse response) {
171         final HttpField field = response.getHeaders().getField(HEADER_X_API_KEY_LIMIT_REMAINING);
172
173         if (field != null) {
174             apiKeyLimitRemaining = field.getIntValue();
175         }
176     }
177 }