]> git.basschouten.com Git - openhab-addons.git/blob
fa324ccbe2e80b55414ccd7da1b3b27954ed49e0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.digitalstrom.internal.lib.event;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.Iterator;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.concurrent.ScheduledExecutorService;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
25 import org.openhab.binding.digitalstrom.internal.lib.event.types.Event;
26 import org.openhab.binding.digitalstrom.internal.lib.event.types.EventItem;
27 import org.openhab.binding.digitalstrom.internal.lib.event.types.JSONEventImpl;
28 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
29 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.constants.JSONApiResponseKeysEnum;
30 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.JSONResponseHandler;
31 import org.openhab.core.common.ThreadPoolManager;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.gson.JsonArray;
36 import com.google.gson.JsonObject;
37
38 /**
39  * The {@link EventListener} listens for events which will be thrown by the digitalSTROM-Server and it notifies the
40  * added {@link EventHandler} about the detected events, if it supports the event-type.<br>
41  * You can add {@link EventHandler}'s through the constructors or the methods {@link #addEventHandler(EventHandler)} and
42  * {@link #addEventHandlers(List)}.<br>
43  * You can also delete a {@link EventHandler} though the method {@link #removeEventHandler(EventHandler)}.<br>
44  * If the {@link EventListener} is started, both methods subscribe respectively unsubscribe the event-types of the
45  * {@link EventHandler}/s automatically.<br>
46  * If you want to dynamically subscribe event-types, e.g. because a configuration has changed and a
47  * {@link EventHandler} needs to be informed of another event-type, you can use the methods
48  * {@link #addSubscribe(String)} or {@link #addSubscribeEvents(List)} to add more than one event-type. To remove a
49  * subscribed event you can use the method {@link #removeSubscribe(String, String)}, you also have to change the return
50  * of the {@link EventHandler} methods {@link EventHandler#getSupportetEvents()} and
51  * {@link EventHandler#supportsEvent(String)}.
52  * <br>
53  * To start and stop the listening you have to call the methods {@link #start()} and {@link #stop()}.
54  *
55  * @author Michael Ochel - Initial contribution
56  * @author Matthias Siegele - Initial contribution
57  */
58 public class EventListener {
59
60     private final Logger logger = LoggerFactory.getLogger(EventListener.class);
61     private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(Config.THREADPOOL_NAME);
62     public static final int SUBSCRIBE_DELAY = 500;
63     private ScheduledFuture<?> pollingScheduler;
64     private ScheduledFuture<?> subscriptionScheduler;
65
66     private int subscriptionID = 15;
67     private final int timeout = 500;
68     private final List<String> subscribedEvents = Collections.synchronizedList(new LinkedList<>());
69     private boolean subscribed = false;
70
71     // error message
72     public static final String INVALID_SESSION = "Invalid session!";
73     public static final String TOKEN_NOT_FOUND = "token not found."; // Complete text: "Event subscription token not
74                                                                      // found."
75
76     private final ConnectionManager connManager;
77     private final List<EventHandler> eventHandlers = Collections.synchronizedList(new LinkedList<>());
78     private final Config config;
79     private boolean isStarted = false;
80
81     /**
82      * Creates a new {@link EventListener} to listen to the supported event-types of the given eventHandler and notify
83      * about a detected event.<br>
84      * <br>
85      * To get notified by events you have to call {@link #start()}.
86      *
87      * @param connectionManager must not be null
88      * @param eventHandler must not be null
89      */
90     public EventListener(ConnectionManager connectionManager, EventHandler eventHandler) {
91         this.connManager = connectionManager;
92         this.config = connectionManager.getConfig();
93         addEventHandler(eventHandler);
94     }
95
96     /**
97      * This constructor can add more than one {@link EventHandler} as a list of {@link EventHandler}'s.
98      *
99      * @param connectionManager must not be null
100      * @param eventHandlers list of {@link EventHandler}'s must not be null
101      * @see #EventListener(ConnectionManager, EventHandler)
102      */
103     public EventListener(ConnectionManager connectionManager, List<EventHandler> eventHandlers) {
104         this.connManager = connectionManager;
105         this.config = connectionManager.getConfig();
106         addEventHandlers(eventHandlers);
107     }
108
109     /**
110      * Creates a new {@link EventListener} without a {@link EventHandler}<br>
111      * <br>
112      * To get notified by events you have to call {@link #start()} and {@link #addEventHandler(EventHandler)} or
113      * {@link #addEventHandlers(List)}.
114      *
115      * @param connectionManager must not be null
116      */
117     public EventListener(ConnectionManager connectionManager) {
118         this.connManager = connectionManager;
119         this.config = connectionManager.getConfig();
120     }
121
122     /**
123      * Stops this {@link EventListener} and unsubscribe events.
124      */
125     public synchronized void stop() {
126         logger.debug("Stop EventListener");
127         isStarted = false;
128         internalStop();
129     }
130
131     /**
132      * Returns true, if the {@link EventListener} is started.
133      *
134      * @return true, if is started
135      */
136     public boolean isStarted() {
137         return isStarted;
138     }
139
140     private void internalStop() {
141         if (subscriptionScheduler != null && !subscriptionScheduler.isCancelled()) {
142             subscriptionScheduler.cancel(true);
143             subscriptionScheduler = null;
144         }
145         if (pollingScheduler != null && !pollingScheduler.isCancelled()) {
146             pollingScheduler.cancel(true);
147             pollingScheduler = null;
148             unsubscribe();
149             logger.debug("internal stop EventListener");
150         }
151     }
152
153     /**
154      * Starts this {@link EventListener} and subscribe events.
155      */
156     public synchronized void start() {
157         logger.debug("Start EventListener");
158         isStarted = true;
159         internalStart();
160     }
161
162     private void internalStart() {
163         if (!eventHandlers.isEmpty() && (pollingScheduler == null || pollingScheduler.isCancelled())) {
164             pollingScheduler = scheduler.scheduleWithFixedDelay(runableListener, 0,
165                     config.getEventListenerRefreshinterval(), TimeUnit.MILLISECONDS);
166             logger.debug("internal start EventListener");
167         }
168     }
169
170     /**
171      * Adds a {@link List} of {@link EventHandler}'s and subscribe the supported event-types, if the
172      * {@link EventListener} is started and the event-types are not already subscribed.
173      *
174      * @param eventHandlers to add
175      */
176     public void addEventHandlers(List<EventHandler> eventHandlers) {
177         if (eventHandlers != null) {
178             for (EventHandler eventHandler : eventHandlers) {
179                 addEventHandler(eventHandler);
180             }
181         }
182     }
183
184     /**
185      * Adds a {@link EventHandler}'s and subscribe the supported event-types, if the
186      * {@link EventListener} is started and the event-types are not already subscribed.<br>
187      * <br>
188      * <b>Note:</b><br>
189      * If {@link #start()} was called before the {@link EventListener} will start now, otherwise you have to call
190      * {@link #start()} to get notified by events.
191      *
192      * @param eventHandler to add
193      */
194     public void addEventHandler(EventHandler eventHandler) {
195         if (eventHandler != null) {
196             boolean handlerExist = false;
197             for (EventHandler handler : eventHandlers) {
198                 if (handler.getUID().equals(eventHandler.getUID())) {
199                     handlerExist = true;
200                 }
201             }
202             if (!handlerExist) {
203                 eventHandlers.add(eventHandler);
204                 addSubscribeEvents(eventHandler.getSupportedEvents());
205                 logger.debug("eventHandler: {} added", eventHandler.getUID());
206                 if (isStarted) {
207                     internalStart();
208                 }
209             }
210         }
211     }
212
213     /**
214      * Remove a {@link EventHandler} and unsubscribes the supported event-types, if the
215      * {@link EventListener} is started and no other {@link EventHandler} needed the event-types.
216      *
217      * @param eventHandler to remove
218      */
219     public void removeEventHandler(EventHandler eventHandler) {
220         if (eventHandler != null && eventHandlers.contains(eventHandler)) {
221             List<String> tempSubsList = new ArrayList<>();
222             int index = -1;
223             EventHandler intEventHandler = null;
224             boolean subscribedEventsChanged = false;
225             for (int i = 0; i < eventHandlers.size(); i++) {
226                 intEventHandler = eventHandlers.get(i);
227                 if (intEventHandler.getUID().equals(eventHandler.getUID())) {
228                     index = i;
229                 } else {
230                     tempSubsList.addAll(intEventHandler.getSupportedEvents());
231                 }
232             }
233             if (index != -1) {
234                 intEventHandler = eventHandlers.remove(index);
235                 for (String eventName : intEventHandler.getSupportedEvents()) {
236                     if (!tempSubsList.contains(eventName)) {
237                         subscribedEvents.remove(eventName);
238                         subscribedEventsChanged = true;
239                     }
240                 }
241             }
242             if (subscribedEventsChanged) {
243                 // Because of the json-call unsubscribe?eventName=XY&subscriptionID=Z doesn't work like it is explained
244                 // in the dS-JSON-API, the whole EventListener will be restarted. The problem is, that not only the
245                 // given eventName, rather all events of the subscitionID will be deleted.
246                 restartListener();
247             }
248         }
249     }
250
251     /**
252      * Removes a subscribed event and unsubscibe it, if it is not needed by other {@link EventHandler}'s.
253      *
254      * @param unsubscribeEvent event name to unsubscibe
255      * @param eventHandlerID EventHandler-ID of the EventHandler that unsubscibe a event
256      */
257     public void removeSubscribe(String unsubscribeEvent, String eventHandlerID) {
258         if (subscribedEvents != null && !subscribedEvents.contains(unsubscribeEvent)) {
259             boolean eventNeededByAnotherHandler = false;
260             for (EventHandler handler : eventHandlers) {
261                 if (!handler.getUID().equals(eventHandlerID)) {
262                     eventNeededByAnotherHandler = handler.getSupportedEvents().contains(unsubscribeEvent);
263                 }
264             }
265             if (!eventNeededByAnotherHandler) {
266                 logger.debug("unsubscribeEvent: {} is not needed by other EventHandler's... unsubscribe it",
267                         unsubscribeEvent);
268                 subscribedEvents.remove(unsubscribeEvent);
269                 restartListener();
270             } else {
271                 logger.debug("unsubscribeEvent: {} is needed by other EventHandler's... dosen't unsubscribe it",
272                         unsubscribeEvent);
273             }
274         }
275     }
276
277     private void restartListener() {
278         internalStop();
279         if (!eventHandlers.isEmpty() && isStarted) {
280             logger.debug("Min one subscribed events was deleted, EventListener will be restarted");
281             internalStart();
282         }
283     }
284
285     /**
286      * Adds a event and subscribed it, if it is not subscribed already.
287      *
288      * @param subscribeEvent event name to subscribe
289      */
290     public void addSubscribe(String subscribeEvent) {
291         if (!subscribedEvents.contains(subscribeEvent)) {
292             subscribedEvents.add(subscribeEvent);
293             logger.debug("subscibeEvent: {} added", subscribeEvent);
294             if (subscribed) {
295                 subscribe(subscribeEvent);
296             }
297         }
298     }
299
300     /**
301      * Adds the events of the {@link List} and subscribe them, if a event is not subscribed already.
302      *
303      * @param subscribeEvents event name to subscribe
304      */
305     public void addSubscribeEvents(List<String> subscribeEvents) {
306         for (String eventName : subscribeEvents) {
307             subscribe(eventName);
308         }
309     }
310
311     private void getSubscriptionID() {
312         boolean subscriptionIDavailable = false;
313         while (!subscriptionIDavailable) {
314             String response = connManager.getDigitalSTROMAPI().getEvent(connManager.getSessionToken(), subscriptionID,
315                     timeout);
316
317             JsonObject responseObj = JSONResponseHandler.toJsonObject(response);
318
319             if (JSONResponseHandler.checkResponse(responseObj)) {
320                 subscriptionID++;
321             } else {
322                 String errorStr = null;
323                 if (responseObj != null && responseObj.get(JSONApiResponseKeysEnum.MESSAGE.getKey()) != null) {
324                     errorStr = responseObj.get(JSONApiResponseKeysEnum.MESSAGE.getKey()).getAsString();
325                 }
326                 if (errorStr != null && errorStr.contains(TOKEN_NOT_FOUND)) {
327                     subscriptionIDavailable = true;
328                 }
329             }
330         }
331     }
332
333     private boolean subscribe(String eventName) {
334         if (!subscribed) {
335             getSubscriptionID();
336         }
337         subscribed = connManager.getDigitalSTROMAPI().subscribeEvent(connManager.getSessionToken(), eventName,
338                 subscriptionID, config.getConnectionTimeout(), config.getReadTimeout());
339
340         if (subscribed) {
341             logger.debug("subscribed event: {} to subscriptionID: {}", eventName, subscriptionID);
342         } else {
343             logger.error(
344                     "Couldn't subscribe event {} ... maybe timeout because system is too busy ... event will be tried to subscribe later again ... ",
345                     eventName);
346         }
347         return subscribed;
348     }
349
350     private void subscribe(final List<String> evetNames) {
351         final Iterator<String> eventNameIter = evetNames.iterator();
352         subscriptionScheduler = scheduler.scheduleWithFixedDelay(new Runnable() {
353
354             @Override
355             public void run() {
356                 while (eventNameIter.hasNext()) {
357                     subscribe(eventNameIter.next());
358                 }
359                 subscriptionScheduler.cancel(true);
360             }
361         }, 0, SUBSCRIBE_DELAY, TimeUnit.MILLISECONDS);
362     }
363
364     private final Runnable runableListener = new Runnable() {
365
366         @Override
367         public void run() {
368             if (subscribed) {
369                 String response = connManager.getDigitalSTROMAPI().getEvent(connManager.getSessionToken(),
370                         subscriptionID, timeout);
371                 JsonObject responseObj = JSONResponseHandler.toJsonObject(response);
372
373                 if (JSONResponseHandler.checkResponse(responseObj)) {
374                     JsonObject obj = JSONResponseHandler.getResultJsonObject(responseObj);
375                     if (obj != null && obj.get(JSONApiResponseKeysEnum.EVENTS.getKey()).isJsonArray()) {
376                         JsonArray array = obj.get(JSONApiResponseKeysEnum.EVENTS.getKey()).getAsJsonArray();
377                         handleEvent(array);
378                     }
379                 } else {
380                     String errorStr = null;
381                     if (responseObj != null && responseObj.get(JSONApiResponseKeysEnum.MESSAGE.getKey()) != null) {
382                         errorStr = responseObj.get(JSONApiResponseKeysEnum.MESSAGE.getKey()).getAsString();
383                     }
384                     if (errorStr != null && (errorStr.equals(INVALID_SESSION) || errorStr.contains(TOKEN_NOT_FOUND))) {
385                         unsubscribe();
386                         subscribe(subscribedEvents);
387                     } else if (errorStr != null) {
388                         pollingScheduler.cancel(true);
389                         logger.error("Unknown error message at event response: {}", errorStr);
390                     }
391                 }
392             } else {
393                 subscribe(subscribedEvents);
394             }
395         }
396     };
397
398     private void unsubscribe() {
399         for (String eventName : this.subscribedEvents) {
400             connManager.getDigitalSTROMAPI().unsubscribeEvent(this.connManager.getSessionToken(), eventName,
401                     this.subscriptionID, config.getConnectionTimeout(), config.getReadTimeout());
402         }
403     }
404
405     private void handleEvent(JsonArray array) {
406         if (array.size() > 0) {
407             Event event = new JSONEventImpl(array);
408             for (EventItem item : event.getEventItems()) {
409                 logger.debug("detect event {}", item.toString());
410                 for (EventHandler handler : eventHandlers) {
411                     if (handler.supportsEvent(item.getName())) {
412                         logger.debug("inform handler with id {} about event {}", handler.getUID(), item.toString());
413                         handler.handleEvent(item);
414                     }
415                 }
416             }
417         }
418     }
419 }