]> git.basschouten.com Git - openhab-addons.git/blob
e8ddd453ffc5e020426ec56ca1c3fe2c1f2fc434
[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.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import com.google.gson.Gson;
38 import com.google.gson.GsonBuilder;
39 import com.google.gson.reflect.TypeToken;
40
41 /**
42  * The {@link WWNTestApiServlet} mocks the Nest API during tests.
43  *
44  * @author Wouter Born - Initial contribution
45  */
46 public class WWNTestApiServlet extends HttpServlet {
47
48     private static final long serialVersionUID = -5414910055159062745L;
49
50     private static final String NEW_LINE = "\n";
51
52     private static final String UPDATE_PATHS[] = { NEST_CAMERA_UPDATE_PATH, NEST_SMOKE_ALARM_UPDATE_PATH,
53             NEST_STRUCTURE_UPDATE_PATH, NEST_THERMOSTAT_UPDATE_PATH };
54
55     private final Logger logger = LoggerFactory.getLogger(WWNTestApiServlet.class);
56
57     private class SseEvent {
58         private String name;
59         private String data;
60
61         public SseEvent(String name) {
62             this.name = name;
63         }
64
65         public SseEvent(String name, String data) {
66             this.name = name;
67             this.data = data;
68         }
69
70         public String getData() {
71             return data;
72         }
73
74         public String getName() {
75             return name;
76         }
77
78         public boolean hasData() {
79             return data != null && !data.isEmpty();
80         }
81     }
82
83     private final Map<String, Map<String, String>> nestIdPropertiesMap = new ConcurrentHashMap<>();
84
85     private final Map<Thread, Queue<SseEvent>> listenerQueues = new ConcurrentHashMap<>();
86
87     private final ThreadLocal<PrintWriter> threadLocalWriter = new ThreadLocal<>();
88
89     private final Gson gson = new GsonBuilder().create();
90
91     public void closeConnections() {
92         Set<Thread> threads = listenerQueues.keySet();
93         listenerQueues.clear();
94         threads.forEach(thread -> thread.interrupt());
95     }
96
97     public void reset() {
98         nestIdPropertiesMap.clear();
99     }
100
101     public void queueEvent(String eventName) {
102         SseEvent event = new SseEvent(eventName);
103         listenerQueues.forEach((thread, queue) -> queue.add(event));
104     }
105
106     public void queueEvent(String eventName, String data) {
107         SseEvent event = new SseEvent(eventName, data);
108         listenerQueues.forEach((thread, queue) -> queue.add(event));
109     }
110
111     @SuppressWarnings("resource")
112     private void writeEvent(SseEvent event) {
113         logger.debug("Writing {} event", event.getName());
114
115         PrintWriter writer = threadLocalWriter.get();
116
117         writer.write("event: ");
118         writer.write(event.getName());
119         writer.write(NEW_LINE);
120
121         if (event.hasData()) {
122             for (String dataLine : event.getData().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 String getNestIdFromURI(String uri) {
199         for (String updatePath : UPDATE_PATHS) {
200             if (uri.startsWith(updatePath)) {
201                 return uri.replaceAll(updatePath, "");
202             }
203         }
204         return null;
205     }
206
207     private Map<String, String> getOrCreateProperties(String nestId) {
208         Map<String, String> properties = nestIdPropertiesMap.get(nestId);
209         if (properties == null) {
210             properties = new HashMap<>();
211             nestIdPropertiesMap.put(nestId, properties);
212         }
213         return properties;
214     }
215
216     public String getNestIdPropertyState(String nestId, String propertyName) {
217         Map<String, String> properties = nestIdPropertiesMap.get(nestId);
218         return properties == null ? null : properties.get(propertyName);
219     }
220 }