]> git.basschouten.com Git - openhab-addons.git/blob
729bda89eda6add8002dd82cf4091ef9a9850668
[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.wundergroundupdatereceiver.internal;
14
15 import static java.util.stream.Collectors.toMap;
16 import static java.util.stream.Collectors.toSet;
17 import static org.openhab.binding.wundergroundupdatereceiver.internal.WundergroundUpdateReceiverBindingConstants.*;
18
19 import java.io.IOException;
20 import java.io.PrintWriter;
21 import java.time.Instant;
22 import java.util.Collections;
23 import java.util.Dictionary;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Hashtable;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.regex.Pattern;
31
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35
36 import org.eclipse.jdt.annotation.NonNullByDefault;
37 import org.eclipse.jdt.annotation.Nullable;
38 import org.openhab.core.io.http.servlet.BaseOpenHABServlet;
39 import org.osgi.service.http.HttpContext;
40 import org.osgi.service.http.HttpService;
41 import org.osgi.service.http.NamespaceException;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link WundergroundUpdateReceiverServlet} is responsible for receiving updates,and
47  * updating the matching channels.
48  *
49  * @author Daniel Demus - Initial contribution
50  */
51 @NonNullByDefault
52 public class WundergroundUpdateReceiverServlet extends BaseOpenHABServlet
53         implements WundergroundUpdateReceiverServletControls {
54
55     public static final String SERVLET_URL = "/weatherstation/updateweatherstation.php";
56     private static final long serialVersionUID = -5296703727081438023L;
57     private static final Pattern CLEANER = Pattern.compile("[^\\w-]");
58
59     private final Logger logger = LoggerFactory.getLogger(WundergroundUpdateReceiverServlet.class);
60     private final Map<String, WundergroundUpdateReceiverHandler> handlers = new HashMap<>();
61
62     private static final Object LOCK = new Object();
63     private final WundergroundUpdateReceiverDiscoveryService discoveryService;
64
65     private boolean active = false;
66     private String errorDetail = "";
67
68     public WundergroundUpdateReceiverServlet(HttpService httpService,
69             WundergroundUpdateReceiverDiscoveryService discoveryService) {
70         super(httpService);
71         this.discoveryService = discoveryService;
72     }
73
74     public boolean isActive() {
75         synchronized (LOCK) {
76             return this.active;
77         }
78     }
79
80     public String getErrorDetail() {
81         synchronized (LOCK) {
82             return this.errorDetail;
83         }
84     }
85
86     public Set<String> getStationIds() {
87         return this.handlers.keySet();
88     }
89
90     public void activate() {
91         activate(SERVLET_URL, httpService.createDefaultHttpContext());
92     }
93
94     public void addHandler(WundergroundUpdateReceiverHandler handler) {
95         synchronized (this.handlers) {
96             if (this.handlers.containsKey(handler.getStationId())) {
97                 errorDetail = "Handler handling request for stationId " + handler.getStationId() + " is already added";
98                 logger.warn("Error during handler registration - StationId {} already being handled",
99                         handler.getStationId());
100                 return;
101             }
102             this.handlers.put(handler.getStationId(), handler);
103             errorDetail = "";
104             if (!isActive()) {
105                 activate();
106             }
107         }
108     }
109
110     public void removeHandler(String stationId) {
111         synchronized (this.handlers) {
112             WundergroundUpdateReceiverHandler handler = this.handlers.get(stationId);
113             if (handler != null) {
114                 this.handlers.remove(stationId);
115             }
116             if (this.handlers.isEmpty() && !this.discoveryService.isBackgroundDiscoveryEnabled()) {
117                 deactivate();
118             }
119         }
120     }
121
122     public void deactivate() {
123         synchronized (LOCK) {
124             logger.debug("Stopping servlet {} at {}", getClass().getSimpleName(), SERVLET_URL);
125             try {
126                 super.deactivate(SERVLET_URL);
127             } catch (IllegalArgumentException ignored) {
128                 // SERVLET_URL is already unregistered
129             }
130             errorDetail = "";
131             active = false;
132         }
133     }
134
135     public void handlerConfigUpdated(WundergroundUpdateReceiverHandler handler) {
136         synchronized (this.handlers) {
137             final Set<Map.Entry<String, WundergroundUpdateReceiverHandler>> changedStationIds = this.handlers.entrySet()
138                     .stream().filter(entry -> handler.getThing().getUID().equals(entry.getValue().getThing().getUID()))
139                     .collect(toSet());
140             changedStationIds.forEach(entry -> {
141                 logger.debug("Re-assigning listener from station id {} to station id {}", entry.getKey(),
142                         handler.getStationId());
143                 this.removeHandler(entry.getKey());
144                 this.addHandler(handler);
145             });
146         }
147     }
148
149     public void dispose() {
150         synchronized (this.handlers) {
151             Set<String> stationIds = new HashSet<>(getStationIds());
152             stationIds.forEach(this::removeHandler);
153             deactivate();
154         }
155     }
156
157     @Override
158     protected void activate(String alias, HttpContext httpContext) {
159         synchronized (LOCK) {
160             try {
161                 logger.debug("Starting servlet {} at {}", getClass().getSimpleName(), alias);
162                 Dictionary<String, String> props = new Hashtable<>(1, 10);
163                 httpService.registerServlet(alias, this, props, httpContext);
164                 errorDetail = "";
165                 active = true;
166             } catch (NamespaceException e) {
167                 active = false;
168                 errorDetail = "Servlet couldn't be registered - alias " + alias + " already in use";
169                 logger.warn("Error during servlet registration - alias {} already in use", alias, e);
170             } catch (ServletException e) {
171                 active = false;
172                 errorDetail = "Servlet couldn't be registered - " + e.getMessage();
173                 logger.warn("Error during servlet registration", e);
174             }
175         }
176     }
177
178     protected Map<String, String> normalizeParameterMap(Map<String, String[]> parameterMap) {
179         return parameterMap.entrySet().stream()
180                 .collect(toMap(e -> makeUidSafeString(e.getKey()), e -> String.join("", e.getValue())));
181     }
182
183     @Override
184     protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
185         if (!active) {
186             return;
187         }
188         if (req == null) {
189             return;
190         }
191         if (resp == null) {
192             return;
193         }
194         if (req.getRequestURI() == null) {
195             return;
196         }
197         logger.trace("doGet {}", req.getQueryString());
198
199         String stationId = req.getParameter(STATION_ID_PARAMETER);
200         Map<String, String> states = normalizeParameterMap(req.getParameterMap());
201         Optional.ofNullable(this.handlers.get(stationId)).ifPresentOrElse(handler -> {
202             String queryString = req.getQueryString();
203             if (queryString != null && queryString.length() > 0) {
204                 states.put(LAST_QUERY, queryString);
205             }
206             handler.updateChannelStates(states);
207         }, () -> {
208             this.discoveryService.addUnhandledStationId(stationId, states);
209         });
210
211         resp.setStatus(HttpServletResponse.SC_OK);
212         resp.setContentType("text/html;charset=utf-8");
213         resp.setContentLength(7);
214         resp.setDateHeader("Date", Instant.now().toEpochMilli());
215         resp.setHeader("Connection", "close");
216         PrintWriter writer = resp.getWriter();
217         writer.write("success");
218         writer.flush();
219         writer.close();
220     }
221
222     protected Map<String, WundergroundUpdateReceiverHandler> getHandlers() {
223         return Collections.unmodifiableMap(this.handlers);
224     }
225
226     private String makeUidSafeString(String key) {
227         return CLEANER.matcher(key).replaceAll("-");
228     }
229 }