]> git.basschouten.com Git - openhab-addons.git/blob
815ee15c5987118d751a8b781999032506398e8d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.groupepsa.internal.rest.api;
14
15 import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.API_URL;
16
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;
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.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;
41
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;
50
51 /**
52  * Allows access to the GroupePSAConnectApi
53  *
54  * @author Arjan Mels - Initial contribution
55  */
56 @NonNullByDefault
57 public class GroupePSAConnectApi {
58     private final Logger logger = LoggerFactory.getLogger(GroupePSAConnectApi.class);
59
60     private final HttpClient httpClient;
61     private final GroupePSABridgeHandler bridge;
62     private final String clientId;
63     private final String realm;
64
65     protected final Gson gson;
66
67     public GroupePSAConnectApi(HttpClient httpClient, GroupePSABridgeHandler bridge, String clientId, String realm) {
68         this.httpClient = httpClient;
69         this.bridge = bridge;
70         this.clientId = clientId;
71         this.realm = realm;
72
73         gson = new GsonBuilder().registerTypeAdapterFactory(new GeometryAdapterFactory())
74                 .registerTypeAdapter(ZonedDateTime.class, new JsonDeserializer<ZonedDateTime>() {
75                     @Override
76                     public @Nullable ZonedDateTime deserialize(JsonElement json, Type typeOfT,
77                             JsonDeserializationContext context) throws JsonParseException {
78                         return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString());
79                     }
80                 }).registerTypeAdapter(Duration.class, new JsonDeserializer<Duration>() {
81                     @Override
82                     public @Nullable Duration deserialize(JsonElement json, Type typeOfT,
83                             JsonDeserializationContext context) throws JsonParseException {
84                         return Duration.parse(json.getAsJsonPrimitive().getAsString());
85                     }
86                 }).create();
87     }
88
89     protected HttpClient getHttpClient() {
90         return httpClient;
91     }
92
93     protected GroupePSABridgeHandler getBridge() {
94         return bridge;
95     }
96
97     public String getBaseUrl() {
98         return API_URL;
99     }
100
101     private ContentResponse executeRequest(final String uri) throws GroupePSACommunicationException {
102         return executeRequest(uri, "application/hal+json");
103     }
104
105     static Throwable getRootCause(Throwable e) {
106         Throwable nextE;
107         do {
108             nextE = e.getCause();
109             if (nextE != null) {
110                 e = nextE;
111             }
112         } while (nextE != null);
113         return e;
114     }
115
116     public ContentResponse executeRequest(final String uri, final String accept)
117             throws GroupePSACommunicationException {
118         Request request = getHttpClient().newRequest(uri);
119
120         String token = getBridge().authenticate();
121
122         request.timeout(10, TimeUnit.SECONDS);
123
124         request.param("client_id", this.clientId);
125
126         request.header("Authorization", "Bearer " + token);
127         request.header("Accept", accept);
128         request.header("x-introspect-realm", this.realm);
129
130         request.method(HttpMethod.GET);
131
132         logger.trace("HttpRequest {}", request.getURI());
133         logger.trace("HttpRequest Headers:\n{}", request.getHeaders());
134
135         try {
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());
140             return response;
141         } catch (InterruptedException | TimeoutException | ExecutionException e) {
142             throw new GroupePSACommunicationException("Unable to perform Http Request: " + getRootCause(e).getMessage(),
143                     e);
144         }
145     }
146
147     private void checkForError(ContentResponse response, int statusCode) throws GroupePSACommunicationException {
148         if (statusCode >= 200 && statusCode < 300) {
149             return;
150         }
151
152         switch (statusCode) {
153             case HttpStatus.NOT_FOUND_404:
154                 ErrorObject error = null;
155                 try {
156                     error = gson.fromJson(response.getContentAsString(), ErrorObject.class);
157                 } catch (JsonSyntaxException e) {
158                     throw new GroupePSACommunicationException("Error in received JSON: " + getRootCause(e).getMessage(),
159                             e);
160                 }
161                 String message = (error == null) ? null : error.getMessage();
162                 throw new GroupePSACommunicationException(statusCode, message == null ? "Unknown" : message);
163
164             case HttpStatus.FORBIDDEN_403:
165             case HttpStatus.UNAUTHORIZED_401:
166                 throw new UnauthorizedException(statusCode, response.getContentAsString());
167
168             default:
169                 throw new GroupePSACommunicationException(statusCode, response.getContentAsString());
170         }
171     }
172
173     private <T> @Nullable T parseResponse(ContentResponse response, Class<T> type)
174             throws GroupePSACommunicationException {
175         int statusCode = response.getStatus();
176
177         checkForError(response, statusCode);
178
179         try {
180             return gson.fromJson(response.getContentAsString(), type);
181         } catch (JsonSyntaxException e) {
182             throw new GroupePSACommunicationException("Error in received JSON: " + getRootCause(e).getMessage(), e);
183         }
184     }
185
186     public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
187         ContentResponse response = executeRequest(getBaseUrl() + "/user");
188         User user = parseResponse(response, User.class);
189
190         if (user != null) {
191             return user.getVehicles();
192         } else {
193             return null;
194         }
195     }
196
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);
200
201         return status;
202     }
203 }