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.digitalstrom.internal.lib.manager.impl;
15 import java.net.HttpURLConnection;
17 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
18 import org.openhab.binding.digitalstrom.internal.lib.listener.ConnectionListener;
19 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
20 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
21 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.HttpTransport;
22 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.DsAPIImpl;
23 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.HttpTransportImpl;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
27 import com.google.gson.JsonArray;
28 import com.google.gson.JsonObject;
31 * The {@link ConnectionManagerImpl} is the implementation of the {@link ConnectionManager}.
33 * @author Michael Ochel - Initial contribution
34 * @author Matthias Siegele - Initial contribution
36 public class ConnectionManagerImpl implements ConnectionManager {
39 * Query to get all enabled application tokens. Can be executed with {@link DsAPI#query(String, String)} or
40 * {@link DsAPI#query2(String, String)}.
42 public final String QUERY_GET_ENABLED_APPLICATION_TOKENS = "/system/security/applicationTokens/enabled/*(*)";
43 private final Logger logger = LoggerFactory.getLogger(ConnectionManagerImpl.class);
45 private Config config;
46 private ConnectionListener connListener;
47 private HttpTransport transport;
48 private String sessionToken;
49 private Boolean connectionEstablished = false;
50 private boolean genAppToken;
51 private DsAPI digitalSTROMClient;
54 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but the connection
55 * timeout and read timeout can be set, too.
57 * @param hostArddress (must not be null)
58 * @param connectTimeout (if connectTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be
60 * @param readTimeout (if readTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be set)
61 * @param username (can be null, if application token is set)
62 * @param password (can be null, if application token is set
63 * @param applicationToken (can be null, if username and password is set)
64 * @see #ConnectionManagerImpl(String, String, String, String)
66 public ConnectionManagerImpl(String hostArddress, int connectTimeout, int readTimeout, String username,
67 String password, String applicationToken) {
68 init(hostArddress, connectTimeout, readTimeout, username, password, applicationToken, false);
72 * Creates a new {@link ConnectionManagerImpl} through a {@link Config} object, which has all configurations set.
74 * @param config (must not be null)
76 public ConnectionManagerImpl(Config config) {
81 * The same constructor like {@link #ConnectionManagerImpl(Config)}, but a {@link ConnectionListener} can be
84 * @param config (must not be null)
85 * @param connectionListener (can be null)
86 * @see #ConnectionManagerImpl(Config)
88 public ConnectionManagerImpl(Config config, ConnectionListener connectionListener) {
89 this.connListener = connectionListener;
94 * The same constructor like {@link #ConnectionManagerImpl(Config, ConnectionListener)}, but through genApToken it
95 * can be set, if an application token will be automatically generated.
97 * @param config (must not be null)
98 * @param connectionListener (can be null)
99 * @param genAppToken (true = application token will be generated, otherwise false)
100 * @see #ConnectionManagerImpl(Config, ConnectionListener)
102 public ConnectionManagerImpl(Config config, ConnectionListener connectionListener, boolean genAppToken) {
103 this.connListener = connectionListener;
104 this.genAppToken = genAppToken;
109 * Creates a new {@link ConnectionManagerImpl} with the given parameters, which are needed to create the
110 * {@link HttpTransport} and to login into the digitalSTROM server. If the application token is null and the
111 * username and password are valid, an application token will be automatically generated or an existing application
112 * token for the at {@link Config#getApplicationName()} set application name will be set.
114 * @param hostAddress (must not be null)
115 * @param username (can be null, if application token is set)
116 * @param password (can be null, if application token is set
117 * @param applicationToken (can be null, if username and password is set)
119 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken) {
120 init(hostAddress, -1, -1, username, password, applicationToken, false);
124 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without username
127 * @param hostAddress (must not be null)
128 * @param applicationToken (must not be null)
130 public ConnectionManagerImpl(String hostAddress, String applicationToken) {
131 init(hostAddress, -1, -1, null, null, applicationToken, false);
135 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without application
138 * @param hostAddress (must not be null)
139 * @param username (must not be null)
140 * @param password (must not be null)
141 * @see #ConnectionManagerImpl(String, String, String, String)
143 public ConnectionManagerImpl(String hostAddress, String username, String password) {
144 init(hostAddress, -1, -1, username, password, null, false);
148 * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but a
149 * {@link ConnectionListener} can be set, too.
151 * @param hostAddress (must not be null)
152 * @param username (must not be null)
153 * @param password (must not be null)
154 * @param connectionListener (can be null)
155 * @see #ConnectionManagerImpl(String, String, String)
157 public ConnectionManagerImpl(String hostAddress, String username, String password,
158 ConnectionListener connectionListener) {
159 this.connListener = connectionListener;
160 init(hostAddress, -1, -1, username, password, null, false);
164 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but a
165 * {@link ConnectionListener} can be set, too.
167 * @param hostAddress (must not be null)
168 * @param username (can be null, if application token is set)
169 * @param password (can be null, if application token is set)
170 * @param applicationToken (can be null, if username and password is set)
171 * @param connectionListener (can be null)
172 * @see #ConnectionManagerImpl(String, String, String, String)
174 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
175 ConnectionListener connectionListener) {
176 this.connListener = connectionListener;
177 init(hostAddress, -1, -1, username, password, null, false);
181 * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but through genApToken it
182 * can be set, if an application token will be automatically generated.
184 * @param hostAddress (must not be null)
185 * @param username (must not be null)
186 * @param password (must not be null)
187 * @param genAppToken (true = application token will be generated, otherwise false)
188 * @see #ConnectionManagerImpl(String, String, String, String)
190 public ConnectionManagerImpl(String hostAddress, String username, String password, boolean genAppToken) {
191 this.genAppToken = genAppToken;
192 init(hostAddress, -1, -1, username, password, null, false);
196 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but through genApToken
197 * it can be set, if an application token will be automatically generated.
199 * @param hostAddress (must not be null)
200 * @param username (can be null, if application token is set)
201 * @param password (can be null, if application token is set)
202 * @param applicationToken (can be null, if username and password is set)
203 * @param genAppToken (true = application token will be generated, otherwise false)
204 * @see #ConnectionManagerImpl(String, String, String, String)
206 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
207 boolean genAppToken) {
208 this.genAppToken = genAppToken;
209 init(hostAddress, -1, -1, username, password, applicationToken, false);
213 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but through
214 * acceptAllCerts it can be set, if all SSL-Certificates will be accept.
216 * @param hostAddress (must not be null)
217 * @param username (can be null, if application token is set)
218 * @param password (can be null, if application token is set)
219 * @param applicationToken (can be null, if username and password is set)
220 * @param genAppToken (true = application token will be generated, otherwise false)
221 * @param acceptAllCerts (true = all SSL-Certificates will be accept, otherwise false)
222 * @see #ConnectionManagerImpl(String, String, String, String, boolean)
224 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
225 boolean genAppToken, boolean acceptAllCerts) {
226 this.genAppToken = genAppToken;
227 init(hostAddress, -1, -1, username, password, applicationToken, acceptAllCerts);
231 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but a
232 * {@link ConnectionListener} can be set, too.
234 * @param hostAddress (must not be null)
235 * @param username (can be null, if application token is set)
236 * @param password (can be null, if application token is set)
237 * @param applicationToken (can be null, if username and password is set)
238 * @param genAppToken (true = application token will be generated, otherwise false)
239 * @param connectionListener (can be null)
240 * @see #ConnectionManagerImpl(String, String, String, String, boolean)
242 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
243 boolean genAppToken, ConnectionListener connectionListener) {
244 this.connListener = connectionListener;
245 this.genAppToken = genAppToken;
246 init(hostAddress, -1, -1, username, password, applicationToken, false);
249 private void init(String hostAddress, int connectionTimeout, int readTimeout, String username, String password,
250 String applicationToken, boolean acceptAllCerts) {
251 config = new Config(hostAddress, username, password, applicationToken);
252 if (connectionTimeout >= 0) {
253 config.setConnectionTimeout(connectionTimeout);
255 if (readTimeout >= 0) {
256 config.setReadTimeout(readTimeout);
258 init(config, acceptAllCerts);
261 private void init(Config config, boolean acceptAllCerts) {
262 this.config = config;
263 this.transport = new HttpTransportImpl(this, acceptAllCerts);
264 this.digitalSTROMClient = new DsAPIImpl(transport);
265 if (this.genAppToken) {
266 this.onNotAuthenticated();
271 public HttpTransport getHttpTransport() {
276 public DsAPI getDigitalSTROMAPI() {
277 return this.digitalSTROMClient;
281 public String getSessionToken() {
282 return this.sessionToken;
286 public String getNewSessionToken() {
287 if (this.genAppToken) {
288 String token = config.getAppToken();
289 if (token != null && !token.isBlank()) {
290 sessionToken = this.digitalSTROMClient.loginApplication(token);
291 } else if (codeIsAuthentificationFaild()) {
292 onNotAuthenticated();
295 sessionToken = this.digitalSTROMClient.login(this.config.getUserName(), this.config.getPassword());
301 public synchronized boolean checkConnection() {
302 return checkConnection(this.digitalSTROMClient.checkConnection(null));
305 private final short code = HttpURLConnection.HTTP_OK;
307 private boolean codeIsAuthentificationFaild() {
308 return this.code == HttpURLConnection.HTTP_FORBIDDEN;
312 public boolean checkConnection(int code) {
314 case HttpURLConnection.HTTP_INTERNAL_ERROR:
315 case HttpURLConnection.HTTP_OK:
316 if (!connectionEstablished) {
317 onConnectionResumed();
320 case HttpURLConnection.HTTP_UNAUTHORIZED:
321 connectionEstablished = false;
323 case HttpURLConnection.HTTP_FORBIDDEN:
324 getNewSessionToken();
325 if (sessionToken != null) {
326 if (!connectionEstablished) {
327 onConnectionResumed();
330 if (this.genAppToken) {
331 onNotAuthenticated();
333 connectionEstablished = false;
336 case ConnectionManager.MALFORMED_URL_EXCEPTION:
337 onConnectionLost(ConnectionListener.INVALID_URL);
339 case ConnectionManager.CONNECTION_EXCEPTION:
340 case ConnectionManager.SOCKET_TIMEOUT_EXCEPTION:
341 onConnectionLost(ConnectionListener.CONNECTON_TIMEOUT);
343 case ConnectionManager.SSL_HANDSHAKE_EXCEPTION:
344 onConnectionLost(ConnectionListener.SSL_HANDSHAKE_ERROR);
346 case ConnectionManager.GENERAL_EXCEPTION:
347 onConnectionLost(ConnectionListener.CONNECTION_LOST);
349 case ConnectionManager.UNKNOWN_HOST_EXCEPTION:
350 onConnectionLost(ConnectionListener.UNKNOWN_HOST);
352 case ConnectionManager.AUTHENTIFICATION_PROBLEM:
353 if (connListener != null) {
354 if (config.getAppToken() != null) {
355 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
356 ConnectionListener.WRONG_APP_TOKEN);
358 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
359 ConnectionListener.WRONG_USER_OR_PASSWORD);
363 case HttpURLConnection.HTTP_NOT_FOUND:
364 onConnectionLost(ConnectionListener.HOST_NOT_FOUND);
367 return connectionEstablished;
371 public boolean connectionEstablished() {
372 return connectionEstablished;
376 * This method is called whenever the connection to the digitalSTROM-Server is available,
377 * but requests are not allowed due to a missing or invalid authentication.
379 private void onNotAuthenticated() {
380 String applicationToken = null;
381 boolean isAuthenticated = false;
382 String token = config.getAppToken();
383 if (token != null && !token.isBlank()) {
384 sessionToken = digitalSTROMClient.loginApplication(token);
385 if (sessionToken != null) {
386 isAuthenticated = true;
388 if (connListener != null) {
389 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
390 ConnectionListener.WRONG_APP_TOKEN);
391 if (!checkUserPassword()) {
397 if (checkUserPassword()) {
398 if (!isAuthenticated) {
399 // if an application-token for the application exists, use this application-token and test host is
401 logger.debug("check existing application-tokens");
402 sessionToken = digitalSTROMClient.login(config.getUserName(), config.getPassword());
403 if (sessionToken != null) {
404 JsonObject jObj = digitalSTROMClient.query(sessionToken, QUERY_GET_ENABLED_APPLICATION_TOKENS);
407 if (jObj.get("enabled") != null && jObj.get("enabled").isJsonArray()) {
408 JsonArray jArray = jObj.get("enabled").getAsJsonArray();
409 // application-token check
410 for (int i = 0; i < jArray.size(); i++) {
411 JsonObject appToken = jArray.get(i).getAsJsonObject();
412 if (appToken.get("applicationName") != null && appToken.get("applicationName")
413 .getAsString().equals(config.getApplicationName())) {
414 // found application-token, set as application-token
415 applicationToken = appToken.get("token").getAsString();
416 logger.debug("found application-token {} for application {}", applicationToken,
417 config.getApplicationName());
422 if (applicationToken == null) {
423 // no token found, generate applicationToken
424 applicationToken = this.digitalSTROMClient
425 .requestAppplicationToken(config.getApplicationName());
427 "no application-token for application {} found, generate an application-token {}",
428 config.getApplicationName(), applicationToken);
429 if (applicationToken != null && !applicationToken.isBlank()) {
430 // enable applicationToken
431 if (!digitalSTROMClient.enableApplicationToken(applicationToken,
432 digitalSTROMClient.login(config.getUserName(), config.getPassword()))) {
433 // if enable failed set application-token = null so thats not will be set
434 applicationToken = null;
438 if (applicationToken != null) {
439 logger.debug("application-token can be used");
440 config.setAppToken(applicationToken);
441 isAuthenticated = true;
445 if (connListener != null) {
446 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
447 ConnectionListener.WRONG_USER_OR_PASSWORD);
452 // remove password and username, to don't store them persistently
453 if (isAuthenticated) {
454 config.removeUsernameAndPassword();
455 if (connListener != null) {
456 connListener.onConnectionStateChange(ConnectionListener.APPLICATION_TOKEN_GENERATED);
459 } else if (!isAuthenticated) {
460 if (connListener != null) {
461 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
462 ConnectionListener.NO_USER_PASSWORD);
467 private boolean checkUserPassword() {
468 String userName = config.getUserName();
469 String password = config.getPassword();
470 return userName != null && !userName.isBlank() && password != null && !password.isBlank();
474 * This method is called whenever the connection to the digitalSTROM-Server is lost.
478 private void onConnectionLost(String reason) {
479 if (connListener != null) {
480 connListener.onConnectionStateChange(ConnectionListener.CONNECTION_LOST, reason);
482 connectionEstablished = false;
486 * This method is called whenever the connection to the digitalSTROM-Server is resumed.
488 private void onConnectionResumed() {
489 if (connListener != null) {
490 connListener.onConnectionStateChange(ConnectionListener.CONNECTION_RESUMED);
492 connectionEstablished = true;
496 public void registerConnectionListener(ConnectionListener listener) {
497 this.connListener = listener;
501 public void unregisterConnectionListener() {
502 this.connListener = null;
506 public String getApplicationToken() {
507 return config.getAppToken();
511 public boolean removeApplicationToken() {
512 String token = config.getAppToken();
513 if (token != null && !token.isBlank()) {
514 return digitalSTROMClient.revokeToken(token, null);
520 public void updateConfig(String host, String username, String password, String applicationToken) {
521 init(host, -1, -1, username, password, applicationToken, false);
525 public void updateConfig(Config config) {
526 if (this.config != null) {
527 this.config.updateConfig(config);
529 this.config = config;
531 init(this.config, false);
535 public void configHasBeenUpdated() {
536 init(this.config, false);
540 public Config getConfig() {