]> git.basschouten.com Git - openhab-addons.git/blob
f0f9d99b87c767362cecb9f0e219236084747ea1
[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.volvooncall.internal.api;
14
15 import java.io.IOException;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Objects;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.eclipse.jetty.client.api.ContentProvider;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.client.util.StringContentProvider;
29 import org.eclipse.jetty.http.HttpField;
30 import org.eclipse.jetty.http.HttpHeader;
31 import org.eclipse.jetty.http.HttpMethod;
32 import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
33 import org.openhab.binding.volvooncall.internal.VolvoOnCallException.ErrorType;
34 import org.openhab.binding.volvooncall.internal.config.ApiBridgeConfiguration;
35 import org.openhab.binding.volvooncall.internal.dto.PostResponse;
36 import org.openhab.binding.volvooncall.internal.dto.VocAnswer;
37 import org.openhab.core.cache.ExpiringCacheMap;
38 import org.openhab.core.id.InstanceUUID;
39 import org.openhab.core.io.net.http.HttpClientFactory;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import com.google.gson.Gson;
44 import com.google.gson.JsonSyntaxException;
45
46 /**
47  * {@link VocHttpApi} wraps the VolvoOnCall REST API.
48  *
49  * @author GaĆ«l L'hopital - Initial contribution
50  */
51 @NonNullByDefault
52 public class VocHttpApi {
53     // The URL to use to connect to VocAPI.
54     // For North America and China syntax changes to vocapi-cn.xxx
55     private static final String SERVICE_URL = "https://vocapi.wirelesscar.net/customerapi/rest/v3.0/";
56     private static final int TIMEOUT_MS = 10000;
57     private static final String JSON_CONTENT_TYPE = "application/json";
58
59     private final Logger logger = LoggerFactory.getLogger(VocHttpApi.class);
60     private final Gson gson;
61     private final ExpiringCacheMap<String, @Nullable String> cache;
62     private final HttpClient httpClient;
63     private final ApiBridgeConfiguration configuration;
64
65     public VocHttpApi(String clientName, ApiBridgeConfiguration configuration, Gson gson,
66             HttpClientFactory httpClientFactory) throws VolvoOnCallException {
67         this.gson = gson;
68         this.cache = new ExpiringCacheMap<>(120 * 1000);
69         this.configuration = configuration;
70         this.httpClient = httpClientFactory.createHttpClient(clientName);
71
72         httpClient.setUserAgentField(new HttpField(HttpHeader.USER_AGENT, "openhab/voc_binding/" + InstanceUUID.get()));
73         try {
74             httpClient.start();
75         } catch (Exception e) {
76             throw new VolvoOnCallException(new IOException("Unable to start Jetty HttpClient", e));
77         }
78     }
79
80     public void dispose() throws Exception {
81         httpClient.stop();
82     }
83
84     private @Nullable String getResponse(HttpMethod method, String url, @Nullable String body) {
85         try {
86             Request request = httpClient.newRequest(url).header(HttpHeader.CACHE_CONTROL, "no-cache")
87                     .header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE).header(HttpHeader.ACCEPT, "*/*")
88                     .header(HttpHeader.AUTHORIZATION, configuration.getAuthorization()).header("x-device-id", "Device")
89                     .header("x-originator-type", "App").header("x-os-type", "Android").header("x-os-version", "22")
90                     .timeout(TIMEOUT_MS, TimeUnit.MILLISECONDS);
91             if (body != null) {
92                 ContentProvider content = new StringContentProvider(JSON_CONTENT_TYPE, body, StandardCharsets.UTF_8);
93                 request = request.content(content);
94             }
95             ContentResponse contentResponse = request.method(method).send();
96             return contentResponse.getContentAsString();
97         } catch (InterruptedException | TimeoutException | ExecutionException e) {
98             return null;
99         }
100     }
101
102     private <T extends VocAnswer> T callUrl(HttpMethod method, String endpoint, Class<T> objectClass,
103             @Nullable String body) throws VolvoOnCallException {
104         try {
105             String url = endpoint.startsWith("http") ? endpoint : SERVICE_URL + endpoint;
106             String jsonResponse = method == HttpMethod.GET
107                     ? cache.putIfAbsentAndGet(endpoint, () -> getResponse(method, url, body))
108                     : getResponse(method, url, body);
109             if (jsonResponse == null) {
110                 throw new IOException();
111             } else {
112                 logger.debug("Request to `{}` answered : {}", url, jsonResponse);
113                 T responseDTO = Objects.requireNonNull(gson.fromJson(jsonResponse, objectClass));
114                 String error = responseDTO.getErrorLabel();
115                 if (error != null) {
116                     throw new VolvoOnCallException(error, responseDTO.getErrorDescription());
117                 }
118                 return responseDTO;
119             }
120         } catch (JsonSyntaxException | IOException e) {
121             throw new VolvoOnCallException(e);
122         }
123     }
124
125     public <T extends VocAnswer> T getURL(String endpoint, Class<T> objectClass) throws VolvoOnCallException {
126         return callUrl(HttpMethod.GET, endpoint, objectClass, null);
127     }
128
129     public @Nullable PostResponse postURL(String endpoint, @Nullable String body) throws VolvoOnCallException {
130         try {
131             return callUrl(HttpMethod.POST, endpoint, PostResponse.class, body);
132         } catch (VolvoOnCallException e) {
133             if (e.getType() == ErrorType.SERVICE_UNABLE_TO_START) {
134                 logger.info("Unable to start service request sent to VoC");
135                 return null;
136             } else {
137                 throw e;
138             }
139         }
140     }
141
142     public <T extends VocAnswer> T getURL(Class<T> objectClass, String vin) throws VolvoOnCallException {
143         String url = String.format("vehicles/%s/%s", vin, objectClass.getSimpleName().toLowerCase());
144         return getURL(url, objectClass);
145     }
146 }