]> git.basschouten.com Git - openhab-addons.git/blob
f7c5f2cbcbc419da53a359d1a07637a7bb830cae
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.pushover.internal.connection;
14
15 import java.net.URLEncoder;
16 import java.nio.charset.StandardCharsets;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23 import java.util.stream.Collectors;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.eclipse.jetty.client.api.ContentProvider;
29 import org.eclipse.jetty.client.api.ContentResponse;
30 import org.eclipse.jetty.client.api.Request;
31 import org.eclipse.jetty.http.HttpMethod;
32 import org.eclipse.jetty.http.HttpStatus;
33 import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration;
34 import org.openhab.binding.pushover.internal.dto.Sound;
35 import org.openhab.core.cache.ExpiringCacheMap;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import com.google.gson.JsonElement;
40 import com.google.gson.JsonObject;
41 import com.google.gson.JsonParser;
42
43 /**
44  * The {@link PushoverAPIConnection} is responsible for handling the connections to Pushover Messages API.
45  *
46  * @author Christoph Weitkamp - Initial contribution
47  */
48 @NonNullByDefault
49 public class PushoverAPIConnection {
50
51     private final Logger logger = LoggerFactory.getLogger(PushoverAPIConnection.class);
52
53     private static final String VALIDATE_URL = "https://api.pushover.net/1/users/validate.json";
54     private static final String MESSAGE_URL = "https://api.pushover.net/1/messages.json";
55     private static final String CANCEL_MESSAGE_URL = "https://api.pushover.net/1/receipts/{receipt}/cancel.json";
56     private static final String SOUNDS_URL = "https://api.pushover.net/1/sounds.json";
57
58     private final HttpClient httpClient;
59     private final PushoverAccountConfiguration config;
60
61     private final ExpiringCacheMap<String, String> cache = new ExpiringCacheMap<>(TimeUnit.DAYS.toMillis(1));
62
63     public PushoverAPIConnection(HttpClient httpClient, PushoverAccountConfiguration config) {
64         this.httpClient = httpClient;
65         this.config = config;
66     }
67
68     public boolean validateUser() throws PushoverCommunicationException, PushoverConfigurationException {
69         return getMessageStatus(
70                 post(VALIDATE_URL, PushoverMessageBuilder.getInstance(config.apikey, config.user).build()));
71     }
72
73     public boolean sendMessage(PushoverMessageBuilder message)
74             throws PushoverCommunicationException, PushoverConfigurationException {
75         return getMessageStatus(post(MESSAGE_URL, message.build()));
76     }
77
78     public String sendPriorityMessage(PushoverMessageBuilder message)
79             throws PushoverCommunicationException, PushoverConfigurationException {
80         final JsonObject json = JsonParser.parseString(post(MESSAGE_URL, message.build())).getAsJsonObject();
81         return getMessageStatus(json) && json.has("receipt") ? json.get("receipt").getAsString() : "";
82     }
83
84     public boolean cancelPriorityMessage(String receipt)
85             throws PushoverCommunicationException, PushoverConfigurationException {
86         return getMessageStatus(post(CANCEL_MESSAGE_URL.replace("{receipt}", receipt),
87                 PushoverMessageBuilder.getInstance(config.apikey, config.user).build()));
88     }
89
90     public List<Sound> getSounds() throws PushoverCommunicationException, PushoverConfigurationException {
91         final String localApikey = config.apikey;
92         if (localApikey == null || localApikey.isEmpty()) {
93             throw new PushoverConfigurationException("@text/offline.conf-error-missing-apikey");
94         }
95
96         final Map<String, String> params = new HashMap<>(1);
97         params.put(PushoverMessageBuilder.MESSAGE_KEY_TOKEN, localApikey);
98
99         // TODO do not cache the response, cache the parsed list of sounds
100         final String content = getFromCache(buildURL(SOUNDS_URL, params));
101         final JsonObject json = content == null ? null : JsonParser.parseString(content).getAsJsonObject();
102         final JsonObject sounds = json == null || !json.has("sounds") ? null : json.get("sounds").getAsJsonObject();
103
104         return sounds == null ? List.of()
105                 : sounds.entrySet().stream().map(entry -> new Sound(entry.getKey(), entry.getValue().getAsString()))
106                         .collect(Collectors.toUnmodifiableList());
107     }
108
109     private String buildURL(String url, Map<String, String> requestParams) {
110         return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key)))
111                 .collect(Collectors.joining("&", url + "?", ""));
112     }
113
114     private String encodeParam(@Nullable String value) {
115         return value == null ? "" : URLEncoder.encode(value, StandardCharsets.UTF_8);
116     }
117
118     private @Nullable String getFromCache(String url) {
119         return cache.putIfAbsentAndGet(url, () -> get(url));
120     }
121
122     private String get(String url) throws PushoverCommunicationException, PushoverConfigurationException {
123         return executeRequest(HttpMethod.GET, url, null);
124     }
125
126     private String post(String url, ContentProvider body)
127             throws PushoverCommunicationException, PushoverConfigurationException {
128         return executeRequest(HttpMethod.POST, url, body);
129     }
130
131     private synchronized String executeRequest(HttpMethod httpMethod, String url, @Nullable ContentProvider body)
132             throws PushoverCommunicationException, PushoverConfigurationException {
133         logger.trace("Pushover request: {} - URL = '{}'", httpMethod, url);
134         try {
135             final Request request = httpClient.newRequest(url).method(httpMethod).timeout(config.timeout,
136                     TimeUnit.SECONDS);
137
138             if (body != null) {
139                 if (logger.isTraceEnabled()) {
140                     logger.trace("Pushover request body: '{}'", body);
141                 }
142                 request.content(body);
143             }
144
145             final ContentResponse contentResponse = request.send();
146
147             final int httpStatus = contentResponse.getStatus();
148             final String content = contentResponse.getContentAsString();
149             logger.trace("Pushover response: status = {}, content = '{}'", httpStatus, content);
150             switch (httpStatus) {
151                 case HttpStatus.OK_200:
152                     return content;
153                 case HttpStatus.BAD_REQUEST_400:
154                     logger.debug("Pushover server responded with status code {}: {}", httpStatus, content);
155                     throw new PushoverConfigurationException(getMessageError(content));
156                 default:
157                     logger.debug("Pushover server responded with status code {}: {}", httpStatus, content);
158                     throw new PushoverCommunicationException(content);
159             }
160         } catch (ExecutionException e) {
161             logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
162             throw new PushoverCommunicationException(e.getLocalizedMessage(), e.getCause());
163         } catch (InterruptedException | TimeoutException e) {
164             logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
165             throw new PushoverCommunicationException(e.getLocalizedMessage());
166         }
167     }
168
169     private String getMessageError(String content) {
170         final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
171         final JsonElement errorsElement = json.get("errors");
172         if (errorsElement != null && errorsElement.isJsonArray()) {
173             return errorsElement.getAsJsonArray().toString();
174         }
175         return "@text/offline.conf-error-unknown";
176     }
177
178     private boolean getMessageStatus(String content) {
179         final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
180         return json.has("status") ? json.get("status").getAsInt() == 1 : false;
181     }
182
183     private boolean getMessageStatus(JsonObject json) {
184         return json.has("status") ? json.get("status").getAsInt() == 1 : false;
185     }
186 }