]> git.basschouten.com Git - openhab-addons.git/blob
510a6aa553809a9d20069d7f9081d74b7d141a92
[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      */
69     public RefreshService(OJElectronicsBridgeConfiguration config, HttpClient httpClient) {
70         this.config = config;
71         this.httpClient = httpClient;
72         Platform.loadPlatformComponent(null);
73     }
74
75     /**
76      * Starts refreshing all thing values
77      *
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.
82      */
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;
93
94         signalRConnection = createSignalRConnection();
95         destroyed = false;
96         isInitializing = false;
97         initializeGroups(true);
98     }
99
100     /**
101      * Stops refreshing.
102      */
103     public void stop() {
104         destroyed = true;
105         final Connection localSignalRConnection = signalRConnection;
106         if (localSignalRConnection != null) {
107             localSignalRConnection.stop();
108             signalRConnection = null;
109         }
110     }
111
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);
120                     try {
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());
127                     }
128                 }
129             }
130         });
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");
135             }
136         });
137         signalRConnection.reconnected(() -> {
138             initializeGroups(false);
139         });
140         signalRConnection.connected(() -> {
141             signalRConnection.send(sessionId);
142         });
143         signalRConnection.error(error -> logger.info("SignalR error {}", error.getLocalizedMessage()));
144         return signalRConnection;
145     }
146
147     private void initializeGroups(boolean shouldStartSignalRService) {
148         if (destroyed || isInitializing) {
149             return;
150         }
151         final String sessionId = this.sessionId;
152         if (sessionId == null) {
153             handleConnectionLost("No session id");
154         }
155         isInitializing = true;
156         logger.trace("initializeGroups started");
157         final Runnable unauthorized = this.unauthorized;
158         createRequest().send(new BufferingResponseListener() {
159             @Override
160             public void onComplete(@Nullable Result result) {
161                 try {
162                     if (destroyed || result == null) {
163                         return;
164                     }
165                     if (result.isFailed()) {
166                         final Throwable failure = result.getFailure();
167                         logger.error("Error initializing groups", failure);
168                         handleConnectionLost(failure.getLocalizedMessage());
169                     } else {
170                         int status = result.getResponse().getStatus();
171                         logger.trace("HTTP-Status {}", status);
172                         if (status == HttpStatus.FORBIDDEN_403) {
173                             if (unauthorized != null) {
174                                 unauthorized.run();
175                             } else {
176                                 handleConnectionLost(null);
177                             }
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();
183                             }
184                         } else {
185                             logger.warn("unsupported HTTP-Status {}", status);
186                             handleConnectionLost(null);
187                         }
188                     }
189                 } finally {
190                     logger.trace("initializeGroups completed");
191                     isInitializing = false;
192                 }
193             }
194         });
195     }
196
197     private Request createRequest() {
198         return httpClient.newRequest(config.getRestApiUrl() + "/Group/GroupContents").param("sessionid", sessionId)
199                 .param("apiKey", config.apiKey).method(HttpMethod.GET);
200     }
201
202     private void initializationDone(String responseBody) {
203         BiConsumer<@Nullable GroupContentResponseModel, @Nullable String> refreshDone = this.initializationDone;
204         if (refreshDone != null) {
205             logger.trace("initializationDone {}", responseBody);
206             try {
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());
213             }
214         }
215     }
216
217     private void handleConnectionLost(@Nullable String message) {
218         final Consumer<@Nullable String> connectionLost = this.connectionLost;
219         if (connectionLost != null) {
220             connectionLost.accept(message);
221         }
222     }
223
224     @Override
225     public void close() {
226         stop();
227     }
228 }