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