/*==========================================================================*\ | $Id: MassRegraderPage.java,v 1.3 2011/12/25 21:11:41 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2011 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT 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 General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.grader; import org.webcat.core.User; import org.webcat.ui.generators.JavascriptGenerator; import org.webcat.woextensions.ECAction; import org.webcat.woextensions.WCEC; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOResponse; import com.webobjects.eocontrol.EOFetchSpecification; import com.webobjects.eocontrol.EOQualifier; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.eof.ERXSortOrdering.ERXSortOrderings; //-------------------------------------------------------------------------- /** * Implements a simple interface for performing mass-regrades of submissions to * an assignment (typically for data collection with an updated grading * plug-in). * * @author Tony Allevato * @author Last changed by: $Author: stedwar2 $ * @version $Revision: 1.3 $, $Date: 2011/12/25 21:11:41 $ */ public class MassRegraderPage extends GraderAssignmentComponent { //~ Constructors .......................................................... // ---------------------------------------------------------- public MassRegraderPage(WOContext context) { super(context); } //~ KVC attributes (must be public) ....................................... public String qualifierString; public NSMutableDictionary<String, String> qualifierErrors; public int numberOfSubmissions; public EnqueuedJob job; public int index; public int cachedCountOfRegradingJobsInQueue = 0; public int cachedCountOfSuspendedJobsInQueue = 0; //~ Methods ............................................................... // ---------------------------------------------------------- protected void beforeAppendToResponse( WOResponse response, WOContext context) { updateSubmissionCount(); super.beforeAppendToResponse(response, context); } // ---------------------------------------------------------- public JavascriptGenerator massRegrade() { JavascriptGenerator result = new JavascriptGenerator() .append("regraderWatcher.startEnqueueMonitor();"); EOQualifier q; qualifierErrors = null; boolean hasQualString = false; if (qualifierString != null && qualifierString.trim().length() > 0) { hasQualString = true; } if (!hasQualString && (prefs().assignment() == null && prefs().assignmentOffering() == null)) { return result; } ERXSortOrderings sortOrderings = Submission.user.dot(User.userName).ascInsensitive().then( Submission.submitNumber.asc()); if (hasQualString) { try { q = EOQualifier.qualifierWithQualifierFormat( qualifierString, null); } catch (Exception e) { String msg = e.getMessage(); qualifierErrors = new NSMutableDictionary<String, String>(); qualifierErrors.setObjectForKey(msg, msg); q = null; } } else { if (prefs().assignmentOffering() != null) { q = Submission.assignmentOffering.is( prefs().assignmentOffering()); } else { q = Submission.assignmentOffering.dot( AssignmentOffering.assignment).is(prefs().assignment()); } } if (q != null) { NSArray<Submission> submissions = null; try { submissions = Submission.objectsMatchingQualifier( localContext(), q, sortOrderings); } catch (Exception e) { String msg = e.getMessage(); qualifierErrors = new NSMutableDictionary<String, String>(); qualifierErrors.setObjectForKey(msg, msg); } // Enqueue the whole lot of submissions for regrading. if (submissions != null) { if (enqueueThread == null) { enqueueThread = new EnqueueSubmissionsThread(); enqueueThread.addSubmissions(submissions); enqueueThread.start(); } else { enqueueThread.addSubmissions(submissions); } } } return result; } // ---------------------------------------------------------- public boolean isCurrentlyEnqueuing() { if (enqueueThread != null) { if (enqueueThread.isDone()) { enqueueThread = null; return false; } else { return true; } } return false; } // ---------------------------------------------------------- public int submissionsEnqueuedSoFar() { if (enqueueThread != null) { return enqueueThread.enqueuedSoFar(); } else { return 0; } } // ---------------------------------------------------------- public int totalSubmissionsToEnqueue() { if (enqueueThread != null) { return enqueueThread.submissionsAddedSoFar(); } else { return 0; } } // ---------------------------------------------------------- public String enqueueProgress() { int soFar = submissionsEnqueuedSoFar(); int total = totalSubmissionsToEnqueue(); if (total == 0) { return "100%"; } else { int progress = (int) ((double) soFar / total * 100 + 0.5); return "" + progress + "%"; } } // ---------------------------------------------------------- public JavascriptGenerator updateSubmissionCount() { JavascriptGenerator response = new JavascriptGenerator(); response.refresh("submissionCount", "qualifierErrors"); EOQualifier q; qualifierErrors = null; boolean hasQualString = false; if (qualifierString != null && qualifierString.trim().length() > 0) { hasQualString = true; } if (!hasQualString && (prefs().assignment() == null && prefs().assignmentOffering() == null)) { numberOfSubmissions = 0; return response; } if (hasQualString) { try { q = EOQualifier.qualifierWithQualifierFormat( qualifierString, null); } catch (Exception e) { String msg = e.getMessage(); qualifierErrors = new NSMutableDictionary<String, String>(); qualifierErrors.setObjectForKey(msg, msg); q = null; } } else { if (prefs().assignmentOffering() != null) { q = Submission.assignmentOffering.is( prefs().assignmentOffering()); } else { q = Submission.assignmentOffering.dot( AssignmentOffering.assignment).is(prefs().assignment()); } } if (q != null) { try { numberOfSubmissions = Submission.countOfObjectsMatchingQualifier( localContext(), q); } catch (Exception e) { String msg = e.getMessage(); qualifierErrors = new NSMutableDictionary<String, String>(); qualifierErrors.setObjectForKey(msg, msg); numberOfSubmissions = 0; } } else { numberOfSubmissions = 0; } return response; } // ---------------------------------------------------------- public int updateCountOfRegradingJobsInQueue() { cachedCountOfRegradingJobsInQueue = EnqueuedJob.countOfObjectsMatchingQualifier(localContext(), EnqueuedJob.regrading.isTrue().and( EnqueuedJob.paused.isFalse())); return cachedCountOfRegradingJobsInQueue; } // ---------------------------------------------------------- public int updateCountOfSuspendedJobsInQueue() { cachedCountOfSuspendedJobsInQueue = EnqueuedJob.countOfObjectsMatchingQualifier(localContext(), EnqueuedJob.regrading.isTrue().and( EnqueuedJob.paused.isTrue())); return cachedCountOfSuspendedJobsInQueue; } // ---------------------------------------------------------- public NSArray<EnqueuedJob> nextSetOfJobsInQueue() { EOQualifier qualifier = EnqueuedJob.regrading.isTrue().and( EnqueuedJob.paused.isFalse()); ERXSortOrderings sortOrderings = EnqueuedJob.queueTime.ascs(); EOFetchSpecification fspec = new EOFetchSpecification( EnqueuedJob.ENTITY_NAME, qualifier, sortOrderings); fspec.setFetchLimit(10); return EnqueuedJob.objectsWithFetchSpecification(localContext(), fspec); } //~ Private classes ....................................................... // ---------------------------------------------------------- /** * A separate thread that pops submissions off an array and enqueues them * to the grader. */ private class EnqueueSubmissionsThread extends Thread { //~ Constructor ....................................................... // ---------------------------------------------------------- public EnqueueSubmissionsThread() { super("MassRegraderPage.EnqueueSubmissionsThread"); submissions = new NSMutableArray<Submission>(); } //~ Methods ........................................................... // ---------------------------------------------------------- public void run() { new ECAction() { public void action() { int countForRecycling = 0; clearEnqueuedSoFar(); while (hasMoreSubmissions()) { popNextSubmission().requeueForGrading(ec); incrementEnqueuedSoFar(); // Recycle the editing context every 100 submissions. countForRecycling++; if (countForRecycling == 100) { ec.saveChanges(); ec.unlock(); ec.dispose(); ec = WCEC.newEditingContext(); ec.lock(); countForRecycling = 0; } } setIsDone(true); ec.saveChanges(); Grader.getInstance().graderQueue().enqueue( null ); }}.run(); } // ---------------------------------------------------------- public synchronized void addSubmissions(NSArray<Submission> subs) { subsAdded += subs.count(); submissions.addObjectsFromArray(subs); } // ---------------------------------------------------------- public synchronized Submission popNextSubmission() { return submissions.removeObjectAtIndex(0); } // ---------------------------------------------------------- public synchronized boolean hasMoreSubmissions() { return !submissions.isEmpty(); } // ---------------------------------------------------------- public synchronized boolean isDone() { return isDone; } // ---------------------------------------------------------- public synchronized int enqueuedSoFar() { return enqueuedSoFar; } // ---------------------------------------------------------- public synchronized int submissionsAddedSoFar() { return subsAdded; } // ---------------------------------------------------------- private synchronized void clearEnqueuedSoFar() { enqueuedSoFar = 0; } // ---------------------------------------------------------- private synchronized void incrementEnqueuedSoFar() { enqueuedSoFar++; } // ---------------------------------------------------------- private synchronized void setIsDone(boolean value) { isDone = value; if (isDone) { subsAdded = 0; enqueuedSoFar = 0; } } //~ Static/instance variables ......................................... private boolean isDone = false; private int enqueuedSoFar = 0; private int subsAdded = 0; private NSMutableArray<Submission> submissions; } //~ Static/instance variables ............................................. /** * This is static because it needs to stay alive even if the user navigates * away from the page and back while submissions are being enqueued, but * it's not really significant enough to create a separate queue processor * for it. */ private static EnqueueSubmissionsThread enqueueThread; }