]> git.basschouten.com Git - openhab-addons.git/blob
399307660811437537b1bd2c57c8172a8a7b8f93
[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.ojelectronics.internal.services;
14
15 import java.util.Objects;
16 import java.util.function.BiConsumer;
17 import java.util.function.Consumer;
18
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;
34
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;
40
41 /**
42  * Handles the refreshing of the devices of a session
43  *
44  * @author Christian Kittel - Initial Contribution
45  */
46 @NonNullByDefault
47 public final class RefreshService implements AutoCloseable {
48
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();
53
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;
62
63     /**
64      * Creates a new instance of {@link RefreshService}
65      *
66      * @param config Configuration of the bridge
67      * @param httpClient HTTP client
68      * @param updateService Service to update the thermostat in the cloud
69      */
70     public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) {
71         this.config = config;
72         this.httpClient = httpClient;
73         Platform.loadPlatformComponent(null);
74     }
75
76     /**
77      * Starts refreshing all thing values
78      *
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.
83      */
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;
94
95         signalRConnection = createSignalRConnection();
96         destroyed = false;
97         isInitializing = false;
98         initializeGroups(true);
99     }
100
101     /**
102      * Stops refreshing.
103      */
104     public void stop() {
105         destroyed = true;
106         final Connection localSignalRConnection = signalRConnection;
107         if (localSignalRConnection != null) {
108             localSignalRConnection.stop();
109             signalRConnection = null;
110         }
111     }
112
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);
121                     try {
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());
128                     }
129                 }
130             }
131         });
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");
136             }
137         });
138         signalRConnection.reconnected(() -> {
139             initializeGroups(false);
140         });
141         signalRConnection.connected(() -> {
142             signalRConnection.send(sessionId);
143         });
144         signalRConnection.error(error -> logger.info("SignalR error {}", error.getLocalizedMessage()));
145         return signalRConnection;
146     }
147
148     private void initializeGroups(boolean shouldStartSignalRService) {
149         if (destroyed || isInitializing) {
150             return;
151         }
152         final String sessionId = this.sessionId;
153         if (sessionId == null) {
154             handleConnectionLost("No session id");
155         }
156         isInitializing = true;
157         logger.trace("initializeGroups started");
158         final Runnable unauthorized = this.unauthorized;
159         createRequest().send(new BufferingResponseListener() {
160             @Override
161             public void onComplete(@Nullable Result result) {
162                 try {
163                     if (destroyed || result == null) {
164                         return;
165                     }
166                     if (result.isFailed()) {
167                         final Throwable failure = result.getFailure();
168                         logger.error("Error initializing groups", failure);
169                         handleConnectionLost(failure.getLocalizedMessage());
170                     } else {
171                         int status = result.getResponse().getStatus();
172                         logger.trace("HTTP-Status {}", status);
173                         if (status == HttpStatus.FORBIDDEN_403) {
174                             if (unauthorized != null) {
175                                 unauthorized.run();
176                             } else {
177                                 handleConnectionLost(null);
178                             }
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();
184                             }
185                         } else {
186                             logger.warn("unsupported HTTP-Status {}", status);
187                             handleConnectionLost(null);
188                         }
189                     }
190                 } finally {
191                     logger.trace("initializeGroups completed");
192                     isInitializing = false;
193                 }
194             }
195         });
196     }
197
198     private Request createRequest() {
199         Request request = httpClient.newRequest(config.getRestApiUrl() + "/Group/GroupContents")
200                 .param("sessionid", sessionId).param("apiKey", config.apiKey).method(HttpMethod.GET);
201         return request;
202     }
203
204     private void initializationDone(String responseBody) {
205         BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.initializationDone;
206         if (refreshDone != null) {
207             logger.trace("initializationDone {}", responseBody);
208             try {
209                 GroupContentResponseModel content = Objects
210                         .requireNonNull(gson.fromJson(responseBody, GroupContentResponseModel.class));
211                 refreshDone.accept(content, null);
212             } catch (JsonSyntaxException exception) {
213                 logger.debug("Error mapping Result to model", exception);
214                 refreshDone.accept(null, exception.getMessage());
215             }
216         }
217     }
218
219     private void handleConnectionLost(@Nullable String message) {
220         final Consumer<@Nullable String> connectionLost = this.connectionLost;
221         if (connectionLost != null) {
222             connectionLost.accept(message);
223         }
224     }
225
226     @Override
227     public void close() {
228         stop();
229     }
230 }