package org.mapfish.print.servlet.job.impl.hibernate; import org.mapfish.print.servlet.job.JobQueue; import org.mapfish.print.servlet.job.NoSuchReferenceException; import org.mapfish.print.servlet.job.PrintJobEntry; import org.mapfish.print.servlet.job.PrintJobResult; import org.mapfish.print.servlet.job.PrintJobStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; /** * * Db Job Manager. * */ @Transactional public class HibernateJobQueue implements JobQueue { private static final int DEFAULT_TIME_TO_KEEP_AFTER_ACCESS = 30; /* minutes */ private static final long DEFAULT_CLEAN_UP_INTERVAL = 300; /* seconds */ @Autowired private PrintJobDao dao; @Autowired private PlatformTransactionManager txManager; private ScheduledExecutorService cleanUpTimer; /** * The interval at which old records are deleted (in seconds). */ private long cleanupInterval = DEFAULT_CLEAN_UP_INTERVAL; /** * The minimum time to keep records after last access. */ private int timeToKeepAfterAccessInMinutes = DEFAULT_TIME_TO_KEEP_AFTER_ACCESS; public final void setTimeToKeepAfterAccessInMinutes(final int timeToKeepAfterAccessInMinutes) { this.timeToKeepAfterAccessInMinutes = timeToKeepAfterAccessInMinutes; } @Override public final long getTimeToKeepAfterAccessInMillis() { return TimeUnit.MINUTES.toMillis(this.timeToKeepAfterAccessInMinutes); } @Override public final int getLastPrintCount() { return this.dao.count(PrintJobStatus.Status.FINISHED, PrintJobStatus.Status.CANCELLED, PrintJobStatus.Status.ERROR); } @Override public final int getWaitingJobsCount() { return this.dao.count(PrintJobStatus.Status.WAITING, PrintJobStatus.Status.RUNNING); } @Override public final int getNumberOfRequestsMade() { return this.dao.count(); } @Override public final long getAverageTimeSpentPrinting() { return this.dao.getTotalTimeSpentPrinting() / Math.max(1, getLastPrintCount()); } @Override @Transactional(readOnly = true) public final long timeSinceLastStatusCheck(final String referenceId) { return System.currentTimeMillis() - ((Number) this.dao.getValue(referenceId, "lastCheckTime")).longValue(); } @Override public final PrintJobStatus get(final String referenceId, final boolean external) throws NoSuchReferenceException { long now = System.currentTimeMillis(); PrintJobStatusExtImpl record = this.dao.get(referenceId); if (record == null) { throw new NoSuchReferenceException(referenceId); } record.setStatusTime(now); if (!record.isDone() && external) { this.dao.updateLastCheckTime(referenceId, System.currentTimeMillis()); } return record; } @Override public final synchronized void add(final PrintJobEntry jobEntry) { this.dao.save(new PrintJobStatusExtImpl(jobEntry, getNumberOfRequestsMade())); } @Override public final synchronized void cancel(final String referenceId, final String message, final boolean forceFinal) throws NoSuchReferenceException { PrintJobStatusExtImpl record = this.dao.get(referenceId, true); if (record == null) { throw new NoSuchReferenceException(referenceId); } if (!forceFinal && record.getStatus() == PrintJobStatus.Status.RUNNING) { record.setStatus(PrintJobStatus.Status.CANCELING); } else { record.setCompletionTime(System.currentTimeMillis()); record.setStatus(PrintJobStatus.Status.CANCELLED); } record.setError(message); this.dao.save(record); } @Override public final synchronized void fail(final String referenceId, final String message) throws NoSuchReferenceException { PrintJobStatusExtImpl record = this.dao.get(referenceId, true); if (record == null) { throw new NoSuchReferenceException(referenceId); } record.setCompletionTime(System.currentTimeMillis()); record.setStatus(PrintJobStatus.Status.ERROR); record.setError(message); this.dao.save(record); } @Override public final synchronized void start(final String referenceId) throws NoSuchReferenceException { PrintJobStatusExtImpl record = this.dao.get(referenceId, true); if (record == null) { throw new NoSuchReferenceException(referenceId); } record.setStatus(PrintJobStatus.Status.RUNNING); record.setWaitingTime(0); this.dao.save(record); } @Override public final synchronized void done(final String referenceId, final PrintJobResult result) throws NoSuchReferenceException { PrintJobStatusExtImpl record = this.dao.get(referenceId, true); if (record == null) { throw new NoSuchReferenceException(referenceId); } record.setStatus(record.getStatus() == PrintJobStatus.Status.CANCELING ? PrintJobStatus.Status.CANCELLED : PrintJobStatus.Status.FINISHED); record.setResult(result); record.setCompletionTime(System.currentTimeMillis()); this.dao.save(record); } @Override public final synchronized void cancelOld(final long startTimeOut, final long abandonTimeout, final String message) { long now = System.currentTimeMillis(); this.dao.cancelOld(now - startTimeOut, now - abandonTimeout, message); } @Override public final synchronized List<? extends PrintJobStatus> start(final int number) { List<PrintJobStatusExtImpl> list = this.dao.poll(number); for (PrintJobStatusExtImpl record : list) { record.setStatus(PrintJobStatus.Status.RUNNING); record.setWaitingTime(0); this.dao.save(record); } return list; } @Override public final List<? extends PrintJobStatus> toCancel() { return this.dao.get(PrintJobStatus.Status.CANCELING); } /** * Called by spring on initialization. */ @PostConstruct public final void init() { this.cleanUpTimer = Executors.newScheduledThreadPool(1, new ThreadFactory() { @Override public Thread newThread(final Runnable timerTask) { final Thread thread = new Thread(timerTask, "Clean up old job records"); thread.setDaemon(true); return thread; } }); this.cleanUpTimer.scheduleAtFixedRate(new Runnable() { @Override public void run() { cleanup(); } }, this.cleanupInterval, this.cleanupInterval, TimeUnit.SECONDS); } /** * Called by spring when application context is being destroyed. */ @PreDestroy public final void shutdown() { this.cleanUpTimer.shutdownNow(); } private void cleanup() { TransactionTemplate tmpl = new TransactionTemplate(this.txManager); tmpl.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { HibernateJobQueue.this.dao.deleteOld(System.currentTimeMillis() - getTimeToKeepAfterAccessInMillis()); } }); } }