2 * Copyright (c) 2010-2024 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.pushbullet.internal;
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;
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;
41 import com.google.gson.Gson;
42 import com.google.gson.GsonBuilder;
43 import com.google.gson.JsonSyntaxException;
46 * The {@link PushbulletHttpClient} handles requests to Pushbullet API
48 * @author Jeremy Setton - Initial contribution
51 public class PushbulletHttpClient {
52 private static final String AGENT = "openHAB/" + OpenHAB.getVersion();
54 private static final int TIMEOUT = 30; // in seconds
56 private static final String HEADER_RATELIMIT_RESET = "X-Ratelimit-Reset";
58 private final Logger logger = LoggerFactory.getLogger(PushbulletHttpClient.class);
60 private final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Instant.class, new InstantDeserializer())
63 private PushbulletConfiguration config = new PushbulletConfiguration();
65 private final HttpClient httpClient;
67 public PushbulletHttpClient(HttpClient httpClient) {
68 this.httpClient = httpClient;
71 public void setConfiguration(PushbulletConfiguration config) {
76 * Executes an api request
78 * @param apiEndpoint the request api endpoint
79 * @param responseType the response type
80 * @return the unpacked response
81 * @throws PushbulletApiException
83 public <T> T executeRequest(String apiEndpoint, Class<T> responseType) throws PushbulletApiException {
84 return executeRequest(apiEndpoint, null, responseType);
88 * Executes an api request
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
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();
101 Request request = newRequest(url).header("Access-Token", accessToken);
104 StringContentProvider content = new StringContentProvider(gson.toJson(body));
105 String contentType = MimeTypes.Type.APPLICATION_JSON.asString();
107 request.method(HttpMethod.POST).content(content, contentType);
110 String responseBody = sendRequest(request);
113 T response = Objects.requireNonNull(gson.fromJson(responseBody, responseType));
114 logger.debug("Unpacked Response: {}", response);
116 } catch (JsonSyntaxException e) {
117 logger.debug("Failed to unpack response as '{}': {}", responseType.getSimpleName(), e.getMessage());
118 throw new PushbulletApiException(e);
125 * @param url the upload url
126 * @param data the file data
127 * @throws PushbulletApiException
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);
133 Request request = newRequest(url).method(HttpMethod.POST).content(content);
135 sendRequest(request);
139 * Creates a new http request
141 * @param url the request url
142 * @return the new Request object with default parameters
144 private Request newRequest(String url) {
145 return httpClient.newRequest(url).agent(AGENT).timeout(TIMEOUT, TimeUnit.SECONDS);
149 * Sends a http request
151 * @param request the request to send
152 * @return the response body
153 * @throws PushbulletApiException
155 private String sendRequest(Request request) throws PushbulletApiException {
157 logger.debug("Request {} {}", request.getMethod(), request.getURI());
158 logger.debug("Request Headers: {}", request.getHeaders());
160 ContentResponse response = request.send();
162 int statusCode = response.getStatus();
163 String statusReason = response.getReason();
164 String responseBody = response.getContentAsString();
166 logger.debug("Got HTTP {} Response: '{}'", statusCode, responseBody);
168 switch (statusCode) {
169 case HttpStatus.OK_200:
170 case HttpStatus.NO_CONTENT_204:
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()));
179 throw new PushbulletApiException(statusReason);
181 } catch (InterruptedException | TimeoutException | ExecutionException e) {
182 logger.debug("Failed to send request: {}", e.getMessage());
183 throw new PushbulletApiException(e);
188 * Returns the rate limit reset time included in response headers
190 * @param headers the response headers
191 * @return the rate limit reset time if found in headers, otherwise null
193 private @Nullable Instant getRateLimitResetTime(HttpFields headers) {
195 long resetTime = headers.getLongField(HEADER_RATELIMIT_RESET);
196 if (resetTime != -1) {
197 return Instant.ofEpochSecond(resetTime);
199 } catch (NumberFormatException ignored) {