package org.iatoki.judgels.uriel.controllers; import com.google.common.collect.Lists; import org.iatoki.judgels.FileSystemProvider; import org.iatoki.judgels.play.IdentityUtils; import org.iatoki.judgels.play.InternalLink; import org.iatoki.judgels.play.LazyHtml; import org.iatoki.judgels.play.forms.ListTableSelectionForm; import org.iatoki.judgels.play.Page; import org.iatoki.judgels.play.controllers.AbstractJudgelsController; import org.iatoki.judgels.play.views.html.layouts.heading3Layout; import org.iatoki.judgels.gabriel.GradingLanguageRegistry; import org.iatoki.judgels.gabriel.GradingSource; import org.iatoki.judgels.sandalphon.Submission; import org.iatoki.judgels.sandalphon.adapters.impls.SubmissionAdapters; import org.iatoki.judgels.sandalphon.SubmissionException; import org.iatoki.judgels.sandalphon.SubmissionNotFoundException; import org.iatoki.judgels.sandalphon.services.SubmissionService; import org.iatoki.judgels.uriel.Contest; import org.iatoki.judgels.uriel.ContestContestant; import org.iatoki.judgels.uriel.ContestNotFoundException; import org.iatoki.judgels.uriel.ContestPermissions; import org.iatoki.judgels.uriel.ContestProblem; import org.iatoki.judgels.uriel.ContestProblemStatus; import org.iatoki.judgels.uriel.config.SubmissionLocalFile; import org.iatoki.judgels.uriel.config.SubmissionRemoteFile; import org.iatoki.judgels.uriel.services.ContestContestantService; import org.iatoki.judgels.uriel.services.ContestProblemService; import org.iatoki.judgels.uriel.services.ContestService; import org.iatoki.judgels.uriel.services.ContestSupervisorService; import org.iatoki.judgels.uriel.services.impls.JidCacheServiceImpl; import org.iatoki.judgels.uriel.controllers.securities.Authenticated; import org.iatoki.judgels.uriel.controllers.securities.HasRole; import org.iatoki.judgels.uriel.controllers.securities.LoggedIn; import org.iatoki.judgels.uriel.views.html.contest.submission.listScreenedSubmissionsView; import org.iatoki.judgels.uriel.views.html.contest.submission.listSubmissionsView; import org.iatoki.judgels.uriel.views.html.layouts.accessTypeByStatusLayout; import play.data.Form; import play.db.jpa.Transactional; import play.i18n.Messages; import play.mvc.Http; import play.mvc.Result; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.util.Arrays; import java.util.List; import java.util.Map; @Authenticated(value = {LoggedIn.class, HasRole.class}) @Singleton @Named public final class ContestSubmissionController extends AbstractJudgelsController { private static final long PAGE_SIZE = 20; private final ContestService contestService; private final ContestProblemService contestProblemService; private final ContestContestantService contestContestantService; private final ContestSupervisorService contestSupervisorService; private final SubmissionService submissionService; private final FileSystemProvider submissionLocalFileSystemProvider; private final FileSystemProvider submissionRemoteFileSystemProvider; @Inject public ContestSubmissionController(ContestService contestService, ContestProblemService contestProblemService, ContestContestantService contestContestantService, ContestSupervisorService contestSupervisorService, SubmissionService submissionService, @SubmissionLocalFile FileSystemProvider submissionLocalFileSystemProvider, @SubmissionRemoteFile @Nullable FileSystemProvider submissionRemoteFileSystemProvider) { this.contestService = contestService; this.contestProblemService = contestProblemService; this.contestContestantService = contestContestantService; this.contestSupervisorService = contestSupervisorService; this.submissionService = submissionService; this.submissionLocalFileSystemProvider = submissionLocalFileSystemProvider; this.submissionRemoteFileSystemProvider = submissionRemoteFileSystemProvider; } @Transactional public Result postSubmitProblem(long contestId, String problemJid) throws ContestNotFoundException { Contest contest = contestService.findContestById(contestId); ContestProblem contestProblem = contestProblemService.findContestProblemByContestJidAndContestProblemJid(contest.getJid(), problemJid); if (ContestControllerUtils.getInstance().isAllowedToDoContest(contest) && contestProblem.getContestJid().equals(contest.getJid()) && !ContestControllerUtils.getInstance().isCoach(contest) && contestProblem.getStatus() != ContestProblemStatus.UNUSED) { if (contestProblem.getStatus() == ContestProblemStatus.CLOSED) { return redirect(routes.ContestProblemController.viewProblem(contestId, contestProblem.getId())); } else if ((contestProblem.getSubmissionsLimit() == 0) || (submissionService.countSubmissionsByContestJidByUser(contest.getJid(), contestProblem.getProblemJid(), IdentityUtils.getUserJid()) < contestProblem.getSubmissionsLimit())) { Http.MultipartFormData body = request().body().asMultipartFormData(); String gradingLanguage = body.asFormUrlEncoded().get("language")[0]; String gradingEngine = body.asFormUrlEncoded().get("engine")[0]; try { GradingSource source = SubmissionAdapters.fromGradingEngine(gradingEngine).createGradingSourceFromNewSubmission(body); String submissionJid = submissionService.submit(problemJid, contest.getJid(), gradingEngine, gradingLanguage, null, source, IdentityUtils.getUserJid(), IdentityUtils.getIpAddress()); SubmissionAdapters.fromGradingEngine(gradingEngine).storeSubmissionFiles(submissionLocalFileSystemProvider, submissionRemoteFileSystemProvider, submissionJid, source); ControllerUtils.getInstance().addActivityLog("Submit to problem " + contestProblem.getAlias() + " in contest " + contest.getName() + "."); } catch (SubmissionException e) { flash("submissionError", e.getMessage()); return redirect(routes.ContestProblemController.viewProblem(contestId, contestProblem.getId())); } } else { flash("submissionError", "submission.limit.reached"); return redirect(routes.ContestProblemController.viewProblem(contestId, contestProblem.getId())); } return redirect(routes.ContestSubmissionController.viewScreenedSubmissions(contestId)); } else { return ContestControllerUtils.getInstance().tryEnteringContest(contest); } } @Transactional(readOnly = true) public Result viewScreenedSubmissions(long contestId) throws ContestNotFoundException { return listScreenedSubmissions(contestId, 0, "id", "desc", null); } @Transactional(readOnly = true) public Result listScreenedSubmissions(long contestId, long pageIndex, String orderBy, String orderDir, String problemJid) throws ContestNotFoundException { Contest contest = contestService.findContestById(contestId); if (ContestControllerUtils.getInstance().isAllowedToEnterContest(contest) && !ContestControllerUtils.getInstance().isCoach(contest)) { String actualProblemJid = "(none)".equals(problemJid) ? null : problemJid; Page<Submission> submissions = submissionService.pageSubmissions(pageIndex, PAGE_SIZE, orderBy, orderDir, IdentityUtils.getUserJid(), actualProblemJid, contest.getJid()); Map<String, String> problemJidToAliasMap = contestProblemService.findProblemJidToAliasMapByContestJid(contest.getJid()); Map<String, String> gradingLanguageToNameMap = GradingLanguageRegistry.getInstance().getGradingLanguages(); LazyHtml content = new LazyHtml(listScreenedSubmissionsView.render(contestId, submissions, problemJidToAliasMap, gradingLanguageToNameMap, pageIndex, orderBy, orderDir, actualProblemJid)); content.appendLayout(c -> heading3Layout.render(Messages.get("submission.submissions"), c)); if (isAllowedToSuperviseSubmissions(contest)) { appendSubtabsLayout(content, contest); } ContestControllerUtils.getInstance().appendTabsLayout(content, contest); ControllerUtils.getInstance().appendSidebarLayout(content); appendBreadcrumbsLayout(content, contest, new InternalLink(Messages.get("submission.list"), routes.ContestSubmissionController.viewScreenedSubmissions(contest.getId())) ); ControllerUtils.getInstance().appendTemplateLayout(content, "Contest - Submissions"); ControllerUtils.getInstance().addActivityLog("List own submissions in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>."); return ControllerUtils.getInstance().lazyOk(content); } else { return ContestControllerUtils.getInstance().tryEnteringContest(contest); } } @Transactional(readOnly = true) public Result viewSubmission(long contestId, long submissionId) throws ContestNotFoundException, SubmissionNotFoundException { Contest contest = contestService.findContestById(contestId); Submission submission = submissionService.findSubmissionById(submissionId); if (ContestControllerUtils.getInstance().isAllowedToEnterContest(contest) && isAllowedToViewSubmission(contest, submission)) { GradingSource source = SubmissionAdapters.fromGradingEngine(submission.getGradingEngine()).createGradingSourceFromPastSubmission(submissionLocalFileSystemProvider, submissionRemoteFileSystemProvider, submission.getJid()); String authorName = JidCacheServiceImpl.getInstance().getDisplayName(submission.getAuthorJid()); ContestProblem contestProblem = contestProblemService.findContestProblemByContestJidAndContestProblemJid(contest.getJid(), submission.getProblemJid()); String contestProblemAlias = contestProblem.getAlias(); String contestProblemName = JidCacheServiceImpl.getInstance().getDisplayName(contestProblem.getProblemJid()); String gradingLanguageName = GradingLanguageRegistry.getInstance().getLanguage(submission.getGradingLanguage()).getName(); LazyHtml content = new LazyHtml(SubmissionAdapters.fromGradingEngine(submission.getGradingEngine()).renderViewSubmission(submission, source, authorName, contestProblemAlias, contestProblemName, gradingLanguageName, contest.getName())); ContestControllerUtils.getInstance().appendTabsLayout(content, contest); ControllerUtils.getInstance().appendSidebarLayout(content); appendBreadcrumbsLayout(content, contest, new InternalLink(Messages.get("status.supervisor"), routes.ContestSubmissionController.viewSubmissions(contest.getId())), new InternalLink(Messages.get("submission.view"), routes.ContestSubmissionController.viewSubmission(contest.getId(), submission.getId())) ); ControllerUtils.getInstance().appendTemplateLayout(content, "Contest - Submission - View"); ControllerUtils.getInstance().addActivityLog("View submission " + submission.getId() + " in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>."); return ControllerUtils.getInstance().lazyOk(content); } else { return ContestControllerUtils.getInstance().tryEnteringContest(contest); } } @Transactional(readOnly = true) public Result viewSubmissions(long contestId) throws ContestNotFoundException { return listSubmissions(contestId, 0, "id", "desc", null, null); } @Transactional(readOnly = true) public Result listSubmissions(long contestId, long pageIndex, String orderBy, String orderDir, String contestantJid, String problemJid) throws ContestNotFoundException { Contest contest = contestService.findContestById(contestId); if (isAllowedToSuperviseSubmissions(contest)) { String actualContestantJid = "(none)".equals(contestantJid) ? null : contestantJid; String actualProblemJid = "(none)".equals(problemJid) ? null : problemJid; Page<Submission> submissions = submissionService.pageSubmissions(pageIndex, PAGE_SIZE, orderBy, orderDir, actualContestantJid, actualProblemJid, contest.getJid()); Map<String, String> problemJidToAliasMap = contestProblemService.findProblemJidToAliasMapByContestJid(contest.getJid()); List<ContestContestant> contestants = contestContestantService.findAllContestContestantsByContestJid(contest.getJid()); List<String> contestantJids = Lists.transform(contestants, c -> c.getUserJid()); Map<String, String> gradingLanguageToNameMap = GradingLanguageRegistry.getInstance().getGradingLanguages(); LazyHtml content = new LazyHtml(listSubmissionsView.render(contestId, submissions, contestantJids, problemJidToAliasMap, gradingLanguageToNameMap, pageIndex, orderBy, orderDir, actualContestantJid, actualProblemJid)); content.appendLayout(c -> heading3Layout.render(Messages.get("submission.submissions"), c)); appendSubtabsLayout(content, contest); ContestControllerUtils.getInstance().appendTabsLayout(content, contest); ControllerUtils.getInstance().appendSidebarLayout(content); appendBreadcrumbsLayout(content, contest, new InternalLink(Messages.get("status.supervisor"), routes.ContestSubmissionController.viewSubmissions(contest.getId())) ); ControllerUtils.getInstance().appendTemplateLayout(content, "Contest - All Submissions"); ControllerUtils.getInstance().addActivityLog("List all submissions in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>."); return ControllerUtils.getInstance().lazyOk(content); } else { return ContestControllerUtils.getInstance().tryEnteringContest(contest); } } @Transactional public Result regradeSubmission(long contestId, long submissionId, long pageIndex, String orderBy, String orderDir, String contestantJid, String problemJid) throws ContestNotFoundException, SubmissionNotFoundException { Contest contest = contestService.findContestById(contestId); if (isAllowedToSuperviseSubmissions(contest)) { Submission submission = submissionService.findSubmissionById(submissionId); GradingSource source = SubmissionAdapters.fromGradingEngine(submission.getGradingEngine()).createGradingSourceFromPastSubmission(submissionLocalFileSystemProvider, submissionRemoteFileSystemProvider, submission.getJid()); submissionService.regrade(submission.getJid(), source, IdentityUtils.getUserJid(), IdentityUtils.getIpAddress()); ControllerUtils.getInstance().addActivityLog("Regrade submission " + submission.getId() + " in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>."); return redirect(routes.ContestSubmissionController.listSubmissions(contestId, pageIndex, orderBy, orderDir, contestantJid, problemJid)); } else { return ContestControllerUtils.getInstance().tryEnteringContest(contest); } } @Transactional public Result regradeSubmissions(long contestId, long pageIndex, String orderBy, String orderDir, String contestantJid, String problemJid) throws ContestNotFoundException, SubmissionNotFoundException { Contest contest = contestService.findContestById(contestId); if (isAllowedToSuperviseSubmissions(contest)) { ListTableSelectionForm data = Form.form(ListTableSelectionForm.class).bindFromRequest().get(); List<Submission> submissions; if (data.selectAll) { submissions = submissionService.findSubmissionsWithoutGradingsByFilters(orderBy, orderDir, contestantJid, problemJid, contest.getJid()); } else if (data.selectJids != null) { submissions = submissionService.findSubmissionsWithoutGradingsByJids(data.selectJids); } else { return redirect(routes.ContestSubmissionController.listSubmissions(contestId, pageIndex, orderBy, orderDir, contestantJid, problemJid)); } for (Submission submission : submissions) { GradingSource source = SubmissionAdapters.fromGradingEngine(submission.getGradingEngine()).createGradingSourceFromPastSubmission(submissionLocalFileSystemProvider, submissionRemoteFileSystemProvider, submission.getJid()); submissionService.regrade(submission.getJid(), source, IdentityUtils.getUserJid(), IdentityUtils.getIpAddress()); } ControllerUtils.getInstance().addActivityLog("Regrade some submissions in contest " + contest.getName() + " <a href=\"" + "http://" + Http.Context.current().request().host() + Http.Context.current().request().uri() + "\">link</a>."); return redirect(routes.ContestSubmissionController.listSubmissions(contestId, pageIndex, orderBy, orderDir, contestantJid, problemJid)); } else { return ContestControllerUtils.getInstance().tryEnteringContest(contest); } } private void appendSubtabsLayout(LazyHtml content, Contest contest) { content.appendLayout(c -> accessTypeByStatusLayout.render(routes.ContestSubmissionController.viewScreenedSubmissions(contest.getId()), routes.ContestSubmissionController.viewSubmissions(contest.getId()), c)); } private void appendBreadcrumbsLayout(LazyHtml content, Contest contest, InternalLink... lastLinks) { ControllerUtils.getInstance().appendBreadcrumbsLayout(content, ContestControllerUtils.getInstance().getContestBreadcrumbsBuilder(contest) .add(new InternalLink(Messages.get("submission.submissions"), routes.ContestController.jumpToSubmissions(contest.getId()))) .addAll(Arrays.asList(lastLinks)) .build() ); } private boolean isAllowedToSuperviseSubmissions(Contest contest) { return ControllerUtils.getInstance().isAdmin() || ContestControllerUtils.getInstance().isManager(contest) || (ContestControllerUtils.getInstance().isSupervisor(contest) && contestSupervisorService.findContestSupervisorByContestJidAndUserJid(contest.getJid(), IdentityUtils.getUserJid()).getContestPermission().isAllowed(ContestPermissions.SUBMISSION)); } private boolean isAllowedToViewSubmission(Contest contest, Submission submission) { return submission.getContestJid().equals(contest.getJid()) && (ControllerUtils.getInstance().isAdmin() || isAllowedToSuperviseSubmissions(contest) || submission.getAuthorJid().equals(IdentityUtils.getUserJid())); } }