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