/* * Copyright 2015-2017 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.hawkular.alerts.rest; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ws.rs.core.StreamingOutput; import org.hawkular.alerts.api.json.JsonUtil; import org.hawkular.alerts.api.model.event.Alert; import org.hawkular.alerts.api.model.event.Event; import org.hawkular.alerts.api.model.paging.Order; import org.hawkular.alerts.api.model.paging.Page; import org.hawkular.alerts.api.model.paging.PageContext; import org.hawkular.alerts.api.model.paging.Pager; import org.hawkular.alerts.api.services.AlertsCriteria; import org.hawkular.alerts.api.services.AlertsService; import org.hawkular.alerts.api.services.EventsCriteria; import org.jboss.logging.Logger; /** * Handle StreamingOutput logic for events/alerts Watchers. * * @author Jay Shaughnessy * @author Lucas Ponce */ @Stateless public class StreamWatcher { private static final Logger log = Logger.getLogger(StreamWatcher.class); private static final Pager ctimePager; private static final Pager stimePager; private static final long WATCHER_INTERVAL_DEFAULT = 5 * 1000; private static final long CLEAN_INTERVAL = 10 * 1000; private static final long LEAP_INTERVAL = 1 * 1000; @EJB AlertsService alertsService; static { List<Order> ordering = new ArrayList<>(); ordering.add(Order.by("stime", Order.Direction.ASCENDING)); stimePager = new Pager(0, PageContext.UNLIMITED_PAGE_SIZE, ordering); ordering = new ArrayList<>(); ordering.add(Order.by("ctime", Order.Direction.ASCENDING)); ctimePager = new Pager(0, PageContext.UNLIMITED_PAGE_SIZE, ordering); } public StreamingOutput watchAlerts(Set<String> tenantIds, AlertsCriteria criteria, Long watchInterval) { return output -> { Writer writer = new BufferedWriter(new OutputStreamWriter(output)); Long startWatchTime = criteria.getEndStatusTime(); Page<Alert> initialAlerts; try { initialAlerts = alertsService.getAlerts(tenantIds, criteria, stimePager); } catch (Exception e) { log.debug(e.getMessage(), e); try { writer.write(JsonUtil.toJson(new ResponseUtil.ApiError(e.getMessage())) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); try { writer.close(); } catch (Exception ignored) {} } return; } if (initialAlerts == null) { return; } Set<WatchedId> watchedIds = new HashSet<>(); initialAlerts.stream().forEach(alert -> { try { writer.write(JsonUtil.toJson(alert) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); try { writer.close(); } catch (Exception ignored) {} return; } watchedIds.add(new WatchedId(alert.getId(), alert.getCurrentLifecycle().getStime())); }); startWatchTime = startWatchTime == null ? System.currentTimeMillis() : startWatchTime; long sleepWatcher = watchInterval == null ? WATCHER_INTERVAL_DEFAULT : watchInterval * 1000; /* Watcher will reuse the AlertsCriteria but without time constraints. Time constraints are defined by the watcher on stime via regular intervals. */ criteria.setStartTime(null); criteria.setEndTime(null); criteria.setStartAckTime(null); criteria.setEndAckTime(null); criteria.setStartResolvedTime(null); criteria.setEndResolvedTime(null); boolean connected = true; long lastWatched = System.currentTimeMillis(); Set<WatchedId> newWatchedIds = new HashSet<>(); while (connected) { startWatchTime = criteria.getEndStatusTime() == null ? startWatchTime : criteria.getEndStatusTime(); criteria.setStartStatusTime(startWatchTime); criteria.setEndStatusTime(System.currentTimeMillis()); try { Thread.sleep(LEAP_INTERVAL); log.debugf("Query timestamp %s. startStatusTime: %s endStatusTime: %s", System.currentTimeMillis(), criteria.getStartStatusTime(), criteria.getEndStatusTime()); Page<Alert> watchedAlerts = alertsService.getAlerts(tenantIds, criteria, stimePager); for (Alert alert : watchedAlerts) { WatchedId watchedId = new WatchedId(alert.getId(), alert.getCurrentLifecycle().getStime()); if (!watchedIds.contains(watchedId)) { try { writer.write(JsonUtil.toJson(alert) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); connected = false; try { writer.close(); } catch (Exception ignored) {} } newWatchedIds.add(watchedId); } } if (System.currentTimeMillis() - lastWatched > CLEAN_INTERVAL) { watchedIds.clear(); lastWatched = System.currentTimeMillis(); } watchedIds.addAll(newWatchedIds); try { writer.write(0); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); connected = false; try { writer.close(); } catch (Exception ignored) {} } Thread.sleep(sleepWatcher); } catch (InterruptedException e) { log.debug("Watcher interrupted"); try { writer.close(); } catch (Exception ignored) {} return; } catch (Exception e) { log.debug(e.getMessage(), e); try { writer.write(JsonUtil.toJson(new ResponseUtil.ApiError(e.getMessage())) + "\r\n"); writer.flush(); writer.close(); } catch (IOException io) { log.debug("Watcher client disconnected"); } return; } } }; } public StreamingOutput watchEvents(Set<String> tenantIds, EventsCriteria criteria, Long watchInterval) { return output -> { Writer writer = new BufferedWriter(new OutputStreamWriter(output)); Long startWatchTime = criteria.getEndTime(); Page<Event> initialEvents; try { initialEvents = alertsService.getEvents(tenantIds, criteria, ctimePager); } catch (Exception e) { log.debug(e.getMessage(), e); try { writer.write(JsonUtil.toJson(new ResponseUtil.ApiError(e.getMessage())) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); } return; } if (initialEvents == null) { return; } Set<WatchedId> watchedIds = new HashSet<>(); initialEvents.stream().forEach(event -> { try { writer.write(JsonUtil.toJson(event) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); return; } watchedIds.add(new WatchedId(event.getId(), event.getCtime())); }); startWatchTime = startWatchTime == null ? System.currentTimeMillis() : startWatchTime; long sleepWatcher = watchInterval == null ? WATCHER_INTERVAL_DEFAULT : watchInterval * 1000; /* Watcher will reuse the AlertsCriteria but without time constraints. Time constraints are defined by the watcher on stime via regular intervals. */ criteria.setStartTime(null); criteria.setEndTime(null); boolean connected = true; long lastWatched = System.currentTimeMillis(); Set<WatchedId> newWatchedIds = new HashSet<>(); while (connected) { startWatchTime = criteria.getEndTime() == null ? startWatchTime : criteria.getEndTime(); criteria.setStartTime(startWatchTime); criteria.setEndTime(System.currentTimeMillis()); try { Thread.sleep(LEAP_INTERVAL); log.debugf("Query timestamp %s. startTime: %s endTime: %s", System.currentTimeMillis(), criteria.getStartTime(), criteria.getEndTime()); Page<Event> watchedEvents = alertsService.getEvents(tenantIds, criteria, ctimePager); for (Event event : watchedEvents) { WatchedId watchedId = new WatchedId(event.getId(), event.getCtime()); if (!watchedIds.contains(watchedId)) { try { writer.write(JsonUtil.toJson(event) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); connected = false; } newWatchedIds.add(watchedId); } } if (System.currentTimeMillis() - lastWatched > CLEAN_INTERVAL) { watchedIds.clear(); lastWatched = System.currentTimeMillis(); } watchedIds.addAll(newWatchedIds); try { writer.write(0); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); connected = false; } Thread.sleep(sleepWatcher); } catch (InterruptedException e) { log.debug("Watcher interrupted"); return; } catch (Exception e) { log.debug(e.getMessage(), e); try { writer.write(JsonUtil.toJson(new ResponseUtil.ApiError(e.getMessage())) + "\r\n"); writer.flush(); } catch (IOException io) { log.debug("Watcher client disconnected"); } return; } } }; } private static class WatchedId { String id; long stime; public WatchedId(String id, long stime) { this.id = id; this.stime = stime; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WatchedId watchedId = (WatchedId) o; if (stime != watchedId.stime) return false; return id != null ? id.equals(watchedId.id) : watchedId.id == null; } @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; result = 31 * result + (int) (stime ^ (stime >>> 32)); return result; } } }