]> git.basschouten.com Git - openhab-addons.git/blob
6f760e52cbb589c89fb38efd9479c46bc1f7199d
[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.freeboxos.internal.api;
14
15 import java.net.URI;
16 import java.nio.charset.Charset;
17 import java.nio.charset.StandardCharsets;
18 import java.time.Instant;
19 import java.time.ZonedDateTime;
20 import java.util.List;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.TimeoutException;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.eclipse.jetty.client.api.ContentResponse;
29 import org.eclipse.jetty.client.api.Request;
30 import org.eclipse.jetty.client.util.StringContentProvider;
31 import org.eclipse.jetty.http.HttpHeader;
32 import org.eclipse.jetty.http.HttpMethod;
33 import org.eclipse.jetty.http.HttpStatus;
34 import org.eclipse.jetty.http.HttpStatus.Code;
35 import org.openhab.binding.freeboxos.internal.api.deserialization.ForegroundAppDeserializer;
36 import org.openhab.binding.freeboxos.internal.api.deserialization.ListDeserializer;
37 import org.openhab.binding.freeboxos.internal.api.deserialization.StrictEnumTypeAdapterFactory;
38 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
39 import org.openhab.core.i18n.TimeZoneProvider;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import com.google.gson.FieldNamingPolicy;
44 import com.google.gson.Gson;
45 import com.google.gson.GsonBuilder;
46 import com.google.gson.JsonDeserializer;
47
48 import inet.ipaddr.IPAddress;
49 import inet.ipaddr.IPAddressString;
50 import inet.ipaddr.MACAddressString;
51 import inet.ipaddr.mac.MACAddress;
52
53 /**
54  * The {@link ApiHandler} is responsible for sending requests toward a given url and transform the answer in appropriate
55  * DTO.
56  *
57  * @author GaĆ«l L'hopital - Initial contribution
58  */
59 @NonNullByDefault
60 public class ApiHandler {
61     public static final String AUTH_HEADER = "X-Fbx-App-Auth";
62     private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
63     private static final String CONTENT_TYPE = "application/json; charset=" + DEFAULT_CHARSET.name();
64
65     private final Logger logger = LoggerFactory.getLogger(ApiHandler.class);
66     private final HttpClient httpClient;
67     private final Gson gson;
68
69     private long timeoutInMs = TimeUnit.SECONDS.toMillis(8);
70
71     public ApiHandler(HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
72         this.httpClient = httpClient;
73         this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
74                 .registerTypeAdapter(ZonedDateTime.class,
75                         (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
76                             long timestamp = json.getAsJsonPrimitive().getAsLong();
77                             Instant i = Instant.ofEpochSecond(timestamp);
78                             return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
79                         })
80                 .registerTypeAdapter(MACAddress.class,
81                         (JsonDeserializer<MACAddress>) (json, type,
82                                 jsonDeserializationContext) -> new MACAddressString(json.getAsString()).getAddress())
83                 .registerTypeAdapter(IPAddress.class,
84                         (JsonDeserializer<IPAddress>) (json, type,
85                                 jsonDeserializationContext) -> new IPAddressString(json.getAsString()).getAddress())
86                 .registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer())
87                 .registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls()
88                 .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create();
89     }
90
91     public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken,
92             @Nullable Object payload) throws FreeboxException, InterruptedException {
93         logger.debug("executeUrl {}: {} ", method, uri);
94
95         Request request = httpClient.newRequest(uri).method(method).timeout(timeoutInMs, TimeUnit.MILLISECONDS)
96                 .header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE);
97
98         if (sessionToken != null) {
99             request.header(AUTH_HEADER, sessionToken);
100         }
101
102         if (payload != null) {
103             request.content(new StringContentProvider(serialize(payload), DEFAULT_CHARSET), null);
104         }
105
106         try {
107             ContentResponse response = request.send();
108
109             Code statusCode = HttpStatus.getCode(response.getStatus());
110
111             if (statusCode != Code.OK && statusCode != Code.FORBIDDEN) {
112                 throw new FreeboxException(statusCode.getMessage());
113             }
114
115             String content = new String(response.getContent(), DEFAULT_CHARSET);
116             T result = deserialize(clazz, content);
117             logger.trace("executeUrl {} - {} returned {}", method, uri, content);
118
119             if (statusCode == Code.OK) {
120                 return result;
121             } else if (statusCode == Code.FORBIDDEN) {
122                 logger.debug("Fobidden, serviceReponse was {}, ", content);
123                 if (result instanceof Response<?> errorResponse) {
124                     if (errorResponse.getErrorCode() == Response.ErrorCode.INSUFFICIENT_RIGHTS) {
125                         throw new PermissionException(errorResponse.getMissingRight(), errorResponse.getMsg());
126                     } else {
127                         throw new FreeboxException(errorResponse.getErrorCode(), errorResponse.getMsg());
128                     }
129                 }
130             }
131
132             throw new FreeboxException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString());
133         } catch (TimeoutException | ExecutionException e) {
134             throw new FreeboxException(e, "Exception while calling %s", request.getURI());
135         }
136     }
137
138     public <T> T deserialize(Class<T> clazz, String json) {
139         @Nullable
140         T result = gson.fromJson(json, clazz);
141         if (result != null) {
142             return result;
143         }
144         throw new IllegalArgumentException("Null result deserializing '%s', please file a bug report.".formatted(json));
145     }
146
147     public String serialize(Object payload) {
148         return gson.toJson(payload);
149     }
150
151     public HttpClient getHttpClient() {
152         return httpClient;
153     }
154
155     public void setTimeout(long millis) {
156         timeoutInMs = millis;
157         logger.debug("Timeout set to {} ms", timeoutInMs);
158     }
159 }