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
69 public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) {
71 this.httpClient = httpClient;
72 Platform.loadPlatformComponent(null);
76 * Starts refreshing all thing values
78 * @param sessionId Session-Id
79 * @param refreshDone This method is called if refreshing is done.
80 * @param connectionLost This method is called if no connection could established.
81 * @param unauthorized This method is called if the result is unauthorized.
83 public void start(String sessionId,
84 BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> initializationDone,
85 BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone,
86 Consumer<@Nullable String> connectionLost, Runnable unauthorized) {
87 logger.trace("RefreshService.startService({})", sessionId);
88 this.connectionLost = connectionLost;
89 this.initializationDone = initializationDone;
90 this.refreshDone = refreshDone;
91 this.unauthorized = unauthorized;
92 this.sessionId = sessionId;
94 signalRConnection = createSignalRConnection();
96 isInitializing = false;
97 initializeGroups(true);
105 final Connection localSignalRConnection = signalRConnection;
106 if (localSignalRConnection != null) {
107 localSignalRConnection.stop();
108 signalRConnection = null;
112 private Connection createSignalRConnection() {
113 Connection signalRConnection = new Connection(config.getSignalRUrl(), new SignalRLogger());
114 signalRConnection.setReconnectOnError(false);
115 signalRConnection.received(json -> {
116 if (json != null && json.isJsonObject()) {
117 BiConsumer<@Nullable SignalRResultModel, @Nullable String> refreshDone = this.refreshDone;
118 if (refreshDone != null) {
119 logger.trace("refresh {}", json);
121 SignalRResultModel content = Objects
122 .requireNonNull(gson.fromJson(json, SignalRResultModel.class));
123 refreshDone.accept(content, null);
124 } catch (JsonSyntaxException exception) {
125 logger.debug("Error mapping Result to model", exception);
126 refreshDone.accept(null, exception.getMessage());
131 signalRConnection.stateChanged((oldState, newState) -> {
132 logger.trace("Connection state changed from {} to {}", oldState, newState);
133 if (newState == ConnectionState.Disconnected && !destroyed) {
134 handleConnectionLost("Connection broken");
137 signalRConnection.reconnected(() -> {
138 initializeGroups(false);
140 signalRConnection.connected(() -> {
141 signalRConnection.send(sessionId);
143 signalRConnection.error(error -> logger.info("SignalR error {}", error.getLocalizedMessage()));
144 return signalRConnection;
147 private void initializeGroups(boolean shouldStartSignalRService) {
148 if (destroyed || isInitializing) {
151 final String sessionId = this.sessionId;
152 if (sessionId == null) {
153 handleConnectionLost("No session id");
155 isInitializing = true;
156 logger.trace("initializeGroups started");
157 final Runnable unauthorized = this.unauthorized;
158 createRequest().send(new BufferingResponseListener() {
160 public void onComplete(@Nullable Result result) {
162 if (destroyed || result == null) {
165 if (result.isFailed()) {
166 final Throwable failure = result.getFailure();
167 logger.error("Error initializing groups", failure);
168 handleConnectionLost(failure.getLocalizedMessage());
170 int status = result.getResponse().getStatus();
171 logger.trace("HTTP-Status {}", status);
172 if (status == HttpStatus.FORBIDDEN_403) {
173 if (unauthorized != null) {
176 handleConnectionLost(null);
178 } else if (status == HttpStatus.OK_200) {
179 initializationDone(Objects.requireNonNull(getContentAsString()));
180 final Connection localSignalRConnection = signalRConnection;
181 if (shouldStartSignalRService && localSignalRConnection != null) {
182 localSignalRConnection.start();
185 logger.warn("unsupported HTTP-Status {}", status);
186 handleConnectionLost(null);
190 logger.trace("initializeGroups completed");
191 isInitializing = false;
197 private Request createRequest() {
198 return httpClient.newRequest(config.getRestApiUrl() + "/Group/GroupContents").param("sessionid", sessionId)
199 .param("apiKey", config.apiKey).method(HttpMethod.GET);
202 private void initializationDone(String responseBody) {
203 BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.initializationDone;
204 if (refreshDone != null) {
205 logger.trace("initializationDone {}", responseBody);
207 GroupContentResponseModel content = Objects
208 .requireNonNull(gson.fromJson(responseBody, GroupContentResponseModel.class));
209 refreshDone.accept(content, null);
210 } catch (JsonSyntaxException exception) {
211 logger.debug("Error mapping Result to model", exception);
212 refreshDone.accept(null, exception.getMessage());
217 private void handleConnectionLost(@Nullable String message) {
218 final Consumer<@Nullable String> connectionLost = this.connectionLost;
219 if (connectionLost != null) {
220 connectionLost.accept(message);
225 public void close() {