]> git.basschouten.com Git - openhab-addons.git/blob
b25d689bd0eeb8d4d514687be2998c9cc6185c1e
[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.io.neeo.internal.servletservices;
14
15 import java.io.IOException;
16 import java.nio.charset.StandardCharsets;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Objects;
20
21 import javax.servlet.http.HttpServletRequest;
22 import javax.servlet.http.HttpServletResponse;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.openhab.core.thing.ChannelUID;
26 import org.openhab.io.neeo.NeeoService;
27 import org.openhab.io.neeo.internal.NeeoBrainServlet;
28 import org.openhab.io.neeo.internal.NeeoConstants;
29 import org.openhab.io.neeo.internal.NeeoUtil;
30 import org.openhab.io.neeo.internal.ServiceContext;
31 import org.openhab.io.neeo.internal.models.NeeoDevice;
32 import org.openhab.io.neeo.internal.models.NeeoDeviceChannel;
33 import org.openhab.io.neeo.internal.models.NeeoDeviceChannelKind;
34 import org.openhab.io.neeo.internal.models.NeeoDeviceType;
35 import org.openhab.io.neeo.internal.models.NeeoThingUID;
36 import org.openhab.io.neeo.internal.servletservices.models.ReturnStatus;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.google.gson.Gson;
41 import com.google.gson.JsonParseException;
42
43 /**
44  * A subclass of {@link DefaultServletService} that handles thing status/definitions for the web pages
45  *
46  * @author Tim Roberts - Initial Contribution
47  */
48 @NonNullByDefault
49 public class ThingDashboardService extends DefaultServletService {
50
51     /** The logger */
52     private final Logger logger = LoggerFactory.getLogger(ThingDashboardService.class);
53
54     /** The gson used for json manipulation */
55     private final Gson gson;
56
57     /** The service context */
58     private final ServiceContext context;
59
60     /** The service */
61     private final NeeoService service;
62
63     /**
64      * Constructs the servlet from the {@link NeeoService} and {@link ServiceContext}
65      *
66      * @param service the non-null {@link NeeoService}
67      * @param context the non-null {@link ServiceContext}
68      */
69     public ThingDashboardService(NeeoService service, ServiceContext context) {
70         Objects.requireNonNull(service, "service cannot be null");
71         Objects.requireNonNull(context, "context cannot be null");
72
73         this.context = context;
74         this.service = service;
75         gson = NeeoUtil.createNeeoDeviceGsonBuilder(service, context).create();
76     }
77
78     /**
79      * Returns true if the path starts with "thingstatus", "getchannel", "getvirtualdevice", "restoredevice",
80      * "refreshdevice", "deletedevice", "exportrules", "updatedevice"
81      *
82      * @see DefaultServletService#canHandleRoute(String[])
83      */
84     @Override
85     public boolean canHandleRoute(String[] paths) {
86         return paths.length >= 1 && (paths[0].equalsIgnoreCase("thingstatus") //
87                 || paths[0].equalsIgnoreCase("getchannel") //
88                 || paths[0].equalsIgnoreCase("getvirtualdevice") //
89                 || paths[0].equalsIgnoreCase("restoredevice") //
90                 || paths[0].equalsIgnoreCase("refreshdevice") //
91                 || paths[0].equalsIgnoreCase("deletedevice") //
92                 || paths[0].equalsIgnoreCase("exportrules") //
93                 || paths[0].equalsIgnoreCase("updatedevice"));
94     }
95
96     /**
97      * Handles the get for the 'thingstatus' and 'getchannel' URL (all other URLs do posts)
98      *
99      * @see DefaultServletService#handleGet(HttpServletRequest, String[], HttpServletResponse)
100      */
101     @Override
102     public void handleGet(HttpServletRequest req, String[] paths, HttpServletResponse resp) throws IOException {
103         Objects.requireNonNull(req, "req cannot be null");
104         Objects.requireNonNull(paths, "paths cannot be null");
105         Objects.requireNonNull(resp, "resp cannot be null");
106
107         try {
108             if (paths[0].equalsIgnoreCase("thingstatus")) {
109                 final List<NeeoDevice> devices = context.getDefinitions().getAllDevices();
110                 NeeoUtil.write(resp, gson.toJson(devices));
111             } else if (paths[0].equalsIgnoreCase("getchannel")) {
112                 final String itemName = NeeoUtil.decodeURIComponent(req.getParameter("itemname"));
113                 final List<NeeoDeviceChannel> channels = context.getDefinitions().getNeeoDeviceChannel(itemName);
114                 if (channels == null) {
115                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus("Channel no longer exists")));
116                 } else {
117                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus(channels)));
118                 }
119             } else if (paths[0].equalsIgnoreCase("getvirtualdevice")) {
120                 final NeeoThingUID uid = context.generate(NeeoConstants.VIRTUAL_THING_TYPE);
121                 final NeeoDevice device = new NeeoDevice(uid, 0, NeeoDeviceType.EXCLUDE, "NEEO Integration",
122                         "New Virtual Thing", new ArrayList<>(), null, null, null, null);
123                 NeeoUtil.write(resp, gson.toJson(new ReturnStatus(device)));
124             } else {
125                 logger.debug("Unknown get path: {}", String.join(",", paths));
126             }
127         } catch (JsonParseException | IllegalArgumentException | NullPointerException e) {
128             logger.debug("Exception handling get: {}", e.getMessage(), e);
129             NeeoUtil.write(resp, gson.toJson(new ReturnStatus(e.getMessage())));
130         }
131     }
132
133     /**
134      * Handles the post for the 'updatedevice', 'restoredevice' or 'refreshdevice'.
135      *
136      * @see DefaultServletService#handlePost(HttpServletRequest, String[], HttpServletResponse)
137      */
138     @Override
139     public void handlePost(HttpServletRequest req, String[] paths, HttpServletResponse resp) throws IOException {
140         Objects.requireNonNull(req, "req cannot be null");
141         Objects.requireNonNull(paths, "paths cannot be null");
142         Objects.requireNonNull(resp, "resp cannot be null");
143         if (paths.length == 0) {
144             throw new IllegalArgumentException("paths cannot be empty");
145         }
146
147         try {
148             if (paths[0].equalsIgnoreCase("updatedevice")) {
149                 final NeeoDevice device = gson.fromJson(req.getReader(), NeeoDevice.class);
150                 context.getDefinitions().put(device);
151
152                 for (NeeoBrainServlet servlet : service.getServlets()) {
153                     servlet.getBrainApi().restart(); // restart so brain will query changes
154                 }
155
156                 NeeoUtil.write(resp, gson.toJson(ReturnStatus.SUCCESS));
157             } else if (paths[0].equalsIgnoreCase("restoredevice")) {
158                 final NeeoThingUID uid = new NeeoThingUID(
159                         new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
160                 context.getDefinitions().remove(uid);
161                 final NeeoDevice device = context.getDefinitions().getDevice(uid);
162                 if (device == null) {
163                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus("Device no longer exists in openHAB!")));
164                 } else {
165                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus(device)));
166                 }
167             } else if (paths[0].equalsIgnoreCase("refreshdevice")) {
168                 final NeeoThingUID uid = new NeeoThingUID(
169                         new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
170                 final NeeoDevice device = context.getDefinitions().getDevice(uid);
171                 if (device == null) {
172                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus("Device no longer exists in openHAB!")));
173                 } else {
174                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus(device)));
175                 }
176             } else if (paths[0].equalsIgnoreCase("deletedevice")) {
177                 final NeeoThingUID uid = new NeeoThingUID(
178                         new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
179                 final boolean deleted = context.getDefinitions().remove(uid);
180                 NeeoUtil.write(resp, gson.toJson(new ReturnStatus(
181                         deleted ? null : "Device " + uid + " was not found (possibly already deleted?)")));
182             } else if (paths[0].equalsIgnoreCase("exportrules")) {
183                 final NeeoThingUID uid = new NeeoThingUID(
184                         new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
185                 final NeeoDevice device = context.getDefinitions().getDevice(uid);
186                 if (device == null) {
187                     NeeoUtil.write(resp, gson.toJson(new ReturnStatus("Device " + uid + " was not found")));
188                 } else {
189                     writeExampleRules(resp, device);
190                 }
191             } else {
192                 logger.debug("Unknown post path: {}", String.join(",", paths));
193             }
194         } catch (JsonParseException | IllegalArgumentException | NullPointerException e) {
195             logger.debug("Exception handling post: {}", e.getMessage(), e);
196             NeeoUtil.write(resp, gson.toJson(new ReturnStatus(e.getMessage())));
197         }
198     }
199
200     /**
201      * Helper method to produce an examples rules file and write it to the {@link HttpServletResponse}
202      *
203      * @param resp the non-null {@link HttpServletResponse}
204      * @param device the non-null {@link NeeoDevice}
205      * @throws IOException if an IOException occurs while writing the file
206      */
207     private void writeExampleRules(HttpServletResponse resp, NeeoDevice device) throws IOException {
208         Objects.requireNonNull(resp, "resp cannot be null");
209         Objects.requireNonNull(device, "device cannot be null");
210
211         final StringBuilder sb = new StringBuilder(5000);
212         appendLine(sb, "//////////////////////////////////////////////////////////////////////");
213         sb.append("// Example Rules for ");
214         appendLine(sb, device.getName());
215         appendLine(sb, "//////////////////////////////////////////////////////////////////////");
216         sb.append(System.lineSeparator());
217
218         device.getChannels().stream().filter(c -> c.getKind() == NeeoDeviceChannelKind.TRIGGER).forEach(channel -> {
219             sb.append("rule \"");
220             sb.append(channel.getItemName());
221             appendLine(sb, "\"");
222
223             appendLine(sb, "when");
224             sb.append("   Channel '");
225             sb.append(new ChannelUID(device.getUid(), channel.getItemName()));
226             appendLine(sb, "' triggered");
227             appendLine(sb, "then");
228             appendLine(sb, "   var data = receivedEvent.getEvent()");
229             appendLine(sb, "   # do something here with your data");
230             appendLine(sb, "end");
231         });
232
233         resp.setContentType("text/plain");
234         resp.setHeader("Content-disposition", "attachment; filename=\"" + device.getName() + ".rules\"");
235         resp.getOutputStream().write(sb.toString().getBytes(StandardCharsets.UTF_8));
236     }
237
238     /**
239      * Helper method to append a line of text ot the string builder with a line separator
240      *
241      * @param sb a non-null string builder
242      * @param text the non-null, possibly empty text
243      */
244     private void appendLine(StringBuilder sb, String text) {
245         Objects.requireNonNull(sb, "sb cannot be null");
246         Objects.requireNonNull(text, "text cannot be null");
247
248         sb.append(text);
249         sb.append(System.lineSeparator());
250     }
251 }