2 * Copyright (c) 2010-2022 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.mielecloud.internal.config.servlet;
15 import java.util.concurrent.TimeUnit;
16 import java.util.function.BooleanSupplier;
18 import javax.servlet.http.HttpServletRequest;
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;
39 * Servlet that automatically creates a bridge and then redirects the browser to the account overview page.
41 * @author Björn Lange - Initial Contribution
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";
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";
52 private static final long serialVersionUID = -2912042079128722887L;
54 private static final String DEFAULT_LOCALE = "en";
56 private static final long ONLINE_WAIT_TIMEOUT_IN_MILLISECONDS = 5000;
57 private static final long DISCOVERY_COMPLETION_TIMEOUT_IN_MILLISECONDS = 5000;
58 private static final long CHECK_INTERVAL_IN_MILLISECONDS = 100;
60 private final Logger logger = LoggerFactory.getLogger(CreateBridgeServlet.class);
62 private final Inbox inbox;
63 private final ThingRegistry thingRegistry;
66 * Creates a new {@link CreateBridgeServlet}.
68 * @param inbox openHAB inbox for discovery results.
69 * @param thingRegistry openHAB thing registry.
71 public CreateBridgeServlet(Inbox inbox, ThingRegistry thingRegistry) {
73 this.thingRegistry = thingRegistry;
77 protected String getRedirectionDestination(HttpServletRequest request) {
78 String bridgeUidString = request.getParameter(BRIDGE_UID_PARAMETER_NAME);
79 if (bridgeUidString == null || bridgeUidString.isEmpty()) {
80 logger.warn("Cannot create bridge: Bridge UID is missing.");
81 return "/mielecloud/failure?" + FailureServlet.MISSING_BRIDGE_UID_PARAMETER_NAME + "=true";
84 String email = request.getParameter(EMAIL_PARAMETER_NAME);
85 if (email == null || email.isEmpty()) {
86 logger.warn("Cannot create bridge: E-mail address is missing.");
87 return "/mielecloud/failure?" + FailureServlet.MISSING_EMAIL_PARAMETER_NAME + "=true";
90 ThingUID bridgeUid = null;
92 bridgeUid = new ThingUID(bridgeUidString);
93 } catch (IllegalArgumentException e) {
94 logger.warn("Cannot create bridge: Bridge UID '{}' is malformed.", bridgeUid);
95 return "/mielecloud/failure?" + FailureServlet.MALFORMED_BRIDGE_UID_PARAMETER_NAME + "=true";
98 String locale = getValidLocale(request.getParameter(LOCALE_PARAMETER_NAME));
100 logger.debug("Auto configuring Miele account using locale '{}' (requested locale was '{}')", locale,
101 request.getParameter(LOCALE_PARAMETER_NAME));
103 Thing bridge = pairOrReconfigureBridge(locale, bridgeUid, email);
104 waitForBridgeToComeOnline(bridge);
105 return "/mielecloud";
106 } catch (BridgeReconfigurationFailedException e) {
107 logger.warn("{}", e.getMessage());
108 return "/mielecloud/success?" + SuccessServlet.BRIDGE_RECONFIGURATION_FAILED_PARAMETER_NAME + "=true&"
109 + SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString + "&"
110 + SuccessServlet.EMAIL_PARAMETER_NAME + "=" + email;
111 } catch (BridgeCreationFailedException e) {
112 logger.warn("Thing creation failed because there was no binding available that supports the thing.");
113 return "/mielecloud/success?" + SuccessServlet.BRIDGE_CREATION_FAILED_PARAMETER_NAME + "=true&"
114 + SuccessServlet.BRIDGE_UID_PARAMETER_NAME + "=" + bridgeUidString + "&"
115 + SuccessServlet.EMAIL_PARAMETER_NAME + "=" + email;
119 private Thing pairOrReconfigureBridge(String locale, ThingUID bridgeUid, String email) {
120 DiscoveryResult result = DiscoveryResultBuilder.create(bridgeUid)
121 .withRepresentationProperty(Thing.PROPERTY_MODEL_ID).withLabel(MIELE_CLOUD_BRIDGE_LABEL)
122 .withProperty(Thing.PROPERTY_MODEL_ID, MIELE_CLOUD_BRIDGE_NAME)
123 .withProperty(MieleCloudBindingConstants.CONFIG_PARAM_LOCALE, locale)
124 .withProperty(MieleCloudBindingConstants.CONFIG_PARAM_EMAIL, email).build();
125 if (inbox.add(result)) {
126 return pairBridge(bridgeUid);
128 return reconfigureBridge(bridgeUid);
132 private Thing pairBridge(ThingUID thingUid) {
133 Thing thing = inbox.approve(thingUid, MIELE_CLOUD_BRIDGE_LABEL, null);
135 throw new BridgeCreationFailedException();
138 logger.debug("Successfully created bridge {}", thingUid);
142 private Thing reconfigureBridge(ThingUID thingUid) {
143 logger.debug("Thing already exists. Modifying configuration.");
144 Thing thing = thingRegistry.get(thingUid);
146 throw new BridgeReconfigurationFailedException(
147 "Cannot modify non existing bridge: Could neither add bridge via inbox nor find existing bridge.");
150 ThingHandler handler = thing.getHandler();
151 if (handler == null) {
152 throw new BridgeReconfigurationFailedException("Bridge exists but has no handler.");
154 if (!(handler instanceof MieleBridgeHandler)) {
155 throw new BridgeReconfigurationFailedException("Bridge handler is of wrong type, expected '"
156 + MieleBridgeHandler.class.getSimpleName() + "' but got '" + handler.getClass().getName() + "'.");
159 MieleBridgeHandler bridgeHandler = (MieleBridgeHandler) handler;
160 bridgeHandler.disposeWebservice();
161 bridgeHandler.initializeWebservice();
166 private String getValidLocale(@Nullable String localeParameterValue) {
167 if (localeParameterValue == null || localeParameterValue.isEmpty()
168 || !LocaleValidator.isValidLanguage(localeParameterValue)) {
169 return DEFAULT_LOCALE;
171 return localeParameterValue;
175 private void waitForBridgeToComeOnline(Thing bridge) {
177 waitForConditionWithTimeout(() -> bridge.getStatus() == ThingStatus.ONLINE,
178 ONLINE_WAIT_TIMEOUT_IN_MILLISECONDS);
179 waitForConditionWithTimeout(new DiscoveryResultCountDoesNotChangeCondition(),
180 DISCOVERY_COMPLETION_TIMEOUT_IN_MILLISECONDS);
181 } catch (InterruptedException e) {
182 Thread.currentThread().interrupt();
186 private void waitForConditionWithTimeout(BooleanSupplier condition, long timeoutInMilliseconds)
187 throws InterruptedException {
188 long remainingWaitTime = timeoutInMilliseconds;
189 while (!condition.getAsBoolean() && remainingWaitTime > 0) {
190 TimeUnit.MILLISECONDS.sleep(CHECK_INTERVAL_IN_MILLISECONDS);
191 remainingWaitTime -= CHECK_INTERVAL_IN_MILLISECONDS;
195 private class DiscoveryResultCountDoesNotChangeCondition implements BooleanSupplier {
196 private long previousDiscoveryResultCount = 0;
199 public boolean getAsBoolean() {
200 var discoveryResultCount = countOwnDiscoveryResults();
201 var discoveryResultCountUnchanged = previousDiscoveryResultCount == discoveryResultCount;
202 previousDiscoveryResultCount = discoveryResultCount;
203 return discoveryResultCountUnchanged;
206 private long countOwnDiscoveryResults() {
207 return inbox.stream().map(DiscoveryResult::getBindingId)
208 .filter(MieleCloudBindingConstants.BINDING_ID::equals).count();