/*
* ELW : e-learning workspace
* Copyright (C) 2010 Anton Kraievoy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package elw.web;
import base.pattern.Result;
import elw.dao.Queries;
import elw.dao.ctx.*;
import elw.dp.mips.MipsValidator;
import elw.dp.mips.TaskBean;
import elw.vo.*;
import org.akraievoy.base.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
public class StudentCodeValidator extends Executors.Task {
private static final Logger log = LoggerFactory.getLogger(StudentCodeValidator.class);
// LATER this should be configurable/injectable on per-course/taskType basis
public static final String COURSE_ID_AOS = "aos";
public static final String TASKTYPE_ID_LR = "lr";
public static final String SLOT_ID_SOLUTIONS = "code";
public static final String SLOT_ID_STATEMENT = "statement";
public static final String SLOT_ID_TEST = "test";
private static final String[] NO_PENDING_ENROLLMENTS = new String[0];
private int periodMillis = 300000;
private final Queries queries;
private final MipsValidator validator;
public StudentCodeValidator(
ScheduledExecutorService executor,
Queries queries) {
super(executor);
this.validator = new MipsValidator();
this.queries = queries;
}
private final List<String> pendingEnrollmentIds = new ArrayList<String>();
public void setPeriodMillis(int periodMillis) {
this.periodMillis = periodMillis;
}
protected long getRestartDelay() {
return periodMillis;
}
protected long getInitialDelay() {
return periodMillis;
}
public StudentCodeValidator workPending(String enrollmentId) {
synchronized (pendingEnrollmentIds) {
if (!pendingEnrollmentIds.contains(enrollmentId)) {
pendingEnrollmentIds.add(enrollmentId);
}
}
return this;
}
protected void runInternal() throws Throwable {
final String[] pendingEnrollmentsIdArr;
synchronized (pendingEnrollmentIds) {
if (pendingEnrollmentIds.isEmpty()) {
pendingEnrollmentsIdArr = NO_PENDING_ENROLLMENTS;
} else {
pendingEnrollmentsIdArr = pendingEnrollmentIds.toArray(new String[pendingEnrollmentIds.size()]);
pendingEnrollmentIds.clear();
Arrays.sort(pendingEnrollmentsIdArr);
}
}
if (pendingEnrollmentsIdArr.length == 0) {
return;
}
final List<Enrollment> enrs = queries.enrollments();
for (Enrollment enr : enrs) {
final CtxEnrollment ctxEnr;
{
final Course course = queries.course(enr.getCourseId());
final Group group = queries.group(enr.getGroupId());
ctxEnr = new CtxEnrollment(enr, course, group);
}
if (
!ctxEnr.course.getId().contains(COURSE_ID_AOS) ||
Arrays.binarySearch(pendingEnrollmentsIdArr, ctxEnr.enr.getId()) < 0
) {
continue;
}
processEnrollment(ctxEnr);
}
}
protected void processEnrollment(CtxEnrollment ctxEnr) {
for (CtxStudent ctxStud : ctxEnr.students) {
for (CtxTask ctxVer : ctxStud.tasks) {
if (!TASKTYPE_ID_LR.equals(ctxVer.tType.getId())) {
continue;
}
final CtxSlot ctxSlotCode =
ctxVer.slot(SLOT_ID_SOLUTIONS);
final CtxSlot ctxSlotStatement =
ctxVer.slot(SLOT_ID_STATEMENT);
final CtxSlot ctxSlotTest =
ctxVer.slot(SLOT_ID_TEST);
final List<Solution> files =
queries.solutions(ctxSlotCode);
for (Solution f : files) {
if (f.getValidatorStamp() > 0 && f.getScore() != null) {
continue;
}
final CtxSolution ctxSolution =
ctxSlotCode.solution(f);
processSolution(
ctxSlotStatement,
ctxSlotTest,
ctxSolution
);
}
}
}
}
protected void processSolution(
CtxSlot ctxSlotStatement,
CtxSlot ctxSlotTest,
CtxSolution ctxSolution
) {
Score score = null;
try {
final Result[] resRef = {new Result("unknown", false)};
final int[] passFailCounts = new int[2];
final List<Attachment> allStatements =
queries.attachments(ctxSlotStatement);
final List<Attachment> allTests =
queries.attachments(ctxSlotTest);
final List<String> allTestsStr =
new ArrayList<String>();
for (Attachment allTest : allTests) {
allTestsStr.add(
queries.fileText(
allTest,
FileBase.CONTENT
)
);
}
final TaskBean taskBean = new TaskBean(
queries.fileText(
allStatements.get(
allStatements.size() - 1
),
FileBase.CONTENT
),
allTestsStr,
""
);
validator.batch(
resRef,
taskBean,
queries.fileLines(ctxSolution.solution, FileBase.CONTENT),
passFailCounts
);
ctxSolution.solution.setTestsFailed(passFailCounts[1]);
ctxSolution.solution.setTestsPassed(passFailCounts[0]);
score = ctxSolution.preliminary();
if (passFailCounts[0] > 0) {
score.setApproved(passFailCounts[1] == 0);
}
} catch (Throwable t) {
log.warn(
"failed to validate {} / {} / {}: {}",
new Object[]{
ctxSolution,
ctxSolution.solution.getId(),
ctxSolution.solution.getStamp(),
String.valueOf(t)
}
);
log.debug("exception trace", t);
}
if (score != null) {
try {
final long scoreStamp =
queries.createScore(ctxSolution, score);
ctxSolution.solution.setValidatorStamp(scoreStamp);
queries.updateSolution(ctxSolution.solution);
} catch (Throwable t) {
log.warn(
"failed to store update {} / {} / {}: {}",
new Object[]{
ctxSolution,
ctxSolution.solution.getId(),
ctxSolution.solution.getStamp(),
String.valueOf(t)
}
);
log.debug("exception trace", t);
}
}
}
}