]> git.basschouten.com Git - openhab-addons.git/blob
c05eda2009304e0b3b8f17c4a9533cf940ebba99
[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.pushsafer.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.pushsafer.internal.config.PushsaferAccountConfiguration;
34 import org.openhab.binding.pushsafer.internal.dto.Icon;
35 import org.openhab.binding.pushsafer.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.JsonElement;
41 import com.google.gson.JsonObject;
42 import com.google.gson.JsonParser;
43
44 /**
45  * The {@link PushsaferAPIConnection} is responsible for handling the connections to Pushsafer Messages API.
46  *
47  * @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
48  */
49 @NonNullByDefault
50 public class PushsaferAPIConnection {
51
52     private final Logger logger = LoggerFactory.getLogger(PushsaferAPIConnection.class);
53
54     private static final String VALIDATE_URL = "https://www.pushsafer.com/api-k";
55     private static final String MESSAGE_URL = "https://www.pushsafer.com/api";
56     private static final String CANCEL_MESSAGE_URL = "https://www.pushsafer.com/api-m";
57     private static final String SOUNDS_URL = "https://www.pushsafer.com/api-s";
58     private static final String ICONS_URL = "https://www.pushsafer.com/api-i";
59
60     private final HttpClient httpClient;
61     private final PushsaferAccountConfiguration config;
62
63     private final ExpiringCacheMap<String, String> cache = new ExpiringCacheMap<>(TimeUnit.DAYS.toMillis(1));
64
65     public PushsaferAPIConnection(HttpClient httpClient, PushsaferAccountConfiguration config) {
66         this.httpClient = httpClient;
67         this.config = config;
68     }
69
70     public boolean validateUser() throws PushsaferCommunicationException, PushsaferConfigurationException {
71         final String localApikey = config.apikey;
72         if (localApikey == null || localApikey.isEmpty()) {
73             throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
74         }
75         final String localUser = config.user;
76         if (localUser == null || localUser.isEmpty()) {
77             throw new PushsaferConfigurationException("@text/offline.conf-error-missing-user");
78         }
79
80         final String content = get(buildURL(VALIDATE_URL, Map.of(PushsaferMessageBuilder.MESSAGE_KEY_TOKEN, localApikey,
81                 PushsaferMessageBuilder.MESSAGE_KEY_USER, localUser)));
82         final JsonObject json = content == null || content.isBlank() ? null
83                 : JsonParser.parseString(content).getAsJsonObject();
84         return json == null ? false : getMessageStatus(json);
85     }
86
87     public boolean sendPushsaferMessage(PushsaferMessageBuilder message)
88             throws PushsaferCommunicationException, PushsaferConfigurationException {
89         return getMessageStatus(post(MESSAGE_URL, message.build()));
90     }
91
92     public String sendPushsaferPriorityMessage(PushsaferMessageBuilder message)
93             throws PushsaferCommunicationException, PushsaferConfigurationException {
94         final JsonObject json = JsonParser.parseString(post(MESSAGE_URL, message.build())).getAsJsonObject();
95         return getMessageStatus(json) && json.has("receipt") ? json.get("receipt").getAsString() : "";
96     }
97
98     public boolean cancelPushsaferPriorityMessage(String receipt)
99             throws PushsaferCommunicationException, PushsaferConfigurationException {
100         return getMessageStatus(post(CANCEL_MESSAGE_URL.replace("{receipt}", receipt),
101                 PushsaferMessageBuilder.getInstance(config.apikey, config.device).build()));
102     }
103
104     public List<Sound> getSounds() throws PushsaferCommunicationException, PushsaferConfigurationException {
105         final String localApikey = config.apikey;
106         if (localApikey == null || localApikey.isEmpty()) {
107             throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
108         }
109
110         final Map<String, String> params = new HashMap<>(1);
111         params.put(PushsaferMessageBuilder.MESSAGE_KEY_TOKEN, localApikey);
112
113         final String content = getFromCache(buildURL(SOUNDS_URL, params));
114         final JsonObject json = content == null || content.isBlank() ? null
115                 : JsonParser.parseString(content).getAsJsonObject();
116         final JsonObject sounds = json == null || !json.has("sounds") ? null : json.get("sounds").getAsJsonObject();
117
118         return sounds == null ? List.of()
119                 : sounds.entrySet().stream().map(entry -> new Sound(entry.getKey(), entry.getValue().getAsString()))
120                         .collect(Collectors.toUnmodifiableList());
121     }
122
123     public List<Icon> getIcons() throws PushsaferCommunicationException, PushsaferConfigurationException {
124         final String localApikey = config.apikey;
125         if (localApikey == null || localApikey.isEmpty()) {
126             throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
127         }
128
129         final Map<String, String> params = new HashMap<>(1);
130         params.put(PushsaferMessageBuilder.MESSAGE_KEY_TOKEN, localApikey);
131
132         final String content = getFromCache(buildURL(ICONS_URL, params));
133         final JsonObject json = content == null || content.isBlank() ? null
134                 : JsonParser.parseString(content).getAsJsonObject();
135         final JsonObject icons = json == null || !json.has("icons") ? null : json.get("icons").getAsJsonObject();
136
137         return icons == null ? List.of()
138                 : icons.entrySet().stream().map(entry -> new Icon(entry.getKey(), entry.getValue().getAsString()))
139                         .collect(Collectors.toUnmodifiableList());
140     }
141
142     private String buildURL(String url, Map<String, String> requestParams) {
143         return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key)))
144                 .collect(Collectors.joining("&", url + "?", ""));
145     }
146
147     private String encodeParam(@Nullable String value) {
148         return value == null ? "" : URLEncoder.encode(value, StandardCharsets.UTF_8);
149     }
150
151     private @Nullable String getFromCache(String url)
152             throws PushsaferCommunicationException, PushsaferConfigurationException {
153         return cache.putIfAbsentAndGet(url, () -> get(url));
154     }
155
156     private String get(String url) throws PushsaferCommunicationException, PushsaferConfigurationException {
157         return executeRequest(HttpMethod.GET, url, null);
158     }
159
160     private String post(String url, ContentProvider body)
161             throws PushsaferCommunicationException, PushsaferConfigurationException {
162         return executeRequest(HttpMethod.POST, url, body);
163     }
164
165     private String executeRequest(HttpMethod httpMethod, String url, @Nullable ContentProvider body)
166             throws PushsaferCommunicationException, PushsaferConfigurationException {
167         logger.trace("Pushsafer request: {} - URL = '{}'", httpMethod, uglifyApikey(url));
168         try {
169             final Request request = httpClient.newRequest(url).method(httpMethod).timeout(10, TimeUnit.SECONDS);
170
171             if (body != null) {
172                 if (logger.isTraceEnabled()) {
173                     logger.trace("Pushsafer request body: '{}'", body);
174                 }
175                 request.content(body);
176             }
177
178             final ContentResponse contentResponse = request.send();
179
180             final int httpStatus = contentResponse.getStatus();
181             final String content = contentResponse.getContentAsString();
182             logger.trace("Pushsafer response: status = {}, content = '{}'", httpStatus, content);
183             switch (httpStatus) {
184                 case HttpStatus.OK_200:
185                     return content;
186                 case 250:
187                 case HttpStatus.BAD_REQUEST_400:
188                     logger.debug("Pushsafer server responded with status code {}: {}", httpStatus, content);
189                     throw new PushsaferConfigurationException(getMessageError(content));
190                 default:
191                     logger.debug("Pushsafer server responded with status code {}: {}", httpStatus, content);
192                     throw new PushsaferCommunicationException(content);
193             }
194         } catch (ExecutionException e) {
195             logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
196             throw new PushsaferCommunicationException(e.getLocalizedMessage(), e.getCause());
197         } catch (InterruptedException | TimeoutException e) {
198             logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
199             throw new PushsaferCommunicationException(e.getLocalizedMessage());
200         }
201     }
202
203     private String uglifyApikey(String url) {
204         return url.replaceAll("(k=)+\\w+", "k=*****");
205     }
206
207     private String getMessageError(String content) {
208         final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
209         final JsonElement errorsElement = json.get("errors");
210         if (errorsElement != null && errorsElement.isJsonArray()) {
211             return errorsElement.getAsJsonArray().toString();
212         }
213         return "@text/offline.conf-error-unknown";
214     }
215
216     private boolean getMessageStatus(String content) {
217         return getMessageStatus(JsonParser.parseString(content).getAsJsonObject());
218     }
219
220     private boolean getMessageStatus(JsonObject json) {
221         return json.has("status") ? json.get("status").getAsInt() == 1 : false;
222     }
223 }