]> git.basschouten.com Git - openhab-addons.git/blob
de8520c742ac7e001289e4f003d9e68af3447c62
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.homeconnect.internal.servlet;
14
15 import static java.nio.charset.StandardCharsets.UTF_8;
16 import static java.time.ZonedDateTime.now;
17 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
18 import static org.openhab.binding.homeconnect.internal.HomeConnectBindingConstants.*;
19
20 import java.io.IOException;
21 import java.time.ZonedDateTime;
22 import java.time.format.DateTimeFormatter;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.concurrent.CopyOnWriteArraySet;
30 import java.util.stream.Collectors;
31
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServlet;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36 import javax.ws.rs.core.MediaType;
37
38 import org.eclipse.jdt.annotation.NonNullByDefault;
39 import org.eclipse.jdt.annotation.Nullable;
40 import org.eclipse.jetty.http.HttpStatus;
41 import org.openhab.binding.homeconnect.internal.client.exception.ApplianceOfflineException;
42 import org.openhab.binding.homeconnect.internal.client.exception.AuthorizationException;
43 import org.openhab.binding.homeconnect.internal.client.exception.CommunicationException;
44 import org.openhab.binding.homeconnect.internal.client.model.ApiRequest;
45 import org.openhab.binding.homeconnect.internal.handler.AbstractHomeConnectThingHandler;
46 import org.openhab.binding.homeconnect.internal.handler.HomeConnectBridgeHandler;
47 import org.openhab.core.OpenHAB;
48 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
49 import org.openhab.core.auth.client.oauth2.OAuthException;
50 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
51 import org.osgi.framework.FrameworkUtil;
52 import org.osgi.service.component.annotations.Activate;
53 import org.osgi.service.component.annotations.Component;
54 import org.osgi.service.component.annotations.Deactivate;
55 import org.osgi.service.component.annotations.Reference;
56 import org.osgi.service.component.annotations.ServiceScope;
57 import org.osgi.service.http.HttpService;
58 import org.osgi.service.http.NamespaceException;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.thymeleaf.TemplateEngine;
62 import org.thymeleaf.context.WebContext;
63 import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
64 import org.thymeleaf.templatemode.TemplateMode;
65 import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
66
67 import com.google.gson.Gson;
68 import com.google.gson.GsonBuilder;
69 import com.google.gson.JsonPrimitive;
70 import com.google.gson.JsonSerializer;
71
72 /**
73  *
74  * Home Connect servlet.
75  *
76  * @author Jonas BrĂ¼stel - Initial Contribution
77  */
78 @NonNullByDefault
79 @Component(service = HomeConnectServlet.class, scope = ServiceScope.SINGLETON, immediate = true)
80 public class HomeConnectServlet extends HttpServlet {
81
82     private static final String SLASH = "/";
83     private static final String SERVLET_NAME = "homeconnect";
84     private static final String SERVLET_PATH = SLASH + SERVLET_NAME;
85     private static final String ASSETS_PATH = SERVLET_PATH + "/asset";
86     private static final String ROOT_PATH = SLASH;
87     private static final String APPLIANCES_PATH = "/appliances";
88     private static final String REQUEST_LOG_PATH = "/log/requests";
89     private static final String EVENT_LOG_PATH = "/log/events";
90     private static final String DEFAULT_CONTENT_TYPE = "text/html; charset=UTF-8";
91     private static final String PARAM_CODE = "code";
92     private static final String PARAM_STATE = "state";
93     private static final String PARAM_EXPORT = "export";
94     private static final String PARAM_ACTION = "action";
95     private static final String PARAM_BRIDGE_ID = "bridgeId";
96     private static final String PARAM_THING_ID = "thingId";
97     private static final String PARAM_PATH = "path";
98     private static final String ACTION_AUTHORIZE = "authorize";
99     private static final String ACTION_CLEAR_CREDENTIALS = "clearCredentials";
100     private static final String ACTION_SHOW_DETAILS = "show-details";
101     private static final String ACTION_ALL_PROGRAMS = "all-programs";
102     private static final String ACTION_AVAILABLE_PROGRAMS = "available-programs";
103     private static final String ACTION_SELECTED_PROGRAM = "selected-program";
104     private static final String ACTION_ACTIVE_PROGRAM = "active-program";
105     private static final String ACTION_OPERATION_STATE = "operation-state";
106     private static final String ACTION_POWER_STATE = "power-state";
107     private static final String ACTION_DOOR_STATE = "door-state";
108     private static final String ACTION_REMOTE_START_ALLOWED = "remote-control-start-allowed";
109     private static final String ACTION_REMOTE_CONTROL_ACTIVE = "remote-control-active";
110     private static final String ACTION_PUT_RAW = "put-raw";
111     private static final String ACTION_GET_RAW = "get-raw";
112     private static final DateTimeFormatter FILE_EXPORT_DTF = ISO_OFFSET_DATE_TIME;
113     private static final String EMPTY_RESPONSE = "{}";
114     private static final long serialVersionUID = -2449763690208703307L;
115
116     private final Logger logger = LoggerFactory.getLogger(HomeConnectServlet.class);
117     private final HttpService httpService;
118     private final TemplateEngine templateEngine;
119     private final Set<HomeConnectBridgeHandler> bridgeHandlers;
120     private final Gson gson;
121
122     @Activate
123     public HomeConnectServlet(@Reference HttpService httpService) {
124         bridgeHandlers = new CopyOnWriteArraySet<>();
125         gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, (JsonSerializer<ZonedDateTime>) (src,
126                 typeOfSrc, context) -> new JsonPrimitive(src.format(DateTimeFormatter.ISO_DATE_TIME))).create();
127         this.httpService = httpService;
128
129         // register servlet
130         try {
131             logger.debug("Initialize Home Connect configuration servlet ({})", SERVLET_PATH);
132             httpService.registerServlet(SERVLET_PATH, this, null, httpService.createDefaultHttpContext());
133             httpService.registerResources(ASSETS_PATH, "assets", null);
134         } catch (ServletException | NamespaceException e) {
135             logger.warn("Could not register Home Connect servlet! ({})", SERVLET_PATH, e);
136         }
137
138         // setup template engine
139         ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(getServletContext());
140         templateResolver.setTemplateMode(TemplateMode.HTML);
141         templateResolver.setPrefix("/templates/");
142         templateResolver.setSuffix(".html");
143         templateResolver.setCacheable(true);
144         templateEngine = new TemplateEngine();
145         templateEngine.addDialect(new Java8TimeDialect());
146         templateEngine.setTemplateResolver(templateResolver);
147     }
148
149     @Deactivate
150     protected void dispose() {
151         httpService.unregister(SERVLET_PATH);
152         httpService.unregister(ASSETS_PATH);
153     }
154
155     @Override
156     protected void doGet(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response)
157             throws IOException {
158         if (request == null || response == null) {
159             return;
160         }
161         response.setContentType(DEFAULT_CONTENT_TYPE);
162         response.setCharacterEncoding(UTF_8.name());
163
164         String path = request.getPathInfo();
165         if (path == null || path.isEmpty() || path.equals(ROOT_PATH)) {
166             @Nullable
167             String code = request.getParameter(PARAM_CODE);
168             @Nullable
169             String state = request.getParameter(PARAM_STATE);
170             if (code != null && state != null && !code.trim().isEmpty() && !state.trim().isEmpty()) {
171                 getBridgeAuthenticationPage(request, response, code, state);
172             } else {
173                 getBridgesPage(request, response);
174             }
175         } else if (pathMatches(path, APPLIANCES_PATH)) {
176             @Nullable
177             String action = request.getParameter(PARAM_ACTION);
178             @Nullable
179             String thingId = request.getParameter(PARAM_THING_ID);
180             if (action != null && thingId != null && !action.trim().isEmpty() && !thingId.trim().isEmpty()) {
181                 processApplianceActions(response, action, thingId);
182             } else {
183                 getAppliancesPage(request, response);
184             }
185         } else if (pathMatches(path, REQUEST_LOG_PATH)) {
186             @Nullable
187             String export = request.getParameter(PARAM_EXPORT);
188             @Nullable
189             String bridgeId = request.getParameter(PARAM_BRIDGE_ID);
190             if (export != null && bridgeId != null && !export.trim().isEmpty() && !bridgeId.trim().isEmpty()) {
191                 getRequestLogExport(response, bridgeId);
192             } else {
193                 getRequestLogPage(request, response);
194             }
195         } else if (pathMatches(path, EVENT_LOG_PATH)) {
196             @Nullable
197             String export = request.getParameter(PARAM_EXPORT);
198             @Nullable
199             String bridgeId = request.getParameter(PARAM_BRIDGE_ID);
200             if (export != null && bridgeId != null && !export.trim().isEmpty() && !bridgeId.trim().isEmpty()) {
201                 getEventLogExport(response, bridgeId);
202             } else {
203                 getEventLogPage(request, response);
204             }
205         } else {
206             response.sendError(HttpServletResponse.SC_NOT_FOUND);
207         }
208     }
209
210     @Override
211     protected void doPost(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response)
212             throws IOException {
213         if (request == null || response == null) {
214             return;
215         }
216         response.setContentType("text/html; charset=UTF-8");
217         response.setCharacterEncoding("UTF-8");
218
219         String path = request.getPathInfo();
220         if (path == null || path.isEmpty() || path.equals(ROOT_PATH)) {
221             if (request.getParameter(PARAM_ACTION) != null && request.getParameter(PARAM_BRIDGE_ID) != null) {
222                 postBridgesPage(request, response);
223             } else {
224                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
225             }
226         } else if (pathMatches(path, APPLIANCES_PATH)) {
227             String requestPayload = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
228             @Nullable
229             String action = request.getParameter(PARAM_ACTION);
230             @Nullable
231             String thingId = request.getParameter(PARAM_THING_ID);
232             @Nullable
233             String targetPath = request.getParameter(PARAM_PATH);
234
235             if ((ACTION_PUT_RAW.equals(action) || ACTION_GET_RAW.equals(action)) && thingId != null
236                     && targetPath != null && action != null) {
237                 processRawApplianceActions(response, action, thingId, targetPath, requestPayload);
238             } else {
239                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
240             }
241         } else {
242             response.sendError(HttpServletResponse.SC_NOT_FOUND);
243         }
244     }
245
246     /**
247      * Add Home Connect bridge handler to configuration servlet, to allow user to authenticate against Home Connect API.
248      *
249      * @param bridgeHandler bridge handler
250      */
251     public void addBridgeHandler(HomeConnectBridgeHandler bridgeHandler) {
252         bridgeHandlers.add(bridgeHandler);
253     }
254
255     /**
256      * Remove Home Connect bridge handler from configuration servlet.
257      *
258      * @param bridgeHandler bridge handler
259      */
260     public void removeBridgeHandler(HomeConnectBridgeHandler bridgeHandler) {
261         bridgeHandlers.remove(bridgeHandler);
262     }
263
264     private void getAppliancesPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
265         WebContext context = new WebContext(request, response, request.getServletContext());
266         context.setVariable("bridgeHandlers", bridgeHandlers);
267         templateEngine.process("appliances", context, response.getWriter());
268     }
269
270     private void processApplianceActions(HttpServletResponse response, String action, String thingId)
271             throws IOException {
272         Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandlerForThing(thingId);
273         Optional<AbstractHomeConnectThingHandler> thingHandler = getThingHandler(thingId);
274
275         if (bridgeHandler.isPresent() && thingHandler.isPresent()) {
276             try {
277                 response.setContentType(MediaType.APPLICATION_JSON);
278                 String haId = thingHandler.get().getThingHaId();
279
280                 switch (action) {
281                     case ACTION_SHOW_DETAILS: {
282                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
283                                 "/api/homeappliances/" + haId);
284                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
285                         break;
286                     }
287                     case ACTION_ALL_PROGRAMS: {
288                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
289                                 "/api/homeappliances/" + haId + "/programs");
290                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
291                         break;
292                     }
293                     case ACTION_AVAILABLE_PROGRAMS: {
294                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
295                                 "/api/homeappliances/" + haId + "/programs/available");
296                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
297                         break;
298                     }
299                     case ACTION_SELECTED_PROGRAM: {
300                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
301                                 "/api/homeappliances/" + haId + "/programs/selected");
302                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
303                         break;
304                     }
305                     case ACTION_ACTIVE_PROGRAM: {
306                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
307                                 "/api/homeappliances/" + haId + "/programs/active");
308                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
309                         break;
310                     }
311                     case ACTION_OPERATION_STATE: {
312                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
313                                 "/api/homeappliances/" + haId + "/status/" + EVENT_OPERATION_STATE);
314                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
315                         break;
316                     }
317                     case ACTION_POWER_STATE: {
318                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
319                                 "/api/homeappliances/" + haId + "/settings/" + EVENT_POWER_STATE);
320                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
321                         break;
322                     }
323                     case ACTION_DOOR_STATE: {
324                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
325                                 "/api/homeappliances/" + haId + "/status/" + EVENT_DOOR_STATE);
326                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
327                         break;
328                     }
329                     case ACTION_REMOTE_START_ALLOWED: {
330                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
331                                 "/api/homeappliances/" + haId + "/status/" + EVENT_REMOTE_CONTROL_START_ALLOWED);
332                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
333                         break;
334                     }
335                     case ACTION_REMOTE_CONTROL_ACTIVE: {
336                         String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId,
337                                 "/api/homeappliances/" + haId + "/status/" + EVENT_REMOTE_CONTROL_ACTIVE);
338                         response.getWriter().write(actionResponse != null ? actionResponse : EMPTY_RESPONSE);
339                         break;
340                     }
341                     default:
342                         response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown action");
343                         break;
344                 }
345             } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
346                 logger.debug("Could not execute request! thingId={}, action={}, error={}", thingId, action,
347                         e.getMessage());
348                 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage());
349             }
350         } else {
351             response.sendError(HttpStatus.BAD_REQUEST_400, "Thing or bridge not found!");
352         }
353     }
354
355     private void processRawApplianceActions(HttpServletResponse response, String action, String thingId, String path,
356             String body) throws IOException {
357         Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandlerForThing(thingId);
358         Optional<AbstractHomeConnectThingHandler> thingHandler = getThingHandler(thingId);
359
360         if (bridgeHandler.isPresent() && thingHandler.isPresent()) {
361             try {
362                 response.setContentType(MediaType.APPLICATION_JSON);
363                 String haId = thingHandler.get().getThingHaId();
364
365                 if (ACTION_PUT_RAW.equals(action)) {
366                     String actionResponse = bridgeHandler.get().getApiClient().putRaw(haId, path, body);
367                     response.getWriter().write(actionResponse);
368                 } else if (ACTION_GET_RAW.equals(action)) {
369                     @Nullable
370                     String actionResponse = bridgeHandler.get().getApiClient().getRaw(haId, path, true);
371                     if (actionResponse == null) {
372                         response.getWriter().write("{\"status\": \"No response\"}");
373                     } else {
374                         response.getWriter().write(actionResponse);
375                     }
376                 } else {
377                     response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown action");
378                 }
379             } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
380                 logger.debug("Could not execute request! thingId={}, action={}, error={}", thingId, action,
381                         e.getMessage());
382                 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage());
383             }
384         } else {
385             response.sendError(HttpStatus.BAD_REQUEST_400, "Bridge or Thing not found!");
386         }
387     }
388
389     private void getBridgesPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
390         WebContext context = new WebContext(request, response, request.getServletContext());
391         context.setVariable("bridgeHandlers", bridgeHandlers);
392         templateEngine.process("bridges", context, response.getWriter());
393     }
394
395     private void postBridgesPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
396         @Nullable
397         String action = request.getParameter(PARAM_ACTION);
398         @Nullable
399         String bridgeId = request.getParameter(PARAM_BRIDGE_ID);
400         Optional<HomeConnectBridgeHandler> bridgeHandlerOptional = bridgeHandlers.stream().filter(
401                 homeConnectBridgeHandler -> homeConnectBridgeHandler.getThing().getUID().toString().equals(bridgeId))
402                 .findFirst();
403
404         if (bridgeHandlerOptional.isPresent()
405                 && (ACTION_AUTHORIZE.equals(action) || ACTION_CLEAR_CREDENTIALS.equals(action))) {
406             HomeConnectBridgeHandler bridgeHandler = bridgeHandlerOptional.get();
407             if (ACTION_AUTHORIZE.equals(action)) {
408                 try {
409                     String authorizationUrl = bridgeHandler.getOAuthClientService().getAuthorizationUrl(null, null,
410                             bridgeHandler.getThing().getUID().getAsString());
411                     logger.debug("Generated authorization url: {}", authorizationUrl);
412
413                     response.sendRedirect(authorizationUrl);
414                 } catch (OAuthException e) {
415                     logger.error("Could not create authorization url!", e);
416                     response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "Could not create authorization url!");
417                 }
418             } else {
419                 logger.info("Remove access token for '{}' bridge.", bridgeHandler.getThing().getLabel());
420                 try {
421                     bridgeHandler.getOAuthClientService().remove();
422                 } catch (OAuthException e) {
423                     logger.debug("Could not clear oAuth credentials. error={}", e.getMessage());
424                 }
425                 bridgeHandler.reinitialize();
426
427                 WebContext context = new WebContext(request, response, request.getServletContext());
428                 context.setVariable("action",
429                         bridgeHandler.getThing().getUID().getAsString() + ACTION_CLEAR_CREDENTIALS);
430                 context.setVariable("bridgeHandlers", bridgeHandlers);
431                 templateEngine.process("bridges", context, response.getWriter());
432             }
433         } else {
434             response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge or action is missing!");
435         }
436     }
437
438     private void getRequestLogPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
439         ArrayList<ApiRequest> requests = new ArrayList<>();
440         bridgeHandlers.forEach(homeConnectBridgeHandler -> requests
441                 .addAll(homeConnectBridgeHandler.getApiClient().getLatestApiRequests()));
442
443         WebContext context = new WebContext(request, response, request.getServletContext());
444         context.setVariable("bridgeHandlers", bridgeHandlers);
445         context.setVariable("requests", gson.toJson(requests));
446         templateEngine.process("log-requests", context, response.getWriter());
447     }
448
449     private void getRequestLogExport(HttpServletResponse response, String bridgeId) throws IOException {
450         Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandler(bridgeId);
451         if (bridgeHandler.isPresent()) {
452             response.setContentType(MediaType.APPLICATION_JSON);
453             String fileName = String.format("%s__%s__requests.json", now().format(FILE_EXPORT_DTF),
454                     bridgeId.replaceAll("[^a-zA-Z0-9]", "_"));
455             response.setHeader("Content-disposition", "attachment; filename=" + fileName);
456
457             HashMap<String, Object> responsePayload = new HashMap<>();
458             responsePayload.put("openHAB", OpenHAB.getVersion());
459             responsePayload.put("bundle", FrameworkUtil.getBundle(this.getClass()).getVersion().toString());
460             List<ApiRequest> apiRequestList = bridgeHandler.get().getApiClient().getLatestApiRequests().stream()
461                     .peek(apiRequest -> {
462                         Map<String, String> headers = apiRequest.getRequest().getHeader();
463                         if (headers.containsKey("authorization")) {
464                             headers.put("authorization", "*replaced*");
465                         } else if (headers.containsKey("Authorization")) {
466                             headers.put("Authorization", "*replaced*");
467                         }
468                     }).collect(Collectors.toList());
469             responsePayload.put("requests", apiRequestList);
470             response.getWriter().write(gson.toJson(responsePayload));
471         } else {
472             response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge");
473         }
474     }
475
476     private void getEventLogPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
477         WebContext context = new WebContext(request, response, request.getServletContext());
478         context.setVariable("bridgeHandlers", bridgeHandlers);
479         templateEngine.process("log-events", context, response.getWriter());
480     }
481
482     private void getEventLogExport(HttpServletResponse response, String bridgeId) throws IOException {
483         Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandler(bridgeId);
484         if (bridgeHandler.isPresent()) {
485             response.setContentType(MediaType.APPLICATION_JSON);
486             String fileName = String.format("%s__%s__events.json", now().format(FILE_EXPORT_DTF),
487                     bridgeId.replaceAll("[^a-zA-Z0-9]", "_"));
488             response.setHeader("Content-disposition", "attachment; filename=" + fileName);
489
490             HashMap<String, Object> responsePayload = new HashMap<>();
491             responsePayload.put("openHAB", OpenHAB.getVersion());
492             responsePayload.put("bundle", FrameworkUtil.getBundle(this.getClass()).getVersion().toString());
493             responsePayload.put("events", bridgeHandler.get().getEventSourceClient().getLatestEvents());
494             response.getWriter().write(gson.toJson(responsePayload));
495         } else {
496             response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge");
497         }
498     }
499
500     private void getBridgeAuthenticationPage(HttpServletRequest request, HttpServletResponse response, String code,
501             String state) throws IOException {
502         // callback handling from authorization server
503         logger.debug("[oAuth] redirect from authorization server (code={}, state={}).", code, state);
504
505         Optional<HomeConnectBridgeHandler> bridgeHandler = getBridgeHandler(state);
506         if (bridgeHandler.isPresent()) {
507             try {
508                 AccessTokenResponse accessTokenResponse = bridgeHandler.get().getOAuthClientService()
509                         .getAccessTokenResponseByAuthorizationCode(code, null);
510
511                 logger.debug("access token response: {}", accessTokenResponse);
512
513                 // inform bridge
514                 bridgeHandler.get().reinitialize();
515
516                 WebContext context = new WebContext(request, response, request.getServletContext());
517                 context.setVariable("action", bridgeHandler.get().getThing().getUID().getAsString() + ACTION_AUTHORIZE);
518                 context.setVariable("bridgeHandlers", bridgeHandlers);
519                 templateEngine.process("bridges", context, response.getWriter());
520             } catch (OAuthException | OAuthResponseException e) {
521                 logger.error("Could not fetch token!", e);
522                 response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "Could not fetch token!");
523             }
524         } else {
525             response.sendError(HttpStatus.BAD_REQUEST_400, "Unknown bridge");
526         }
527     }
528
529     private boolean pathMatches(String path, String targetPath) {
530         return targetPath.equals(path) || (targetPath + SLASH).equals(path);
531     }
532
533     private Optional<HomeConnectBridgeHandler> getBridgeHandler(String bridgeUid) {
534         for (HomeConnectBridgeHandler handler : bridgeHandlers) {
535             if (handler.getThing().getUID().getAsString().equals(bridgeUid)) {
536                 return Optional.of(handler);
537             }
538         }
539         return Optional.empty();
540     }
541
542     private Optional<AbstractHomeConnectThingHandler> getThingHandler(String thingUid) {
543         for (HomeConnectBridgeHandler handler : bridgeHandlers) {
544             for (AbstractHomeConnectThingHandler thingHandler : handler.getThingHandler()) {
545                 if (thingHandler.getThing().getUID().getAsString().equals(thingUid)) {
546                     return Optional.of(thingHandler);
547                 }
548             }
549         }
550         return Optional.empty();
551     }
552
553     private Optional<HomeConnectBridgeHandler> getBridgeHandlerForThing(String thingUid) {
554         for (HomeConnectBridgeHandler handler : bridgeHandlers) {
555             for (AbstractHomeConnectThingHandler thingHandler : handler.getThingHandler()) {
556                 if (thingHandler.getThing().getUID().getAsString().equals(thingUid)) {
557                     return Optional.of(handler);
558                 }
559             }
560         }
561         return Optional.empty();
562     }
563 }