]> git.basschouten.com Git - openhab-addons.git/blob
cad3e3b7deb1cf9b415d36ed1f3bfafdfa64b3ad
[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.nest.internal.wwn.test;
14
15 import static org.openhab.binding.nest.internal.wwn.WWNBindingConstants.*;
16 import static org.openhab.binding.nest.internal.wwn.rest.WWNStreamingRestClient.*;
17
18 import java.io.IOException;
19 import java.io.InputStreamReader;
20 import java.io.PrintWriter;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Queue;
24 import java.util.Set;
25 import java.util.concurrent.ArrayBlockingQueue;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.TimeUnit;
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
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import com.google.gson.Gson;
40 import com.google.gson.GsonBuilder;
41 import com.google.gson.reflect.TypeToken;
42
43 /**
44  * The {@link WWNTestApiServlet} mocks the Nest API during tests.
45  *
46  * @author Wouter Born - Initial contribution
47  */
48 @NonNullByDefault
49 public class WWNTestApiServlet extends HttpServlet {
50
51     private static final long serialVersionUID = -5414910055159062745L;
52
53     private static final String NEW_LINE = "\n";
54
55     private static final String UPDATE_PATHS[] = { NEST_CAMERA_UPDATE_PATH, NEST_SMOKE_ALARM_UPDATE_PATH,
56             NEST_STRUCTURE_UPDATE_PATH, NEST_THERMOSTAT_UPDATE_PATH };
57
58     private final Logger logger = LoggerFactory.getLogger(WWNTestApiServlet.class);
59
60     private static class SseEvent {
61         private String name;
62         private @Nullable String data;
63
64         public SseEvent(String name) {
65             this.name = name;
66         }
67
68         public SseEvent(String name, String data) {
69             this.name = name;
70             this.data = data;
71         }
72
73         public @Nullable String getData() {
74             return data;
75         }
76
77         public String getName() {
78             return name;
79         }
80     }
81
82     private final Map<String, Map<String, String>> nestIdPropertiesMap = new ConcurrentHashMap<>();
83
84     private final Map<Thread, Queue<SseEvent>> listenerQueues = new ConcurrentHashMap<>();
85
86     private final ThreadLocal<PrintWriter> threadLocalWriter = new ThreadLocal<>();
87
88     private final Gson gson = new GsonBuilder().create();
89
90     public void closeConnections() {
91         Set<Thread> threads = listenerQueues.keySet();
92         listenerQueues.clear();
93         threads.forEach(Thread::interrupt);
94     }
95
96     public void reset() {
97         nestIdPropertiesMap.clear();
98     }
99
100     public void queueEvent(String eventName) {
101         SseEvent event = new SseEvent(eventName);
102         listenerQueues.forEach((thread, queue) -> queue.add(event));
103     }
104
105     public void queueEvent(String eventName, String data) {
106         SseEvent event = new SseEvent(eventName, data);
107         listenerQueues.forEach((thread, queue) -> queue.add(event));
108     }
109
110     @SuppressWarnings("resource")
111     private void writeEvent(SseEvent event) {
112         logger.debug("Writing {} event", event.getName());
113
114         PrintWriter writer = threadLocalWriter.get();
115
116         writer.write("event: ");
117         writer.write(event.getName());
118         writer.write(NEW_LINE);
119
120         String eventData = event.getData();
121         if (eventData != null) {
122             for (String dataLine : eventData.split(NEW_LINE)) {
123                 writer.write("data: ");
124                 writer.write(dataLine);
125                 writer.write(NEW_LINE);
126             }
127         }
128
129         writer.write(NEW_LINE);
130         writer.flush();
131     }
132
133     private void writeEvent(String eventName) {
134         writeEvent(new SseEvent(eventName));
135     }
136
137     @Override
138     public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
139         ArrayBlockingQueue<SseEvent> queue = new ArrayBlockingQueue<>(10);
140         listenerQueues.put(Thread.currentThread(), queue);
141
142         response.setContentType("text/event-stream");
143         response.setCharacterEncoding("UTF-8");
144         response.flushBuffer();
145
146         logger.debug("Opened event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
147
148         PrintWriter writer = response.getWriter();
149         threadLocalWriter.set(writer);
150         writeEvent(OPEN);
151
152         while (listenerQueues.containsKey(Thread.currentThread()) && !writer.checkError()) {
153             try {
154                 SseEvent event = queue.poll(KEEP_ALIVE_MILLIS, TimeUnit.MILLISECONDS);
155                 if (event != null) {
156                     writeEvent(event);
157                 } else {
158                     writeEvent(KEEP_ALIVE);
159                 }
160             } catch (InterruptedException e) {
161                 logger.debug("Evaluating loop conditions after interrupt");
162             }
163         }
164
165         listenerQueues.remove(Thread.currentThread());
166         threadLocalWriter.remove();
167         writer.close();
168
169         logger.debug("Closed event stream to {}:{}", request.getRemoteHost(), request.getRemotePort());
170     }
171
172     @Override
173     protected void doPut(HttpServletRequest request, HttpServletResponse response)
174             throws ServletException, IOException {
175         logger.debug("Received put request: {}", request);
176
177         String uri = request.getRequestURI();
178         String nestId = getNestIdFromURI(uri);
179
180         if (nestId == null) {
181             logger.error("Unsupported URI: {}", uri);
182             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
183             return;
184         }
185
186         InputStreamReader reader = new InputStreamReader(request.getInputStream());
187         Map<String, String> propertiesUpdate = gson.fromJson(reader, new TypeToken<Map<String, String>>() {
188         }.getType());
189
190         Map<String, String> properties = getOrCreateProperties(nestId);
191         properties.putAll(propertiesUpdate);
192
193         gson.toJson(propertiesUpdate, response.getWriter());
194
195         response.setStatus(HttpServletResponse.SC_OK);
196     }
197
198     private @Nullable String getNestIdFromURI(@Nullable String uri) {
199         if (uri == null) {
200             return null;
201         }
202         for (String updatePath : UPDATE_PATHS) {
203             if (uri.startsWith(updatePath)) {
204                 return uri.replaceAll(updatePath, "");
205             }
206         }
207         return null;
208     }
209
210     private Map<String, String> getOrCreateProperties(String nestId) {
211         Map<String, String> properties = nestIdPropertiesMap.get(nestId);
212         if (properties == null) {
213             properties = new HashMap<>();
214             nestIdPropertiesMap.put(nestId, properties);
215         }
216         return properties;
217     }
218
219     public @Nullable String getNestIdPropertyState(String nestId, String propertyName) {
220         Map<String, String> properties = nestIdPropertiesMap.get(nestId);
221         return properties == null ? null : properties.get(propertyName);
222     }
223 }