diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java index fbcd0840..07aa9998 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java @@ -98,16 +98,26 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup * * @param examId the exam identifier * @param updateId an update identifier - * @return Result refer to the specified exam or to an error if happened */ - Result placeLock(Long examId, String updateId); + * @return Result refer to the specified exam identifier or to an error if happened */ + Result placeLock(Long examId, String updateId); + + default Result 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. * The exam will be marked as not locked on the persistence level. * * @param examId the exam identifier * @param updateId an update identifier - * @return Result refer to the specified exam or to an error if happened */ - Result releaseLock(Long examId, String updateId); + * @return Result refer to the specified exam identifier or to an error if happened */ + Result releaseLock(Long examId, String updateId); + + default Result 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. * The exam will be marked as not locked on the persistence level even if it is currently locked by another process diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index a258846e..ce0d8420 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; import static org.mybatis.dynamic.sql.SqlBuilder.*; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -28,8 +27,6 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; 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.annotation.Lazy; import org.springframework.stereotype.Component; @@ -52,18 +49,13 @@ import ch.ethz.seb.sebserver.gbl.util.Result; 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.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.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.ExamRecord; 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.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.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; @@ -77,22 +69,22 @@ public class ExamDAOImpl implements ExamDAO { public static final String FAILED_TO_LOAD_QUIZ_DATA_MARK = "[FAILED TO LOAD DATA FROM LMS]"; private final ExamRecordMapper examRecordMapper; - private final ClientConnectionRecordMapper clientConnectionRecordMapper; - private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper; + private final ExamRecordDAO examRecordDAO; private final ApplicationEventPublisher applicationEventPublisher; + private final AdditionalAttributeRecordMapper additionalAttributeRecordMapper; private final LmsAPIService lmsAPIService; public ExamDAOImpl( final ExamRecordMapper examRecordMapper, - final ClientConnectionRecordMapper clientConnectionRecordMapper, - final AdditionalAttributeRecordMapper additionalAttributeRecordMapper, + final ExamRecordDAO examRecordDAO, final ApplicationEventPublisher applicationEventPublisher, + final AdditionalAttributeRecordMapper additionalAttributeRecordMapper, final LmsAPIService lmsAPIService) { this.examRecordMapper = examRecordMapper; - this.clientConnectionRecordMapper = clientConnectionRecordMapper; - this.additionalAttributeRecordMapper = additionalAttributeRecordMapper; + this.examRecordDAO = examRecordDAO; this.applicationEventPublisher = applicationEventPublisher; + this.additionalAttributeRecordMapper = additionalAttributeRecordMapper; this.lmsAPIService = lmsAPIService; } @@ -102,63 +94,35 @@ public class ExamDAOImpl implements ExamDAO { } @Override - @Transactional(readOnly = true) public Result byPK(final Long id) { - return recordById(id) + return this.examRecordDAO + .recordById(id) .flatMap(this::toDomainModel); } @Override - @Transactional(readOnly = true) public Result examGrantEntityByPK(final Long id) { - return recordById(id) + return this.examRecordDAO.recordById(id) .map(record -> toDomainModel(record, null, null).getOrThrow()); } @Override - @Transactional(readOnly = true) public Result examGrantEntityByClientConnection(final Long connectionId) { - return Result.tryCatch(() -> this.clientConnectionRecordMapper - .selectByPrimaryKey(connectionId)) - .flatMap(ccRecord -> recordById(ccRecord.getExamId())) + return this.examRecordDAO + .recordByClientConnection(connectionId) .map(record -> toDomainModel(record, null, null).getOrThrow()); } @Override - @Transactional(readOnly = true) public Result> 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()) + return this.examRecordDAO + .all(institutionId, active) .flatMap(this::toDomainModel); } @Override public Result> 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()); - }); + return this.examRecordDAO.allInstitutionIdsByQuizId(quizId); } @Override @@ -189,51 +153,9 @@ public class ExamDAOImpl implements ExamDAO { return true; }; - // 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>>.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 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) + return this.examRecordDAO + .allMatching(filterMap) + .flatMap(this::toDomainModel) .getOrThrow() .stream() .filter(quizDataFilter.and(predicate)) @@ -243,128 +165,32 @@ public class ExamDAOImpl implements ExamDAO { @Override public Result 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); - - this.examRecordMapper.updateByPrimaryKeySelective(newExamRecord); - return this.examRecordMapper.selectByPrimaryKey(examId); - }) - .flatMap(this::toDomainModel) - .onError(TransactionHandler::rollback); + return this.examRecordDAO + .updateState(examId, status, updateId) + .flatMap(this::toDomainModel); } @Override - @Transactional public Result 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 - ); - - this.examRecordMapper.updateByPrimaryKeySelective(examRecord); - return this.examRecordMapper.selectByPrimaryKey(exam.id); - }) - .flatMap(this::toDomainModel) - .onError(TransactionHandler::rollback); + return this.examRecordDAO + .save(exam) + .flatMap(this::toDomainModel); } @Override @Transactional public Result 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); - - this.examRecordMapper.updateByPrimaryKeySelective(examRecord); - return this.examRecordMapper.selectByPrimaryKey(examId); - }) - .flatMap(this::toDomainModel) - .onError(TransactionHandler::rollback); + return this.examRecordDAO + .setSEBRestriction(examId, sebRestriction) + .flatMap(this::toDomainModel); } @Override @Transactional public Result createNew(final Exam exam) { - return Result.tryCatch(() -> { - - // fist check if it is not already existing - final List 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)); - - this.examRecordMapper.insert(examRecord); - return examRecord; - }) - .flatMap(this::toDomainModel) - .onError(TransactionHandler::rollback); + return this.examRecordDAO + .createNew(exam) + .flatMap(this::toDomainModel); } @Override @@ -442,57 +268,27 @@ public class ExamDAOImpl implements ExamDAO { } @Override - @Transactional(readOnly = true) public Result> allForRunCheck() { - return Result.tryCatch(() -> { - final List records = 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(); - - return new ArrayList<>(this.toDomainModel(records) - .getOrThrow()); - }); + return this.examRecordDAO + .allForRunCheck() + .flatMap(this::toDomainModel); } @Override @Transactional(readOnly = true) public Result> allForEndCheck() { - return Result.tryCatch(() -> { - final List records = 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(); - - return new ArrayList<>(this.toDomainModel(records) - .getOrThrow()); - }); + return this.examRecordDAO + .allForEndCheck() + .flatMap(this::toDomainModel); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) - public Result placeLock(final Long examId, final String updateId) { + public Result placeLock(final Long examId, final String updateId) { return Result.tryCatch(() -> { - final ExamRecord examRec = this.recordById(examId) + final ExamRecord examRec = this.examRecordDAO + .recordById(examId) .getOrThrow(); // consistency check @@ -509,19 +305,18 @@ public class ExamDAOImpl implements ExamDAO { null); this.examRecordMapper.updateByPrimaryKeySelective(newRecord); - return newRecord; + return examId; }) - .flatMap(rec -> this.recordById(rec.getId())) - .flatMap(this::toDomainModel) .onError(TransactionHandler::rollback); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) - public Result releaseLock(final Long examId, final String updateId) { + public Result releaseLock(final Long examId, final String updateId) { return Result.tryCatch(() -> { - final ExamRecord examRec = this.recordById(examId) + final ExamRecord examRec = this.examRecordDAO + .recordById(examId) .getOrThrow(); // consistency check @@ -540,10 +335,8 @@ public class ExamDAOImpl implements ExamDAO { null); this.examRecordMapper.updateByPrimaryKeySelective(newRecord); - return newRecord; + return examId; }) - .flatMap(rec -> this.recordById(rec.getId())) - .flatMap(this::toDomainModel) .onError(TransactionHandler::rollback); } @@ -566,7 +359,6 @@ public class ExamDAOImpl implements ExamDAO { return examRecord.getId(); }) .onError(TransactionHandler::rollback); - } @Override @@ -597,7 +389,8 @@ public class ExamDAOImpl implements ExamDAO { @Override @Transactional(readOnly = true) public Result isLocked(final Long examId) { - return this.recordById(examId) + return this.examRecordDAO + .recordById(examId) .map(rec -> BooleanUtils.toBooleanObject(rec.getUpdating())); } @@ -637,7 +430,8 @@ public class ExamDAOImpl implements ExamDAO { @Override @Transactional(readOnly = true) public Result upToDate(final Long examId, final String updateId) { - return this.recordById(examId) + return this.examRecordDAO + .recordById(examId) .map(rec -> { if (updateId == null) { return rec.getLastupdate() == null; @@ -707,10 +501,9 @@ public class ExamDAOImpl implements ExamDAO { @Override @Transactional(readOnly = true) public Result> allOf(final Set pks) { - return Result.tryCatch(() -> this.examRecordMapper.selectByExample() - .where(ExamRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) - .build() - .execute()).flatMap(this::toDomainModel); + return this.examRecordDAO + .allOf(pks) + .flatMap(this::toDomainModel); } @Override @@ -757,18 +550,6 @@ public class ExamDAOImpl implements ExamDAO { userKey)); } - private Result 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 toDependencies( final List records, final EntityKey parent) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java new file mode 100644 index 00000000..8b614b83 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamRecordDAO.java @@ -0,0 +1,333 @@ +/* + * 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 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 recordByClientConnection(final Long connectionId) { + return Result.tryCatch(() -> this.clientConnectionRecordMapper + .selectByPrimaryKey(connectionId)) + .flatMap(ccRecord -> recordById(ccRecord.getExamId())); + } + + @Transactional(readOnly = true) + public Result> 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> 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> 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>>.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 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 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); + + this.examRecordMapper.updateByPrimaryKeySelective(newExamRecord); + return this.examRecordMapper.selectByPrimaryKey(examId); + }) + .onError(TransactionHandler::rollback); + } + + @Transactional + public Result 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 + ); + + this.examRecordMapper.updateByPrimaryKeySelective(examRecord); + return this.examRecordMapper.selectByPrimaryKey(exam.id); + }) + .onError(TransactionHandler::rollback); + } + + @Transactional + public Result 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); + + this.examRecordMapper.updateByPrimaryKeySelective(examRecord); + return this.examRecordMapper.selectByPrimaryKey(examId); + }) + .onError(TransactionHandler::rollback); + } + + @Transactional + public Result createNew(final Exam exam) { + return Result.tryCatch(() -> { + + // fist check if it is not already existing + final List 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)); + + this.examRecordMapper.insert(examRecord); + return examRecord; + }) + .onError(TransactionHandler::rollback); + } + + @Transactional(readOnly = true) + public Result> 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> 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> allOf(final Set pks) { + return Result.tryCatch(() -> this.examRecordMapper.selectByExample() + .where(ExamRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) + .build() + .execute()); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java index 1947a493..dbe1f565 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java @@ -315,7 +315,9 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { private Collection> lockForUpdate(final Collection examIds, final String update) { 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()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java index 95ac0472..3defad8d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java @@ -84,7 +84,7 @@ class ExamUpdateHandler { ExamStatus.RUNNING, updateId)) .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(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id))); } @@ -101,7 +101,7 @@ class ExamUpdateHandler { ExamStatus.FINISHED, updateId)) .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)); }