/* * Copyright (C) 2014 University of Toronto, Computational Biology Lab. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package org.ut.biolab.medsavant.server; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Callable; import org.ut.biolab.medsavant.shared.model.MedSavantServerJobProgress; import org.ut.biolab.medsavant.shared.model.MedSavantServerJobProgress.ScheduleStatus; import static org.ut.biolab.medsavant.shared.model.MedSavantServerJobProgress.ScheduleStatus.SCHEDULED_AS_LONGJOB; import static org.ut.biolab.medsavant.shared.model.MedSavantServerJobProgress.ScheduleStatus.SCHEDULED_AS_SHORTJOB; public abstract class MedSavantServerJob implements Callable<Void> { private static Map<String, List<MedSavantServerJobProgress>> rootJobs; private static final Object rootJobLock = new Object(); private final MedSavantServerJobProgress jobProgress; private long expiryTime = 0; private MedSavantServerJob parentJob; private void setExpiryTime(long expiryTime) { this.expiryTime = expiryTime; } void setScheduleStatus(ScheduleStatus status) { jobProgress.setStatus(status); } public static List<MedSavantServerJobProgress> getJobProgressesForUser(String userId) { if (rootJobs == null) { return null; } return rootJobs.get(userId); } public ScheduleStatus getScheduleStatus() { return jobProgress.getStatus(); } public MedSavantServerJob(String userId, String jobName, MedSavantServerJob parentJob) { jobProgress = new MedSavantServerJobProgress(userId, jobName); this.parentJob = parentJob; if (parentJob != null) { parentJob.jobProgress.addChildJobProgress(jobProgress); } else { if (rootJobs == null) { rootJobs = new HashMap<String, List<MedSavantServerJobProgress>>(); } synchronized (rootJobLock) { List<MedSavantServerJobProgress> myRootJobs = rootJobs.get(userId); if (myRootJobs == null) { myRootJobs = new LinkedList<MedSavantServerJobProgress>(); } myRootJobs.add(jobProgress); rootJobs.put(userId, myRootJobs); } } } public MedSavantServerJobProgress getJobProgress() { return jobProgress; } private static final Object jobExpiryLock = new Object(); private static final List<MedSavantServerJob> jobsToExpire = new LinkedList<MedSavantServerJob>(); private static final long EXPIRATION_CHECK_INTERVAL = 120000l; //2 mins. private static final long JOB_EXPIRATION_TIME = 300000l; //5 mins private static void initTimer() { Timer t = new Timer(); t.scheduleAtFixedRate(new TimerTask() { @Override public void run() { synchronized (jobExpiryLock) { ListIterator<MedSavantServerJob> jobIterator = jobsToExpire.listIterator(); while (jobIterator.hasNext()) { MedSavantServerJob job = jobIterator.next(); if (job.expiryTime <= System.currentTimeMillis() && !job.hasChildren()) { job.expireImmediately(); jobIterator.remove(); } } } } }, EXPIRATION_CHECK_INTERVAL, EXPIRATION_CHECK_INTERVAL); } static { initTimer(); } private void expireImmediately() { //does job have a parent? if (parentJob != null) { parentJob.jobProgress.childJobProgresses.remove(jobProgress); } else { synchronized (rootJobLock) { List<MedSavantServerJobProgress> myJobs = rootJobs.get(jobProgress.getUserId()); if (myJobs != null && !myJobs.isEmpty()) { int i = myJobs.indexOf(jobProgress); if (i >= 0) { myJobs.remove(i); } } } } jobProgress.childJobProgresses = null; } private void expire() { synchronized (jobExpiryLock) { setExpiryTime(System.currentTimeMillis() + JOB_EXPIRATION_TIME); jobProgress.setMessage(jobProgress.getMessage()+" (Message will expire in ~"+(JOB_EXPIRATION_TIME/1000)+" sec)"); jobsToExpire.add(this); } } @Override public final Void call() throws Exception { try { if (getScheduleStatus() == SCHEDULED_AS_LONGJOB) { setScheduleStatus(ScheduleStatus.RUNNING_AS_LONGJOB); } else if (getScheduleStatus() == SCHEDULED_AS_SHORTJOB) { setScheduleStatus(ScheduleStatus.RUNNING_AS_SHORTJOB); } else { throw new IllegalArgumentException("MedSavantJob cannot run in this state: " + getScheduleStatus()); } if (run()) { //note run should BLOCK. setScheduleStatus(ScheduleStatus.FINISHED); } else { setScheduleStatus(ScheduleStatus.CANCELLED); } return null; } catch (Exception ex) { jobProgress.setMessage("Aborted due to error: " + ex.getMessage()); ex.printStackTrace(); setScheduleStatus(ScheduleStatus.CANCELLED); throw (ex); } finally { expire(); } } /* public void suspend(boolean allowPreemption) { throw new UnsupportedOperationException("Not yet implemented"); } public void resume() { throw new UnsupportedOperationException("Not yet implemented"); } public void delete() { throw new UnsupportedOperationException("Not yet implemented"); } public void cancel() { throw new UnsupportedOperationException("Not yet implemented"); } */ public boolean hasChildren() { return jobProgress.childJobProgresses != null && !jobProgress.childJobProgresses.isEmpty(); } public abstract boolean run() throws Exception; }