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