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