]> git.basschouten.com Git - openhab-addons.git/blob
3f665134489a500c9644a60eed6b434b1dd2320c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.smartthings.internal;
14
15 import static org.openhab.binding.smartthings.internal.SmartthingsBindingConstants.*;
16
17 import java.io.BufferedReader;
18 import java.io.IOException;
19 import java.util.Dictionary;
20 import java.util.HashMap;
21 import java.util.Hashtable;
22 import java.util.Map;
23 import java.util.stream.Collectors;
24
25 import javax.servlet.ServletException;
26 import javax.servlet.http.HttpServlet;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.osgi.service.component.ComponentContext;
33 import org.osgi.service.component.annotations.Activate;
34 import org.osgi.service.component.annotations.Component;
35 import org.osgi.service.component.annotations.Deactivate;
36 import org.osgi.service.component.annotations.Reference;
37 import org.osgi.service.event.Event;
38 import org.osgi.service.event.EventAdmin;
39 import org.osgi.service.http.HttpService;
40 import org.osgi.service.http.NamespaceException;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.google.gson.Gson;
45
46 /**
47  * Receives all Http data from the Smartthings Hub
48  *
49  * @author Bob Raker - Initial contribution
50  */
51 @NonNullByDefault
52 @SuppressWarnings("serial")
53 @Component(service = HttpServlet.class)
54 public class SmartthingsServlet extends HttpServlet {
55     private static final String PATH = "/smartthings";
56     private final Logger logger = LoggerFactory.getLogger(SmartthingsServlet.class);
57     private @NonNullByDefault({}) HttpService httpService;
58     private @Nullable EventAdmin eventAdmin;
59     private Gson gson = new Gson();
60
61     @Activate
62     protected void activate(Map<String, Object> config) {
63         if (httpService == null) {
64             logger.warn("SmartthingsServlet.activate: httpService is unexpectedly null");
65             return;
66         }
67         try {
68             Dictionary<String, String> servletParams = new Hashtable<String, String>();
69             httpService.registerServlet(PATH, this, servletParams, httpService.createDefaultHttpContext());
70         } catch (ServletException | NamespaceException e) {
71             logger.warn("Could not start Smartthings servlet service: {}", e.getMessage());
72         }
73     }
74
75     @Deactivate
76     protected void deactivate(ComponentContext componentContext) {
77         if (httpService != null) {
78             try {
79                 httpService.unregister(PATH);
80             } catch (IllegalArgumentException ignored) {
81             }
82         }
83     }
84
85     @Reference
86     protected void setHttpService(HttpService httpService) {
87         this.httpService = httpService;
88     }
89
90     protected void unsetHttpService(HttpService httpService) {
91         this.httpService = null;
92     }
93
94     @Reference
95     protected void setEventAdmin(EventAdmin eventAdmin) {
96         this.eventAdmin = eventAdmin;
97     }
98
99     protected void unsetEventAdmin(EventAdmin eventAdmin) {
100         this.eventAdmin = null;
101     }
102
103     @Override
104     protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
105             throws ServletException, IOException {
106         if (req == null) {
107             logger.debug("SmartthingsServlet.service unexpectedly received a null request. Request not processed");
108             return;
109         }
110         String path = req.getRequestURI();
111
112         // See what is in the path
113         String[] pathParts = path.replace(PATH + "/", "").split("/");
114         if (pathParts.length != 1) {
115             logger.warn(
116                     "Smartthing servlet received a path with zero or more than one parts. Only one part is allowed. path {}",
117                     path);
118             return;
119         }
120
121         BufferedReader rdr = new BufferedReader(req.getReader());
122         String s = rdr.lines().collect(Collectors.joining());
123         switch (pathParts[0]) {
124             case "state":
125                 // This is device state info returned from Smartthings
126                 logger.debug("Smartthing servlet processing \"state\" request. data: {}", s);
127                 publishEvent(STATE_EVENT_TOPIC, "data", s);
128                 break;
129             case "discovery":
130                 // This is discovery data returned from Smartthings
131                 logger.trace("Smartthing servlet processing \"discovery\" request. data: {}", s);
132                 publishEvent(DISCOVERY_EVENT_TOPIC, "data", s);
133                 break;
134             case "error":
135                 // This is an error message from smartthings
136                 Map<String, String> map = new HashMap<String, String>();
137                 map = gson.fromJson(s, map.getClass());
138                 logger.warn("Error message from Smartthings: {}", map.get("message"));
139                 break;
140             default:
141                 logger.warn("Smartthings servlet received a path that is not supported {}", pathParts[0]);
142         }
143
144         // A user @fx submitted a pull request stating:
145         // It appears that the HubAction queue will choke for a timeout of 6-8s~ if a http action doesn't return a body
146         // (or possibly on the 204 http code, I didn't test them separately.)
147         // I tested the following scenarios:
148         // 1. Return status 204 with a response of OK
149         // 2. Return status 202 with no response
150         // 3. No response.
151         // In all cases the time was about the same - 3.5 sec/request
152         // Both the 202 and 204 responses resulted in the hub logging an error: received a request with an unknown path:
153         // HTTP/1.1 200 OK, content-Length: 0
154         // Therefore I am opting to return nothing since no error message occurs.
155         // resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
156         // resp.setStatus(HttpServletResponse.SC_OK);
157         // resp.getWriter().write("OK");
158         // resp.getWriter().flush();
159         // resp.getWriter().close();
160         logger.trace("Smartthings servlet returning.");
161         return;
162     }
163
164     private void publishEvent(String topic, String name, String data) {
165         Dictionary<String, String> props = new Hashtable<String, String>();
166         props.put(name, data);
167         Event event = new Event(topic, props);
168         if (eventAdmin != null) {
169             eventAdmin.postEvent(event);
170         } else {
171             logger.debug("SmartthingsServlet:publishEvent eventAdmin is unexpectedly null");
172         }
173     }
174 }