2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.neeo.internal.servletservices;
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Objects;
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
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;
41 import com.google.gson.Gson;
42 import com.google.gson.JsonParseException;
45 * A subclass of {@link DefaultServletService} that handles thing status/definitions for the web pages
47 * @author Tim Roberts - Initial Contribution
50 public class ThingDashboardService extends DefaultServletService {
53 private final Logger logger = LoggerFactory.getLogger(ThingDashboardService.class);
55 /** The gson used for json manipulation */
56 private final Gson gson;
58 /** The service context */
59 private final ServiceContext context;
62 private final NeeoService service;
65 * Constructs the servlet from the {@link NeeoService} and {@link ServiceContext}
67 * @param service the non-null {@link NeeoService}
68 * @param context the non-null {@link ServiceContext}
70 public ThingDashboardService(NeeoService service, ServiceContext context) {
71 Objects.requireNonNull(service, "service cannot be null");
72 Objects.requireNonNull(context, "context cannot be null");
74 this.context = context;
75 this.service = service;
76 gson = NeeoUtil.createNeeoDeviceGsonBuilder(service, context).create();
80 * Returns true if the path starts with "thingstatus", "getchannel", "getvirtualdevice", "restoredevice",
81 * "refreshdevice", "deletedevice", "exportrules", "updatedevice"
83 * @see DefaultServletService#canHandleRoute(String[])
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"));
98 * Handles the get for the 'thingstatus' and 'getchannel' URL (all other URLs do posts)
100 * @see DefaultServletService#handleGet(HttpServletRequest, String[], HttpServletResponse)
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");
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")));
118 NeeoUtil.write(resp, gson.toJson(new ReturnStatus(channels)));
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)));
126 logger.debug("Unknown get path: {}", StringUtils.join(paths, ','));
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())));
135 * Handles the post for the 'updatedevice', 'restoredevice' or 'refreshdevice'.
137 * @see DefaultServletService#handlePost(HttpServletRequest, String[], HttpServletResponse)
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");
149 if (StringUtils.equalsIgnoreCase(paths[0], "updatedevice")) {
150 final NeeoDevice device = gson.fromJson(req.getReader(), NeeoDevice.class);
151 context.getDefinitions().put(device);
153 for (NeeoBrainServlet servlet : service.getServlets()) {
154 servlet.getBrainApi().restart(); // restart so brain will query changes
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!")));
165 NeeoUtil.write(resp, gson.toJson(new ReturnStatus(device)));
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!")));
173 NeeoUtil.write(resp, gson.toJson(new ReturnStatus(device)));
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")));
186 writeExampleRules(resp, device);
189 logger.debug("Unknown post path: {}", StringUtils.join(paths, ','));
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())));
198 * Helper method to produce an examples rules file and write it to the {@link HttpServletResponse}
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
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");
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());
215 device.getChannels().stream().filter(c -> c.getKind() == NeeoDeviceChannelKind.TRIGGER).forEach(channel -> {
216 sb.append("rule \"");
217 sb.append(channel.getItemName());
218 appendLine(sb, "\"");
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");
230 resp.setContentType("text/plain");
231 resp.setHeader("Content-disposition", "attachment; filename=\"" + device.getName() + ".rules\"");
232 IOUtils.write(sb, resp.getOutputStream());
236 * Helper method to append a line of text ot the string builder with a line separator
238 * @param sb a non-null string builder
239 * @param text the non-null, possibly empty text
241 private void appendLine(StringBuilder sb, String text) {
242 Objects.requireNonNull(sb, "sb cannot be null");
243 Objects.requireNonNull(text, "text cannot be null");
246 sb.append(System.lineSeparator());