]> git.basschouten.com Git - openhab-addons.git/blob
da5542307c980de1dc9b4b8b99893e203a4d3da9
[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.verisure.internal;
14
15 import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.net.CookieStore;
19 import java.net.HttpCookie;
20 import java.net.URI;
21 import java.net.URISyntaxException;
22 import java.nio.charset.StandardCharsets;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Base64;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.CopyOnWriteArrayList;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.TimeoutException;
34
35 import org.eclipse.jdt.annotation.NonNullByDefault;
36 import org.eclipse.jdt.annotation.Nullable;
37 import org.eclipse.jetty.client.HttpClient;
38 import org.eclipse.jetty.client.HttpResponseException;
39 import org.eclipse.jetty.client.api.ContentResponse;
40 import org.eclipse.jetty.client.api.Request;
41 import org.eclipse.jetty.client.util.BytesContentProvider;
42 import org.eclipse.jetty.http.HttpMethod;
43 import org.eclipse.jetty.http.HttpStatus;
44 import org.jsoup.Jsoup;
45 import org.jsoup.nodes.Document;
46 import org.jsoup.nodes.Element;
47 import org.openhab.binding.verisure.internal.dto.VerisureAlarmsDTO;
48 import org.openhab.binding.verisure.internal.dto.VerisureBatteryStatusDTO;
49 import org.openhab.binding.verisure.internal.dto.VerisureBroadbandConnectionsDTO;
50 import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO;
51 import org.openhab.binding.verisure.internal.dto.VerisureDoorWindowsDTO;
52 import org.openhab.binding.verisure.internal.dto.VerisureEventLogDTO;
53 import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO;
54 import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO.CommunicationState;
55 import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO;
56 import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO.Owainstallation;
57 import org.openhab.binding.verisure.internal.dto.VerisureMiceDetectionDTO;
58 import org.openhab.binding.verisure.internal.dto.VerisureSmartLockDTO;
59 import org.openhab.binding.verisure.internal.dto.VerisureSmartLocksDTO;
60 import org.openhab.binding.verisure.internal.dto.VerisureSmartPlugsDTO;
61 import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
62 import org.openhab.binding.verisure.internal.dto.VerisureUserPresencesDTO;
63 import org.openhab.binding.verisure.internal.handler.VerisureThingHandler;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 import com.google.gson.Gson;
68 import com.google.gson.JsonSyntaxException;
69
70 /**
71  * This class performs the communication with Verisure My Pages.
72  *
73  * @author Jarle Hjortland - Initial contribution
74  * @author Jan Gustafsson - Re-design and support for several sites and update to new Verisure API
75  *
76  */
77 @NonNullByDefault
78 public class VerisureSession {
79
80     @NonNullByDefault({})
81     private final Map<String, VerisureThingDTO> verisureThings = new ConcurrentHashMap<>();
82     private final Map<String, VerisureThingHandler<?>> verisureHandlers = new ConcurrentHashMap<>();
83     private final Logger logger = LoggerFactory.getLogger(VerisureSession.class);
84     private final Gson gson = new Gson();
85     private final List<DeviceStatusListener<VerisureThingDTO>> deviceStatusListeners = new CopyOnWriteArrayList<>();
86     private final Map<BigDecimal, VerisureInstallation> verisureInstallations = new ConcurrentHashMap<>();
87     private static final List<String> APISERVERLIST = Arrays.asList("https://m-api01.verisure.com",
88             "https://m-api02.verisure.com");
89     private int apiServerInUseIndex = 0;
90     private int numberOfEvents = 15;
91     private static final String USER_NAME = "username";
92     private static final String VID = "vid";
93     private static final String VS_STEPUP = "vs-stepup";
94     private static final String VS_ACCESS = "vs-access";
95     private String apiServerInUse = APISERVERLIST.get(apiServerInUseIndex);
96     private String authstring = "";
97     private @Nullable String csrf;
98     private @Nullable String pinCode;
99     private HttpClient httpClient;
100     private String userName = "";
101     private String password = "";
102     private String vid = "";
103     private String vsAccess = "";
104     private String vsStepup = "";
105
106     public VerisureSession(HttpClient httpClient) {
107         this.httpClient = httpClient;
108     }
109
110     public boolean initialize(@Nullable String authstring, @Nullable String pinCode, String userName, String password) {
111         if (authstring != null) {
112             this.authstring = authstring.substring(0);
113             this.pinCode = pinCode;
114             this.userName = userName;
115             this.password = password;
116             // Try to login to Verisure
117             if (logIn()) {
118                 return getInstallations();
119             } else {
120                 return false;
121             }
122         }
123         return false;
124     }
125
126     public boolean refresh() {
127         try {
128             if (logIn()) {
129                 if (updateStatus()) {
130                     return true;
131                 }
132             }
133             return false;
134         } catch (HttpResponseException e) {
135             logger.warn("Failed to do a refresh {}", e.getMessage());
136             return false;
137         }
138     }
139
140     public int sendCommand(String url, String data, BigDecimal installationId) {
141         logger.debug("Sending command with URL {} and data {}", url, data);
142         try {
143             configureInstallationInstance(installationId);
144             int httpResultCode = setSessionCookieAuthLogin();
145             if (httpResultCode == HttpStatus.OK_200) {
146                 return postVerisureAPI(url, data);
147             } else {
148                 return httpResultCode;
149             }
150         } catch (ExecutionException | InterruptedException | TimeoutException e) {
151             logger.debug("Failed to send command {}", e.getMessage());
152         }
153         return HttpStatus.BAD_REQUEST_400;
154     }
155
156     public boolean unregisterDeviceStatusListener(
157             DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
158         return deviceStatusListeners.remove(deviceStatusListener);
159     }
160
161     @SuppressWarnings("unchecked")
162     public boolean registerDeviceStatusListener(DeviceStatusListener<? extends VerisureThingDTO> deviceStatusListener) {
163         return deviceStatusListeners.add((DeviceStatusListener<VerisureThingDTO>) deviceStatusListener);
164     }
165
166     @SuppressWarnings({ "unchecked" })
167     public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId, Class<T> thingType) {
168         VerisureThingDTO thing = verisureThings.get(deviceId);
169         if (thingType.isInstance(thing)) {
170             return (T) thing;
171         }
172         return null;
173     }
174
175     public <T extends VerisureThingDTO> @Nullable T getVerisureThing(String deviceId) {
176         VerisureThingDTO thing = verisureThings.get(deviceId);
177         if (thing != null) {
178             @SuppressWarnings("unchecked")
179             T thing2 = (T) thing;
180             return thing2;
181         }
182         return null;
183     }
184
185     public @Nullable VerisureThingHandler<?> getVerisureThinghandler(String deviceId) {
186         VerisureThingHandler<?> thingHandler = verisureHandlers.get(deviceId);
187         return thingHandler;
188     }
189
190     public void setVerisureThingHandler(VerisureThingHandler<?> vth, String deviceId) {
191         verisureHandlers.put(deviceId, vth);
192     };
193
194     public void removeVerisureThingHandler(String deviceId) {
195         verisureHandlers.remove(deviceId);
196     }
197
198     public Collection<VerisureThingDTO> getVerisureThings() {
199         return verisureThings.values();
200     }
201
202     public @Nullable String getCsrf() {
203         return csrf;
204     }
205
206     public @Nullable String getPinCode() {
207         return pinCode;
208     }
209
210     public String getApiServerInUse() {
211         return apiServerInUse;
212     }
213
214     public void setApiServerInUse(String apiServerInUse) {
215         this.apiServerInUse = apiServerInUse;
216     }
217
218     public String getNextApiServer() {
219         apiServerInUseIndex++;
220         if (apiServerInUseIndex > (APISERVERLIST.size() - 1)) {
221             apiServerInUseIndex = 0;
222         }
223         return APISERVERLIST.get(apiServerInUseIndex);
224     }
225
226     public void setNumberOfEvents(int numberOfEvents) {
227         this.numberOfEvents = numberOfEvents;
228     }
229
230     public void configureInstallationInstance(BigDecimal installationId)
231             throws ExecutionException, InterruptedException, TimeoutException {
232         csrf = getCsrfToken(installationId);
233         logger.debug("Got CSRF: {}", csrf);
234         // Set installation
235         String url = SET_INSTALLATION + installationId;
236         httpClient.GET(url);
237     }
238
239     public @Nullable String getCsrfToken(BigDecimal installationId)
240             throws ExecutionException, InterruptedException, TimeoutException {
241         String html = null;
242         String url = SETTINGS + installationId;
243
244         ContentResponse resp = httpClient.GET(url);
245         html = resp.getContentAsString();
246         logger.trace("url: {} html: {}", url, html);
247
248         Document htmlDocument = Jsoup.parse(html);
249         Element nameInput = htmlDocument.select("input[name=_csrf]").first();
250         if (nameInput != null) {
251             return nameInput.attr("value");
252         } else {
253             return null;
254         }
255     }
256
257     public @Nullable String getPinCode(BigDecimal installationId) {
258         VerisureInstallation inst = verisureInstallations.get(installationId);
259         if (inst != null) {
260             return inst.getPinCode();
261         } else {
262             logger.debug("Installation is null!");
263             return null;
264         }
265     }
266
267     private void analyzeCookies() {
268         CookieStore c = httpClient.getCookieStore();
269         List<HttpCookie> cookies = c.getCookies();
270         final List<HttpCookie> unmodifiableList = List.of(cookies.toArray(new HttpCookie[] {}));
271         unmodifiableList.forEach(cookie -> {
272             logger.trace("Response Cookie: {}", cookie);
273             if (VID.equals(cookie.getName())) {
274                 vid = cookie.getValue();
275                 logger.debug("Fetching vid {} from cookie", vid);
276             } else if (VS_ACCESS.equals(cookie.getName())) {
277                 vsAccess = cookie.getValue();
278                 logger.debug("Fetching vs-access {} from cookie", vsAccess);
279             } else if (VS_STEPUP.equals(cookie.getName())) {
280                 vsStepup = cookie.getValue();
281                 logger.debug("Fetching vs-stepup {} from cookie", vsStepup);
282             }
283         });
284     }
285
286     private void logTraceWithPattern(int responseStatus, String content) {
287         if (logger.isTraceEnabled()) {
288             String pattern = "(?m)^\\s*\\r?\\n|\\r?\\n\\s*(?!.*\\r?\\n)";
289             String replacement = "";
290             logger.trace("HTTP Response ({}) Body:{}", responseStatus, content.replaceAll(pattern, replacement));
291         }
292     }
293
294     private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, TimeoutException {
295         logger.debug("Checking if we are logged in");
296         String url = STATUS;
297
298         ContentResponse response = httpClient.newRequest(url).method(HttpMethod.GET).send();
299         String content = response.getContentAsString();
300         logTraceWithPattern(response.getStatus(), content);
301
302         switch (response.getStatus()) {
303             case HttpStatus.OK_200:
304                 if (content.contains("<link href=\"/newapp")) {
305                     return true;
306                 } else {
307                     logger.debug("We need to login again!");
308                     return false;
309                 }
310             case HttpStatus.MOVED_TEMPORARILY_302:
311                 // Redirection
312                 logger.debug("Status code 302. Redirected. Probably not logged in");
313                 return false;
314             case HttpStatus.INTERNAL_SERVER_ERROR_500:
315             case HttpStatus.SERVICE_UNAVAILABLE_503:
316                 throw new HttpResponseException(
317                         "Status code " + response.getStatus() + ". Verisure service temporarily down", response);
318             default:
319                 logger.debug("Status code {} body {}", response.getStatus(), content);
320                 break;
321         }
322         return false;
323     }
324
325     private <T> @Nullable T getJSONVerisureAPI(String url, Class<T> jsonClass)
326             throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
327         logger.debug("HTTP GET: {}", BASE_URL + url);
328
329         ContentResponse response = httpClient.GET(BASE_URL + url + "?_=" + System.currentTimeMillis());
330         String content = response.getContentAsString();
331         logTraceWithPattern(response.getStatus(), content);
332
333         return gson.fromJson(content, jsonClass);
334     }
335
336     private ContentResponse postVerisureAPI(String url, String data, boolean isJSON)
337             throws ExecutionException, InterruptedException, TimeoutException {
338         logger.debug("postVerisureAPI URL: {} Data:{}", url, data);
339
340         Request request = httpClient.newRequest(url).method(HttpMethod.POST);
341         if (isJSON) {
342             request.header("content-type", "application/json");
343         } else {
344             if (csrf != null) {
345                 request.header("X-CSRF-TOKEN", csrf);
346             }
347         }
348         request.header("Accept", "application/json");
349
350         if (url.contains(AUTH_LOGIN)) {
351             request.header("APPLICATION_ID", "OpenHAB Verisure");
352             String basicAuhentication = Base64.getEncoder().encodeToString((userName + ":" + password).getBytes());
353             request.header("authorization", "Basic " + basicAuhentication);
354         } else {
355             if (!vid.isEmpty()) {
356                 request.cookie(new HttpCookie(VID, vid));
357                 logger.debug("Setting cookie with vid {}", vid);
358             }
359             if (!vsAccess.isEmpty()) {
360                 request.cookie(new HttpCookie(VS_ACCESS, vsAccess));
361                 logger.debug("Setting cookie with vs-access {}", vsAccess);
362             }
363             logger.debug("Setting cookie with username {}", userName);
364             request.cookie(new HttpCookie(USER_NAME, userName));
365         }
366
367         if (!"empty".equals(data)) {
368             request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)),
369                     "application/x-www-form-urlencoded; charset=UTF-8");
370         }
371
372         logger.debug("HTTP POST Request {}.", request.toString());
373         return request.send();
374     }
375
376     private <T> T postJSONVerisureAPI(String url, String data, Class<T> jsonClass)
377             throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException, PostToAPIException {
378         for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
379             ContentResponse response = postVerisureAPI(apiServerInUse + url, data, Boolean.TRUE);
380             logger.debug("HTTP Response ({})", response.getStatus());
381             if (response.getStatus() == HttpStatus.OK_200) {
382                 String content = response.getContentAsString();
383                 if (content.contains("\"message\":\"Request Failed") && content.contains("503")) {
384                     // Maybe Verisure has switched API server in use?
385                     logger.debug("Changed API server! Response: {}", content);
386                     setApiServerInUse(getNextApiServer());
387                 } else if (content.contains("\"message\":\"Request Failed")
388                         && content.contains("Invalid session cookie")) {
389                     throw new PostToAPIException("Invalid session cookie");
390                 } else {
391                     String contentChomped = content.trim();
392                     logger.trace("Response body: {}", content);
393                     return gson.fromJson(contentChomped, jsonClass);
394                 }
395             } else {
396                 logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
397             }
398         }
399         throw new PostToAPIException("Failed to POST to API");
400     }
401
402     private int postVerisureAPI(String urlString, String data) {
403         String url;
404         if (urlString.contains("https://mypages")) {
405             url = urlString;
406         } else {
407             url = apiServerInUse + urlString;
408         }
409
410         for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
411             try {
412                 ContentResponse response = postVerisureAPI(url, data, Boolean.FALSE);
413                 logger.debug("HTTP Response ({})", response.getStatus());
414                 int httpStatus = response.getStatus();
415                 if (httpStatus == HttpStatus.OK_200) {
416                     String content = response.getContentAsString();
417                     if (content.contains("\"message\":\"Request Failed. Code 503 from")) {
418                         if (url.contains("https://mypages")) {
419                             // Not an API URL
420                             return HttpStatus.SERVICE_UNAVAILABLE_503;
421                         } else {
422                             // Maybe Verisure has switched API server in use
423                             setApiServerInUse(getNextApiServer());
424                             url = apiServerInUse + urlString;
425                         }
426                     } else {
427                         logTraceWithPattern(httpStatus, content);
428                         return httpStatus;
429                     }
430                 } else if (httpStatus == HttpStatus.BAD_REQUEST_400) {
431                     setApiServerInUse(getNextApiServer());
432                     url = apiServerInUse + urlString;
433                 } else {
434                     logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
435                 }
436             } catch (ExecutionException | InterruptedException | TimeoutException e) {
437                 logger.warn("Failed to send a POST to the API {}", e.getMessage());
438             }
439         }
440         return HttpStatus.SERVICE_UNAVAILABLE_503;
441     }
442
443     private int setSessionCookieAuthLogin() throws ExecutionException, InterruptedException, TimeoutException {
444         // URL to set status which will give us 2 cookies with username and password used for the session
445         String url = STATUS;
446         ContentResponse response = httpClient.GET(url);
447         logTraceWithPattern(response.getStatus(), response.getContentAsString());
448
449         url = AUTH_LOGIN;
450         int httpStatusCode = postVerisureAPI(url, "empty");
451         analyzeCookies();
452
453         // return response.getStatus();
454         return httpStatusCode;
455     }
456
457     private boolean getInstallations() {
458         int httpResultCode = 0;
459
460         try {
461             httpResultCode = setSessionCookieAuthLogin();
462         } catch (ExecutionException | InterruptedException | TimeoutException e) {
463             logger.warn("Failed to set session cookie {}", e.getMessage());
464             return false;
465         }
466
467         if (httpResultCode == HttpStatus.OK_200) {
468             String url = START_GRAPHQL;
469
470             String queryQLAccountInstallations = "[{\"operationName\":\"AccountInstallations\",\"variables\":{\"email\":\""
471                     + userName
472                     + "\"},\"query\":\"query AccountInstallations($email: String!) {\\n  account(email: $email) {\\n    owainstallations {\\n      giid\\n      alias\\n      type\\n      subsidiary\\n      dealerId\\n      __typename\\n    }\\n    __typename\\n  }\\n}\\n\"}]";
473             try {
474                 VerisureInstallationsDTO installations = postJSONVerisureAPI(url, queryQLAccountInstallations,
475                         VerisureInstallationsDTO.class);
476                 logger.debug("Installation: {}", installations.toString());
477                 List<Owainstallation> owaInstList = installations.getData().getAccount().getOwainstallations();
478                 boolean pinCodesMatchInstallations = true;
479                 List<String> pinCodes = null;
480                 String pinCode = this.pinCode;
481                 if (pinCode != null) {
482                     pinCodes = Arrays.asList(pinCode.split(","));
483                     if (owaInstList.size() != pinCodes.size()) {
484                         logger.debug("Number of installations {} does not match number of pin codes configured {}",
485                                 owaInstList.size(), pinCodes.size());
486                         pinCodesMatchInstallations = false;
487                     }
488                 } else {
489                     logger.debug("No pin-code defined for user {}", userName);
490                 }
491
492                 for (int i = 0; i < owaInstList.size(); i++) {
493                     VerisureInstallation vInst = new VerisureInstallation();
494                     Owainstallation owaInstallation = owaInstList.get(i);
495                     String installationId = owaInstallation.getGiid();
496                     if (owaInstallation.getAlias() != null && installationId != null) {
497                         vInst.setInstallationId(new BigDecimal(installationId));
498                         vInst.setInstallationName(owaInstallation.getAlias());
499                         if (pinCode != null && pinCodes != null) {
500                             int pinCodeIndex = pinCodesMatchInstallations ? i : 0;
501                             vInst.setPinCode(pinCodes.get(pinCodeIndex));
502                             logger.debug("Setting configured pincode index[{}] to installation ID {}", pinCodeIndex,
503                                     installationId);
504                         }
505                         verisureInstallations.put(new BigDecimal(installationId), vInst);
506                     } else {
507                         logger.warn("Failed to get alias and/or giid");
508                         return false;
509                     }
510                 }
511             } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
512                     | PostToAPIException e) {
513                 logger.warn("Failed to send a POST to the API {}", e.getMessage());
514             }
515         } else {
516             logger.warn("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
517             return false;
518         }
519         return true;
520     }
521
522     private synchronized boolean logIn() {
523         try {
524             if (!areWeLoggedIn()) {
525                 vid = "";
526                 vsAccess = "";
527                 logger.debug("Attempting to log in to {}, remove all cookies to ensure a fresh session", BASE_URL);
528                 URI authUri = new URI(BASE_URL);
529                 CookieStore store = httpClient.getCookieStore();
530                 store.get(authUri).forEach(cookie -> {
531                     store.remove(authUri, cookie);
532                 });
533
534                 String url = AUTH_LOGIN;
535                 int httpStatusCode = postVerisureAPI(url, "empty");
536                 analyzeCookies();
537                 if (!vsStepup.isEmpty()) {
538                     logger.warn("MFA is activated on this user! Not supported by binding!");
539                     return false;
540                 }
541
542                 url = LOGON_SUF;
543                 logger.debug("Login URL: {}", url);
544                 httpStatusCode = postVerisureAPI(url, authstring);
545                 if (httpStatusCode != HttpStatus.OK_200) {
546                     logger.debug("Failed to login, HTTP status code: {}", httpStatusCode);
547                     return false;
548                 }
549                 return true;
550             } else {
551                 return true;
552             }
553         } catch (ExecutionException | InterruptedException | TimeoutException | URISyntaxException e) {
554             logger.warn("Failed to login {}", e.getMessage());
555         }
556         return false;
557     }
558
559     private <T extends VerisureThingDTO> void notifyListeners(T thing) {
560         deviceStatusListeners.forEach(listener -> {
561             if (listener.getVerisureThingClass().equals(thing.getClass())) {
562                 listener.onDeviceStateChanged(thing);
563             }
564         });
565     }
566
567     private void notifyListenersIfChanged(VerisureThingDTO thing, VerisureInstallation installation, String deviceId) {
568         String normalizedDeviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
569         thing.setDeviceId(normalizedDeviceId);
570         thing.setSiteId(installation.getInstallationId());
571         thing.setSiteName(installation.getInstallationName());
572         VerisureThingDTO oldObj = verisureThings.get(normalizedDeviceId);
573         if (!thing.equals(oldObj)) {
574             verisureThings.put(thing.getDeviceId(), thing);
575             notifyListeners(thing);
576         } else {
577             logger.trace("No need to notify listeners for thing: {}", thing);
578         }
579     }
580
581     private boolean updateStatus() {
582         logger.debug("Update status");
583         for (Map.Entry<BigDecimal, VerisureInstallation> verisureInstallations : verisureInstallations.entrySet()) {
584             VerisureInstallation installation = verisureInstallations.getValue();
585             try {
586                 configureInstallationInstance(installation.getInstallationId());
587                 int httpResultCode = setSessionCookieAuthLogin();
588                 if (httpResultCode == HttpStatus.OK_200) {
589                     updateAlarmStatus(installation);
590                     updateSmartLockStatus(installation);
591                     updateMiceDetectionStatus(installation);
592                     updateClimateStatus(installation);
593                     updateDoorWindowStatus(installation);
594                     updateUserPresenceStatus(installation);
595                     updateSmartPlugStatus(installation);
596                     updateBroadbandConnectionStatus(installation);
597                     updateEventLogStatus(installation);
598                     updateGatewayStatus(installation);
599                 } else {
600                     logger.debug("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
601                     return false;
602                 }
603             } catch (ExecutionException | InterruptedException | TimeoutException | PostToAPIException e) {
604                 logger.debug("Failed to update status {}", e.getMessage());
605                 return false;
606             }
607         }
608         return true;
609     }
610
611     private String createOperationJSON(String operation, VariablesDTO variables, String query) {
612         OperationDTO operationJSON = new OperationDTO();
613         operationJSON.setOperationName(operation);
614         operationJSON.setVariables(variables);
615         operationJSON.setQuery(query);
616         return gson.toJson(Collections.singletonList(operationJSON));
617     }
618
619     private synchronized void updateAlarmStatus(VerisureInstallation installation) throws PostToAPIException {
620         BigDecimal installationId = installation.getInstallationId();
621         String url = START_GRAPHQL;
622         String operation = "ArmState";
623         VariablesDTO variables = new VariablesDTO();
624         variables.setGiid(installationId.toString());
625         String query = "query " + operation
626                 + "($giid: String!) {\n  installation(giid: $giid) {\n armState {\n type\n statusType\n date\n name\n changedVia\n allowedForFirstLine\n allowed\n errorCodes {\n value\n message\n __typename\n}\n __typename\n}\n __typename\n}\n}\n";
627
628         String queryQLAlarmStatus = createOperationJSON(operation, variables, query);
629         logger.debug("Quering API for alarm status!");
630         try {
631             VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLAlarmStatus, VerisureAlarmsDTO.class);
632             logger.debug("REST Response ({})", thing);
633             // Set unique deviceID
634             String deviceId = "alarm" + installationId;
635             thing.setDeviceId(deviceId);
636             notifyListenersIfChanged(thing, installation, deviceId);
637         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
638             logger.warn("Failed to send a POST to the API {}", e.getMessage());
639         }
640     }
641
642     private synchronized void updateSmartLockStatus(VerisureInstallation installation) {
643         BigDecimal installationId = installation.getInstallationId();
644         String url = START_GRAPHQL;
645         String operation = "DoorLock";
646         String query = "query " + operation
647                 + "($giid: String!) {\n  installation(giid: $giid) {\n doorlocks {\n device {\n deviceLabel\n area\n __typename\n}\n currentLockState\n eventTime\n secureModeActive\n motorJam\n userString\n method\n __typename\n}\n __typename\n}\n}\n";
648         VariablesDTO variables = new VariablesDTO();
649         variables.setGiid(installationId.toString());
650         String queryQLSmartLock = createOperationJSON(operation, variables, query);
651         logger.debug("Quering API for smart lock status");
652
653         try {
654             VerisureSmartLocksDTO thing = postJSONVerisureAPI(url, queryQLSmartLock, VerisureSmartLocksDTO.class);
655             logger.debug("REST Response ({})", thing);
656             List<VerisureSmartLocksDTO.Doorlock> doorLockList = thing.getData().getInstallation().getDoorlocks();
657             doorLockList.forEach(doorLock -> {
658                 VerisureSmartLocksDTO slThing = new VerisureSmartLocksDTO();
659                 VerisureSmartLocksDTO.Installation inst = new VerisureSmartLocksDTO.Installation();
660                 inst.setDoorlocks(Collections.singletonList(doorLock));
661                 VerisureSmartLocksDTO.Data data = new VerisureSmartLocksDTO.Data();
662                 data.setInstallation(inst);
663                 slThing.setData(data);
664                 // Set unique deviceID
665                 String deviceId = doorLock.getDevice().getDeviceLabel();
666                 if (deviceId != null) {
667                     // Set location
668                     slThing.setLocation(doorLock.getDevice().getArea());
669                     slThing.setDeviceId(deviceId);
670
671                     // Fetch more info from old endpoint
672                     try {
673                         VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(),
674                                 VerisureSmartLockDTO.class);
675                         logger.debug("REST Response ({})", smartLockThing);
676                         slThing.setSmartLockJSON(smartLockThing);
677                     } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
678                         logger.warn("Failed to query for smartlock status: {}", e.getMessage());
679                     }
680                     notifyListenersIfChanged(slThing, installation, deviceId);
681                 }
682             });
683
684         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
685                 | PostToAPIException e) {
686             logger.warn("Failed to send a POST to the API {}", e.getMessage());
687         }
688     }
689
690     private synchronized void updateSmartPlugStatus(VerisureInstallation installation) {
691         BigDecimal installationId = installation.getInstallationId();
692         String url = START_GRAPHQL;
693         String operation = "SmartPlug";
694         VariablesDTO variables = new VariablesDTO();
695         variables.setGiid(installationId.toString());
696         String query = "query " + operation
697                 + "($giid: String!) {\n  installation(giid: $giid) {\n smartplugs {\n device {\n deviceLabel\n area\n gui {\n support\n label\n __typename\n}\n __typename\n}\n currentState\n icon\n isHazardous\n __typename\n}\n __typename\n}\n}\n";
698         String queryQLSmartPlug = createOperationJSON(operation, variables, query);
699         logger.debug("Quering API for smart plug status");
700
701         try {
702             VerisureSmartPlugsDTO thing = postJSONVerisureAPI(url, queryQLSmartPlug, VerisureSmartPlugsDTO.class);
703             logger.debug("REST Response ({})", thing);
704             List<VerisureSmartPlugsDTO.Smartplug> smartPlugList = thing.getData().getInstallation().getSmartplugs();
705             smartPlugList.forEach(smartPlug -> {
706                 VerisureSmartPlugsDTO spThing = new VerisureSmartPlugsDTO();
707                 VerisureSmartPlugsDTO.Installation inst = new VerisureSmartPlugsDTO.Installation();
708                 inst.setSmartplugs(Collections.singletonList(smartPlug));
709                 VerisureSmartPlugsDTO.Data data = new VerisureSmartPlugsDTO.Data();
710                 data.setInstallation(inst);
711                 spThing.setData(data);
712                 // Set unique deviceID
713                 String deviceId = smartPlug.getDevice().getDeviceLabel();
714                 if (deviceId != null) {
715                     // Set location
716                     spThing.setLocation(smartPlug.getDevice().getArea());
717                     notifyListenersIfChanged(spThing, installation, deviceId);
718                 }
719             });
720         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
721                 | PostToAPIException e) {
722             logger.warn("Failed to send a POST to the API {}", e.getMessage());
723         }
724     }
725
726     private @Nullable VerisureBatteryStatusDTO getBatteryStatus(String deviceId,
727             VerisureBatteryStatusDTO @Nullable [] batteryStatus) {
728         if (batteryStatus != null) {
729             for (VerisureBatteryStatusDTO verisureBatteryStatusDTO : batteryStatus) {
730                 String id = verisureBatteryStatusDTO.getId();
731                 if (id != null && id.equals(deviceId)) {
732                     return verisureBatteryStatusDTO;
733                 }
734             }
735         }
736         return null;
737     }
738
739     private synchronized void updateClimateStatus(VerisureInstallation installation) {
740         BigDecimal installationId = installation.getInstallationId();
741         String url = START_GRAPHQL;
742         VariablesDTO variables = new VariablesDTO();
743         variables.setGiid(installationId.toString());
744         String operation = "Climate";
745         String query = "query " + operation
746                 + "($giid: String!) {\n installation(giid: $giid) {\n climates {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n humidityEnabled\n humidityTimestamp\n humidityValue\n temperatureTimestamp\n temperatureValue\n __typename\n }\n __typename\n}\n}\n";
747
748         String queryQLClimates = createOperationJSON(operation, variables, query);
749         logger.debug("Quering API for climate status");
750
751         try {
752             VerisureClimatesDTO thing = postJSONVerisureAPI(url, queryQLClimates, VerisureClimatesDTO.class);
753             logger.debug("REST Response ({})", thing);
754             List<VerisureClimatesDTO.Climate> climateList = thing.getData().getInstallation().getClimates();
755             if (climateList != null) {
756                 climateList.forEach(climate -> {
757                     // If thing is Mouse detection device, then skip it, but fetch temperature from it
758                     String type = climate.getDevice().getGui().getLabel();
759                     if ("MOUSE".equals(type)) {
760                         logger.debug("Mouse detection device!");
761                         String deviceId = climate.getDevice().getDeviceLabel();
762                         if (deviceId != null) {
763                             deviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
764                             VerisureThingDTO mouseThing = verisureThings.get(deviceId);
765                             if (mouseThing != null && mouseThing instanceof VerisureMiceDetectionDTO) {
766                                 VerisureMiceDetectionDTO miceDetectorThing = (VerisureMiceDetectionDTO) mouseThing;
767                                 miceDetectorThing.setTemperatureValue(climate.getTemperatureValue());
768                                 miceDetectorThing.setTemperatureTime(climate.getTemperatureTimestamp());
769                                 notifyListeners(miceDetectorThing);
770                                 logger.debug("Found climate thing for a Verisure Mouse Detector");
771                             }
772                         }
773                         return;
774                     }
775                     VerisureClimatesDTO cThing = new VerisureClimatesDTO();
776                     VerisureClimatesDTO.Installation inst = new VerisureClimatesDTO.Installation();
777                     inst.setClimates(Collections.singletonList(climate));
778                     VerisureClimatesDTO.Data data = new VerisureClimatesDTO.Data();
779                     data.setInstallation(inst);
780                     cThing.setData(data);
781                     // Set unique deviceID
782                     String deviceId = climate.getDevice().getDeviceLabel();
783                     if (deviceId != null) {
784                         try {
785                             VerisureBatteryStatusDTO[] batteryStatusThingArray = getJSONVerisureAPI(BATTERY_STATUS,
786                                     VerisureBatteryStatusDTO[].class);
787                             VerisureBatteryStatusDTO batteryStatus = getBatteryStatus(deviceId,
788                                     batteryStatusThingArray);
789                             if (batteryStatus != null) {
790                                 logger.debug("REST Response ({})", batteryStatus);
791                                 cThing.setBatteryStatus(batteryStatus);
792                             }
793                         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
794                             logger.debug("Failed to query for battery status: {}", e.getMessage());
795                         }
796                         // Set location
797                         cThing.setLocation(climate.getDevice().getArea());
798                         notifyListenersIfChanged(cThing, installation, deviceId);
799                     }
800                 });
801             }
802         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
803                 | PostToAPIException e) {
804             logger.warn("Failed to send a POST to the API {}", e.getMessage());
805         }
806     }
807
808     private synchronized void updateDoorWindowStatus(VerisureInstallation installation) {
809         BigDecimal installationId = installation.getInstallationId();
810         String url = START_GRAPHQL;
811         String operation = "DoorWindow";
812         VariablesDTO variables = new VariablesDTO();
813         variables.setGiid(installationId.toString());
814         String query = "query " + operation
815                 + "($giid: String!) {\n installation(giid: $giid) {\n doorWindows {\n device {\n deviceLabel\n area\n __typename\n }\n type\n state\n wired\n reportTime\n __typename\n }\n __typename\n}\n}\n";
816
817         String queryQLDoorWindow = createOperationJSON(operation, variables, query);
818         logger.debug("Quering API for door&window status");
819
820         try {
821             VerisureDoorWindowsDTO thing = postJSONVerisureAPI(url, queryQLDoorWindow, VerisureDoorWindowsDTO.class);
822             logger.debug("REST Response ({})", thing);
823             List<VerisureDoorWindowsDTO.DoorWindow> doorWindowList = thing.getData().getInstallation().getDoorWindows();
824             doorWindowList.forEach(doorWindow -> {
825                 VerisureDoorWindowsDTO dThing = new VerisureDoorWindowsDTO();
826                 VerisureDoorWindowsDTO.Installation inst = new VerisureDoorWindowsDTO.Installation();
827                 inst.setDoorWindows(Collections.singletonList(doorWindow));
828                 VerisureDoorWindowsDTO.Data data = new VerisureDoorWindowsDTO.Data();
829                 data.setInstallation(inst);
830                 dThing.setData(data);
831                 // Set unique deviceID
832                 String deviceId = doorWindow.getDevice().getDeviceLabel();
833                 if (deviceId != null) {
834                     try {
835                         VerisureBatteryStatusDTO[] batteryStatusThingArray = getJSONVerisureAPI(BATTERY_STATUS,
836                                 VerisureBatteryStatusDTO[].class);
837                         VerisureBatteryStatusDTO batteryStatus = getBatteryStatus(deviceId, batteryStatusThingArray);
838                         if (batteryStatus != null) {
839                             logger.debug("REST Response ({})", batteryStatus);
840                             dThing.setBatteryStatus(batteryStatus);
841                         }
842                     } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
843                         logger.warn("Failed to query for door&window status: {}", e.getMessage());
844                     }
845                     // Set location
846                     dThing.setLocation(doorWindow.getDevice().getArea());
847                     notifyListenersIfChanged(dThing, installation, deviceId);
848                 }
849             });
850         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
851                 | PostToAPIException e) {
852             logger.warn("Failed to send a POST to the API {}", e.getMessage());
853         }
854     }
855
856     private synchronized void updateBroadbandConnectionStatus(VerisureInstallation inst) {
857         BigDecimal installationId = inst.getInstallationId();
858         String url = START_GRAPHQL;
859         String operation = "Broadband";
860         VariablesDTO variables = new VariablesDTO();
861         variables.setGiid(installationId.toString());
862         String query = "query " + operation
863                 + "($giid: String!) {\n installation(giid: $giid) {\n broadband {\n testDate\n isBroadbandConnected\n __typename\n }\n __typename\n}\n}\n";
864
865         String queryQLBroadbandConnection = createOperationJSON(operation, variables, query);
866         logger.debug("Quering API for broadband connection status");
867
868         try {
869             VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLBroadbandConnection,
870                     VerisureBroadbandConnectionsDTO.class);
871             logger.debug("REST Response ({})", thing);
872             // Set unique deviceID
873             String deviceId = "bc" + installationId;
874             notifyListenersIfChanged(thing, inst, deviceId);
875         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
876                 | PostToAPIException e) {
877             logger.warn("Failed to send a POST to the API {}", e.getMessage());
878         }
879     }
880
881     private synchronized void updateUserPresenceStatus(VerisureInstallation installation) {
882         BigDecimal installationId = installation.getInstallationId();
883         String url = START_GRAPHQL;
884         String operation = "userTrackings";
885         VariablesDTO variables = new VariablesDTO();
886         variables.setGiid(installationId.toString());
887         String query = "query " + operation
888                 + "($giid: String!) {\ninstallation(giid: $giid) {\n userTrackings {\n isCallingUser\n webAccount\n status\n xbnContactId\n currentLocationName\n deviceId\n name\n currentLocationTimestamp\n deviceName\n currentLocationId\n __typename\n}\n __typename\n}\n}\n";
889
890         String queryQLUserPresence = createOperationJSON(operation, variables, query);
891         logger.debug("Quering API for user presence status");
892
893         try {
894             VerisureUserPresencesDTO thing = postJSONVerisureAPI(url, queryQLUserPresence,
895                     VerisureUserPresencesDTO.class);
896             logger.debug("REST Response ({})", thing);
897             List<VerisureUserPresencesDTO.UserTracking> userTrackingList = thing.getData().getInstallation()
898                     .getUserTrackings();
899             userTrackingList.forEach(userTracking -> {
900                 String localUserTrackingStatus = userTracking.getStatus();
901                 if ("ACTIVE".equals(localUserTrackingStatus)) {
902                     VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
903                     VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
904                     inst.setUserTrackings(Collections.singletonList(userTracking));
905                     VerisureUserPresencesDTO.Data data = new VerisureUserPresencesDTO.Data();
906                     data.setInstallation(inst);
907                     upThing.setData(data);
908                     // Set unique deviceID
909                     String deviceId = "up" + userTracking.getWebAccount() + installationId;
910                     notifyListenersIfChanged(upThing, installation, deviceId);
911                 }
912             });
913         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
914                 | PostToAPIException e) {
915             logger.warn("Failed to send a POST to the API {}", e.getMessage());
916         }
917     }
918
919     private synchronized void updateMiceDetectionStatus(VerisureInstallation installation) {
920         BigDecimal installationId = installation.getInstallationId();
921         String url = START_GRAPHQL;
922         String operation = "Mouse";
923         VariablesDTO variables = new VariablesDTO();
924         variables.setGiid(installationId.toString());
925         String query = "query " + operation
926                 + "($giid: String!) {\n installation(giid: $giid) {\n mice {\n device {\n deviceLabel\n area\n gui {\n support\n __typename\n}\n __typename\n}\n type\n detections {\n count\n gatewayTime\n nodeTime\n duration\n __typename\n}\n __typename\n}\n __typename\n}\n}\n";
927
928         String queryQLMiceDetection = createOperationJSON(operation, variables, query);
929         logger.debug("Quering API for mice detection status");
930
931         try {
932             VerisureMiceDetectionDTO thing = postJSONVerisureAPI(url, queryQLMiceDetection,
933                     VerisureMiceDetectionDTO.class);
934             logger.debug("REST Response ({})", thing);
935             List<VerisureMiceDetectionDTO.Mouse> miceList = thing.getData().getInstallation().getMice();
936             miceList.forEach(mouse -> {
937                 VerisureMiceDetectionDTO miceThing = new VerisureMiceDetectionDTO();
938                 VerisureMiceDetectionDTO.Installation inst = new VerisureMiceDetectionDTO.Installation();
939                 inst.setMice(Collections.singletonList(mouse));
940                 VerisureMiceDetectionDTO.Data data = new VerisureMiceDetectionDTO.Data();
941                 data.setInstallation(inst);
942                 miceThing.setData(data);
943                 // Set unique deviceID
944                 String deviceId = mouse.getDevice().getDeviceLabel();
945                 logger.debug("Mouse id: {} for thing: {}", deviceId, mouse);
946                 if (deviceId != null) {
947                     // Set location
948                     miceThing.setLocation(mouse.getDevice().getArea());
949                     notifyListenersIfChanged(miceThing, installation, deviceId);
950                 }
951             });
952         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
953                 | PostToAPIException e) {
954             logger.warn("Failed to send a POST to the API {}", e.getMessage());
955         }
956     }
957
958     private synchronized void updateEventLogStatus(VerisureInstallation installation) {
959         BigDecimal installationId = installation.getInstallationId();
960         String url = START_GRAPHQL;
961         String operation = "EventLog";
962         int offset = 0;
963         int numberOfEvents = this.numberOfEvents;
964         List<String> eventCategories = new ArrayList<>(Arrays.asList("INTRUSION", "FIRE", "SOS", "WATER", "ANIMAL",
965                 "TECHNICAL", "WARNING", "ARM", "DISARM", "LOCK", "UNLOCK", "PICTURE", "CLIMATE", "CAMERA_SETTINGS",
966                 "DOORWINDOW_STATE_OPENED", "DOORWINDOW_STATE_CLOSED", "USERTRACKING"));
967         VariablesDTO variables = new VariablesDTO();
968         variables.setGiid(installationId.toString());
969         variables.setHideNotifications(true);
970         variables.setOffset(offset);
971         variables.setPagesize(numberOfEvents);
972         variables.setEventCategories(eventCategories);
973         String query = "query " + operation
974                 + "($giid: String!, $offset: Int!, $pagesize: Int!, $eventCategories: [String], $fromDate: String, $toDate: String, $eventContactIds: [String]) {\n installation(giid: $giid) {\n eventLog(offset: $offset, pagesize: $pagesize, eventCategories: $eventCategories, eventContactIds: $eventContactIds, fromDate: $fromDate, toDate: $toDate) {\n moreDataAvailable\n pagedList {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n gatewayArea\n eventType\n eventCategory\n eventSource\n eventId\n eventTime\n userName\n armState\n userType\n climateValue\n sensorType\n eventCount\n  __typename\n }\n __typename\n }\n __typename\n }\n}\n";
975
976         String queryQLEventLog = createOperationJSON(operation, variables, query);
977         logger.debug("Quering API for event log status");
978
979         try {
980             VerisureEventLogDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureEventLogDTO.class);
981             logger.debug("REST Response ({})", thing);
982             // Set unique deviceID
983             String deviceId = "el" + installationId;
984             notifyListenersIfChanged(thing, installation, deviceId);
985         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
986                 | PostToAPIException e) {
987             logger.warn("Failed to send a POST to the API {}", e.getMessage());
988         }
989     }
990
991     private synchronized void updateGatewayStatus(VerisureInstallation installation) {
992         BigDecimal installationId = installation.getInstallationId();
993         String url = START_GRAPHQL;
994         String operation = "communicationState";
995         VariablesDTO variables = new VariablesDTO();
996         variables.setGiid(installationId.toString());
997
998         String query = "query " + operation
999                 + "($giid: String!) {\n installation(giid: $giid) {\n communicationState {\n hardwareCarrierType\n result\n mediaType\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n testDate\n __typename\n }\n __typename\n }\n}";
1000
1001         String queryQLEventLog = createOperationJSON(operation, variables, query);
1002         logger.debug("Quering API for gateway status");
1003
1004         try {
1005             VerisureGatewayDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureGatewayDTO.class);
1006             logger.debug("REST Response ({})", thing);
1007             // Set unique deviceID
1008             List<CommunicationState> communicationStateList = thing.getData().getInstallation().getCommunicationState();
1009             if (!communicationStateList.isEmpty()) {
1010                 String deviceId = communicationStateList.get(0).getDevice().getDeviceLabel();
1011                 if (deviceId != null) {
1012                     notifyListenersIfChanged(thing, installation, deviceId);
1013                 }
1014             }
1015         } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
1016                 | PostToAPIException e) {
1017             logger.warn("Failed to send a POST to the API {}", e.getMessage());
1018         }
1019     }
1020
1021     private final class VerisureInstallation {
1022         private @Nullable String installationName;
1023         private BigDecimal installationId = BigDecimal.ZERO;
1024         private @Nullable String pinCode;
1025
1026         public @Nullable String getPinCode() {
1027             return pinCode;
1028         }
1029
1030         public void setPinCode(@Nullable String pinCode) {
1031             this.pinCode = pinCode;
1032         }
1033
1034         public VerisureInstallation() {
1035         }
1036
1037         public BigDecimal getInstallationId() {
1038             return installationId;
1039         }
1040
1041         public @Nullable String getInstallationName() {
1042             return installationName;
1043         }
1044
1045         public void setInstallationId(BigDecimal installationId) {
1046             this.installationId = installationId;
1047         }
1048
1049         public void setInstallationName(@Nullable String installationName) {
1050             this.installationName = installationName;
1051         }
1052     }
1053
1054     private static class OperationDTO {
1055
1056         @SuppressWarnings("unused")
1057         private @Nullable String operationName;
1058         @SuppressWarnings("unused")
1059         private VariablesDTO variables = new VariablesDTO();
1060         @SuppressWarnings("unused")
1061         private @Nullable String query;
1062
1063         public void setOperationName(String operationName) {
1064             this.operationName = operationName;
1065         }
1066
1067         public void setVariables(VariablesDTO variables) {
1068             this.variables = variables;
1069         }
1070
1071         public void setQuery(String query) {
1072             this.query = query;
1073         }
1074     }
1075
1076     public static class VariablesDTO {
1077
1078         @SuppressWarnings("unused")
1079         private boolean hideNotifications;
1080         @SuppressWarnings("unused")
1081         private int offset;
1082         @SuppressWarnings("unused")
1083         private int pagesize;
1084         @SuppressWarnings("unused")
1085         private @Nullable List<String> eventCategories = null;
1086         @SuppressWarnings("unused")
1087         private @Nullable String giid;
1088
1089         public void setHideNotifications(boolean hideNotifications) {
1090             this.hideNotifications = hideNotifications;
1091         }
1092
1093         public void setOffset(int offset) {
1094             this.offset = offset;
1095         }
1096
1097         public void setPagesize(int pagesize) {
1098             this.pagesize = pagesize;
1099         }
1100
1101         public void setEventCategories(List<String> eventCategories) {
1102             this.eventCategories = eventCategories;
1103         }
1104
1105         public void setGiid(String giid) {
1106             this.giid = giid;
1107         }
1108     }
1109
1110     private class PostToAPIException extends Exception {
1111
1112         private static final long serialVersionUID = 1L;
1113
1114         public PostToAPIException(String message) {
1115             super(message);
1116         }
1117     }
1118 }