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.ojelectronics.internal.services;
15 import java.util.Objects;
16 import java.util.function.BiConsumer;
17 import java.util.function.Consumer;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.eclipse.jetty.client.HttpClient;
22 import org.eclipse.jetty.client.api.Request;
23 import org.eclipse.jetty.client.api.Result;
24 import org.eclipse.jetty.client.util.BufferingResponseListener;
25 import org.eclipse.jetty.http.HttpMethod;
26 import org.eclipse.jetty.http.HttpStatus;
27 import org.openhab.binding.ojelectronics.internal.common.OJGSonBuilder;
28 import org.openhab.binding.ojelectronics.internal.common.SignalRLogger;
29 import org.openhab.binding.ojelectronics.internal.config.OJElectronicsBridgeConfiguration;
30 import org.openhab.binding.ojelectronics.internal.models.SignalRResultModel;
31 import org.openhab.binding.ojelectronics.internal.models.groups.GroupContentResponseModel;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
35 import com.github.signalr4j.client.Connection;
36 import com.github.signalr4j.client.ConnectionState;
37 import com.github.signalr4j.client.Platform;
38 import com.google.gson.Gson;
39 import com.google.gson.JsonSyntaxException;
42 * Handles the refreshing of the devices of a session
44 * @author Christian Kittel - Initial Contribution
47 public final class RefreshService implements AutoCloseable {
49 private final OJElectronicsBridgeConfiguration config;
50 private final Logger logger = LoggerFactory.getLogger(RefreshService.class);
51 private final HttpClient httpClient;
52 private final Gson gson = OJGSonBuilder.getGSon();
54 private @Nullable Consumer<@Nullable String> connectionLost;
55 private @Nullable BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone;
56 private @Nullable BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone;
57 private @Nullable Runnable unauthorized;
58 private @Nullable String sessionId;
59 private @Nullable Connection signalRConnection;
60 private boolean destroyed = false;
61 private boolean isInitializing = false;
64 * Creates a new instance of {@link RefreshService}
66 * @param config Configuration of the bridge
67 * @param httpClient HTTP client
68 * @param updateService Service to update the thermostat in the cloud
70 public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) {
72 this.httpClient = httpClient;
73 Platform.loadPlatformComponent(null);
77 * Starts refreshing all thing values
79 * @param sessionId Session-Id
80 * @param refreshDone This method is called if refreshing is done.
81 * @param connectionLosed This method is called if no connection could established.
82 * @param unauthorized This method is called if the result is unauthorized.
84 public void start(String sessionId,
85 BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone,
86 BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone,
87 Consumer<@Nullable String> connectionLost, Runnable unauthorized) {
88 logger.trace("RefreshService.startService({})", sessionId);
89 this.connectionLost = connectionLost;
90 this.initializationDone = initializationDone;
91 this.refreshDone = refreshDone;
92 this.unauthorized = unauthorized;
93 this.sessionId = sessionId;
95 signalRConnection = createSignalRConnection();
97 isInitializing = false;
98 initializeGroups(true);
106 final Connection localSignalRConnection = signalRConnection;
107 if (localSignalRConnection != null) {
108 localSignalRConnection.stop();
109 signalRConnection = null;
113 private Connection createSignalRConnection() {
114 Connection signalRConnection = new Connection(config.getSignalRUrl(), new SignalRLogger());
115 signalRConnection.setReconnectOnError(false);
116 signalRConnection.received(json -> {
117 if (json != null && json.isJsonObject()) {
118 BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone = this.refreshDone;
119 if (refreshDone != null) {
120 logger.trace("refresh {}", json);
122 SignalRResultModel content = Objects
123 .requireNonNull(gson.fromJson(json, SignalRResultModel.class));
124 refreshDone.accept(content, null);
125 } catch (JsonSyntaxException exception) {
126 logger.debug("Error mapping Result to model", exception);
127 refreshDone.accept(null, exception.getMessage());
132 signalRConnection.stateChanged((oldState, newState) -> {
133 logger.trace("Connection state changed from {} to {}", oldState, newState);
134 if (newState == ConnectionState.Disconnected && !destroyed) {
135 handleConnectionLost("Connection broken");
138 signalRConnection.reconnected(() -> {
139 initializeGroups(false);
141 signalRConnection.connected(() -> {
142 signalRConnection.send(sessionId);
144 signalRConnection.error(error -> logger.info("SignalR error {}", error.getLocalizedMessage()));
145 return signalRConnection;
148 private void initializeGroups(boolean shouldStartSignalRService) {
149 if (destroyed || isInitializing) {
152 final String sessionId = this.sessionId;
153 if (sessionId == null) {
154 handleConnectionLost("No session id");
156 isInitializing = true;
157 logger.trace("initializeGroups started");
158 final Runnable unauthorized = this.unauthorized;
159 createRequest().send(new BufferingResponseListener() {
161 public void onComplete(@Nullable Result result) {
163 if (destroyed || result == null) {
166 if (result.isFailed()) {
167 final Throwable failure = result.getFailure();
168 logger.error("Error initializing groups", failure);
169 handleConnectionLost(failure.getLocalizedMessage());
171 int status = result.getResponse().getStatus();
172 logger.trace("HTTP-Status {}", status);
173 if (status == HttpStatus.FORBIDDEN_403) {
174 if (unauthorized != null) {
177 handleConnectionLost(null);
179 } else if (status == HttpStatus.OK_200) {
180 initializationDone(Objects.requireNonNull(getContentAsString()));
181 final Connection localSignalRConnection = signalRConnection;
182 if (shouldStartSignalRService && localSignalRConnection != null) {
183 localSignalRConnection.start();
186 logger.warn("unsupported HTTP-Status {}", status);
187 handleConnectionLost(null);
191 logger.trace("initializeGroups completed");
192 isInitializing = false;
198 private Request createRequest() {
199 return httpClient.newRequest(config.getRestApiUrl() + "/Group/GroupContents").param("sessionid", sessionId)
200 .param("apiKey", config.apiKey).method(HttpMethod.GET);
203 private void initializationDone(String responseBody) {
204 BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.initializationDone;
205 if (refreshDone != null) {
206 logger.trace("initializationDone {}", responseBody);
208 GroupContentResponseModel content = Objects
209 .requireNonNull(gson.fromJson(responseBody, GroupContentResponseModel.class));
210 refreshDone.accept(content, null);
211 } catch (JsonSyntaxException exception) {
212 logger.debug("Error mapping Result to model", exception);
213 refreshDone.accept(null, exception.getMessage());
218 private void handleConnectionLost(@Nullable String message) {
219 final Consumer<@Nullable String> connectionLost = this.connectionLost;
220 if (connectionLost != null) {
221 connectionLost.accept(message);
226 public void close() {