2 * Copyright (c) 2010-2020 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.apache.commons.lang.StringUtils;
18 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
19 import org.openhab.binding.digitalstrom.internal.lib.listener.ConnectionListener;
20 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
21 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
22 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.HttpTransport;
23 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.DsAPIImpl;
24 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.HttpTransportImpl;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
28 import com.google.gson.JsonArray;
29 import com.google.gson.JsonObject;
32 * The {@link ConnectionManagerImpl} is the implementation of the {@link ConnectionManager}.
34 * @author Michael Ochel - Initial contribution
35 * @author Matthias Siegele - Initial contribution
37 public class ConnectionManagerImpl implements ConnectionManager {
40 * Query to get all enabled application tokens. Can be executed with {@link DsAPI#query(String, String)} or
41 * {@link DsAPI#query2(String, String)}.
43 public final String QUERY_GET_ENABLED_APPLICATION_TOKENS = "/system/security/applicationTokens/enabled/*(*)";
44 private final Logger logger = LoggerFactory.getLogger(ConnectionManagerImpl.class);
46 private Config config;
47 private ConnectionListener connListener;
48 private HttpTransport transport;
49 private String sessionToken;
50 private Boolean connectionEstablished = false;
51 private boolean genAppToken;
52 private DsAPI digitalSTROMClient;
55 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but the connection
56 * timeout and read timeout can be set, too.
58 * @param hostArddress (must not be null)
59 * @param connectTimeout (if connectTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be
61 * @param readTimeout (if readTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be set)
62 * @param username (can be null, if application token is set)
63 * @param password (can be null, if application token is set
64 * @param applicationToken (can be null, if username and password is set)
65 * @see #ConnectionManagerImpl(String, String, String, String)
67 public ConnectionManagerImpl(String hostArddress, int connectTimeout, int readTimeout, String username,
68 String password, String applicationToken) {
69 init(hostArddress, connectTimeout, readTimeout, username, password, applicationToken, false);
73 * Creates a new {@link ConnectionManagerImpl} through a {@link Config} object, which has all configurations set.
75 * @param config (must not be null)
77 public ConnectionManagerImpl(Config config) {
82 * The same constructor like {@link #ConnectionManagerImpl(Config)}, but a {@link ConnectionListener} can be
85 * @param config (must not be null)
86 * @param connectionListener (can be null)
87 * @see #ConnectionManagerImpl(Config)
89 public ConnectionManagerImpl(Config config, ConnectionListener connectionListener) {
90 this.connListener = connectionListener;
95 * The same constructor like {@link #ConnectionManagerImpl(Config, ConnectionListener)}, but through genApToken it
96 * can be set, if a application token will be automatically generated.
98 * @param config (must not be null)
99 * @param connectionListener (can be null)
100 * @param genAppToken (true = application token will be generated, otherwise false)
101 * @see #ConnectionManagerImpl(Config, ConnectionListener)
103 public ConnectionManagerImpl(Config config, ConnectionListener connectionListener, boolean genAppToken) {
104 this.connListener = connectionListener;
105 this.genAppToken = genAppToken;
110 * Creates a new {@link ConnectionManagerImpl} with the given parameters, which are needed to create the
111 * {@link HttpTransport} and to login into the digitalSTROM server. If the application token is null and the
112 * username and password are valid, a application token will be automatically generated or a existing application
113 * token for the at {@link Config#getApplicationName()} set application name will be set.
115 * @param hostAddress (must not be null)
116 * @param username (can be null, if application token is set)
117 * @param password (can be null, if application token is set
118 * @param applicationToken (can be null, if username and password is set)
120 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken) {
121 init(hostAddress, -1, -1, username, password, applicationToken, false);
125 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without username
128 * @param hostAddress (must not be null)
129 * @param applicationToken (must not be null)
131 public ConnectionManagerImpl(String hostAddress, String applicationToken) {
132 init(hostAddress, -1, -1, null, null, applicationToken, false);
136 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without application
139 * @param hostAddress (must not be null)
140 * @param username (must not be null)
141 * @param password (must not be null)
142 * @see #ConnectionManagerImpl(String, String, String, String)
144 public ConnectionManagerImpl(String hostAddress, String username, String password) {
145 init(hostAddress, -1, -1, username, password, null, false);
149 * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but a
150 * {@link ConnectionListener} can be set, too.
152 * @param hostAddress (must not be null)
153 * @param username (must not be null)
154 * @param password (must not be null)
155 * @param connectionListener (can be null)
156 * @see #ConnectionManagerImpl(String, String, String)
158 public ConnectionManagerImpl(String hostAddress, String username, String password,
159 ConnectionListener connectionListener) {
160 this.connListener = connectionListener;
161 init(hostAddress, -1, -1, username, password, null, false);
165 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but a
166 * {@link ConnectionListener} can be set, too.
168 * @param hostAddress (must not be null)
169 * @param username (can be null, if application token is set)
170 * @param password (can be null, if application token is set)
171 * @param applicationToken (can be null, if username and password is set)
172 * @param connectionListener (can be null)
173 * @see #ConnectionManagerImpl(String, String, String, String)
175 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
176 ConnectionListener connectionListener) {
177 this.connListener = connectionListener;
178 init(hostAddress, -1, -1, username, password, null, false);
182 * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but through genApToken it
183 * can be set, if a application token will be automatically generated.
185 * @param hostAddress (must not be null)
186 * @param username (must not be null)
187 * @param password (must not be null)
188 * @param genAppToken (true = application token will be generated, otherwise false)
189 * @see #ConnectionManagerImpl(String, String, String, String)
191 public ConnectionManagerImpl(String hostAddress, String username, String password, boolean genAppToken) {
192 this.genAppToken = genAppToken;
193 init(hostAddress, -1, -1, username, password, null, false);
197 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but through genApToken
198 * it can be set, if a application token will be automatically generated.
200 * @param hostAddress (must not be null)
201 * @param username (can be null, if application token is set)
202 * @param password (can be null, if application token is set)
203 * @param applicationToken (can be null, if username and password is set)
204 * @param genAppToken (true = application token will be generated, otherwise false)
205 * @see #ConnectionManagerImpl(String, String, String, String)
207 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
208 boolean genAppToken) {
209 this.genAppToken = genAppToken;
210 init(hostAddress, -1, -1, username, password, applicationToken, false);
214 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but through
215 * acceptAllCerts it can be set, if all SSL-Certificates will be accept.
217 * @param hostAddress (must not be null)
218 * @param username (can be null, if application token is set)
219 * @param password (can be null, if application token is set)
220 * @param applicationToken (can be null, if username and password is set)
221 * @param genAppToken (true = application token will be generated, otherwise false)
222 * @param acceptAllCerts (true = all SSL-Certificates will be accept, otherwise false)
223 * @see #ConnectionManagerImpl(String, String, String, String, boolean)
225 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
226 boolean genAppToken, boolean acceptAllCerts) {
227 this.genAppToken = genAppToken;
228 init(hostAddress, -1, -1, username, password, applicationToken, acceptAllCerts);
232 * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but a
233 * {@link ConnectionListener} can be set, too.
235 * @param hostAddress (must not be null)
236 * @param username (can be null, if application token is set)
237 * @param password (can be null, if application token is set)
238 * @param applicationToken (can be null, if username and password is set)
239 * @param genAppToken (true = application token will be generated, otherwise false)
240 * @param connectionListener (can be null)
241 * @see #ConnectionManagerImpl(String, String, String, String, boolean)
243 public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
244 boolean genAppToken, ConnectionListener connectionListener) {
245 this.connListener = connectionListener;
246 this.genAppToken = genAppToken;
247 init(hostAddress, -1, -1, username, password, applicationToken, false);
250 private void init(String hostAddress, int connectionTimeout, int readTimeout, String username, String password,
251 String applicationToken, boolean acceptAllCerts) {
252 config = new Config(hostAddress, username, password, applicationToken);
253 if (connectionTimeout >= 0) {
254 config.setConnectionTimeout(connectionTimeout);
256 if (readTimeout >= 0) {
257 config.setReadTimeout(readTimeout);
259 init(config, acceptAllCerts);
262 private void init(Config config, boolean acceptAllCerts) {
263 this.config = config;
264 this.transport = new HttpTransportImpl(this, acceptAllCerts);
265 this.digitalSTROMClient = new DsAPIImpl(transport);
266 if (this.genAppToken) {
267 this.onNotAuthenticated();
272 public HttpTransport getHttpTransport() {
277 public DsAPI getDigitalSTROMAPI() {
278 return this.digitalSTROMClient;
282 public String getSessionToken() {
283 return this.sessionToken;
287 public String getNewSessionToken() {
288 if (this.genAppToken) {
289 if (StringUtils.isNotBlank(config.getAppToken())) {
290 sessionToken = this.digitalSTROMClient.loginApplication(config.getAppToken());
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 if (StringUtils.isNotBlank(config.getAppToken())) {
383 sessionToken = digitalSTROMClient.loginApplication(config.getAppToken());
384 if (sessionToken != null) {
385 isAuthenticated = true;
387 if (connListener != null) {
388 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
389 ConnectionListener.WRONG_APP_TOKEN);
390 if (!checkUserPassword()) {
396 if (checkUserPassword()) {
397 if (!isAuthenticated) {
398 // if an application-token for the application exists, use this application-token and test host is
400 logger.debug("check existing application-tokens");
401 sessionToken = digitalSTROMClient.login(config.getUserName(), config.getPassword());
402 if (sessionToken != null) {
403 JsonObject jObj = digitalSTROMClient.query(sessionToken, QUERY_GET_ENABLED_APPLICATION_TOKENS);
406 if (jObj.get("enabled") != null && jObj.get("enabled").isJsonArray()) {
407 JsonArray jArray = jObj.get("enabled").getAsJsonArray();
408 // application-token check
409 for (int i = 0; i < jArray.size(); i++) {
410 JsonObject appToken = jArray.get(i).getAsJsonObject();
411 if (appToken.get("applicationName") != null && appToken.get("applicationName")
412 .getAsString().equals(config.getApplicationName())) {
413 // found application-token, set as application-token
414 applicationToken = appToken.get("token").getAsString();
415 logger.debug("found application-token {} for application {}", applicationToken,
416 config.getApplicationName());
421 if (applicationToken == null) {
422 // no token found, generate applicationToken
423 applicationToken = this.digitalSTROMClient
424 .requestAppplicationToken(config.getApplicationName());
426 "no application-token for application {} found, generate a application-token {}",
427 config.getApplicationName(), applicationToken);
428 if (StringUtils.isNotBlank(applicationToken)) {
429 // enable applicationToken
430 if (!digitalSTROMClient.enableApplicationToken(applicationToken,
431 digitalSTROMClient.login(config.getUserName(), config.getPassword()))) {
432 // if enable failed set application-token = null so thats not will be set
433 applicationToken = null;
437 if (applicationToken != null) {
438 logger.debug("application-token can be used");
439 config.setAppToken(applicationToken);
440 isAuthenticated = true;
444 if (connListener != null) {
445 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
446 ConnectionListener.WRONG_USER_OR_PASSWORD);
451 // remove password and username, to don't store them persistently
452 if (isAuthenticated) {
453 config.removeUsernameAndPassword();
454 if (connListener != null) {
455 connListener.onConnectionStateChange(ConnectionListener.APPLICATION_TOKEN_GENERATED);
458 } else if (!isAuthenticated) {
459 if (connListener != null) {
460 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
461 ConnectionListener.NO_USER_PASSWORD);
466 private boolean checkUserPassword() {
467 if (StringUtils.isNotBlank(config.getUserName()) && StringUtils.isNotBlank(config.getPassword())) {
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 if (StringUtils.isNotBlank(config.getAppToken())) {
513 return digitalSTROMClient.revokeToken(config.getAppToken(), null);
519 public void updateConfig(String host, String username, String password, String applicationToken) {
520 init(host, -1, -1, username, password, applicationToken, false);
524 public void updateConfig(Config config) {
525 if (this.config != null) {
526 this.config.updateConfig(config);
528 this.config = config;
530 init(this.config, false);
534 public void configHasBeenUpdated() {
535 init(this.config, false);
539 public Config getConfig() {