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