package org.mapfish.print.servlet.job.impl.hibernate;

import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.LockMode;
import org.hibernate.PessimisticLockException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.type.LongType;
import org.hibernate.type.Type;
import org.mapfish.print.servlet.job.PrintJobStatus;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.URI;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;

/**
 * JobEntryDao.
 *
 */
public class PrintJobDao {

    @Autowired
    private SessionFactory sf;

    /**
     * Initialize db manager.
     */
    @PostConstruct
    public final void init() {
        this.sf.openSession();
    }

    public final Session getSession() {
        return this.sf.getCurrentSession();
    }

    /**
     *
     * Save Job Record.
     *
     * @param entry the entry
     */
    public final void save(final PrintJobStatusExtImpl entry) {
        getSession().merge(entry);
        getSession().flush();
        getSession().evict(entry);
    }

    /**
     *
     * Get Job Record.
     *
     * @param id the id
     * @return
     */
    public final PrintJobStatusExtImpl get(final String id) {
        final PrintJobStatusExtImpl result = get(id, false);
        getSession().evict(result);
        return result;
    }

    /**
     *
     * Get Job Record.
     *
     * @param id the id
     * @param lock whether record should be locked for transaction
     * @return the job status.
     */
    public final PrintJobStatusExtImpl get(final String id, final boolean lock) {
        Criteria c = getSession().createCriteria(PrintJobStatusExtImpl.class);
        c.add(Restrictions.idEq(id));
        if (lock) { //LOCK means SELECT FOR UPDATE which prevents these records to be pulled by different instances
            c.setLockMode("pj", LockMode.PESSIMISTIC_READ);
            c.setFetchMode("result", FetchMode.SELECT);
        } else {
            c.setReadOnly(true);  // make sure the object is not updated if there is no lock
        }
        return (PrintJobStatusExtImpl) c.uniqueResult();
    }

    /**
     * get specific property value of job.
     *
     * @param id the id
     * @param property the property name/path
     * @return the property value
     */
    public final Object getValue(final String id, final String property) {
        Criteria c = getSession().createCriteria(PrintJobStatusExtImpl.class);
        c.add(Restrictions.idEq(id));
        c.setProjection(Projections.property(property));
        return c.uniqueResult();
    }

    /**
     *
     * @param statuses the statuses to include (or none if all)
     * @return the total amount of jobs
     */
    public final int count(final PrintJobStatus.Status... statuses) {
        Criteria c = getSession().createCriteria(PrintJobStatusExtImpl.class);
        if (statuses.length > 0) {
            c.add(Restrictions.in("status" , statuses));
        }
        c.setProjection(Projections.rowCount());
        return ((Number) c.uniqueResult()).intValue();
    }

    /**
     *
     * @param statuses the statuses to include (or none if all)
     * @return the jobs
     */
    @SuppressWarnings("unchecked")
    public final List<PrintJobStatusExtImpl> get(final PrintJobStatus.Status... statuses) {
        Criteria c = getSession().createCriteria(PrintJobStatusExtImpl.class);
        if (statuses.length > 0) {
            c.add(Restrictions.in("status" , statuses));
        }
        return (List<PrintJobStatusExtImpl>) c.list();
    }

    /**
     *
     * @return total time spent printing
     */
    public final long getTotalTimeSpentPrinting() {
        Criteria c = getSession().createCriteria(PrintJobStatusExtImpl.class);
        c.add(Restrictions.isNotNull("completionTime"));
        c.setProjection(Projections.sqlProjection("sum(completionTime - startTime) as totalTime",
                new String[] {"totalTime"}, new Type[] { LongType.INSTANCE }));
        Number result = (Number) c.uniqueResult();
        return result == null ? 0 : result.longValue();
    }

    /**
     * Cancel old waiting jobs.
     *
     * @param starttimeThreshold threshold for start time
     * @param checkTimeThreshold threshold for last check time
     * @param message the error message
     */
    public final void cancelOld(final long starttimeThreshold, final long checkTimeThreshold, final String message) {
        Query query = getSession().createQuery("update PrintJobStatusExtImpl pj " + "set status=:newstatus, error=:msg "
                + "where pj.status = :oldstatus " + "and (startTime < :starttimethreshold "
                + "or lastCheckTime < :checktimethreshold)");
        query.setParameter("oldstatus", PrintJobStatus.Status.WAITING);
        query.setParameter("newstatus", PrintJobStatus.Status.CANCELLED);
        query.setParameter("msg", message);
        query.setParameter("starttimethreshold", starttimeThreshold);
        query.setParameter("checktimethreshold", checkTimeThreshold);
        query.executeUpdate();
    }

    /**
     * Update the lastCheckTime of the given record.
     * @param id the id
     * @param lastCheckTime the new value
     */
    public final void updateLastCheckTime(final String id, final long lastCheckTime) {
        Query query = getSession().createQuery("update PrintJobStatusExtImpl pj " + "set lastCheckTime=:lastCheckTime "
                + "where pj.referenceId = :id");
        query.setParameter("id", id);
        query.setParameter("lastCheckTime", lastCheckTime);
        query.executeUpdate();
    }

    /**
     * Delete old jobs.
     *
     * @param checkTimeThreshold
     *            threshold for last check time
     */
    public final void deleteOld(final long checkTimeThreshold) {
        Query query = getSession()
                .createQuery("delete from PrintJobStatusExtImpl " + "where lastCheckTime < :checktimethreshold)");
        query.setParameter("checktimethreshold", checkTimeThreshold);
        query.executeUpdate();
    }

    /**
     * Poll for the next N waiting jobs in line.
     *
     * @param size maximum amount of jobs to poll for
     * @return
     */
    @SuppressWarnings("unchecked")
    public final List<PrintJobStatusExtImpl> poll(final int size) {
        Query query = getSession()
                .createQuery("from PrintJobStatusExtImpl pj " + "where status = :status " + "order by startTime");
        query.setParameter("status", PrintJobStatus.Status.WAITING);
        query.setMaxResults(size);
        // LOCK but don't wait for release (since this is run continuously
        // anyway, no wait prevents deadlock)
        query.setLockMode("pj", LockMode.UPGRADE_NOWAIT);
        try {
            return (List<PrintJobStatusExtImpl>) query.list();
        } catch (PessimisticLockException ex) {
            // Another process was polling at the same time. We can ignore this error
            return Collections.emptyList();
        }
    }

    /**
     * Get result report.
     *
     * @param reportURI
     *            the URI of the report
     * @return the result report.
     */
    public final PrintJobResultExtImpl getResult(final URI reportURI) {
        Criteria c = getSession().createCriteria(PrintJobResultExtImpl.class);
        c.add(Restrictions.idEq(reportURI.toString()));
        return (PrintJobResultExtImpl) c.uniqueResult();
    }
}

@@ -633,16 +633,50 @@
 .List;%0A%0A
+import javax.annotation.Nullable;%0A
 import j
@@ -1373,32 +1373,46 @@
 @return%0A     */%0A
+    @Nullable%0A
     public final
@@ -1514,24 +1514,58 @@
 id, false);%0A
+        if (result != null) %7B%0A    
         getS
@@ -1588,16 +1588,26 @@
 esult);%0A
+        %7D%0A
         
@@ -1793,16 +1793,24 @@
 b status
+ or null
 .%0A     *
@@ -1803,32 +1803,46 @@
 r null.%0A     */%0A
+    @Nullable%0A
     public final

Fix NPE when getting status for unknown job