]> git.basschouten.com Git - openhab-addons.git/blob
1ca7b226f8d638a54e228d5cb12ee9eaa503b79c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.homeconnect.internal.client;
14
15 import static io.github.bucket4j.Bandwidth.classic;
16 import static io.github.bucket4j.Refill.intervally;
17
18 import java.io.IOException;
19 import java.time.Duration;
20 import java.time.Instant;
21 import java.time.LocalDateTime;
22 import java.time.ZoneId;
23 import java.time.format.DateTimeFormatter;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.TimeoutException;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.api.ContentResponse;
32 import org.eclipse.jetty.client.api.Request;
33 import org.eclipse.jetty.http.HttpMethod;
34 import org.openhab.binding.homeconnect.internal.client.exception.AuthorizationException;
35 import org.openhab.binding.homeconnect.internal.client.exception.CommunicationException;
36 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
37 import org.openhab.core.auth.client.oauth2.OAuthClientService;
38 import org.openhab.core.auth.client.oauth2.OAuthException;
39 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.gson.Gson;
43 import com.google.gson.GsonBuilder;
44 import com.google.gson.JsonElement;
45 import com.google.gson.JsonObject;
46 import com.google.gson.JsonParser;
47
48 import io.github.bucket4j.Bucket;
49 import io.github.bucket4j.Bucket4j;
50
51 /**
52  * okHttp helper.
53  *
54  * @author Jonas BrĂ¼stel - Initial contribution
55  * @author Laurent Garnier - Removed okhttp
56  *
57  */
58 @NonNullByDefault
59 public class HttpHelper {
60     private static final String BEARER = "Bearer ";
61     private static final int OAUTH_EXPIRE_BUFFER = 10;
62     private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
63     private static final JsonParser JSON_PARSER = new JsonParser();
64     private static final Map<String, Bucket> BUCKET_MAP = new HashMap<>();
65     private static final Object AUTHORIZATION_HEADER_MONITOR = new Object();
66     private static final Object BUCKET_MONITOR = new Object();
67
68     private static @Nullable String lastAccessToken = null;
69
70     public static ContentResponse sendRequest(Request request, String clientId)
71             throws InterruptedException, TimeoutException, ExecutionException {
72         if (HttpMethod.GET.name().equals(request.getMethod())) {
73             try {
74                 getBucket(clientId).asScheduler().consume(1);
75             } catch (InterruptedException e) {
76                 LoggerFactory.getLogger(HttpHelper.class).debug("Could not consume from bucket! clientId={}, error={}",
77                         clientId, e.getMessage());
78             }
79         }
80         return request.send();
81     }
82
83     public static String formatJsonBody(@Nullable String jsonString) {
84         if (jsonString == null) {
85             return "";
86         }
87         try {
88             JsonObject json = parseString(jsonString).getAsJsonObject();
89             return GSON.toJson(json);
90         } catch (Exception e) {
91             return jsonString;
92         }
93     }
94
95     public static String getAuthorizationHeader(OAuthClientService oAuthClientService)
96             throws AuthorizationException, CommunicationException {
97         synchronized (AUTHORIZATION_HEADER_MONITOR) {
98             try {
99                 AccessTokenResponse accessTokenResponse = oAuthClientService.getAccessTokenResponse();
100                 // refresh the token if it's about to expire
101                 if (accessTokenResponse != null && accessTokenResponse.isExpired(Instant.now(), OAUTH_EXPIRE_BUFFER)) {
102                     LoggerFactory.getLogger(HttpHelper.class).debug("Requesting a refresh of the access token.");
103                     accessTokenResponse = oAuthClientService.refreshToken();
104                 }
105
106                 if (accessTokenResponse != null) {
107                     String lastToken = lastAccessToken;
108                     if (lastToken == null) {
109                         LoggerFactory.getLogger(HttpHelper.class).debug("The used access token was created at {}",
110                                 LocalDateTime.ofInstant(accessTokenResponse.getCreatedOn(), ZoneId.systemDefault())
111                                         .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
112                     } else if (!lastToken.equals(accessTokenResponse.getAccessToken())) {
113                         LoggerFactory.getLogger(HttpHelper.class)
114                                 .debug("The access token changed. New one created at {}",
115                                         LocalDateTime
116                                                 .ofInstant(accessTokenResponse.getCreatedOn(), ZoneId.systemDefault())
117                                                 .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
118                     }
119                     lastAccessToken = accessTokenResponse.getAccessToken();
120
121                     LoggerFactory.getLogger(HttpHelper.class).debug("Current access token: {}",
122                             accessTokenResponse.getAccessToken());
123                     return BEARER + accessTokenResponse.getAccessToken();
124                 } else {
125                     LoggerFactory.getLogger(HttpHelper.class).error("No access token available! Fatal error.");
126                     throw new AuthorizationException("No access token available!");
127                 }
128             } catch (IOException e) {
129                 String errorMessage = e.getMessage();
130                 throw new CommunicationException(errorMessage != null ? errorMessage : "IOException", e);
131             } catch (OAuthException | OAuthResponseException e) {
132                 String errorMessage = e.getMessage();
133                 throw new AuthorizationException(errorMessage != null ? errorMessage : "oAuth exception", e);
134             }
135         }
136     }
137
138     public static JsonElement parseString(String json) {
139         return JSON_PARSER.parse(json);
140     }
141
142     private static Bucket getBucket(String clientId) {
143         synchronized (BUCKET_MONITOR) {
144             Bucket bucket = null;
145             if (BUCKET_MAP.containsKey(clientId)) {
146                 bucket = BUCKET_MAP.get(clientId);
147             }
148
149             if (bucket == null) {
150                 bucket = Bucket4j.builder()
151                         // allows 50 tokens per minute (added 10 second buffer)
152                         .addLimit(classic(50, intervally(50, Duration.ofSeconds(70))).withInitialTokens(40))
153                         // but not often then 50 tokens per second
154                         .addLimit(classic(10, intervally(10, Duration.ofSeconds(1)))).build();
155                 BUCKET_MAP.put(clientId, bucket);
156             }
157             return bucket;
158         }
159     }
160 }