]> git.basschouten.com Git - openhab-addons.git/blob
764c016e21fedea690ea4df0447ea6615fd092ce
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.mielecloud.internal.config.servlet;
14
15 import java.util.concurrent.TimeUnit;
16 import java.util.function.BooleanSupplier;
17
18 import javax.servlet.http.HttpServletRequest;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
23 import org.openhab.binding.mielecloud.internal.config.exception.BridgeCreationFailedException;
24 import org.openhab.binding.mielecloud.internal.config.exception.BridgeReconfigurationFailedException;
25 import org.openhab.binding.mielecloud.internal.handler.MieleBridgeHandler;
26 import org.openhab.binding.mielecloud.internal.util.LocaleValidator;
27 import org.openhab.core.config.discovery.DiscoveryResult;
28 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
29 import org.openhab.core.config.discovery.inbox.Inbox;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingRegistry;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingUID;
34 import org.openhab.core.thing.binding.ThingHandler;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Servlet that automatically creates a bridge and then redirects the browser to the account overview page.
40  *
41  * @author Björn Lange - Initial Contribution
42  */
43 @NonNullByDefault
44 public final class CreateBridgeServlet extends AbstractRedirectionServlet {
45     private static final String MIELE_CLOUD_BRIDGE_NAME = "Cloud Connector";
46     private static final String MIELE_CLOUD_BRIDGE_LABEL = "Miele@home Account";
47
48     private static final String LOCALE_PARAMETER_NAME = "locale";
49     public static final String BRIDGE_UID_PARAMETER_NAME = "bridgeUid";
50     public static final String EMAIL_PARAMETER_NAME = "email";
51
52     private static final long serialVersionUID = -2912042079128722887L;
53
54     private static final String DEFAULT_LOCALE = "en";
55
56     private static final long DISCOVERY_COMPLETION_TIMEOUT_IN_MILLISECONDS = 5000;
57     private static final long CHECK_INTERVAL_IN_MILLISECONDS = 100;
58
59     private final Logger logger = LoggerFactory.getLogger(CreateBridgeServlet.class);
60
61     private final Inbox inbox;
62     private final ThingRegistry thingRegistry;
63
64     private long onlineWaitTimeoutInMilliseconds = 5000;
65
66     /**
67      * Creates a new {@link CreateBridgeServlet}.
68      *
69      * @param inbox openHAB inbox for discovery results.
70      * @param thingRegistry openHAB thing registry.
71      */
72     public CreateBridgeServlet(Inbox inbox, ThingRegistry thingRegistry) {
73         this.inbox = inbox;
74         this.thingRegistry = thingRegistry;
75     }
76
77     public void setOnlineWaitTimeoutInMilliseconds(long onlineWaitTimeoutInMilliseconds) {
78         this.onlineWaitTimeoutInMilliseconds = onlineWaitTimeoutInMilliseconds;
79     }
80
81     @Override
82     protected String getRedirectionDestination(HttpServletRequest request) {
83         String bridgeUidString = request.getParameter(BRIDGE_UID_PARAMETER_NAME);
84         if (bridgeUidString == null || bridgeUidString.isEmpty()) {
85             logger.warn("Cannot create bridge: Bridge UID is missing.");
86             return "/mielecloud/failure?" + FailureServlet.MISSING_BRIDGE_UID_PARAMETER_NAME + "=true";
87         }
88
89         String email = request.getParameter(EMAIL_PARAMETER_NAME);
90         if (email == null || email.isEmpty()) {
91             logger.warn("Cannot create bridge: E-mail address is missing.");
92             return "/mielecloud/failure?" + FailureServlet.MISSING_EMAIL_PARAMETER_NAME + "=true";
93         }
94
95         ThingUID bridgeUid = null;
96         try {
97             bridgeUid = new ThingUID(bridgeUidString);
98         } catch (IllegalArgumentException e) {
99             logger.warn("Cannot create bridge: Bridge UID '{}' is malformed.", bridgeUid);
100             return "/mielecloud/failure?" + FailureServlet.MALFORMED_BRIDGE_UID_PARAMETER_NAME + "=true";
101         }
102
103         String locale = getValidLocale(request.getParameter(LOCALE_PARAMETER_NAME));
104
105         logger.debug("Auto configuring Miele account using locale '{}' (requested locale was '{}')", locale,
106                 request.getParameter(LOCALE_PARAMETER_NAME));
107         try {
108             Thing bridge = pairOrReconfigureBridge(locale, bridgeUid, email);
109             waitForBridgeToComeOnline(bridge);
110             return "/mielecloud";
111         } catch (BridgeReconfigurationFailedException e) {
112             logger.warn("{}", e.getMessage());
113             return "/mielecloud/success?" + SuccessServlet.BRIDGE_RECONFIGURATION_FAILED_PARAMETER_NAME + "=true&"
114                     + SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString + "&"
115                     + SuccessServlet.EMAIL_PARAMETER_NAME + "=" + email;
116         } catch (BridgeCreationFailedException e) {
117             logger.warn("Thing creation failed because there was no binding available that supports the thing.");
118             return "/mielecloud/success?" + SuccessServlet.BRIDGE_CREATION_FAILED_PARAMETER_NAME + "=true&"
119                     + SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString + "&"
120                     + SuccessServlet.EMAIL_PARAMETER_NAME + "=" + email;
121         }
122     }
123
124     private Thing pairOrReconfigureBridge(String locale, ThingUID bridgeUid, String email) {
125         DiscoveryResult result = DiscoveryResultBuilder.create(bridgeUid)
126                 .withRepresentationProperty(Thing.PROPERTY_MODEL_ID).withLabel(MIELE_CLOUD_BRIDGE_LABEL)
127                 .withProperty(Thing.PROPERTY_MODEL_ID, MIELE_CLOUD_BRIDGE_NAME)
128                 .withProperty(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE, locale)
129                 .withProperty(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL, email).build();
130         if (thingRegistry.get(bridgeUid) != null) {
131             return reconfigureBridge(bridgeUid);
132         } else {
133             inbox.add(result);
134             return pairBridge(bridgeUid);
135         }
136     }
137
138     private Thing pairBridge(ThingUID thingUid) {
139         Thing thing = inbox.approve(thingUid, MIELE_CLOUD_BRIDGE_LABEL, null);
140         if (thing == null) {
141             throw new BridgeCreationFailedException();
142         }
143
144         logger.debug("Successfully created bridge {}", thingUid);
145         return thing;
146     }
147
148     private Thing reconfigureBridge(ThingUID thingUid) {
149         logger.debug("Thing already exists. Modifying configuration.");
150         Thing thing = thingRegistry.get(thingUid);
151         if (thing == null) {
152             throw new BridgeReconfigurationFailedException(
153                     "Cannot modify non existing bridge: Could neither add bridge via inbox nor find existing bridge.");
154         }
155
156         ThingHandler handler = thing.getHandler();
157         if (handler == null) {
158             throw new BridgeReconfigurationFailedException("Bridge exists but has no handler.");
159         }
160         if (!(handler instanceof MieleBridgeHandler)) {
161             throw new BridgeReconfigurationFailedException("Bridge handler is of wrong type, expected '"
162                     + MieleBridgeHandler.class.getSimpleName() + "' but got '" + handler.getClass().getName() + "'.");
163         }
164
165         MieleBridgeHandler bridgeHandler = (MieleBridgeHandler) handler;
166         bridgeHandler.disposeWebservice();
167         bridgeHandler.initializeWebservice();
168
169         return thing;
170     }
171
172     private String getValidLocale(@Nullable String localeParameterValue) {
173         if (localeParameterValue == null || localeParameterValue.isEmpty()
174                 || !LocaleValidator.isValidLanguage(localeParameterValue)) {
175             return DEFAULT_LOCALE;
176         } else {
177             return localeParameterValue;
178         }
179     }
180
181     private void waitForBridgeToComeOnline(Thing bridge) {
182         try {
183             waitForConditionWithTimeout(() -> bridge.getStatus() == ThingStatus.ONLINE,
184                     onlineWaitTimeoutInMilliseconds);
185             waitForConditionWithTimeout(new DiscoveryResultCountDoesNotChangeCondition(),
186                     DISCOVERY_COMPLETION_TIMEOUT_IN_MILLISECONDS);
187         } catch (InterruptedException e) {
188             Thread.currentThread().interrupt();
189         }
190     }
191
192     private void waitForConditionWithTimeout(BooleanSupplier condition, long timeoutInMilliseconds)
193             throws InterruptedException {
194         long remainingWaitTime = timeoutInMilliseconds;
195         while (!condition.getAsBoolean() && remainingWaitTime > 0) {
196             TimeUnit.MILLISECONDS.sleep(CHECK_INTERVAL_IN_MILLISECONDS);
197             remainingWaitTime -= CHECK_INTERVAL_IN_MILLISECONDS;
198         }
199     }
200
201     private class DiscoveryResultCountDoesNotChangeCondition implements BooleanSupplier {
202         private long previousDiscoveryResultCount = 0;
203
204         @Override
205         public boolean getAsBoolean() {
206             var discoveryResultCount = countOwnDiscoveryResults();
207             var discoveryResultCountUnchanged = previousDiscoveryResultCount == discoveryResultCount;
208             previousDiscoveryResultCount = discoveryResultCount;
209             return discoveryResultCountUnchanged;
210         }
211
212         private long countOwnDiscoveryResults() {
213             return inbox.stream().map(DiscoveryResult::getBindingId)
214                     .filter(MieleCloudBindingConstants.BINDING_ID::equals).count();
215         }
216     }
217 }