implemented event handling for start and finish exams
This commit is contained in:
parent
b6433c7c99
commit
ebbbf56314
8 changed files with 140 additions and 37 deletions
|
@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -38,6 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
|
@ -188,6 +191,30 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
|||
.flatMap(this.examDAO::byPK);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void notifyExamStarted(final ExamStartedEvent event) {
|
||||
|
||||
log.info("ExamStartedEvent received, process applySEBClientRestriction...");
|
||||
|
||||
applySEBClientRestriction(event.exam)
|
||||
.onError(error -> log.error(
|
||||
"Failed to apply SEB restrictions for started exam: {}",
|
||||
event.exam,
|
||||
error));
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
|
||||
log.info("ExamFinishedEvent received, process releaseSEBClientRestriction...");
|
||||
|
||||
releaseSEBClientRestriction(event.exam)
|
||||
.onError(error -> log.error(
|
||||
"Failed to release SEB restrictions for finished exam: {}",
|
||||
event.exam,
|
||||
error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applySEBClientRestriction(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
|
||||
/** This event is fired just after an exam has been finished */
|
||||
public class ExamFinishedEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = -1528880878532843063L;
|
||||
|
||||
public final Exam exam;
|
||||
|
||||
public ExamFinishedEvent(final Exam exam) {
|
||||
super(exam);
|
||||
this.exam = exam;
|
||||
}
|
||||
}
|
|
@ -191,13 +191,6 @@ public interface ExamSessionService {
|
|||
* @return Result refer to the collection of connection tokens or to an error when happened. */
|
||||
Result<Collection<String>> getActiveConnectionTokens(Long examId);
|
||||
|
||||
/** Called to notify that the given exam has just been finished.
|
||||
* This cleanup all exam session caches for the given exam and also cleanup session based stores on the persistent.
|
||||
*
|
||||
* @param exam the Exam that has just been finished
|
||||
* @return Result refer to the finished exam or to an error when happened. */
|
||||
Result<Exam> notifyExamFinished(final Exam exam);
|
||||
|
||||
/** Use this to check if the current cached running exam is up to date
|
||||
* and if not to flush the cache.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
|
||||
/** This event is fired just after an exam has been started */
|
||||
public class ExamStartedEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = -6564345490588661010L;
|
||||
|
||||
public final Exam exam;
|
||||
|
||||
public ExamStartedEvent(final Exam exam) {
|
||||
super(exam);
|
||||
this.exam = exam;
|
||||
}
|
||||
|
||||
}
|
|
@ -29,7 +29,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
|||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
|
||||
|
||||
@Service
|
||||
|
@ -43,7 +42,6 @@ public class ExamSessionControlTask implements DisposableBean {
|
|||
private final ExamUpdateHandler examUpdateHandler;
|
||||
private final ExamProctoringRoomService examProcotringRoomService;
|
||||
private final WebserviceInfo webserviceInfo;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
private final Long examTimePrefix;
|
||||
private final Long examTimeSuffix;
|
||||
|
@ -56,7 +54,6 @@ public class ExamSessionControlTask implements DisposableBean {
|
|||
final ExamUpdateHandler examUpdateHandler,
|
||||
final ExamProctoringRoomService examProcotringRoomService,
|
||||
final WebserviceInfo webserviceInfo,
|
||||
final ExamSessionService examSessionService,
|
||||
@Value("${sebserver.webservice.api.exam.time-prefix:3600000}") final Long examTimePrefix,
|
||||
@Value("${sebserver.webservice.api.exam.time-suffix:3600000}") final Long examTimeSuffix,
|
||||
@Value("${sebserver.webservice.api.exam.update-interval:1 * * * * *}") final String examTaskCron,
|
||||
|
@ -66,7 +63,6 @@ public class ExamSessionControlTask implements DisposableBean {
|
|||
this.sebClientConnectionService = sebClientConnectionService;
|
||||
this.examUpdateHandler = examUpdateHandler;
|
||||
this.webserviceInfo = webserviceInfo;
|
||||
this.examSessionService = examSessionService;
|
||||
this.examTimePrefix = examTimePrefix;
|
||||
this.examTimeSuffix = examTimeSuffix;
|
||||
this.examTaskCron = examTaskCron;
|
||||
|
@ -188,8 +184,6 @@ public class ExamSessionControlTask implements DisposableBean {
|
|||
.stream()
|
||||
.filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isBefore(now))
|
||||
.flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setFinished(exam, updateId)))
|
||||
.flatMap(exam -> Result.skipOnError(this.examProcotringRoomService.disposeRoomsForExam(exam)))
|
||||
.flatMap(exam -> Result.skipOnError(this.examSessionService.notifyExamFinished(exam)))
|
||||
.collect(Collectors.toMap(Exam::getId, Exam::getName));
|
||||
|
||||
if (!updated.isEmpty()) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -47,6 +48,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
||||
@Lazy
|
||||
|
@ -402,20 +404,23 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
.getActiveConnctionTokens(examId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> notifyExamFinished(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
if (!isExamRunning(exam.id)) {
|
||||
this.flushCache(exam);
|
||||
@EventListener
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
|
||||
log.info("ExamFinishedEvent received, process exam session cleanup...");
|
||||
|
||||
try {
|
||||
if (!isExamRunning(event.exam.id)) {
|
||||
this.flushCache(event.exam);
|
||||
if (this.distributedSetup) {
|
||||
this.clientConnectionDAO
|
||||
.deleteClientIndicatorValues(exam)
|
||||
.deleteClientIndicatorValues(event.exam)
|
||||
.getOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
return exam;
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to cleanup on finished exam: {}", event.exam, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.joda.time.DateTimeZone;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -24,6 +25,8 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
|
@ -33,17 +36,20 @@ class ExamUpdateHandler {
|
|||
private static final Logger log = LoggerFactory.getLogger(ExamUpdateHandler.class);
|
||||
|
||||
private final ExamDAO examDAO;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
private final SEBRestrictionService sebRestrictionService;
|
||||
private final String updatePrefix;
|
||||
private final Long examTimeSuffix;
|
||||
|
||||
public ExamUpdateHandler(
|
||||
final ExamDAO examDAO,
|
||||
final ApplicationEventPublisher applicationEventPublisher,
|
||||
final SEBRestrictionService sebRestrictionService,
|
||||
final WebserviceInfo webserviceInfo,
|
||||
@Value("${sebserver.webservice.api.exam.time-suffix:3600000}") final Long examTimeSuffix) {
|
||||
|
||||
this.examDAO = examDAO;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
this.sebRestrictionService = sebRestrictionService;
|
||||
this.updatePrefix = webserviceInfo.getLocalHostAddress()
|
||||
+ "_" + webserviceInfo.getServerPort() + "_";
|
||||
|
@ -79,14 +85,21 @@ class ExamUpdateHandler {
|
|||
|
||||
return this.examDAO
|
||||
.placeLock(exam.id, updateId)
|
||||
.flatMap(e -> this.examDAO.updateState(
|
||||
exam.id,
|
||||
ExamStatus.RUNNING,
|
||||
updateId))
|
||||
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
||||
.flatMap(e -> this.examDAO.releaseLock(e, updateId))
|
||||
.onError(error -> this.examDAO.forceUnlock(exam.id)
|
||||
.onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)));
|
||||
.flatMap(e -> this.examDAO.updateState(exam.id, ExamStatus.RUNNING, updateId))
|
||||
.map(e -> {
|
||||
this.examDAO
|
||||
.releaseLock(e, updateId)
|
||||
.onError(error -> this.examDAO
|
||||
.forceUnlock(exam.id)
|
||||
.onError(unlockError -> log.error(
|
||||
"Failed to force unlock update look for exam: {}",
|
||||
exam.id)));
|
||||
return e;
|
||||
})
|
||||
.map(e -> {
|
||||
this.applicationEventPublisher.publishEvent(new ExamStartedEvent(exam));
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
Result<Exam> setFinished(final Exam exam, final String updateId) {
|
||||
|
@ -96,13 +109,21 @@ class ExamUpdateHandler {
|
|||
|
||||
return this.examDAO
|
||||
.placeLock(exam.id, updateId)
|
||||
.flatMap(e -> this.examDAO.updateState(
|
||||
exam.id,
|
||||
ExamStatus.FINISHED,
|
||||
updateId))
|
||||
.flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
|
||||
.flatMap(e -> this.examDAO.releaseLock(e, updateId))
|
||||
.onError(error -> this.examDAO.forceUnlock(exam.id));
|
||||
.flatMap(e -> this.examDAO.updateState(exam.id, ExamStatus.FINISHED, updateId))
|
||||
.map(e -> {
|
||||
this.examDAO
|
||||
.releaseLock(e, updateId)
|
||||
.onError(error -> this.examDAO
|
||||
.forceUnlock(exam.id)
|
||||
.onError(unlockError -> log.error(
|
||||
"Failed to force unlock update look for exam: {}",
|
||||
exam.id)));
|
||||
return e;
|
||||
})
|
||||
.map(e -> {
|
||||
this.applicationEventPublisher.publishEvent(new ExamFinishedEvent(exam));
|
||||
return exam;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
@ -155,6 +156,15 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
});
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void notifyExamFinished(final ExamFinishedEvent event) {
|
||||
|
||||
log.info("ExamFinishedEvent received, process disposeRoomsForExam...");
|
||||
|
||||
disposeRoomsForExam(event.exam)
|
||||
.onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> disposeRoomsForExam(final Exam exam) {
|
||||
|
||||
|
|
Loading…
Reference in a new issue