diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java index a60278ae..e9ae8497 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.function.BooleanSupplier; import java.util.function.Function; +import java.util.stream.Collectors; import org.eclipse.swt.widgets.Composite; import org.joda.time.DateTime; @@ -100,6 +101,8 @@ public class QuizLookupList implements TemplateComposer { new LocTextKey("sebserver.quizdiscovery.quiz.import.out.dated"); private final static LocTextKey TEXT_KEY_CONFIRM_EXISTING = new LocTextKey("sebserver.quizdiscovery.quiz.import.existing.confirm"); + private final static LocTextKey TEXT_KEY_EXISTING = + new LocTextKey("sebserver.quizdiscovery.quiz.import.existing"); private final static String TEXT_KEY_ADDITIONAL_ATTR_PREFIX = "sebserver.quizdiscovery.quiz.details.additional."; @@ -147,6 +150,7 @@ public class QuizLookupList implements TemplateComposer { final CurrentUser currentUser = this.resourceService.getCurrentUser(); final RestService restService = this.resourceService.getRestService(); final I18nSupport i18nSupport = this.resourceService.getI18nSupport(); + final Long institutionId = currentUser.get().institutionId; // content page layout with title final Composite content = this.widgetFactory.defaultPageLayout( @@ -242,10 +246,10 @@ public class QuizLookupList implements TemplateComposer { .publish(false) .newAction(ActionDefinition.QUIZ_DISCOVERY_EXAM_IMPORT) - .withConfirm(importQuizConfirm(table, restService)) + .withConfirm(importQuizConfirm(institutionId, table, restService)) .withSelect( table.getGrantedSelection(currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION), - action -> this.importQuizData(action, table), + action -> this.importQuizData(institutionId, action, table, restService), EMPTY_SELECTION_TEXT) .publishIf(() -> examGrant.im(), false); } @@ -257,6 +261,7 @@ public class QuizLookupList implements TemplateComposer { } private Function importQuizConfirm( + final Long institutionId, final EntityTable table, final RestService restService) { @@ -264,12 +269,15 @@ public class QuizLookupList implements TemplateComposer { action.getSingleSelection(); final QuizData selectedROWData = table.getSingleSelectedROWData(); - final Collection existingImports = restService.getBuilder(CheckExamImported.class) + final Collection existingImports = restService.getBuilder(CheckExamImported.class) .withURIVariable(API.PARAM_MODEL_ID, selectedROWData.id) .call() - .getOrThrow(); + .getOrThrow() + .stream() + .map(key -> Long.valueOf(key.modelId)) + .collect(Collectors.toList()); - if (existingImports != null && !existingImports.isEmpty()) { + if (existingImports != null && !existingImports.contains(institutionId)) { return TEXT_KEY_CONFIRM_EXISTING; } else { return null; @@ -278,8 +286,10 @@ public class QuizLookupList implements TemplateComposer { } private PageAction importQuizData( + final Long institutionId, final PageAction action, - final EntityTable table) { + final EntityTable table, + final RestService restService) { action.getSingleSelection(); final QuizData selectedROWData = table.getSingleSelectedROWData(); @@ -291,6 +301,18 @@ public class QuizLookupList implements TemplateComposer { } } + final Collection existingImports = restService.getBuilder(CheckExamImported.class) + .withURIVariable(API.PARAM_MODEL_ID, selectedROWData.id) + .call() + .getOrThrow() + .stream() + .map(key -> Long.valueOf(key.modelId)) + .collect(Collectors.toList()); + + if (existingImports.contains(institutionId)) { + throw new PageMessageException(TEXT_KEY_EXISTING); + } + return action .withEntityKey(action.getSingleSelection()) .withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP)) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java index 5a4dbb67..ca6d7858 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java @@ -232,7 +232,7 @@ public class UserAccountList implements TemplateComposer { .newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY) .withExec(this.pageService.activationToggleActionFunction(table, EMPTY_SELECTION_TEXT_KEY)) .withConfirm(this.pageService.confirmDeactivation(table)) - .publishIf(() -> userGrant.m(), false); + .publishIf(() -> userGrant.im(), false); } private PageAction editAction(final PageAction pageAction) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/DuplicateResourceException.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/DuplicateResourceException.java new file mode 100644 index 00000000..41c35a47 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/DuplicateResourceException.java @@ -0,0 +1,31 @@ +/* + * 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; + +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; + +public class DuplicateResourceException extends RuntimeException { + + private static final long serialVersionUID = 2935680103812281185L; + + /** The entity key of the resource that was requested */ + public final EntityKey entityKey; + + public DuplicateResourceException(final EntityType entityType, final String modelId) { + super("Resource " + entityType + " with ID: " + modelId + " already exists"); + this.entityKey = new EntityKey(modelId, entityType); + } + + public DuplicateResourceException(final EntityType entityType, final String modelId, final Throwable cause) { + super("Resource " + entityType + " with ID: " + modelId + " not found", cause); + this.entityKey = new EntityKey(modelId, entityType); + } + +} 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 f651b48f..d5c8b1af 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 @@ -44,7 +44,11 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup * happened */ Result> allIdsOfInstitution(Long institutionId); - Result> allByQuizId(String quizId); + /** Get all institution ids for that a specified exam for given quiz id already exists + * + * @param quizId The quiz or external identifier of the exam (LMS) + * @return Result refer to a collection of primary keys of the institutions or to an error when happened */ + Result> allInstitutionIdsByQuizId(String quizId); /** Updates the exam status for specified exam * 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 1c698528..704ac367 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 @@ -59,6 +59,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDyn 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; @@ -139,7 +140,7 @@ public class ExamDAOImpl implements ExamDAO { } @Override - public Result> allByQuizId(final String quizId) { + public Result> allInstitutionIdsByQuizId(final String quizId) { return Result.tryCatch(() -> { return this.examRecordMapper.selectByExample() .where( @@ -151,7 +152,7 @@ public class ExamDAOImpl implements ExamDAO { .build() .execute() .stream() - .map(rec -> rec.getId()) + .map(rec -> rec.getInstitutionId()) .collect(Collectors.toList()); }); } @@ -331,23 +332,9 @@ public class ExamDAOImpl implements ExamDAO { // 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 - // open the existing. otherwise create new one if requested + // if the same institution tries to import an exam that already exists throw an error if (exam.institutionId.equals(examRecord.getInstitutionId())) { - final ExamRecord newRecord = new ExamRecord( - examRecord.getId(), - null, null, null, null, null, - (exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(), - null, // quitPassword - null, // browser keys - null, // status - null, // lmsSebRestriction (deprecated) - null, // updating - null, // lastUpdate - BooleanUtils.toIntegerObject(exam.active)); - - this.examRecordMapper.updateByPrimaryKeySelective(newRecord); - return this.examRecordMapper.selectByPrimaryKey(examRecord.getId()); + throw new DuplicateResourceException(EntityType.EXAM, exam.externalId); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index 44b552bf..8324b03e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -228,10 +228,10 @@ public class ExamAdministrationController extends EntityController { defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) { checkReadPrivilege(institutionId); - return this.examDAO.allByQuizId(modelId) + return this.examDAO.allInstitutionIdsByQuizId(modelId) .map(ids -> ids .stream() - .map(id -> new EntityKey(id, EntityType.EXAM)) + .map(id -> new EntityKey(id, EntityType.INSTITUTION)) .collect(Collectors.toList())) .getOrThrow(); } @@ -256,10 +256,6 @@ public class ExamAdministrationController extends EntityController { .checkExamConsistency(modelId) .getOrThrow(); - if (includeRestriction) { - // TODO include seb restriction check and status - } - return result; } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index f4131b0b..9b945c8d 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -380,6 +380,7 @@ sebserver.quizdiscovery.action.import=Import as Exam sebserver.quizdiscovery.quiz.import.out.dated=The Selected LMS exam is already finished and can't be imported sebserver.quizdiscovery.action.details=Show LMS Exam Details sebserver.quizdiscovery.quiz.import.existing.confirm=This course was already imported and importing it twice may lead to
unexpected behavior within automated SEB restriction on LMS.

Do you want to import this course as exam anyway? +sebserver.quizdiscovery.quiz.import.existing=This course was already imported as an exam.
You will find it in the Exam section. sebserver.quizdiscovery.quiz.details.title=LMS Exam Details sebserver.quizdiscovery.quiz.details.institution=Institution