]> git.basschouten.com Git - openhab-addons.git/blob
ffd091afe8d87a354bcaa7e654ac7bbf93657d9f
[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.netatmo.internal.webhook;
14
15 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.BINDING_ID;
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.net.URI;
20 import java.nio.charset.StandardCharsets;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Scanner;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.stream.Collectors;
28
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServlet;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.ws.rs.HttpMethod;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.UriBuilder;
36 import javax.ws.rs.core.UriBuilderException;
37
38 import org.eclipse.jdt.annotation.NonNullByDefault;
39 import org.eclipse.jdt.annotation.Nullable;
40 import org.openhab.binding.netatmo.internal.api.NetatmoException;
41 import org.openhab.binding.netatmo.internal.api.SecurityApi;
42 import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
43 import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
44 import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
45 import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
46 import org.osgi.service.http.HttpService;
47 import org.osgi.service.http.NamespaceException;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * HTTP servlet for Netatmo Webhook.
53  *
54  * @author GaĆ«l L'hopital - Initial contribution
55  */
56 @NonNullByDefault
57 public class NetatmoServlet extends HttpServlet {
58     private static final long serialVersionUID = -354583910860541214L;
59     private static final String CALLBACK_URI = "/" + BINDING_ID;
60
61     private final Logger logger = LoggerFactory.getLogger(NetatmoServlet.class);
62     private final Map<String, EventCapability> dataListeners = new ConcurrentHashMap<>();
63     private final HttpService httpService;
64     private final NADeserializer deserializer;
65     private final Optional<SecurityApi> securityApi;
66     private boolean hookSet = false;
67
68     public NetatmoServlet(HttpService httpService, ApiBridgeHandler apiBridge, String webHookUrl) {
69         this.httpService = httpService;
70         this.deserializer = apiBridge.getDeserializer();
71         this.securityApi = Optional.ofNullable(apiBridge.getRestManager(SecurityApi.class));
72         securityApi.ifPresent(api -> {
73             try {
74                 httpService.registerServlet(CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
75                 logger.debug("Started Netatmo Webhook Servlet at '{}'", CALLBACK_URI);
76                 URI uri = UriBuilder.fromUri(webHookUrl).path(BINDING_ID).build();
77                 try {
78                     logger.info("Setting Netatmo Welcome WebHook to {}", uri.toString());
79                     api.addwebhook(uri);
80                     hookSet = true;
81                 } catch (UriBuilderException e) {
82                     logger.info("webhookUrl is not a valid URI '{}' : {}", uri, e.getMessage());
83                 } catch (NetatmoException e) {
84                     logger.info("Error setting webhook : {}", e.getMessage());
85                 }
86             } catch (ServletException | NamespaceException e) {
87                 logger.warn("Could not start Netatmo Webhook Servlet : {}", e.getMessage());
88             }
89         });
90     }
91
92     public void dispose() {
93         securityApi.ifPresent(api -> {
94             if (hookSet) {
95                 logger.info("Releasing Netatmo Welcome WebHook");
96                 try {
97                     api.dropWebhook();
98                 } catch (NetatmoException e) {
99                     logger.warn("Error releasing webhook : {}", e.getMessage());
100                 }
101             }
102             httpService.unregister(CALLBACK_URI);
103         });
104         logger.debug("Netatmo Webhook Servlet stopped");
105     }
106
107     @Override
108     protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
109         if (req != null && resp != null) {
110             String data = inputStreamToString(req.getInputStream());
111             if (!data.isEmpty()) {
112                 logger.debug("Event transmitted from restService : {}", data);
113                 try {
114                     WebhookEvent event = deserializer.deserialize(WebhookEvent.class, data);
115                     List<String> tobeNotified = collectNotified(event);
116                     dataListeners.keySet().stream().filter(tobeNotified::contains).forEach(id -> {
117                         EventCapability module = dataListeners.get(id);
118                         if (module != null) {
119                             module.setNewData(event);
120                         }
121                     });
122                 } catch (NetatmoException e) {
123                     logger.info("Error deserializing webhook data received : {}. {}", data, e.getMessage());
124                 }
125             }
126             resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
127             resp.setContentType(MediaType.APPLICATION_JSON);
128             resp.setHeader("Access-Control-Allow-Origin", "*");
129             resp.setHeader("Access-Control-Allow-Methods", HttpMethod.POST);
130             resp.setIntHeader("Access-Control-Max-Age", 3600);
131             resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
132             resp.getWriter().write("");
133         }
134     }
135
136     private List<String> collectNotified(WebhookEvent event) {
137         List<String> result = new ArrayList<>();
138         result.add(event.getCameraId());
139         String person = event.getPersonId();
140         if (person != null) {
141             result.add(person);
142         }
143         result.addAll(event.getPersons().keySet());
144         return result.stream().distinct().collect(Collectors.toList());
145     }
146
147     public void registerDataListener(String id, EventCapability dataListener) {
148         dataListeners.put(id, dataListener);
149     }
150
151     public void unregisterDataListener(EventCapability dataListener) {
152         dataListeners.entrySet().removeIf(entry -> entry.getValue().equals(dataListener));
153     }
154
155     private String inputStreamToString(InputStream is) throws IOException {
156         String value = "";
157         try (Scanner scanner = new Scanner(is)) {
158             scanner.useDelimiter("\\A");
159             value = scanner.hasNext() ? scanner.next() : "";
160         }
161         return value;
162     }
163 }