/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.ut.biolab.medsavant.client.util; import java.util.EnumMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import org.ut.biolab.medsavant.shared.util.ModificationType; public class CacheController extends Controller<ModificationType> { private static CacheController instance; private final Object timerLock; //Minimum amount of time to wait between firing update events, in ms. private static long minimumWaitInterval = 500l; //Amount of time to wait between firing server updates. private static long periodic_update_interval = 900000l; //15mins //Map for each modification type to a scheduled time for an update. private Map<ModificationType, Long> timers; private Timer timer; private EventFiringTask eventFiringTask; private CacheController() { timers = new EnumMap<ModificationType, Long>(ModificationType.class); for (ModificationType mt : ModificationType.values()) { timers.put(mt, System.currentTimeMillis() + periodic_update_interval); } timerLock = new Object(); timer = new Timer(); eventFiringTask = new EventFiringTask(); timer.scheduleAtFixedRate(eventFiringTask, minimumWaitInterval, minimumWaitInterval); } public static void setMinimumWaitInterval(long ms) { minimumWaitInterval = ms; } public static long getMinimumWaitInterval() { return minimumWaitInterval; } public static CacheController getInstance() { if (instance == null) { instance = new CacheController(); } return instance; } private Set<Long> threadIds = new HashSet<Long>(); /** * Executes the given runnable in a background thread, disabling the cache until * the thread is complete. At that time, cache expiration events are fired corresponding * to the given types. */ public void blockUpdate(final Runnable r, final ModificationType[] types) { Thread t = new Thread() { @Override public void run() { threadIds.add(getId()); r.run(); threadIds.remove(getId()); for(ModificationType t : types){ expire(t); } } }; t.start(); } public void expire(ModificationType t, Long threadId){ if(threadId ==null || !threadIds.contains(threadId)){ timers.put(t, System.currentTimeMillis() + minimumWaitInterval); } } //Send a notification to all listeners that a modifiation has occurred, after //waiting for minimumWaitInterval ms. Calling this method multiple times with //the same modifier type will reset the timer to wait another minimumWaitInterval //ms before firing the event. public void expire(ModificationType t) { synchronized (timerLock) { expire(t, null); } } private class EventFiringTask extends TimerTask { private Map<ModificationType, MedSavantWorker<Void>> threadMap = new EnumMap<ModificationType, MedSavantWorker<Void>>(ModificationType.class); @Override public void run() { synchronized (timerLock) { for (Map.Entry<ModificationType, Long> e : timers.entrySet()) { final ModificationType type = e.getKey(); final Long scheduledTime = e.getValue(); if (scheduledTime == null) { continue; } final Long currentTime = System.currentTimeMillis(); if (currentTime >= scheduledTime) { //Time to fire. MedSavantWorker<Void> worker = threadMap.get(type); if (worker != null && !worker.isDone() && !worker.isCancelled()) { //Worker is already executing, do not fire events until previous //thread is done (after which there is a delay of minimumWaitInterval ms). timers.put(type, currentTime - 1); } else { //event firing happens in a background thread so that other Apps, etc. don't lock up //the timer thread. worker = new MedSavantWorker<Void>("CACHE " + type) { @Override protected Void doInBackground() throws Exception { fireEvent(type); return null; } @Override protected void showSuccess(Void result) { } }; threadMap.put(type, worker); worker.execute(); } timers.put(type, currentTime + periodic_update_interval); } } } } } }