@Override
  protected void handleGet(
      PageContext pageContext, Template template, VelocityContext templateContext)
      throws ServletException, IOException {
    IDAOSession f;
    try {
      DAOFactory df = (DAOFactory) FactoryRegistrar.getFactory(DAOFactory.class);
      f = df.getInstance();
    } catch (FactoryException e) {
      throw new ServletException("dao init error", e);
    }

    // Ascertain sorting
    IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType sortingType = null;
    if (pageContext.getParameter("sorting") != null) {
      if (pageContext.getParameter("sorting").equals("assignment_count_asc")) {
        sortingType =
            IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.ASSIGNMENT_COUNT_ASCENDING;
        templateContext.put("sorting", "assignment_count_asc");
      } else if (pageContext.getParameter("sorting").equals("assignment_count_desc")) {
        sortingType =
            IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.ASSIGNMENT_COUNT_DESCENDING;
        templateContext.put("sorting", "assignment_count_desc");
      } else if (pageContext.getParameter("sorting").equals("student_count_asc")) {
        sortingType =
            IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.STUDENT_COUNT_ASCENDING;
        templateContext.put("sorting", "student_count_asc");
      } else if (pageContext.getParameter("sorting").equals("student_count_desc")) {
        sortingType =
            IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.STUDENT_COUNT_DESCENDING;
        templateContext.put("sorting", "student_count_desc");
      } else if (pageContext.getParameter("sorting").equals("model_id_asc")) {
        sortingType = IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.MODEL_ID_ASCENDING;
        templateContext.put("sorting", "model_id_asc");
      } else if (pageContext.getParameter("sorting").equals("model_id_desc")) {
        sortingType = IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.MODEL_ID_DESCENDING;
        templateContext.put("sorting", "model_id_desc");
      }
    } else {
      sortingType = IStaffInterfaceQueriesDAO.StaffModulesQuerySortingType.MODEL_ID_DESCENDING;
      templateContext.put("sorting", "model_id_desc");
    }

    // Render page
    try {
      f.beginTransaction();

      IStaffInterfaceQueriesDAO dao = f.getStaffInterfaceQueriesDAOInstance();
      Collection<StaffModulesQueryResult> result =
          dao.performStaffModulesQuery(
              sortingType, pageContext.getSession().getPersonBinding().getId());

      f.endTransaction();

      // TODO: localisation
      templateContext.put("greet", pageContext.getSession().getPersonBinding().getChosenName());
      templateContext.put("modules", result);

      // loading plugins' entry pages (if present)
      Collection<? extends IStaffPluginEntryLink> extraLinkProviders =
          Lookup.getDefault().lookupAll(IStaffPluginEntryLink.class);
      if (!extraLinkProviders.isEmpty()) {
        List<String> labels = new LinkedList<String>();
        List<String> links = new LinkedList<String>();
        for (IStaffPluginEntryLink link : extraLinkProviders) {
          labels.add(link.getLinkLabel());
          links.add(pageContext.getPageUrl(StaffPageFactory.SITE_NAME, link.getPageName()));
        }
        templateContext.put("extraLinks", links);
        templateContext.put("extraLabels", labels);
      }

      pageContext.renderTemplate(template, templateContext);
    } catch (DAOException e) {
      f.abortTransaction();
      throw new ServletException("dao exception", e);
    }
  }
  @Override
  protected void handleGet(
      PageContext pageContext, Template template, VelocityContext templateContext)
      throws ServletException, IOException {
    IDAOSession f;
    try {
      DAOFactory df = (DAOFactory) FactoryRegistrar.getFactory(DAOFactory.class);
      f = df.getInstance();
    } catch (FactoryException e) {
      throw new ServletException("dao init error", e);
    }

    // Get assignmentId
    String assignmentString = pageContext.getParameter("assignment");
    if (assignmentString == null) {
      throw new ServletException("No assignment parameter given");
    }
    Long assignmentId = Long.valueOf(pageContext.getParameter("assignment"));

    // Ascertain sorting - TODO more sorting
    StaffDeadlineRevisionsQuerySortingType sortingType = null;
    if (pageContext.getParameter("sorting") != null) {
      if (pageContext.getParameter("sorting").equals("deadline_asc")) {
        sortingType = StaffDeadlineRevisionsQuerySortingType.DEADLINE_ASCENDING;
        templateContext.put("sorting", "deadline_asc");
      } else if (pageContext.getParameter("sorting").equals("deadline_desc")) {
        sortingType = StaffDeadlineRevisionsQuerySortingType.DEADLINE_DESCENDING;
        templateContext.put("sorting", "deadline_desc");
      } else if (pageContext.getParameter("sorting").equals("student_id_asc")) {
        sortingType = StaffDeadlineRevisionsQuerySortingType.STUDENT_ID_ASCENDING;
        templateContext.put("sorting", "student_id_asc");
      } else if (pageContext.getParameter("sorting").equals("student_id_desc")) {
        sortingType = StaffDeadlineRevisionsQuerySortingType.STUDENT_ID_DESCENDING;
        templateContext.put("sorting", "student_id_desc");
      }
    } else {
      sortingType = StaffDeadlineRevisionsQuerySortingType.NONE;
      templateContext.put("sorting", "");
    }

    // Render page
    try {
      f.beginTransaction();

      IStaffInterfaceQueriesDAO staffInterfaceQueriesDAO = f.getStaffInterfaceQueriesDAOInstance();
      IAssignmentDAO assignmentDao = f.getAssignmentDAOInstance();
      Assignment assignment = assignmentDao.retrievePersistentEntity(assignmentId);

      if (!staffInterfaceQueriesDAO.isStaffModuleAccessAllowed(
          pageContext.getSession().getPersonBinding().getId(), assignment.getModuleId())) {
        f.abortTransaction();
        throw new ServletException("permission denied");
      }

      IModuleDAO moduleDao = f.getModuleDAOInstance();
      Module module = moduleDao.retrievePersistentEntity(assignment.getModuleId());

      Collection<StaffDeadlineRevisionsQueryResult> result =
          staffInterfaceQueriesDAO.performStaffDeadlineRevisionsQuery(sortingType, assignmentId);

      f.endTransaction();

      // TODO: localisation
      templateContext.put("greet", pageContext.getSession().getPersonBinding().getChosenName());
      templateContext.put("module", module);
      templateContext.put("assignment", assignment);
      templateContext.put("deadlineRevisions", result);

      pageContext.renderTemplate(template, templateContext);
    } catch (DAOException e) {
      f.abortTransaction();
      throw new ServletException("dao exception", e);
    }
  }
  protected void handlePost(
      PageContext pageContext, Template template, VelocityContext templateContext)
      throws ServletException, IOException {
    IDAOSession f;
    IMailSender mailSender;
    String submissionHash;
    try {
      DAOFactory df = (DAOFactory) FactoryRegistrar.getFactory(DAOFactory.class);
      MailFactory mf = (MailFactory) FactoryRegistrar.getFactory(MailFactory.class);
      f = df.getInstance();
      mailSender = mf.getInstance();
      submissionHash = df.getSubmissionHashSalt();
    } catch (FactoryException e) {
      throw new ServletException("dao init error", e);
    }

    Assignment assignment = null;

    // Get the assignment
    String assignmentString = pageContext.getParameter("assignment");
    if (assignmentString == null) {
      throw new ServletException("No assignment parameter given");
    }
    Long assignmentId = Long.valueOf(pageContext.getParameter("assignment"));

    Collection<String> fileNames = null;

    // Render page
    try {
      f.beginTransaction();

      IAssignmentDAO assignmentDao = f.getAssignmentDAOInstance();
      IStudentInterfaceQueriesDAO studentInterfaceQueriesDAO =
          f.getStudentInterfaceQueriesDAOInstance();
      assignment = assignmentDao.retrievePersistentEntity(assignmentId);

      if (!studentInterfaceQueriesDAO.isStudentModuleAccessAllowed(
          pageContext.getSession().getPersonBinding().getId(), assignment.getModuleId())) {
        f.abortTransaction();
        throw new DAOException("permission denied (not on module)");
      }

      if (!studentInterfaceQueriesDAO.isStudentAllowedToSubmit(
          pageContext.getSession().getPersonBinding().getId(), assignmentId)) {
        f.abortTransaction();
        throw new DAOException("permission denied (after deadline)");
      }

      templateContext.put("assignment", assignment);
      fileNames = assignmentDao.fetchRequiredFilenames(assignmentId);

      f.endTransaction();
    } catch (DAOException e) {
      f.abortTransaction();
      throw new ServletException("dao exception", e);
    }

    // Create resource.
    Long resourceId = null;
    Resource resource = new Resource();
    resource.setTimestamp(new Date());
    resource.setFilename(
        pageContext.getSession().getPersonBinding().getUniqueIdentifier()
            + "-"
            + assignmentId
            + "-"
            + resource.getTimestamp().getTime()
            + ".zip");
    resource.setMimeType("application/zip");

    try {
      f.beginTransaction();
      IResourceDAO resourceDao = f.getResourceDAOInstance();
      resourceId = resourceDao.createPersistentCopy(resource);
      f.endTransaction();
    } catch (DAOException e) {
      f.abortTransaction();
      throw new ServletException("dao exception", e);
    }

    // Handle upload form
    String securityCode = null;

    OutputStream resourceStream = null;
    ZipOutputStream resourceZipStream = null;

    MessageDigest digest = null;
    HashSet<String> remainingFiles = new HashSet<String>(fileNames);
    HashSet<String> omittedFiles = new HashSet<String>();

    // Begin storing upload
    try {
      f.beginTransaction();
      IResourceDAO resourceDao = f.getResourceDAOInstance();

      digest = MessageDigest.getInstance("MD5");
      resourceStream = resourceDao.openOutputStream(resourceId);
      resourceZipStream = new ZipOutputStream(resourceStream);

      FileItemIterator fileIterator = pageContext.getUploadedFiles();

      // Next item in the form.
      while (fileIterator.hasNext()) {
        FileItemStream currentUpload = fileIterator.next();

        // An actual file?
        if (fileNames.contains(currentUpload.getFieldName())) {
          String filename =
              pageContext.getSession().getPersonBinding().getUniqueIdentifier()
                  + "/"
                  + currentUpload.getFieldName();
          InputStream is = currentUpload.openStream();

          try {
            byte buffer[] = new byte[1024];
            int nread = -1;
            long total = 0;

            // Try to read _one_ byte to see if we have an entry.
            nread = is.read(buffer, 0, 1);

            // Read the rest if there was something
            if (nread == 1) {
              resourceZipStream.putNextEntry(new ZipEntry(filename));

              // Put the initial byte
              resourceZipStream.write(buffer, 0, nread);
              digest.update(buffer, 0, nread);

              // Continue writing
              while ((nread = is.read(buffer)) != -1) {
                total += nread;
                resourceZipStream.write(buffer, 0, nread);
                digest.update(buffer, 0, nread);
              }

              resourceZipStream.closeEntry();
              remainingFiles.remove(currentUpload.getFieldName());
              pageContext.log(Level.INFO, "Student uploaded: " + currentUpload.getFieldName());
            }

            // Done with this entry
            is.close();
          } catch (IOException e) {
            throw new DAOException("IO error returning file stream", e);
          }
        }
        // Omitted a file?
        else if (currentUpload.getFieldName().equals("omit")) {
          BufferedReader reader =
              new BufferedReader(new InputStreamReader(currentUpload.openStream()));
          String fileName = reader.readLine();
          omittedFiles.add(fileName);
          reader.close();
          pageContext.log(Level.INFO, "Student omitted: " + fileName);
        }
      }

      // No files uploaded
      if (omittedFiles.equals(fileNames)) {
        pageContext.log(Level.ERROR, "Student tried to upload nothing!");
        resourceStream.close();
        pageContext.performRedirect(
            pageContext.getPageUrl("student", "submit")
                + "?assignment="
                + assignmentId
                + "&missing=true");
        resourceDao.deletePersistentEntity(resourceId);
        f.endTransaction();
        return;
      }

      // Check for missing files not omitted
      if (!remainingFiles.isEmpty()) {
        for (String fileName : remainingFiles) {
          if (!omittedFiles.contains(fileName)) {
            pageContext.log(
                Level.ERROR, "Student didn't upload " + fileName + " but didn't omit it!");
            resourceStream.close();
            pageContext.performRedirect(
                pageContext.getPageUrl("student", "submit")
                    + "?assignment="
                    + assignmentId
                    + "&missing=true");
            resourceDao.deletePersistentEntity(resourceId);
            f.endTransaction();
            return;
          }
        }
      }

      // Finalize the resource.
      resourceZipStream.flush();
      resourceStream.flush();

      resourceZipStream.close();
      resourceStream.close();

      // Finalise the security code.
      securityCode = byteArrayToHexString(digest.digest());

      f.endTransaction();
    } catch (Exception e) {
      resourceStream.close();
      f.abortTransaction();

      try {
        f.beginTransaction();

        IResourceDAO resourceDao = f.getResourceDAOInstance();
        resourceDao.deletePersistentEntity(resourceId);

        f.endTransaction();
      } catch (DAOException e2) {
        throw new ServletException(
            "error storing upload - additional error cleaning stale resource " + resourceId, e);
      }

      throw new ServletException("error storing upload", e);
    }

    // Salt security code, SHA256 it.
    securityCode = securityCode + submissionHash;

    try {
      digest = MessageDigest.getInstance("SHA-256");
      securityCode = byteArrayToHexString(digest.digest(securityCode.getBytes("UTF-8")));
    } catch (Exception e) {
      f.abortTransaction();

      try {
        f.beginTransaction();

        IResourceDAO resourceDao = f.getResourceDAOInstance();
        resourceDao.deletePersistentEntity(resourceId);

        f.endTransaction();
      } catch (DAOException e2) {
        throw new ServletException(
            "error hashing - additional error cleaning stale resource " + resourceId, e);
      }

      throw new ServletException("error hashing", e);
    }

    // Create the submission.
    Submission submission = new Submission();
    submission.setPersonId(pageContext.getSession().getPersonBinding().getId());
    submission.setAssignmentId(assignmentId);
    submission.setSubmissionTime(new Date());
    submission.setSecurityCode(securityCode);
    submission.setResourceId(resourceId);
    submission.setResourceSubdirectory(
        pageContext.getSession().getPersonBinding().getUniqueIdentifier());
    submission.setActive(false); // Must use make-active

    try {
      f.beginTransaction();
      ISubmissionDAO submissionDao = f.getSubmissionDAOInstance();
      IStudentInterfaceQueriesDAO studentInterfaceQueriesDAo =
          f.getStudentInterfaceQueriesDAOInstance();
      submission.setId(submissionDao.createPersistentCopy(submission));
      studentInterfaceQueriesDAo.makeSubmissionActive(
          submission.getPersonId(), submission.getAssignmentId(), submission.getId());
      f.endTransaction();
    } catch (DAOException e) {
      f.abortTransaction();

      try {
        f.beginTransaction();

        IResourceDAO resourceDao = f.getResourceDAOInstance();
        resourceDao.deletePersistentEntity(resourceId);

        f.endTransaction();
      } catch (DAOException e2) {
        throw new ServletException(
            "dao error occured - additional error cleaning stale resource " + resourceId, e);
      }

      throw new ServletException("dao error occured", e);
    }

    // Well, that seemed to be successful(!)
    templateContext.put("person", pageContext.getSession().getPersonBinding());
    templateContext.put("submission", submission);
    templateContext.put("now", new Date());

    // Write out mail.
    StringWriter pw = new StringWriter();
    emailTemplate.merge(templateContext, pw);
    try {
      mailSender.sendMail(
          pageContext.getSession().getPersonBinding().getEmailAddress(),
          "Submission (" + assignment.getName() + ")",
          pw.toString());
      templateContext.put("mailSent", true);
      pageContext.log(
          Level.INFO,
          "student submission mail sent (email: "
              + pageContext.getSession().getPersonBinding().getEmailAddress()
              + ") (code: "
              + securityCode
              + ")");
    } catch (MailException e) {
      templateContext.put("mailSent", false);
      pageContext.log(
          Level.ERROR,
          "student submission mail NOT sent (email: "
              + pageContext.getSession().getPersonBinding().getEmailAddress()
              + ") (code: "
              + securityCode
              + ")");
      pageContext.log(Level.ERROR, e);
    }

    // Display the page
    pageContext.log(
        Level.INFO,
        "student submission successful (student: "
            + pageContext.getSession().getPersonBinding().getUniqueIdentifier()
            + ") (code: "
            + securityCode
            + ") (submission: "
            + submission.getId()
            + ")");
    templateContext.put("greet", pageContext.getSession().getPersonBinding().getChosenName());
    pageContext.renderTemplate(template, templateContext);
  }