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.boschindego.internal.handler;
15 import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
22 import java.util.concurrent.ConcurrentHashMap;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.openhab.binding.boschindego.internal.AuthorizationController;
27 import org.openhab.binding.boschindego.internal.AuthorizationListener;
28 import org.openhab.binding.boschindego.internal.AuthorizationProvider;
29 import org.openhab.binding.boschindego.internal.IndegoController;
30 import org.openhab.binding.boschindego.internal.discovery.IndegoDiscoveryService;
31 import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
32 import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
33 import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
34 import org.openhab.core.auth.client.oauth2.OAuthClientService;
35 import org.openhab.core.auth.client.oauth2.OAuthException;
36 import org.openhab.core.auth.client.oauth2.OAuthFactory;
37 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseBridgeHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.openhab.core.types.Command;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * The {@link BoschAccountHandler} is responsible for handling commands, which are
50 * sent to one of the channels.
52 * @author Jacob Laursen - Initial contribution
55 public class BoschAccountHandler extends BaseBridgeHandler implements AuthorizationListener {
57 private final Logger logger = LoggerFactory.getLogger(BoschAccountHandler.class);
58 private final OAuthFactory oAuthFactory;
59 private final Set<AuthorizationListener> authorizationListeners = ConcurrentHashMap.newKeySet();
61 private OAuthClientService oAuthClientService;
62 private AuthorizationController authorizationController;
63 private IndegoController controller;
65 public BoschAccountHandler(Bridge bridge, HttpClient httpClient, OAuthFactory oAuthFactory) {
68 this.oAuthFactory = oAuthFactory;
70 oAuthClientService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(), BSK_TOKEN_URI,
71 BSK_AUTH_URI, BSK_CLIENT_ID, null, BSK_SCOPE, false);
72 authorizationController = new AuthorizationController(oAuthClientService, this);
73 controller = new IndegoController(httpClient, authorizationController);
77 public void initialize() {
78 OAuthClientService oAuthClientService = oAuthFactory.getOAuthClientService(thing.getUID().getAsString());
79 if (oAuthClientService == null) {
80 throw new IllegalStateException("OAuth handle doesn't exist");
82 authorizationController.setOAuthClientService(oAuthClientService);
83 this.oAuthClientService = oAuthClientService;
85 updateStatus(ThingStatus.UNKNOWN);
87 scheduler.execute(() -> {
89 authorizationController.getAccessToken();
90 updateStatus(ThingStatus.ONLINE);
91 } catch (OAuthException | OAuthResponseException e) {
92 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
93 "@text/offline.conf-error.oauth2-unauthorized");
94 } catch (IOException e) {
95 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
96 "@text/offline.comm-error.oauth2-authorization-failed");
102 public void dispose() {
103 oAuthFactory.ungetOAuthService(thing.getUID().getAsString());
104 authorizationListeners.clear();
108 public void handleCommand(ChannelUID channelUID, Command command) {
112 public void handleRemoval() {
113 oAuthFactory.deleteServiceAndAccessToken(thing.getUID().getAsString());
114 super.handleRemoval();
117 public AuthorizationProvider getAuthorizationProvider() {
118 return authorizationController;
121 public void registerAuthorizationListener(AuthorizationListener listener) {
122 if (!authorizationListeners.add(listener)) {
123 throw new IllegalStateException("Attempt to register already registered authorization listener");
127 public void unregisterAuthorizationListener(AuthorizationListener listener) {
128 if (!authorizationListeners.remove(listener)) {
129 throw new IllegalStateException("Attempt to unregister authorization listener which is not registered");
133 public void onSuccessfulAuthorization() {
134 updateStatus(ThingStatus.ONLINE);
137 public void onFailedAuthorization(Throwable throwable) {
138 logger.debug("Authorization failure", throwable);
139 if (throwable instanceof IndegoAuthenticationException) {
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
141 "@text/offline.comm-error.authentication-failure");
143 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, throwable.getMessage());
147 public void onAuthorizationFlowCompleted() {
152 public Collection<Class<? extends ThingHandlerService>> getServices() {
153 return List.of(IndegoDiscoveryService.class);
156 public void authorize(String authCode) throws IndegoAuthenticationException {
157 logger.info("Attempting to authorize using authorization code");
160 oAuthClientService.getAccessTokenResponseByAuthorizationCode(authCode, BSK_REDIRECT_URI);
161 } catch (OAuthException | OAuthResponseException | IOException e) {
162 throw new IndegoAuthenticationException("Failed to authorize by authorization code " + authCode, e);
165 logger.info("Authorization completed successfully");
167 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/online.authorization-completed");
169 authorizationListeners.forEach(l -> l.onAuthorizationFlowCompleted());
172 public Collection<DevicePropertiesResponse> getDevices() throws IndegoException {
173 Collection<String> serialNumbers = controller.getSerialNumbers();
174 List<DevicePropertiesResponse> devices = new ArrayList<DevicePropertiesResponse>(serialNumbers.size());
176 for (String serialNumber : serialNumbers) {
177 DevicePropertiesResponse properties = controller.getDeviceProperties(serialNumber);
178 if (properties.serialNumber == null) {
179 properties.serialNumber = serialNumber;
181 devices.add(properties);