]> git.basschouten.com Git - openhab-addons.git/blob
400983e3146c21c6e6f6b1a59883d5ed19cc8c9a
[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.windcentrale.internal.api;
14
15 import static org.eclipse.jetty.http.HttpHeader.*;
16 import static org.eclipse.jetty.http.HttpMethod.GET;
17 import static org.openhab.binding.windcentrale.internal.dto.WindcentraleGson.*;
18
19 import java.time.Duration;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Objects;
23 import java.util.Set;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.TimeoutException;
28 import java.util.stream.Collectors;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.eclipse.jetty.client.HttpClient;
33 import org.eclipse.jetty.client.api.ContentResponse;
34 import org.openhab.binding.windcentrale.internal.dto.Project;
35 import org.openhab.binding.windcentrale.internal.dto.Windmill;
36 import org.openhab.binding.windcentrale.internal.dto.WindmillStatus;
37 import org.openhab.binding.windcentrale.internal.exception.FailedGettingDataException;
38 import org.openhab.binding.windcentrale.internal.exception.InvalidAccessTokenException;
39 import org.openhab.core.io.net.http.HttpClientFactory;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * The {@link WindcentraleAPI} implements the Windcentrale REST API which allows for querying project participations and
45  * the current windmill status.
46  *
47  * @author Wouter Born - Initial contribution
48  */
49 @NonNullByDefault
50 public class WindcentraleAPI {
51
52     public static final String URL_PREFIX = "https://mijn.windcentrale.nl/api/v0";
53     private static final String LIVE_DATA_URL = URL_PREFIX + "/livedata";
54     private static final String PROJECTS_URL = URL_PREFIX + "/sustainable/projects";
55
56     private static final String APPLICATION_JSON = "application/json";
57     private static final String BEARER = "Bearer ";
58     private static final Duration REQUEST_TIMEOUT = Duration.ofMinutes(1);
59
60     private final Logger logger = LoggerFactory.getLogger(WindcentraleAPI.class);
61
62     private final HttpClient httpClient;
63     private final TokenProvider tokenProvider;
64
65     private final Set<RequestListener> requestListeners = ConcurrentHashMap.newKeySet();
66
67     public WindcentraleAPI(HttpClientFactory httpClientFactory, TokenProvider tokenProvider) {
68         this.httpClient = httpClientFactory.getCommonHttpClient();
69         this.tokenProvider = tokenProvider;
70     }
71
72     public void dispose() {
73         requestListeners.clear();
74     }
75
76     public void addRequestListener(RequestListener listener) {
77         requestListeners.add(listener);
78     }
79
80     public void removeRequestListener(RequestListener listener) {
81         requestListeners.remove(listener);
82     }
83
84     private String getAuthorizationHeader() throws InvalidAccessTokenException {
85         return BEARER + tokenProvider.getIdToken();
86     }
87
88     private String getJson(String url) throws FailedGettingDataException, InvalidAccessTokenException {
89         try {
90             logger.debug("Getting JSON from: {}", url);
91             ContentResponse contentResponse = httpClient.newRequest(url) //
92                     .method(GET) //
93                     .header(ACCEPT, APPLICATION_JSON) //
94                     .header(AUTHORIZATION, getAuthorizationHeader()) //
95                     .timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS) //
96                     .send();
97
98             if (contentResponse.getStatus() >= 400) {
99                 FailedGettingDataException exception = new FailedGettingDataException(
100                         String.format("Windcentrale API error: %s (HTTP %s)", contentResponse.getReason(),
101                                 contentResponse.getStatus()));
102                 requestListeners.forEach(listener -> listener.onError(exception));
103                 throw exception;
104             }
105             String response = contentResponse.getContentAsString();
106             logger.trace("Response: {}", response);
107             requestListeners.forEach(RequestListener::onSuccess);
108             return response;
109         } catch (ExecutionException | InterruptedException | TimeoutException e) {
110             FailedGettingDataException exception = new FailedGettingDataException(
111                     "Windcentrale API request failed: " + e.getMessage(), e);
112             requestListeners.forEach(listener -> listener.onError(exception));
113             throw exception;
114         } catch (InvalidAccessTokenException e) {
115             requestListeners.forEach(listener -> listener.onError(e));
116             throw e;
117         }
118     }
119
120     public Map<Windmill, WindmillStatus> getLiveData() throws FailedGettingDataException, InvalidAccessTokenException {
121         return getLiveData(Set.of());
122     }
123
124     public @Nullable WindmillStatus getLiveData(Windmill windmill)
125             throws FailedGettingDataException, InvalidAccessTokenException {
126         return getLiveData(Set.of(windmill)).get(windmill);
127     }
128
129     public Map<Windmill, WindmillStatus> getLiveData(Set<Windmill> windmills)
130             throws FailedGettingDataException, InvalidAccessTokenException {
131         logger.debug("Getting live data: {}", windmills);
132
133         String queryParams = "";
134         if (!windmills.isEmpty()) {
135             queryParams = "?projects="
136                     + windmills.stream().map(Windmill::getProjectCode).collect(Collectors.joining(","));
137         }
138
139         String json = getJson(LIVE_DATA_URL + queryParams);
140         return Objects.requireNonNullElse(GSON.fromJson(json, LIVE_DATA_RESPONSE_TYPE), Map.of());
141     }
142
143     public List<Project> getProjects() throws FailedGettingDataException, InvalidAccessTokenException {
144         logger.debug("Getting projects");
145         String json = getJson(PROJECTS_URL);
146         return Objects.requireNonNullElse(GSON.fromJson(json, PROJECTS_RESPONSE_TYPE), List.of());
147     }
148 }