]> git.basschouten.com Git - openhab-addons.git/blob
eebd311576ac4b9a975460332bdd8be29f48d9fd
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.pushbullet.internal;
14
15 import java.time.Instant;
16 import java.util.Objects;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.eclipse.jetty.client.api.ContentResponse;
25 import org.eclipse.jetty.client.api.Request;
26 import org.eclipse.jetty.client.util.BytesContentProvider;
27 import org.eclipse.jetty.client.util.MultiPartContentProvider;
28 import org.eclipse.jetty.client.util.StringContentProvider;
29 import org.eclipse.jetty.http.HttpFields;
30 import org.eclipse.jetty.http.HttpMethod;
31 import org.eclipse.jetty.http.HttpStatus;
32 import org.eclipse.jetty.http.MimeTypes;
33 import org.openhab.binding.pushbullet.internal.exception.PushbulletApiException;
34 import org.openhab.binding.pushbullet.internal.exception.PushbulletAuthenticationException;
35 import org.openhab.binding.pushbullet.internal.model.InstantDeserializer;
36 import org.openhab.core.OpenHAB;
37 import org.openhab.core.library.types.RawType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import com.google.gson.Gson;
42 import com.google.gson.GsonBuilder;
43 import com.google.gson.JsonSyntaxException;
44
45 /**
46  * The {@link PushbulletHttpClient} handles requests to Pushbullet API
47  *
48  * @author Jeremy Setton - Initial contribution
49  */
50 @NonNullByDefault
51 public class PushbulletHttpClient {
52     private static final String AGENT = "openHAB/" + OpenHAB.getVersion();
53
54     private static final int TIMEOUT = 30; // in seconds
55
56     private static final String HEADER_RATELIMIT_RESET = "X-Ratelimit-Reset";
57
58     private final Logger logger = LoggerFactory.getLogger(PushbulletHttpClient.class);
59
60     private final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Instant.class, new InstantDeserializer())
61             .create();
62
63     private PushbulletConfiguration config = new PushbulletConfiguration();
64
65     private final HttpClient httpClient;
66
67     public PushbulletHttpClient(HttpClient httpClient) {
68         this.httpClient = httpClient;
69     }
70
71     public void setConfiguration(PushbulletConfiguration config) {
72         this.config = config;
73     }
74
75     /**
76      * Executes an api request
77      *
78      * @param apiEndpoint the request api endpoint
79      * @param responseType the response type
80      * @return the unpacked response
81      * @throws PushbulletApiException
82      */
83     public <T> T executeRequest(String apiEndpoint, Class<T> responseType) throws PushbulletApiException {
84         return executeRequest(apiEndpoint, null, responseType);
85     }
86
87     /**
88      * Executes an api request
89      *
90      * @param apiEndpoint the request api endpoint
91      * @param body the request body object
92      * @param responseType the response type
93      * @return the unpacked response
94      * @throws PushbulletApiException
95      */
96     public <T> T executeRequest(String apiEndpoint, @Nullable Object body, Class<T> responseType)
97             throws PushbulletApiException {
98         String url = config.getApiUrlBase() + apiEndpoint;
99         String accessToken = config.getAccessToken();
100
101         Request request = newRequest(url).header("Access-Token", accessToken);
102
103         if (body != null) {
104             StringContentProvider content = new StringContentProvider(gson.toJson(body));
105             String contentType = MimeTypes.Type.APPLICATION_JSON.asString();
106
107             request.method(HttpMethod.POST).content(content, contentType);
108         }
109
110         String responseBody = sendRequest(request);
111
112         try {
113             T response = Objects.requireNonNull(gson.fromJson(responseBody, responseType));
114             logger.debug("Unpacked Response: {}", response);
115             return response;
116         } catch (JsonSyntaxException e) {
117             logger.debug("Failed to unpack response as '{}': {}", responseType.getSimpleName(), e.getMessage());
118             throw new PushbulletApiException(e);
119         }
120     }
121
122     /**
123      * Uploads a file
124      *
125      * @param url the upload url
126      * @param data the file data
127      * @throws PushbulletApiException
128      */
129     public void uploadFile(String url, RawType data) throws PushbulletApiException {
130         MultiPartContentProvider content = new MultiPartContentProvider();
131         content.addFieldPart("file", new BytesContentProvider(data.getMimeType(), data.getBytes()), null);
132
133         Request request = newRequest(url).method(HttpMethod.POST).content(content);
134
135         sendRequest(request);
136     }
137
138     /**
139      * Creates a new http request
140      *
141      * @param url the request url
142      * @return the new Request object with default parameters
143      */
144     private Request newRequest(String url) {
145         return httpClient.newRequest(url).agent(AGENT).timeout(TIMEOUT, TimeUnit.SECONDS);
146     }
147
148     /**
149      * Sends a http request
150      *
151      * @param request the request to send
152      * @return the response body
153      * @throws PushbulletApiException
154      */
155     private String sendRequest(Request request) throws PushbulletApiException {
156         try {
157             logger.debug("Request {} {}", request.getMethod(), request.getURI());
158             logger.debug("Request Headers: {}", request.getHeaders());
159
160             ContentResponse response = request.send();
161
162             int statusCode = response.getStatus();
163             String statusReason = response.getReason();
164             String responseBody = response.getContentAsString();
165
166             logger.debug("Got HTTP {} Response: '{}'", statusCode, responseBody);
167
168             switch (statusCode) {
169                 case HttpStatus.OK_200:
170                 case HttpStatus.NO_CONTENT_204:
171                     return responseBody;
172                 case HttpStatus.UNAUTHORIZED_401:
173                 case HttpStatus.FORBIDDEN_403:
174                     throw new PushbulletAuthenticationException(statusReason);
175                 case HttpStatus.TOO_MANY_REQUESTS_429:
176                     logger.warn("Rate limited for making too many requests until {}",
177                             getRateLimitResetTime(response.getHeaders()));
178                 default:
179                     throw new PushbulletApiException(statusReason);
180             }
181         } catch (InterruptedException | TimeoutException | ExecutionException e) {
182             logger.debug("Failed to send request: {}", e.getMessage());
183             throw new PushbulletApiException(e);
184         }
185     }
186
187     /**
188      * Returns the rate limit reset time included in response headers
189      *
190      * @param headers the response headers
191      * @return the rate limit reset time if found in headers, otherwise null
192      */
193     private @Nullable Instant getRateLimitResetTime(HttpFields headers) {
194         try {
195             long resetTime = headers.getLongField(HEADER_RATELIMIT_RESET);
196             if (resetTime != -1) {
197                 return Instant.ofEpochSecond(resetTime);
198             }
199         } catch (NumberFormatException ignored) {
200         }
201         return null;
202     }
203 }