2 * Copyright (c) 2010-2024 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.smartthings.internal;
15 import static org.openhab.binding.smartthings.internal.SmartthingsBindingConstants.*;
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;
23 import java.util.stream.Collectors;
25 import javax.servlet.ServletException;
26 import javax.servlet.http.HttpServlet;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
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;
44 import com.google.gson.Gson;
47 * Receives all Http data from the Smartthings Hub
49 * @author Bob Raker - Initial contribution
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();
62 protected void activate(Map<String, Object> config) {
63 if (httpService == null) {
64 logger.warn("SmartthingsServlet.activate: httpService is unexpectedly null");
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());
76 protected void deactivate(ComponentContext componentContext) {
77 if (httpService != null) {
79 httpService.unregister(PATH);
80 } catch (IllegalArgumentException ignored) {
86 protected void setHttpService(HttpService httpService) {
87 this.httpService = httpService;
90 protected void unsetHttpService(HttpService httpService) {
91 this.httpService = null;
95 protected void setEventAdmin(EventAdmin eventAdmin) {
96 this.eventAdmin = eventAdmin;
99 protected void unsetEventAdmin(EventAdmin eventAdmin) {
100 this.eventAdmin = null;
104 protected void service(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
105 throws ServletException, IOException {
107 logger.debug("SmartthingsServlet.service unexpectedly received a null request. Request not processed");
110 String path = req.getRequestURI();
112 // See what is in the path
113 String[] pathParts = path.replace(PATH + "/", "").split("/");
114 if (pathParts.length != 1) {
116 "Smartthing servlet received a path with zero or more than one parts. Only one part is allowed. path {}",
121 BufferedReader rdr = new BufferedReader(req.getReader());
122 String s = rdr.lines().collect(Collectors.joining());
123 switch (pathParts[0]) {
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);
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);
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"));
141 logger.warn("Smartthings servlet received a path that is not supported {}", pathParts[0]);
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
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.");
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);
171 logger.debug("SmartthingsServlet:publishEvent eventAdmin is unexpectedly null");