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.windcentrale.internal.api;
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.*;
19 import java.time.Duration;
20 import java.util.List;
22 import java.util.Objects;
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;
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;
44 * The {@link WindcentraleAPI} implements the Windcentrale REST API which allows for querying project participations and
45 * the current windmill status.
47 * @author Wouter Born - Initial contribution
50 public class WindcentraleAPI {
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";
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);
60 private final Logger logger = LoggerFactory.getLogger(WindcentraleAPI.class);
62 private final HttpClient httpClient;
63 private final TokenProvider tokenProvider;
65 private final Set<RequestListener> requestListeners = ConcurrentHashMap.newKeySet();
67 public WindcentraleAPI(HttpClientFactory httpClientFactory, TokenProvider tokenProvider) {
68 this.httpClient = httpClientFactory.getCommonHttpClient();
69 this.tokenProvider = tokenProvider;
72 public void dispose() {
73 requestListeners.clear();
76 public void addRequestListener(RequestListener listener) {
77 requestListeners.add(listener);
80 public void removeRequestListener(RequestListener listener) {
81 requestListeners.remove(listener);
84 private String getAuthorizationHeader() throws InvalidAccessTokenException {
85 return BEARER + tokenProvider.getIdToken();
88 private String getJson(String url) throws FailedGettingDataException, InvalidAccessTokenException {
90 logger.debug("Getting JSON from: {}", url);
91 ContentResponse contentResponse = httpClient.newRequest(url) //
93 .header(ACCEPT, APPLICATION_JSON) //
94 .header(AUTHORIZATION, getAuthorizationHeader()) //
95 .timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS) //
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));
105 String response = contentResponse.getContentAsString();
106 logger.trace("Response: {}", response);
107 requestListeners.forEach(RequestListener::onSuccess);
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));
114 } catch (InvalidAccessTokenException e) {
115 requestListeners.forEach(listener -> listener.onError(e));
120 public Map<Windmill, WindmillStatus> getLiveData() throws FailedGettingDataException, InvalidAccessTokenException {
121 return getLiveData(Set.of());
124 public @Nullable WindmillStatus getLiveData(Windmill windmill)
125 throws FailedGettingDataException, InvalidAccessTokenException {
126 return getLiveData(Set.of(windmill)).get(windmill);
129 public Map<Windmill, WindmillStatus> getLiveData(Set<Windmill> windmills)
130 throws FailedGettingDataException, InvalidAccessTokenException {
131 logger.debug("Getting live data: {}", windmills);
133 String queryParams = "";
134 if (!windmills.isEmpty()) {
135 queryParams = "?projects="
136 + windmills.stream().map(Windmill::getProjectCode).collect(Collectors.joining(","));
139 String json = getJson(LIVE_DATA_URL + queryParams);
140 return Objects.requireNonNullElse(GSON.fromJson(json, LIVE_DATA_RESPONSE_TYPE), Map.of());
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());