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.freeboxos.internal.api;
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;
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.FreeboxOsBindingConstants;
36 import org.openhab.binding.freeboxos.internal.api.deserialization.ForegroundAppDeserializer;
37 import org.openhab.binding.freeboxos.internal.api.deserialization.ListDeserializer;
38 import org.openhab.binding.freeboxos.internal.api.deserialization.StrictEnumTypeAdapterFactory;
39 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
40 import org.openhab.core.i18n.TimeZoneProvider;
41 import org.openhab.core.io.net.http.HttpClientFactory;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 import com.google.gson.FieldNamingPolicy;
46 import com.google.gson.Gson;
47 import com.google.gson.GsonBuilder;
48 import com.google.gson.JsonDeserializer;
50 import inet.ipaddr.IPAddress;
51 import inet.ipaddr.IPAddressString;
52 import inet.ipaddr.MACAddressString;
53 import inet.ipaddr.mac.MACAddress;
56 * The {@link ApiHandler} is responsible for sending requests toward a given url and transform the answer in appropriate
59 * @author Gaƫl L'hopital - Initial contribution
62 public class ApiHandler {
63 private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
64 private static final String CONTENT_TYPE = "application/json; charset=" + DEFAULT_CHARSET.name();
65 private static final int RESPONSE_BUFFER_SIZE = 65536;
66 public static final String AUTH_HEADER = "X-Fbx-App-Auth";
68 private final Logger logger = LoggerFactory.getLogger(ApiHandler.class);
69 private final HttpClient httpClient;
70 private final Gson gson;
72 private long timeoutInMs = TimeUnit.SECONDS.toMillis(8);
74 public ApiHandler(HttpClientFactory httpClientFactory, TimeZoneProvider timeZoneProvider) {
75 this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
76 .registerTypeAdapter(ZonedDateTime.class,
77 (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
78 long timestamp = json.getAsJsonPrimitive().getAsLong();
79 Instant i = Instant.ofEpochSecond(timestamp);
80 return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
82 .registerTypeAdapter(MACAddress.class,
83 (JsonDeserializer<MACAddress>) (json, type,
84 jsonDeserializationContext) -> new MACAddressString(json.getAsString()).getAddress())
85 .registerTypeAdapter(IPAddress.class,
86 (JsonDeserializer<IPAddress>) (json, type,
87 jsonDeserializationContext) -> new IPAddressString(json.getAsString()).getAddress())
88 .registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer())
89 .registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls()
90 .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create();
91 httpClient = httpClientFactory.createHttpClient(FreeboxOsBindingConstants.BINDING_ID);
92 httpClient.setResponseBufferSize(RESPONSE_BUFFER_SIZE);
95 } catch (Exception e) {
96 logger.warn("Unable to start httpClient: {}", e.getMessage());
100 public void dispose() {
103 } catch (Exception e) {
104 logger.warn("Unable to stop httpClient: {}", e.getMessage());
108 public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken,
109 @Nullable Object payload) throws FreeboxException, InterruptedException {
110 logger.debug("executeUrl {}: {} ", method, uri);
112 Request request = httpClient.newRequest(uri).method(method).timeout(timeoutInMs, TimeUnit.MILLISECONDS)
113 .header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE);
115 if (sessionToken != null) {
116 request.header(AUTH_HEADER, sessionToken);
119 if (payload != null) {
120 request.content(new StringContentProvider(serialize(payload), DEFAULT_CHARSET), null);
124 ContentResponse response = request.send();
126 Code statusCode = HttpStatus.getCode(response.getStatus());
128 if (statusCode != Code.OK && statusCode != Code.FORBIDDEN) {
129 throw new FreeboxException(statusCode.getMessage());
132 String content = new String(response.getContent(), DEFAULT_CHARSET);
133 T result = deserialize(clazz, content);
134 logger.trace("executeUrl {} - {} returned {}", method, uri, content);
136 if (statusCode == Code.OK) {
138 } else if (statusCode == Code.FORBIDDEN) {
139 logger.debug("Fobidden, serviceReponse was {}, ", content);
140 if (result instanceof Response<?> errorResponse) {
141 if (errorResponse.getErrorCode() == Response.ErrorCode.INSUFFICIENT_RIGHTS) {
142 throw new PermissionException(errorResponse.getMissingRight(), errorResponse.getMsg());
144 throw new FreeboxException(errorResponse.getErrorCode(), errorResponse.getMsg());
149 throw new FreeboxException("Error '%s' requesting: %s", statusCode.getMessage(), uri.toString());
150 } catch (TimeoutException | ExecutionException e) {
151 throw new FreeboxException(e, "Exception while calling %s", request.getURI());
155 public <T> T deserialize(Class<T> clazz, String json) {
157 T result = gson.fromJson(json, clazz);
158 if (result != null) {
161 throw new IllegalArgumentException("Null result deserializing '%s', please file a bug report.".formatted(json));
164 public String serialize(Object payload) {
165 return gson.toJson(payload);
168 public HttpClient getHttpClient() {
172 public void setTimeout(long millis) {
173 timeoutInMs = millis;
174 logger.debug("Timeout set to {} ms", timeoutInMs);