Merge remote-tracking branch 'origin/dev-1.2-isolateExamQuizLoad' into
development Conflicts: pom.xml src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
This commit is contained in:
commit
35905e9dad
14 changed files with 412 additions and 286 deletions
1
docs/requirements.txt
Normal file
1
docs/requirements.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docutils<0.18
|
|
@ -99,16 +99,26 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
||||||
*
|
*
|
||||||
* @param examId the exam identifier
|
* @param examId the exam identifier
|
||||||
* @param updateId an update identifier
|
* @param updateId an update identifier
|
||||||
* @return Result refer to the specified exam or to an error if happened */
|
* @return Result refer to the specified exam identifier or to an error if happened */
|
||||||
Result<Exam> placeLock(Long examId, String updateId);
|
Result<Long> placeLock(Long examId, String updateId);
|
||||||
|
|
||||||
|
default Result<Exam> placeLock(final Exam exam, final String updateId) {
|
||||||
|
return placeLock(exam.id, updateId)
|
||||||
|
.map(id -> exam);
|
||||||
|
}
|
||||||
|
|
||||||
/** This is used to release an internal (write)lock for the specified exam.
|
/** This is used to release an internal (write)lock for the specified exam.
|
||||||
* The exam will be marked as not locked on the persistence level.
|
* The exam will be marked as not locked on the persistence level.
|
||||||
*
|
*
|
||||||
* @param examId the exam identifier
|
* @param examId the exam identifier
|
||||||
* @param updateId an update identifier
|
* @param updateId an update identifier
|
||||||
* @return Result refer to the specified exam or to an error if happened */
|
* @return Result refer to the specified exam identifier or to an error if happened */
|
||||||
Result<Exam> releaseLock(Long examId, String updateId);
|
Result<Long> releaseLock(Long examId, String updateId);
|
||||||
|
|
||||||
|
default Result<Exam> releaseLock(final Exam exam, final String updateId) {
|
||||||
|
return releaseLock(exam.id, updateId)
|
||||||
|
.map(id -> exam);
|
||||||
|
}
|
||||||
|
|
||||||
/** This is used to force release an internal (write)lock for the specified exam.
|
/** This is used to force release an internal (write)lock for the specified exam.
|
||||||
* The exam will be marked as not locked on the persistence level even if it is currently locked by another process
|
* The exam will be marked as not locked on the persistence level even if it is currently locked by another process
|
||||||
|
|
|
@ -28,8 +28,6 @@ import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.mybatis.dynamic.sql.SqlBuilder;
|
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||||
import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter;
|
|
||||||
import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -52,18 +50,13 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.AdditionalAttributeRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.AdditionalAttributeRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.AdditionalAttributeRecordMapper;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.AdditionalAttributeRecordMapper;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.InstitutionRecordDynamicSqlSupport;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DuplicateResourceException;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||||
|
@ -77,22 +70,22 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
public static final String FAILED_TO_LOAD_QUIZ_DATA_MARK = "[FAILED TO LOAD DATA FROM LMS]";
|
public static final String FAILED_TO_LOAD_QUIZ_DATA_MARK = "[FAILED TO LOAD DATA FROM LMS]";
|
||||||
|
|
||||||
private final ExamRecordMapper examRecordMapper;
|
private final ExamRecordMapper examRecordMapper;
|
||||||
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
|
private final ExamRecordDAO examRecordDAO;
|
||||||
private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper;
|
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
|
|
||||||
public ExamDAOImpl(
|
public ExamDAOImpl(
|
||||||
final ExamRecordMapper examRecordMapper,
|
final ExamRecordMapper examRecordMapper,
|
||||||
final ClientConnectionRecordMapper clientConnectionRecordMapper,
|
final ExamRecordDAO examRecordDAO,
|
||||||
final AdditionalAttributeRecordMapper additionalAttributeRecordMapper,
|
|
||||||
final ApplicationEventPublisher applicationEventPublisher,
|
final ApplicationEventPublisher applicationEventPublisher,
|
||||||
|
final AdditionalAttributeRecordMapper additionalAttributeRecordMapper,
|
||||||
final LmsAPIService lmsAPIService) {
|
final LmsAPIService lmsAPIService) {
|
||||||
|
|
||||||
this.examRecordMapper = examRecordMapper;
|
this.examRecordMapper = examRecordMapper;
|
||||||
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
|
this.examRecordDAO = examRecordDAO;
|
||||||
this.additionalAttributeRecordMapper = additionalAttributeRecordMapper;
|
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
|
this.additionalAttributeRecordMapper = additionalAttributeRecordMapper;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,63 +95,35 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Result<Exam> byPK(final Long id) {
|
public Result<Exam> byPK(final Long id) {
|
||||||
return recordById(id)
|
return this.examRecordDAO
|
||||||
|
.recordById(id)
|
||||||
.flatMap(this::toDomainModel);
|
.flatMap(this::toDomainModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Result<GrantEntity> examGrantEntityByPK(final Long id) {
|
public Result<GrantEntity> examGrantEntityByPK(final Long id) {
|
||||||
return recordById(id)
|
return this.examRecordDAO.recordById(id)
|
||||||
.map(record -> toDomainModel(record, null, null).getOrThrow());
|
.map(record -> toDomainModel(record, null, null).getOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Result<GrantEntity> examGrantEntityByClientConnection(final Long connectionId) {
|
public Result<GrantEntity> examGrantEntityByClientConnection(final Long connectionId) {
|
||||||
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
return this.examRecordDAO
|
||||||
.selectByPrimaryKey(connectionId))
|
.recordByClientConnection(connectionId)
|
||||||
.flatMap(ccRecord -> recordById(ccRecord.getExamId()))
|
|
||||||
.map(record -> toDomainModel(record, null, null).getOrThrow());
|
.map(record -> toDomainModel(record, null, null).getOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Result<Collection<Exam>> all(final Long institutionId, final Boolean active) {
|
public Result<Collection<Exam>> all(final Long institutionId, final Boolean active) {
|
||||||
return Result.tryCatch(() -> (active != null)
|
return this.examRecordDAO
|
||||||
? this.examRecordMapper.selectByExample()
|
.all(institutionId, active)
|
||||||
.where(
|
|
||||||
ExamRecordDynamicSqlSupport.institutionId,
|
|
||||||
isEqualToWhenPresent(institutionId))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualToWhenPresent(BooleanUtils.toIntegerObject(active)))
|
|
||||||
.build()
|
|
||||||
.execute()
|
|
||||||
: this.examRecordMapper.selectByExample()
|
|
||||||
.build()
|
|
||||||
.execute())
|
|
||||||
.flatMap(this::toDomainModel);
|
.flatMap(this::toDomainModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
||||||
return Result.tryCatch(() -> {
|
return this.examRecordDAO.allInstitutionIdsByQuizId(quizId);
|
||||||
return this.examRecordMapper.selectByExample()
|
|
||||||
.where(
|
|
||||||
ExamRecordDynamicSqlSupport.externalId,
|
|
||||||
isEqualToWhenPresent(quizId))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
|
||||||
.build()
|
|
||||||
.execute()
|
|
||||||
.stream()
|
|
||||||
.map(rec -> rec.getInstitutionId())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -189,51 +154,9 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we have a sort on institution name, join the institution table
|
return this.examRecordDAO
|
||||||
// If we have a sort on lms setup name, join lms setup table
|
.allMatching(filterMap)
|
||||||
final QueryExpressionDSL<MyBatis3SelectModelAdapter<List<ExamRecord>>>.QueryExpressionWhereBuilder whereClause =
|
.flatMap(this::toDomainModel)
|
||||||
(filterMap.getBoolean(FilterMap.ATTR_ADD_INSITUTION_JOIN))
|
|
||||||
? this.examRecordMapper
|
|
||||||
.selectByExample()
|
|
||||||
.join(InstitutionRecordDynamicSqlSupport.institutionRecord)
|
|
||||||
.on(
|
|
||||||
InstitutionRecordDynamicSqlSupport.id,
|
|
||||||
SqlBuilder.equalTo(ExamRecordDynamicSqlSupport.institutionId))
|
|
||||||
.where(
|
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualToWhenPresent(filterMap.getActiveAsInt()))
|
|
||||||
: (filterMap.getBoolean(FilterMap.ATTR_ADD_LMS_SETUP_JOIN))
|
|
||||||
? this.examRecordMapper
|
|
||||||
.selectByExample()
|
|
||||||
.join(LmsSetupRecordDynamicSqlSupport.lmsSetupRecord)
|
|
||||||
.on(
|
|
||||||
LmsSetupRecordDynamicSqlSupport.id,
|
|
||||||
SqlBuilder.equalTo(ExamRecordDynamicSqlSupport.lmsSetupId))
|
|
||||||
.where(
|
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualToWhenPresent(filterMap.getActiveAsInt()))
|
|
||||||
: this.examRecordMapper.selectByExample()
|
|
||||||
.where(
|
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualToWhenPresent(filterMap.getActiveAsInt()));
|
|
||||||
|
|
||||||
final List<ExamRecord> records = whereClause
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.institutionId,
|
|
||||||
isEqualToWhenPresent(filterMap.getInstitutionId()))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.lmsSetupId,
|
|
||||||
isEqualToWhenPresent(filterMap.getLmsSetupId()))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.type,
|
|
||||||
isEqualToWhenPresent(filterMap.getExamType()))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.status,
|
|
||||||
isEqualToWhenPresent(filterMap.getExamStatus()))
|
|
||||||
.build()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return this.toDomainModel(records)
|
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(quizDataFilter.and(predicate))
|
.filter(quizDataFilter.and(predicate))
|
||||||
|
@ -243,129 +166,32 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
public Result<Exam> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
||||||
return recordById(examId)
|
return this.examRecordDAO
|
||||||
.map(examRecord -> {
|
.updateState(examId, status, updateId)
|
||||||
if (BooleanUtils.isTrue(BooleanUtils.toBooleanObject(examRecord.getUpdating()))) {
|
.flatMap(this::toDomainModel);
|
||||||
if (!updateId.equals(examRecord.getLastupdate())) {
|
|
||||||
throw new IllegalStateException("Exam is currently locked: " + examRecord.getExternalId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ExamRecord newExamRecord = new ExamRecord(
|
|
||||||
examRecord.getId(),
|
|
||||||
null, null, null, null, null, null, null, null,
|
|
||||||
status.name(),
|
|
||||||
null, null, null, null, null);
|
|
||||||
|
|
||||||
this.examRecordMapper.updateByPrimaryKeySelective(newExamRecord);
|
|
||||||
return this.examRecordMapper.selectByPrimaryKey(examId);
|
|
||||||
})
|
|
||||||
.flatMap(this::toDomainModel)
|
|
||||||
.onError(TransactionHandler::rollback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public Result<Exam> save(final Exam exam) {
|
public Result<Exam> save(final Exam exam) {
|
||||||
return Result.tryCatch(() -> {
|
return this.examRecordDAO
|
||||||
|
.save(exam)
|
||||||
// check internal persistent write-lock
|
.flatMap(this::toDomainModel);
|
||||||
final ExamRecord oldRecord = this.examRecordMapper.selectByPrimaryKey(exam.id);
|
|
||||||
if (BooleanUtils.isTrue(BooleanUtils.toBooleanObject(oldRecord.getUpdating()))) {
|
|
||||||
throw new IllegalStateException("Exam is currently locked: " + exam.externalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ExamRecord examRecord = new ExamRecord(
|
|
||||||
exam.id,
|
|
||||||
null, null, null, null,
|
|
||||||
(exam.supporter != null)
|
|
||||||
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
|
||||||
: null,
|
|
||||||
(exam.type != null)
|
|
||||||
? exam.type.name()
|
|
||||||
: null,
|
|
||||||
null,
|
|
||||||
exam.browserExamKeys,
|
|
||||||
(exam.status != null)
|
|
||||||
? exam.status.name()
|
|
||||||
: null,
|
|
||||||
1, // seb restriction (deprecated)
|
|
||||||
null, // updating
|
|
||||||
null, // lastUpdate
|
|
||||||
null, // active
|
|
||||||
exam.examTemplateId);
|
|
||||||
|
|
||||||
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
|
|
||||||
return this.examRecordMapper.selectByPrimaryKey(exam.id);
|
|
||||||
})
|
|
||||||
.flatMap(this::toDomainModel)
|
|
||||||
.onError(TransactionHandler::rollback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Exam> setSEBRestriction(final Long examId, final boolean sebRestriction) {
|
public Result<Exam> setSEBRestriction(final Long examId, final boolean sebRestriction) {
|
||||||
return Result.tryCatch(() -> {
|
return this.examRecordDAO
|
||||||
|
.setSEBRestriction(examId, sebRestriction)
|
||||||
final ExamRecord examRecord = new ExamRecord(
|
.flatMap(this::toDomainModel);
|
||||||
examId,
|
|
||||||
null, null, null, null, null, null, null, null, null,
|
|
||||||
BooleanUtils.toInteger(sebRestriction),
|
|
||||||
null, null, null, null);
|
|
||||||
|
|
||||||
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
|
|
||||||
return this.examRecordMapper.selectByPrimaryKey(examId);
|
|
||||||
})
|
|
||||||
.flatMap(this::toDomainModel)
|
|
||||||
.onError(TransactionHandler::rollback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Exam> createNew(final Exam exam) {
|
public Result<Exam> createNew(final Exam exam) {
|
||||||
return Result.tryCatch(() -> {
|
return this.examRecordDAO
|
||||||
|
.createNew(exam)
|
||||||
// fist check if it is not already existing
|
.flatMap(this::toDomainModel);
|
||||||
final List<ExamRecord> records = this.examRecordMapper.selectByExample()
|
|
||||||
.where(ExamRecordDynamicSqlSupport.lmsSetupId, isEqualTo(exam.lmsSetupId))
|
|
||||||
.and(ExamRecordDynamicSqlSupport.externalId, isEqualTo(exam.externalId))
|
|
||||||
.build()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
// if there is already an existing imported exam for the quiz, this is
|
|
||||||
// used to save instead of create a new one
|
|
||||||
if (records != null && records.size() > 0) {
|
|
||||||
final ExamRecord examRecord = records.get(0);
|
|
||||||
// if the same institution tries to import an exam that already exists throw an error
|
|
||||||
if (exam.institutionId.equals(examRecord.getInstitutionId())) {
|
|
||||||
throw new DuplicateResourceException(EntityType.EXAM, exam.externalId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ExamRecord examRecord = new ExamRecord(
|
|
||||||
null,
|
|
||||||
exam.institutionId,
|
|
||||||
exam.lmsSetupId,
|
|
||||||
exam.externalId,
|
|
||||||
exam.owner,
|
|
||||||
(exam.supporter != null)
|
|
||||||
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
|
||||||
: null,
|
|
||||||
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
|
|
||||||
null, // quitPassword
|
|
||||||
null, // browser keys
|
|
||||||
(exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(),
|
|
||||||
1, // seb restriction (deprecated)
|
|
||||||
BooleanUtils.toInteger(false),
|
|
||||||
null, // lastUpdate
|
|
||||||
BooleanUtils.toInteger(true),
|
|
||||||
exam.examTemplateId);
|
|
||||||
|
|
||||||
this.examRecordMapper.insert(examRecord);
|
|
||||||
return examRecord;
|
|
||||||
})
|
|
||||||
.flatMap(this::toDomainModel)
|
|
||||||
.onError(TransactionHandler::rollback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -443,57 +269,27 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Result<Collection<Exam>> allForRunCheck() {
|
public Result<Collection<Exam>> allForRunCheck() {
|
||||||
return Result.tryCatch(() -> {
|
return this.examRecordDAO
|
||||||
final List<ExamRecord> records = this.examRecordMapper.selectByExample()
|
.allForRunCheck()
|
||||||
.where(
|
.flatMap(this::toDomainModel);
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualTo(BooleanUtils.toInteger(true)))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.status,
|
|
||||||
isNotEqualTo(ExamStatus.RUNNING.name()))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.updating,
|
|
||||||
isEqualTo(BooleanUtils.toInteger(false)))
|
|
||||||
|
|
||||||
.build()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return new ArrayList<>(this.toDomainModel(records)
|
|
||||||
.getOrThrow());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<Exam>> allForEndCheck() {
|
public Result<Collection<Exam>> allForEndCheck() {
|
||||||
return Result.tryCatch(() -> {
|
return this.examRecordDAO
|
||||||
final List<ExamRecord> records = this.examRecordMapper.selectByExample()
|
.allForEndCheck()
|
||||||
.where(
|
.flatMap(this::toDomainModel);
|
||||||
ExamRecordDynamicSqlSupport.active,
|
|
||||||
isEqualTo(BooleanUtils.toInteger(true)))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.status,
|
|
||||||
isEqualTo(ExamStatus.RUNNING.name()))
|
|
||||||
.and(
|
|
||||||
ExamRecordDynamicSqlSupport.updating,
|
|
||||||
isEqualTo(BooleanUtils.toInteger(false)))
|
|
||||||
|
|
||||||
.build()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return new ArrayList<>(this.toDomainModel(records)
|
|
||||||
.getOrThrow());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
public Result<Exam> placeLock(final Long examId, final String updateId) {
|
public Result<Long> placeLock(final Long examId, final String updateId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final ExamRecord examRec = this.recordById(examId)
|
final ExamRecord examRec = this.examRecordDAO
|
||||||
|
.recordById(examId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// consistency check
|
// consistency check
|
||||||
|
@ -510,19 +306,18 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
null, null);
|
null, null);
|
||||||
|
|
||||||
this.examRecordMapper.updateByPrimaryKeySelective(newRecord);
|
this.examRecordMapper.updateByPrimaryKeySelective(newRecord);
|
||||||
return newRecord;
|
return examId;
|
||||||
})
|
})
|
||||||
.flatMap(rec -> this.recordById(rec.getId()))
|
|
||||||
.flatMap(this::toDomainModel)
|
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
public Result<Exam> releaseLock(final Long examId, final String updateId) {
|
public Result<Long> releaseLock(final Long examId, final String updateId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final ExamRecord examRec = this.recordById(examId)
|
final ExamRecord examRec = this.examRecordDAO
|
||||||
|
.recordById(examId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// consistency check
|
// consistency check
|
||||||
|
@ -541,10 +336,8 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
null, null);
|
null, null);
|
||||||
|
|
||||||
this.examRecordMapper.updateByPrimaryKeySelective(newRecord);
|
this.examRecordMapper.updateByPrimaryKeySelective(newRecord);
|
||||||
return newRecord;
|
return examId;
|
||||||
})
|
})
|
||||||
.flatMap(rec -> this.recordById(rec.getId()))
|
|
||||||
.flatMap(this::toDomainModel)
|
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,7 +360,6 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
return examRecord.getId();
|
return examRecord.getId();
|
||||||
})
|
})
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -598,7 +390,8 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Boolean> isLocked(final Long examId) {
|
public Result<Boolean> isLocked(final Long examId) {
|
||||||
return this.recordById(examId)
|
return this.examRecordDAO
|
||||||
|
.recordById(examId)
|
||||||
.map(rec -> BooleanUtils.toBooleanObject(rec.getUpdating()));
|
.map(rec -> BooleanUtils.toBooleanObject(rec.getUpdating()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,7 +431,8 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Boolean> upToDate(final Long examId, final String updateId) {
|
public Result<Boolean> upToDate(final Long examId, final String updateId) {
|
||||||
return this.recordById(examId)
|
return this.examRecordDAO
|
||||||
|
.recordById(examId)
|
||||||
.map(rec -> {
|
.map(rec -> {
|
||||||
if (updateId == null) {
|
if (updateId == null) {
|
||||||
return rec.getLastupdate() == null;
|
return rec.getLastupdate() == null;
|
||||||
|
@ -708,10 +502,9 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<Exam>> allOf(final Set<Long> pks) {
|
public Result<Collection<Exam>> allOf(final Set<Long> pks) {
|
||||||
return Result.tryCatch(() -> this.examRecordMapper.selectByExample()
|
return this.examRecordDAO
|
||||||
.where(ExamRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
.allOf(pks)
|
||||||
.build()
|
.flatMap(this::toDomainModel);
|
||||||
.execute()).flatMap(this::toDomainModel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -805,18 +598,6 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
userKey));
|
userKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<ExamRecord> recordById(final Long id) {
|
|
||||||
return Result.tryCatch(() -> {
|
|
||||||
final ExamRecord record = this.examRecordMapper.selectByPrimaryKey(id);
|
|
||||||
if (record == null) {
|
|
||||||
throw new ResourceNotFoundException(
|
|
||||||
EntityType.EXAM,
|
|
||||||
String.valueOf(id));
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<EntityDependency> toDependencies(
|
private Collection<EntityDependency> toDependencies(
|
||||||
final List<ExamRecord> records,
|
final List<ExamRecord> records,
|
||||||
final EntityKey parent) {
|
final EntityKey parent) {
|
||||||
|
|
|
@ -0,0 +1,334 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.dao.impl;
|
||||||
|
|
||||||
|
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.mybatis.dynamic.sql.SqlBuilder;
|
||||||
|
import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter;
|
||||||
|
import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.InstitutionRecordDynamicSqlSupport;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DuplicateResourceException;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@WebServiceProfile
|
||||||
|
public class ExamRecordDAO {
|
||||||
|
|
||||||
|
private final ExamRecordMapper examRecordMapper;
|
||||||
|
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
|
||||||
|
|
||||||
|
public ExamRecordDAO(
|
||||||
|
final ExamRecordMapper examRecordMapper,
|
||||||
|
final ClientConnectionRecordMapper clientConnectionRecordMapper) {
|
||||||
|
|
||||||
|
this.examRecordMapper = examRecordMapper;
|
||||||
|
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<ExamRecord> recordById(final Long id) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final ExamRecord record = this.examRecordMapper.selectByPrimaryKey(id);
|
||||||
|
if (record == null) {
|
||||||
|
throw new ResourceNotFoundException(
|
||||||
|
EntityType.EXAM,
|
||||||
|
String.valueOf(id));
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<ExamRecord> recordByClientConnection(final Long connectionId) {
|
||||||
|
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||||
|
.selectByPrimaryKey(connectionId))
|
||||||
|
.flatMap(ccRecord -> recordById(ccRecord.getExamId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<ExamRecord>> all(final Long institutionId, final Boolean active) {
|
||||||
|
return Result.tryCatch(() -> (active != null)
|
||||||
|
? this.examRecordMapper.selectByExample()
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.institutionId,
|
||||||
|
isEqualToWhenPresent(institutionId))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualToWhenPresent(BooleanUtils.toIntegerObject(active)))
|
||||||
|
.build()
|
||||||
|
.execute()
|
||||||
|
: this.examRecordMapper.selectByExample()
|
||||||
|
.build()
|
||||||
|
.execute());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<Long>> allInstitutionIdsByQuizId(final String quizId) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
return this.examRecordMapper.selectByExample()
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.externalId,
|
||||||
|
isEqualToWhenPresent(quizId))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualToWhenPresent(BooleanUtils.toIntegerObject(true)))
|
||||||
|
.build()
|
||||||
|
.execute()
|
||||||
|
.stream()
|
||||||
|
.map(rec -> rec.getInstitutionId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<ExamRecord>> allMatching(final FilterMap filterMap) {
|
||||||
|
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
// If we have a sort on institution name, join the institution table
|
||||||
|
// If we have a sort on lms setup name, join lms setup table
|
||||||
|
final QueryExpressionDSL<MyBatis3SelectModelAdapter<List<ExamRecord>>>.QueryExpressionWhereBuilder whereClause =
|
||||||
|
(filterMap.getBoolean(FilterMap.ATTR_ADD_INSITUTION_JOIN))
|
||||||
|
? this.examRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.join(InstitutionRecordDynamicSqlSupport.institutionRecord)
|
||||||
|
.on(
|
||||||
|
InstitutionRecordDynamicSqlSupport.id,
|
||||||
|
SqlBuilder.equalTo(ExamRecordDynamicSqlSupport.institutionId))
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualToWhenPresent(filterMap.getActiveAsInt()))
|
||||||
|
: (filterMap.getBoolean(FilterMap.ATTR_ADD_LMS_SETUP_JOIN))
|
||||||
|
? this.examRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.join(LmsSetupRecordDynamicSqlSupport.lmsSetupRecord)
|
||||||
|
.on(
|
||||||
|
LmsSetupRecordDynamicSqlSupport.id,
|
||||||
|
SqlBuilder.equalTo(ExamRecordDynamicSqlSupport.lmsSetupId))
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualToWhenPresent(filterMap.getActiveAsInt()))
|
||||||
|
: this.examRecordMapper.selectByExample()
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualToWhenPresent(filterMap.getActiveAsInt()));
|
||||||
|
|
||||||
|
final List<ExamRecord> records = whereClause
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.institutionId,
|
||||||
|
isEqualToWhenPresent(filterMap.getInstitutionId()))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.lmsSetupId,
|
||||||
|
isEqualToWhenPresent(filterMap.getLmsSetupId()))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.type,
|
||||||
|
isEqualToWhenPresent(filterMap.getExamType()))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.status,
|
||||||
|
isEqualToWhenPresent(filterMap.getExamStatus()))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return records;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Result<ExamRecord> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
||||||
|
return recordById(examId)
|
||||||
|
.map(examRecord -> {
|
||||||
|
if (BooleanUtils.isTrue(BooleanUtils.toBooleanObject(examRecord.getUpdating()))) {
|
||||||
|
if (!updateId.equals(examRecord.getLastupdate())) {
|
||||||
|
throw new IllegalStateException("Exam is currently locked: " + examRecord.getExternalId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ExamRecord newExamRecord = new ExamRecord(
|
||||||
|
examRecord.getId(),
|
||||||
|
null, null, null, null, null, null, null, null,
|
||||||
|
status.name(),
|
||||||
|
null, null, null, null, null);
|
||||||
|
|
||||||
|
this.examRecordMapper.updateByPrimaryKeySelective(newExamRecord);
|
||||||
|
return this.examRecordMapper.selectByPrimaryKey(examId);
|
||||||
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Result<ExamRecord> save(final Exam exam) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
// check internal persistent write-lock
|
||||||
|
final ExamRecord oldRecord = this.examRecordMapper.selectByPrimaryKey(exam.id);
|
||||||
|
if (BooleanUtils.isTrue(BooleanUtils.toBooleanObject(oldRecord.getUpdating()))) {
|
||||||
|
throw new IllegalStateException("Exam is currently locked: " + exam.externalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ExamRecord examRecord = new ExamRecord(
|
||||||
|
exam.id,
|
||||||
|
null, null, null, null,
|
||||||
|
(exam.supporter != null)
|
||||||
|
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
||||||
|
: null,
|
||||||
|
(exam.type != null)
|
||||||
|
? exam.type.name()
|
||||||
|
: null,
|
||||||
|
null,
|
||||||
|
exam.browserExamKeys,
|
||||||
|
(exam.status != null)
|
||||||
|
? exam.status.name()
|
||||||
|
: null,
|
||||||
|
1, // seb restriction (deprecated)
|
||||||
|
null, // updating
|
||||||
|
null, // lastUpdate
|
||||||
|
null, // active
|
||||||
|
exam.examTemplateId);
|
||||||
|
|
||||||
|
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
|
||||||
|
return this.examRecordMapper.selectByPrimaryKey(exam.id);
|
||||||
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Result<ExamRecord> setSEBRestriction(final Long examId, final boolean sebRestriction) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
final ExamRecord examRecord = new ExamRecord(
|
||||||
|
examId,
|
||||||
|
null, null, null, null, null, null, null, null, null,
|
||||||
|
BooleanUtils.toInteger(sebRestriction),
|
||||||
|
null, null, null, null);
|
||||||
|
|
||||||
|
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
|
||||||
|
return this.examRecordMapper.selectByPrimaryKey(examId);
|
||||||
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Result<ExamRecord> createNew(final Exam exam) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
// fist check if it is not already existing
|
||||||
|
final List<ExamRecord> records = this.examRecordMapper.selectByExample()
|
||||||
|
.where(ExamRecordDynamicSqlSupport.lmsSetupId, isEqualTo(exam.lmsSetupId))
|
||||||
|
.and(ExamRecordDynamicSqlSupport.externalId, isEqualTo(exam.externalId))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// if there is already an existing imported exam for the quiz, this is
|
||||||
|
// used to save instead of create a new one
|
||||||
|
if (records != null && records.size() > 0) {
|
||||||
|
final ExamRecord examRecord = records.get(0);
|
||||||
|
// if the same institution tries to import an exam that already exists throw an error
|
||||||
|
if (exam.institutionId.equals(examRecord.getInstitutionId())) {
|
||||||
|
throw new DuplicateResourceException(EntityType.EXAM, exam.externalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ExamRecord examRecord = new ExamRecord(
|
||||||
|
null,
|
||||||
|
exam.institutionId,
|
||||||
|
exam.lmsSetupId,
|
||||||
|
exam.externalId,
|
||||||
|
exam.owner,
|
||||||
|
(exam.supporter != null)
|
||||||
|
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
||||||
|
: null,
|
||||||
|
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
|
||||||
|
null, // quitPassword
|
||||||
|
null, // browser keys
|
||||||
|
(exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(),
|
||||||
|
1, // seb restriction (deprecated)
|
||||||
|
BooleanUtils.toInteger(false),
|
||||||
|
null, // lastUpdate
|
||||||
|
BooleanUtils.toInteger(true),
|
||||||
|
exam.examTemplateId);
|
||||||
|
|
||||||
|
this.examRecordMapper.insert(examRecord);
|
||||||
|
return examRecord;
|
||||||
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<ExamRecord>> allForRunCheck() {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
return this.examRecordMapper.selectByExample()
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualTo(BooleanUtils.toInteger(true)))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.status,
|
||||||
|
isNotEqualTo(ExamStatus.RUNNING.name()))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.updating,
|
||||||
|
isEqualTo(BooleanUtils.toInteger(false)))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<ExamRecord>> allForEndCheck() {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
return this.examRecordMapper.selectByExample()
|
||||||
|
.where(
|
||||||
|
ExamRecordDynamicSqlSupport.active,
|
||||||
|
isEqualTo(BooleanUtils.toInteger(true)))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.status,
|
||||||
|
isEqualTo(ExamStatus.RUNNING.name()))
|
||||||
|
.and(
|
||||||
|
ExamRecordDynamicSqlSupport.updating,
|
||||||
|
isEqualTo(BooleanUtils.toInteger(false)))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<ExamRecord>> allOf(final Set<Long> pks) {
|
||||||
|
return Result.tryCatch(() -> this.examRecordMapper.selectByExample()
|
||||||
|
.where(ExamRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||||
|
.build()
|
||||||
|
.execute());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -315,7 +315,9 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||||
|
|
||||||
private Collection<Result<Exam>> lockForUpdate(final Collection<Long> examIds, final String update) {
|
private Collection<Result<Exam>> lockForUpdate(final Collection<Long> examIds, final String update) {
|
||||||
return examIds.stream()
|
return examIds.stream()
|
||||||
.map(id -> this.examDAO.placeLock(id, update))
|
.map(id -> this.examDAO.byPK(id)
|
||||||
|
.map(exam -> this.examDAO.placeLock(exam, update))
|
||||||
|
.getOrThrow())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class ExamSessionCacheService {
|
||||||
cacheNames = CACHE_NAME_RUNNING_EXAM,
|
cacheNames = CACHE_NAME_RUNNING_EXAM,
|
||||||
key = "#examId",
|
key = "#examId",
|
||||||
unless = "#result == null")
|
unless = "#result == null")
|
||||||
public Exam getRunningExam(final Long examId) {
|
public synchronized Exam getRunningExam(final Long examId) {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Verify running exam for id: {}", examId);
|
log.debug("Verify running exam for id: {}", examId);
|
||||||
|
|
|
@ -193,7 +193,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> getRunningExam(final Long examId) {
|
public synchronized Result<Exam> getRunningExam(final Long examId) {
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("Running exam request for exam {}", examId);
|
log.trace("Running exam request for exam {}", examId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ class ExamUpdateHandler {
|
||||||
ExamStatus.RUNNING,
|
ExamStatus.RUNNING,
|
||||||
updateId))
|
updateId))
|
||||||
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
||||||
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
|
.flatMap(e -> this.examDAO.releaseLock(e, updateId))
|
||||||
.onError(error -> this.examDAO.forceUnlock(exam.id)
|
.onError(error -> this.examDAO.forceUnlock(exam.id)
|
||||||
.onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)));
|
.onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)));
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ class ExamUpdateHandler {
|
||||||
ExamStatus.FINISHED,
|
ExamStatus.FINISHED,
|
||||||
updateId))
|
updateId))
|
||||||
.flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
|
.flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
|
||||||
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
|
.flatMap(e -> this.examDAO.releaseLock(e, updateId))
|
||||||
.onError(error -> this.examDAO.forceUnlock(exam.id));
|
.onError(error -> this.examDAO.forceUnlock(exam.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ public class CachableJdbcTokenStore implements TokenStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public OAuth2AccessToken getAccessToken(final OAuth2Authentication authentication) {
|
public OAuth2AccessToken getAccessToken(final OAuth2Authentication authentication) {
|
||||||
return this.jdbcTokenStore.getAccessToken(authentication);
|
return this.jdbcTokenStore.getAccessToken(authentication);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ sebserver.gui.webservice.address=localhost
|
||||||
sebserver.gui.webservice.port=8080
|
sebserver.gui.webservice.port=8080
|
||||||
sebserver.gui.webservice.apipath=/admin-api/v1
|
sebserver.gui.webservice.apipath=/admin-api/v1
|
||||||
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
|
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
|
||||||
sebserver.gui.webservice.poll-interval=1000
|
#sebserver.gui.webservice.poll-interval=1000
|
||||||
|
|
||||||
sebserver.gui.theme=css/sebserver.css
|
sebserver.gui.theme=css/sebserver.css
|
||||||
sebserver.gui.list.page.size=15
|
sebserver.gui.list.page.size=15
|
||||||
|
|
|
@ -13,7 +13,7 @@ spring.datasource.hikari.initializationFailTimeout=30000
|
||||||
spring.datasource.hikari.connectionTimeout=30000
|
spring.datasource.hikari.connectionTimeout=30000
|
||||||
spring.datasource.hikari.idleTimeout=600000
|
spring.datasource.hikari.idleTimeout=600000
|
||||||
spring.datasource.hikari.maxLifetime=1800000
|
spring.datasource.hikari.maxLifetime=1800000
|
||||||
spring.datasource.hikari.maximumPoolSize=500
|
spring.datasource.hikari.maximumPoolSize=5
|
||||||
|
|
||||||
sebserver.http.client.connect-timeout=15000
|
sebserver.http.client.connect-timeout=15000
|
||||||
sebserver.http.client.connection-request-timeout=10000
|
sebserver.http.client.connection-request-timeout=10000
|
||||||
|
@ -23,7 +23,7 @@ sebserver.webservice.clean-db-on-startup=false
|
||||||
|
|
||||||
# webservice configuration
|
# webservice configuration
|
||||||
sebserver.init.adminaccount.gen-on-init=false
|
sebserver.init.adminaccount.gen-on-init=false
|
||||||
sebserver.webservice.distributed=false
|
sebserver.webservice.distributed=true
|
||||||
sebserver.webservice.master.delay.threshold=10000
|
sebserver.webservice.master.delay.threshold=10000
|
||||||
sebserver.webservice.http.external.scheme=http
|
sebserver.webservice.http.external.scheme=http
|
||||||
sebserver.webservice.http.external.servername=localhost
|
sebserver.webservice.http.external.servername=localhost
|
||||||
|
|
|
@ -25,14 +25,13 @@ sebserver.gui.entrypoint=/gui
|
||||||
|
|
||||||
sebserver.gui.webservice.apipath=${sebserver.webservice.api.admin.endpoint}
|
sebserver.gui.webservice.apipath=${sebserver.webservice.api.admin.endpoint}
|
||||||
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
|
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
|
||||||
sebserver.gui.webservice.poll-interval=3000
|
sebserver.gui.webservice.poll-interval=2000
|
||||||
sebserver.gui.webservice.mock-lms-enabled=true
|
sebserver.gui.webservice.mock-lms-enabled=true
|
||||||
sebserver.gui.webservice.edx-lms-enabled=true
|
sebserver.gui.webservice.edx-lms-enabled=true
|
||||||
sebserver.gui.webservice.moodle-lms-enabled=true
|
sebserver.gui.webservice.moodle-lms-enabled=true
|
||||||
sebserver.gui.seb.client.config.download.filename=SEBServerSettings.seb
|
sebserver.gui.seb.client.config.download.filename=SEBServerSettings.seb
|
||||||
sebserver.gui.seb.exam.config.download.filename=SEBExamSettings.seb
|
sebserver.gui.seb.exam.config.download.filename=SEBExamSettings.seb
|
||||||
sebserver.gui.proctoring.zoom.websdk.version=1.9.8
|
sebserver.gui.proctoring.zoom.websdk.version=1.9.8
|
||||||
|
|
||||||
sebserver.gui.filter.date.from.years=2
|
sebserver.gui.filter.date.from.years=2
|
||||||
|
|
||||||
# remote proctoring
|
# remote proctoring
|
||||||
|
|
|
@ -31,7 +31,7 @@ spring.datasource.hikari.initializationFailTimeout=3000
|
||||||
spring.datasource.hikari.connectionTimeout=30000
|
spring.datasource.hikari.connectionTimeout=30000
|
||||||
spring.datasource.hikari.idleTimeout=600000
|
spring.datasource.hikari.idleTimeout=600000
|
||||||
spring.datasource.hikari.maxLifetime=1800000
|
spring.datasource.hikari.maxLifetime=1800000
|
||||||
spring.datasource.hikari.maximumPoolSize=500
|
spring.datasource.hikari.maximumPoolSize=100
|
||||||
|
|
||||||
### webservice security
|
### webservice security
|
||||||
spring.datasource.password=${sebserver.mariadb.password}
|
spring.datasource.password=${sebserver.mariadb.password}
|
||||||
|
|
|
@ -6,7 +6,7 @@ sebserver.overall.version=SEB Server Version : {0}
|
||||||
sebserver.overall.about=About
|
sebserver.overall.about=About
|
||||||
sebserver.overall.about.markup=<span style='font-family: Arial, Helvetica,sans-serif;font-size: 25px;font-weight: normal;font-style: normal;color: rgb(31, 64, 122);'>SEB Server About</span><br/><br/><span style='font-family: Arial, Helvetica,sans-serif;font-size: 18px;font-weight: bold;font-style: normal;'>1. Installation.</span><br/><br/><span style='font-family: Arial, Helvetica,sans-serif;font-size: 14px;font-weight: normal;font-style: normal;'>This is the SEB Server development setup.</span>
|
sebserver.overall.about.markup=<span style='font-family: Arial, Helvetica,sans-serif;font-size: 25px;font-weight: normal;font-style: normal;color: rgb(31, 64, 122);'>SEB Server About</span><br/><br/><span style='font-family: Arial, Helvetica,sans-serif;font-size: 18px;font-weight: bold;font-style: normal;'>1. Installation.</span><br/><br/><span style='font-family: Arial, Helvetica,sans-serif;font-size: 14px;font-weight: normal;font-style: normal;'>This is the SEB Server development setup.</span>
|
||||||
sebserver.overall.help=Documentation
|
sebserver.overall.help=Documentation
|
||||||
sebserver.overall.help.link=https://www.safeexambrowser.org/news_en.html
|
sebserver.overall.help.link=https://seb-server.readthedocs.io/en/latest/index.html
|
||||||
|
|
||||||
sebserver.overall.message.leave.without.save=You have unsaved changes!<br/>Are you sure you want to leave the page? The changes will be lost.
|
sebserver.overall.message.leave.without.save=You have unsaved changes!<br/>Are you sure you want to leave the page? The changes will be lost.
|
||||||
sebserver.overall.upload=Please select a file
|
sebserver.overall.upload=Please select a file
|
||||||
|
|
Loading…
Reference in a new issue