2 * Copyright (c) 2010-2023 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.groupepsa.internal.rest.api;
15 import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.API_URL;
17 import java.lang.reflect.Type;
18 import java.time.Duration;
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.http.HttpMethod;
31 import org.eclipse.jetty.http.HttpStatus;
32 import org.openhab.binding.groupepsa.internal.bridge.GroupePSABridgeHandler;
33 import org.openhab.binding.groupepsa.internal.rest.api.dto.ErrorObject;
34 import org.openhab.binding.groupepsa.internal.rest.api.dto.User;
35 import org.openhab.binding.groupepsa.internal.rest.api.dto.Vehicle;
36 import org.openhab.binding.groupepsa.internal.rest.api.dto.VehicleStatus;
37 import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
38 import org.openhab.binding.groupepsa.internal.rest.exceptions.UnauthorizedException;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import com.github.filosganga.geogson.gson.GeometryAdapterFactory;
43 import com.google.gson.Gson;
44 import com.google.gson.GsonBuilder;
45 import com.google.gson.JsonDeserializationContext;
46 import com.google.gson.JsonDeserializer;
47 import com.google.gson.JsonElement;
48 import com.google.gson.JsonParseException;
49 import com.google.gson.JsonSyntaxException;
52 * Allows access to the GroupePSAConnectApi
54 * @author Arjan Mels - Initial contribution
57 public class GroupePSAConnectApi {
58 private final Logger logger = LoggerFactory.getLogger(GroupePSAConnectApi.class);
60 private final HttpClient httpClient;
61 private final GroupePSABridgeHandler bridge;
62 private final String clientId;
63 private final String realm;
65 protected final Gson gson;
67 public GroupePSAConnectApi(HttpClient httpClient, GroupePSABridgeHandler bridge, String clientId, String realm) {
68 this.httpClient = httpClient;
70 this.clientId = clientId;
73 gson = new GsonBuilder().registerTypeAdapterFactory(new GeometryAdapterFactory())
74 .registerTypeAdapter(ZonedDateTime.class, new JsonDeserializer<ZonedDateTime>() {
76 public @Nullable ZonedDateTime deserialize(JsonElement json, Type typeOfT,
77 JsonDeserializationContext context) throws JsonParseException {
78 return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString());
80 }).registerTypeAdapter(Duration.class, new JsonDeserializer<Duration>() {
82 public @Nullable Duration deserialize(JsonElement json, Type typeOfT,
83 JsonDeserializationContext context) throws JsonParseException {
84 return Duration.parse(json.getAsJsonPrimitive().getAsString());
89 protected HttpClient getHttpClient() {
93 protected GroupePSABridgeHandler getBridge() {
97 public String getBaseUrl() {
101 private ContentResponse executeRequest(final String uri) throws GroupePSACommunicationException {
102 return executeRequest(uri, "application/hal+json");
105 static Throwable getRootCause(Throwable e) {
108 nextE = e.getCause();
112 } while (nextE != null);
116 public ContentResponse executeRequest(final String uri, final String accept)
117 throws GroupePSACommunicationException {
118 Request request = getHttpClient().newRequest(uri);
120 String token = getBridge().authenticate();
122 request.timeout(10, TimeUnit.SECONDS);
124 request.param("client_id", this.clientId);
126 request.header("Authorization", "Bearer " + token);
127 request.header("Accept", accept);
128 request.header("x-introspect-realm", this.realm);
130 request.method(HttpMethod.GET);
132 logger.trace("HttpRequest {}", request.getURI());
133 logger.trace("HttpRequest Headers:\n{}", request.getHeaders());
136 ContentResponse response = request.send();
137 logger.trace("HttpResponse {}", response);
138 logger.trace("HttpResponse Headers:\n{}", response.getHeaders());
139 logger.trace("HttpResponse Content: {}", response.getContentAsString());
141 } catch (InterruptedException | TimeoutException | ExecutionException e) {
142 throw new GroupePSACommunicationException("Unable to perform Http Request: " + getRootCause(e).getMessage(),
147 private void checkForError(ContentResponse response, int statusCode) throws GroupePSACommunicationException {
148 if (statusCode >= 200 && statusCode < 300) {
152 switch (statusCode) {
153 case HttpStatus.NOT_FOUND_404:
154 ErrorObject error = null;
156 error = gson.fromJson(response.getContentAsString(), ErrorObject.class);
157 } catch (JsonSyntaxException e) {
158 throw new GroupePSACommunicationException("Error in received JSON: " + getRootCause(e).getMessage(),
161 String message = (error == null) ? null : error.getMessage();
162 throw new GroupePSACommunicationException(statusCode, message == null ? "Unknown" : message);
164 case HttpStatus.FORBIDDEN_403:
165 case HttpStatus.UNAUTHORIZED_401:
166 throw new UnauthorizedException(statusCode, response.getContentAsString());
169 throw new GroupePSACommunicationException(statusCode, response.getContentAsString());
173 private <T> @Nullable T parseResponse(ContentResponse response, Class<T> type)
174 throws GroupePSACommunicationException {
175 int statusCode = response.getStatus();
177 checkForError(response, statusCode);
180 return gson.fromJson(response.getContentAsString(), type);
181 } catch (JsonSyntaxException e) {
182 throw new GroupePSACommunicationException("Error in received JSON: " + getRootCause(e).getMessage(), e);
186 public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
187 ContentResponse response = executeRequest(getBaseUrl() + "/user");
188 User user = parseResponse(response, User.class);
191 return user.getVehicles();
197 public @Nullable VehicleStatus getVehicleStatus(String vin) throws GroupePSACommunicationException {
198 ContentResponse responseOdometer = executeRequest(getBaseUrl() + "/user/vehicles/" + vin + "/status");
199 VehicleStatus status = parseResponse(responseOdometer, VehicleStatus.class);