package org.ardverk.gibson.dashboard; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.ardverk.gibson.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @Singleton public class DefaultTrendService implements TrendService { private static final Logger LOG = LoggerFactory.getLogger(DefaultTrendService.class); private static final long TREND_FREQUENCY = 15; private static final TimeUnit TREND_TIMEUNIT = TimeUnit.SECONDS; private final Map<String, Pair<Trend, TrendBuilder>> trendMap = new ConcurrentHashMap<String, Pair<Trend, TrendBuilder>>(); private final AtomicReference<ScheduledExecutorService> executorService = new AtomicReference<ScheduledExecutorService>(null); @Inject private EventDAO eventDAO; @Inject private EventService eventService; @Inject public void init() { ScheduledExecutorService oldExecutor = executorService.getAndSet(Executors.newScheduledThreadPool(1)); if (oldExecutor != null) { oldExecutor.shutdownNow(); } executorService.get().scheduleAtFixedRate(new Runnable() { @Override public void run() { try { updateTrends(); } catch (Exception e) { LOG.error("Exception while updating", e); } } }, TREND_FREQUENCY, TREND_FREQUENCY, TREND_TIMEUNIT); } @Override public Trend getTrendForEvent(final Event event) { return getTrendInfo(event.getSignature(), new TrendBuilder() { @Override public Trend build(Trend previous) { long count = eventDAO.getEventCount(event.getSignature()); Date lastOccurrence = eventDAO.getEventLastOccurrence(event.getSignature()); Trend trend; if (previous != null) { trend = Trend.create(count, lastOccurrence, previous); } else { Date firstOccurrence = eventDAO.getEventFirstOccurrence(event.getSignature()); trend = new Trend(count, count, count, System.currentTimeMillis(), firstOccurrence, lastOccurrence); } return trend; } }); } @Override public Trend getTrendForType(final String type) { return getTrendInfo(type, new TrendBuilder() { @Override public Trend build(Trend previous) { long count = eventDAO.getTypeNameCount(type); Date lastOccurrence = eventDAO.getTypeNameLastOccurrence(type); Trend trend; if (previous != null) { trend = Trend.create(count, lastOccurrence, previous); } else { Date firstOccurrence = eventDAO.getTypeNameFirstOccurrence(type); trend = new Trend(count, count, count, System.currentTimeMillis(), firstOccurrence, lastOccurrence); } return trend; } }); } @Override public void clear() { trendMap.clear(); // restart executor service init(); } private interface TrendBuilder { public Trend build(Trend previous); } private Trend getTrendInfo(String key, TrendBuilder trendBuilder) { Pair<Trend, TrendBuilder> pair = trendMap.get(key); if (pair == null) { try { Trend trend = trendBuilder.build(null); pair = new ImmutablePair<Trend, TrendBuilder>(trend, trendBuilder); trendMap.put(key, pair); } catch (Exception e) { // TODO better way to handle this? throw new RuntimeException(e); } } return pair.getLeft(); } private void updateTrends() throws InterruptedException { // eagerly build trend data for new typeNames and event signatures TypeItems typeItems = eventService.getTypeItems(); for (TypeItem item : typeItems.elements) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } eventService.getEventItems(item.typeName); } // calculate new trend data for all known objects for (Map.Entry<String, Pair<Trend, TrendBuilder>> e : trendMap.entrySet()) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } String key = e.getKey(); Trend trend = e.getValue().getLeft(); try { TrendBuilder trendBuilder = e.getValue().getRight(); Trend newTrend = trendBuilder.build(trend); trendMap.put(key, new ImmutablePair<>(newTrend, trendBuilder)); } catch (Exception ex) { throw new RuntimeException(ex); } } } }