From 78ffa7bc2c873aa56ee72bce90840f95e6002480 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 5 Nov 2019 09:15:28 +0100 Subject: [PATCH] seb restriction switch, minor fixes for export --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 1 + .../seb/sebserver/gui/content/ExamForm.java | 27 +++++- .../gui/content/SebExamConfigPropForm.java | 1 + .../gui/content/action/ActionDefinition.java | 10 ++ .../api/exam/SetExamSebRestriction.java | 42 +++++++++ .../webservice/servicelayer/dao/ExamDAO.java | 2 + .../servicelayer/dao/impl/ExamDAOImpl.java | 18 ++++ .../lms/impl/OpenEdxSebClientRestriction.java | 71 ++++++++++++--- .../impl/converter/TableConverter.java | 2 +- .../session/ExamSessionService.java | 2 +- .../impl/ExamConfigUpdateServiceImpl.java | 91 ++++++------------- .../session/impl/ExamSessionServiceImpl.java | 2 +- .../session/impl/ExamUpdateHandler.java | 23 +---- .../api/ExamAdministrationController.java | 72 ++++++++++++++- src/main/resources/data-demo.sql | 2 +- src/main/resources/data-prod.sql | 2 +- src/main/resources/messages.properties | 2 + src/test/resources/data-test-additional.sql | 2 +- 18 files changed, 263 insertions(+), 109 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SetExamSebRestriction.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 7394aeeb..80e9d5ec 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -108,6 +108,7 @@ public final class API { public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam"; public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config"; public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency"; + public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction"; public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index b5f376a2..c1c02c70 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -67,6 +67,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SetExamSebRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; @@ -308,7 +309,17 @@ public class ExamForm implements TemplateComposer { .withEntityKey(entityKey) .withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData)) .withExec(this.cancelModifyFunction()) - .publishIf(() -> !readonly); + .publishIf(() -> !readonly) + + .newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION) + .withEntityKey(entityKey) + .withExec(action -> setSebRestriction(action, true)) + .publishIf(() -> readonly && BooleanUtils.isFalse(exam.lmsSebRestriction)) + + .newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION) + .withEntityKey(entityKey) + .withExec(action -> setSebRestriction(action, false)) + .publishIf(() -> readonly && BooleanUtils.isTrue(exam.lmsSebRestriction)); // additional data in read-only view if (readonly && !importFromQuizData) { @@ -491,6 +502,20 @@ public class ExamForm implements TemplateComposer { message)); } + private PageAction setSebRestriction(final PageAction action, final boolean sebRestriction) { + this.restService.getBuilder(SetExamSebRestriction.class) + .withURIVariable( + API.PARAM_MODEL_ID, + action.pageContext().getAttribute(AttributeKeys.ENTITY_ID)) + .withQueryParam( + Domain.EXAM.ATTR_LMS_SEB_RESTRICTION, + sebRestriction ? Constants.TRUE_STRING : Constants.FALSE_STRING) + .call() + .onError(t -> action.pageContext().notifyError(t)); + + return action; + } + private PageAction viewExamConfigPageAction(final EntityTable table) { return this.pageService.pageActionBuilder(table.getPageContext() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java index aa749854..465dbbbe 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropForm.java @@ -237,6 +237,7 @@ public class SebExamConfigPropForm implements TemplateComposer { urlLauncher.openURL(downloadURL); return action; }) + .noEventPropagation() .publishIf(() -> modifyGrant && isReadonly) .newAction(ActionDefinition.SEB_EXAM_CONFIG_GET_CONFIG_KEY) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 31a6283e..c936ac4a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -226,6 +226,16 @@ public enum ActionDefinition { ImageIcon.TOGGLE_ON, PageStateDefinitionImpl.EXAM_VIEW, ActionCategory.FORM), + EXAM_ENABLE_SEB_RESTRICTION( + new LocTextKey("sebserver.exam.action.sebrestriction.enable"), + ImageIcon.TOGGLE_OFF, + PageStateDefinitionImpl.EXAM_VIEW, + ActionCategory.FORM), + EXAM_DISABLE_SEB_RESTRICTION( + new LocTextKey("sebserver.exam.action.deasebrestriction.disable"), + ImageIcon.TOGGLE_ON, + PageStateDefinitionImpl.EXAM_VIEW, + ActionCategory.FORM), EXAM_CONFIGURATION_NEW( new LocTextKey("sebserver.exam.configuration.action.list.new"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SetExamSebRestriction.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SetExamSebRestriction.java new file mode 100644 index 00000000..0c9ca8bc --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SetExamSebRestriction.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 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.gui.service.remote.webservice.api.exam; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class SetExamSebRestriction extends RestCall { + + public SetExamSebRestriction() { + super(new TypeKey<>( + CallType.SAVE, + EntityType.EXAM, + new TypeReference() { + }), + HttpMethod.PATCH, + MediaType.APPLICATION_JSON_UTF8, + API.EXAM_ADMINISTRATION_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT); + } + +} 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 c79d9e46..ba9f3269 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 @@ -54,4 +54,6 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup Result upToDate(Long examId, String lastUpdate); + Result setSebRestriction(Long id, boolean sebRestriction); + } 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 3e610153..3208cf51 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 @@ -190,6 +190,24 @@ public class ExamDAOImpl implements ExamDAO { .onError(TransactionHandler::rollback); } + @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); + } + @Override @Transactional public Result createNew(final Exam exam) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxSebClientRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxSebClientRestriction.java index fe153d5d..2213b7a8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxSebClientRestriction.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxSebClientRestriction.java @@ -8,43 +8,57 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SebRestrictionData; @JsonIgnoreProperties(ignoreUnknown = true) public class OpenEdxSebClientRestriction { - @JsonProperty("CONFIG_KEYS") + private static final String ATTR_USER_BANNING_ENABLED = "USER_BANNING_ENABLED"; + private static final String ATTR_SEB_PERMISSION_COMPONENTS = "SEB_PERMISSION_COMPONENTS"; + private static final String ATTR_BLACKLIST_CHAPTERS = "BLACKLIST_CHAPTERS"; + private static final String ATTR_WHITELIST_PATHS = "WHITELIST_PATHS"; + private static final String ATTR_BROWSER_KEYS = "BROWSER_KEYS"; + private static final String ATTR_CONFIG_KEYS = "CONFIG_KEYS"; + + @JsonProperty(ATTR_CONFIG_KEYS) public final Collection configKeys; - @JsonProperty("BROWSER_KEYS") + @JsonProperty(ATTR_BROWSER_KEYS) public final Collection browserExamKeys; - @JsonProperty("WHITELIST_PATHS") + @JsonProperty(ATTR_WHITELIST_PATHS) public final Collection whiteListPaths; - @JsonProperty("BLACKLIST_CHAPTERS") + @JsonProperty(ATTR_BLACKLIST_CHAPTERS) public final Collection blacklistChapters; - @JsonProperty("SEB_PERMISSION_COMPONENTS") + @JsonProperty(ATTR_SEB_PERMISSION_COMPONENTS) public final Collection permissionComponents; - @JsonProperty("USER_BANNING_ENABLED") + @JsonProperty(ATTR_USER_BANNING_ENABLED) public final boolean banningEnabled; @JsonCreator - protected OpenEdxSebClientRestriction( - @JsonProperty("CONFIG_KEYS") final Collection configKeys, - @JsonProperty("BROWSER_KEYS") final Collection browserExamKeys, - @JsonProperty("WHITELIST_PATHS") final Collection whiteListPaths, - @JsonProperty("BLACKLIST_CHAPTERS") final Collection blacklistChapters, - @JsonProperty("SEB_PERMISSION_COMPONENTS") final Collection permissionComponents, - @JsonProperty("USER_BANNING_ENABLED") final boolean banningEnabled) { + public OpenEdxSebClientRestriction( + @JsonProperty(ATTR_CONFIG_KEYS) final Collection configKeys, + @JsonProperty(ATTR_BROWSER_KEYS) final Collection browserExamKeys, + @JsonProperty(ATTR_WHITELIST_PATHS) final Collection whiteListPaths, + @JsonProperty(ATTR_BLACKLIST_CHAPTERS) final Collection blacklistChapters, + @JsonProperty(ATTR_SEB_PERMISSION_COMPONENTS) final Collection permissionComponents, + @JsonProperty(ATTR_USER_BANNING_ENABLED) final boolean banningEnabled) { this.configKeys = Utils.immutableCollectionOf(configKeys); this.browserExamKeys = Utils.immutableCollectionOf(browserExamKeys); @@ -54,6 +68,37 @@ public class OpenEdxSebClientRestriction { this.banningEnabled = banningEnabled; } + public OpenEdxSebClientRestriction(final SebRestrictionData data) { + this.configKeys = Utils.immutableCollectionOf(data.configKeys); + this.browserExamKeys = Utils.immutableCollectionOf(data.browserExamKeys); + + final String whiteListPaths = data.additionalAttributes.get(ATTR_WHITELIST_PATHS); + if (StringUtils.isNotBlank(whiteListPaths)) { + this.whiteListPaths = Utils.immutableCollectionOf(Arrays.asList( + StringUtils.split(whiteListPaths, Constants.LIST_SEPARATOR))); + } else { + this.whiteListPaths = Collections.emptyList(); + } + + final String blacklistChapters = data.additionalAttributes.get(ATTR_BLACKLIST_CHAPTERS); + if (StringUtils.isNotBlank(blacklistChapters)) { + this.blacklistChapters = Utils.immutableCollectionOf(Arrays.asList( + StringUtils.split(blacklistChapters, Constants.LIST_SEPARATOR))); + } else { + this.blacklistChapters = Collections.emptyList(); + } + + final String permissionComponents = data.additionalAttributes.get(ATTR_SEB_PERMISSION_COMPONENTS); + if (StringUtils.isNotBlank(permissionComponents)) { + this.permissionComponents = Utils.immutableCollectionOf(Arrays.asList( + StringUtils.split(permissionComponents, Constants.LIST_SEPARATOR))); + } else { + this.permissionComponents = Collections.emptyList(); + } + + this.banningEnabled = BooleanUtils.toBoolean(data.additionalAttributes.get(ATTR_USER_BANNING_ENABLED)); + } + public Collection getConfigKeys() { return this.configKeys; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java index eb2814fa..8da1dfee 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/converter/TableConverter.java @@ -174,7 +174,7 @@ public class TableConverter implements AttributeValueConverter { final ConfigurationAttribute attr = attrItr.next(); ConfigurationValue value = rowValues.stream() - .filter(val -> attr.id.equals(val.attributeId)) + .filter(val -> val != null && attr.id.equals(val.attributeId)) .findFirst() .orElse(null); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java index f958c94c..a3f85cce 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java @@ -36,7 +36,7 @@ public interface ExamSessionService { * @param examId the identifier of the Exam to check * @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is * okay. */ - Result> checkRunningExamConsystency(Long examId); + Result> checkRunningExamConsistency(Long examId); /** Checks if a specified Exam has at least a default SEB Exam configuration attached. * 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 536d9d16..72b79aec 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 @@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; import java.util.Collection; import java.util.Collections; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @@ -130,38 +129,23 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { } // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key) - final Collection updatedExams = updateLmsSebRestriction(exams) - .stream() - .map(Result::get) - .filter(Objects::nonNull) - .map(Exam::getId) - .collect(Collectors.toList()); - - if (log.isDebugEnabled()) { - log.debug("Successfully updated ConfigKey for Exams: {}", updatedExams); + for (final Exam exam : exams) { + if (exam.getStatus() == ExamStatus.RUNNING) { + this.updateSebClientRestriction(exam) + .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t)); + } } // evict each Exam from cache and release the update-lock on DB - final Collection evictedExams = evictFromCache(exams) - .stream() - .filter(Result::hasValue) - .map(Result::get) - .map(Exam::getId) - .collect(Collectors.toList()); - - if (log.isDebugEnabled()) { - log.debug("Successfully evicted Exams from cache: {}", evictedExams); + for (final Exam exam : exams) { + this.examSessionService.flushCache(exam) + .onError(t -> log.error("Failed to flush Exam from cache: {}", exam, t)); } // release the update-locks on involved exams - final Collection releasedLocks = releaseUpdateLocks(examIdsFirstCheck, updateId) - .stream() - .map(Result::getOrThrow) - .map(Exam::getId) - .collect(Collectors.toList()); - - if (log.isDebugEnabled()) { - log.debug("Successfully released update-locks on Exams: {}", releasedLocks); + for (final Long examId : examIdsFirstCheck) { + this.examDAO.releaseLock(examId, updateId) + .onError(t -> log.error("Failed to release lock for Exam: {}", examId, t)); } return examIdsFirstCheck; @@ -296,43 +280,6 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { return this.examUpdateHandler.releaseSebClientRestriction(exam); } - private void checkIntegrityDoubleCheck( - final Collection examIdsFirstCheck, - final Collection examIdsSecondCheck) { - - if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) { - throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: " - + examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck); - } - } - - private Collection> lockForUpdate(final Collection examIds, final String update) { - return examIds.stream() - .map(id -> this.examDAO.placeLock(id, update)) - .collect(Collectors.toList()); - } - - private Collection> releaseUpdateLocks(final Collection examIds, final String update) { - return examIds.stream() - .map(id -> this.examDAO.releaseLock(id, update)) - .collect(Collectors.toList()); - } - - private Collection> updateLmsSebRestriction(final Collection exams) { - return exams - .stream() - .filter(exam -> exam.getStatus() == ExamStatus.RUNNING) - .map(this::updateSebClientRestriction) - .collect(Collectors.toList()); - } - - private Collection> evictFromCache(final Collection exams) { - return exams - .stream() - .map(this.examSessionService::flushCache) - .collect(Collectors.toList()); - } - @Override public Result> checkRunningExamIntegrity(final Long configurationNodeId) { final Collection involvedExams = this.examConfigurationMapDAO @@ -397,4 +344,20 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { return false; } + private void checkIntegrityDoubleCheck( + final Collection examIdsFirstCheck, + final Collection examIdsSecondCheck) { + + if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) { + throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: " + + examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck); + } + } + + private Collection> lockForUpdate(final Collection examIds, final String update) { + return examIds.stream() + .map(id -> this.examDAO.placeLock(id, update)) + .collect(Collectors.toList()); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 0166167f..0ac7906f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -78,7 +78,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { } @Override - public Result> checkRunningExamConsystency(final Long examId) { + public Result> checkRunningExamConsistency(final Long examId) { return Result.tryCatch(() -> { final Collection result = new ArrayList<>(); 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 61291447..798bcc23 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 @@ -120,10 +120,6 @@ class ExamUpdateHandler { return Result.of(exam); } - if (log.isDebugEnabled()) { - log.debug("Apply SEB Client restrictions for exam: {}", exam); - } - return Result.tryCatch(() -> { final SebRestrictionData sebRestrictionData = createSebRestrictionData(exam); @@ -153,10 +149,6 @@ class ExamUpdateHandler { final SebRestrictionData sebRestrictionData = createSebRestrictionData(exam); - if (log.isDebugEnabled()) { - log.debug("Update SEB Client restrictions for exam: {}", exam); - } - return this.lmsAPIService .getLmsAPITemplate(exam.lmsSetupId) .flatMap(lmsTemplate -> lmsTemplate.updateSebClientRestriction(sebRestrictionData)) @@ -176,19 +168,6 @@ class ExamUpdateHandler { .flatMap(template -> template.releaseSebClientRestriction(exam)); } -// @Transactional -// public Result updateExamConfigurationMappingChange( -// final ExamConfigurationMap mapping, -// final Function> changeAction, -// final Exam exam) { -// -// return Result.tryCatch(() -> { -// -// return result; -// }) -// .onError(TransactionHandler::rollback); -// } - private SebRestrictionData createSebRestrictionData(final Exam exam) { final Collection configKeys = this.sebExamConfigService .generateConfigKeys(exam.institutionId, exam.id) @@ -206,7 +185,7 @@ class ExamUpdateHandler { exam, configKeys, browserExamKeys, - // TODO when we have more restriction details available form the Exam, put it to the map + // TODO when we have more restriction details available from the Exam, put it to the map Collections.emptyMap()); return sebRestrictionData; } 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 eeb38af7..f0376fcd 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 @@ -19,6 +19,7 @@ import java.util.stream.Collectors; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.dynamic.sql.SqlTable; import org.slf4j.Logger; @@ -60,6 +61,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @@ -75,6 +77,7 @@ public class ExamAdministrationController extends EntityController { private final LmsAPIService lmsAPIService; private final SebExamConfigService sebExamConfigService; private final ExamSessionService examSessionService; + private final ExamConfigUpdateService examConfigUpdateService; public ExamAdministrationController( final AuthorizationService authorization, @@ -86,7 +89,8 @@ public class ExamAdministrationController extends EntityController { final LmsAPIService lmsAPIService, final UserDAO userDAO, final SebExamConfigService sebExamConfigService, - final ExamSessionService examSessionService) { + final ExamSessionService examSessionService, + final ExamConfigUpdateService examConfigUpdateService) { super(authorization, bulkActionService, @@ -100,6 +104,7 @@ public class ExamAdministrationController extends EntityController { this.lmsAPIService = lmsAPIService; this.sebExamConfigService = sebExamConfigService; this.examSessionService = examSessionService; + this.examConfigUpdateService = examConfigUpdateService; } @Override @@ -197,9 +202,39 @@ public class ExamAdministrationController extends EntityController { + API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public Collection checkExamConsistency(@PathVariable final Long modelId) { + public Collection checkExamConsistency( + @PathVariable final Long modelId, + @RequestParam( + name = API.PARAM_INSTITUTION_ID, + required = true, + defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) { + + checkModifyPrivilege(institutionId); return this.examSessionService - .checkRunningExamConsystency(modelId) + .checkRunningExamConsistency(modelId) + .getOrThrow(); + } + + @RequestMapping( + path = API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT, + method = RequestMethod.PATCH, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Exam switchSebRestriction( + @PathVariable final Long modelId, + @RequestParam(name = Domain.EXAM.ATTR_LMS_SEB_RESTRICTION, required = true) final boolean sebRestriction, + @RequestParam( + name = API.PARAM_INSTITUTION_ID, + required = true, + defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) { + + checkModifyPrivilege(institutionId); + + return this.entityDAO.byPK(modelId) + .flatMap(this.authorization::checkModify) + .flatMap(this::checkNoActiveSebClientConnections) + .flatMap(exam -> this.setSebRestriction(exam, sebRestriction)) + .flatMap(this.userActivityLogDAO::logModify) .getOrThrow(); } @@ -291,4 +326,35 @@ public class ExamAdministrationController extends EntityController { return exam; } + private Result checkNoActiveSebClientConnections(final Exam exam) { + if (this.examConfigUpdateService.hasActiveSebClientConnections(exam.id)) { + return Result.ofError(new APIMessageException( + APIMessage.ErrorMessage.INTEGRITY_VALIDATION + .of("Exam currently has active SEB Client connections."))); + } + + return Result.of(exam); + } + + private Result setSebRestriction(final Exam exam, final boolean sebRestriction) { + return Result.tryCatch(() -> { + if (BooleanUtils.toBoolean(exam.lmsSebRestriction) == sebRestriction) { + return exam; + } + + final Exam examUpdate = this.examDAO.setSebRestriction(exam.id, sebRestriction) + .getOrThrow(); + + if (sebRestriction) { + this.examConfigUpdateService.applySebClientRestriction(examUpdate) + .getOrThrow(); + } else { + this.examConfigUpdateService.releaseSebClientRestriction(examUpdate) + .getOrThrow(); + } + + return examUpdate; + }); + } + } diff --git a/src/main/resources/data-demo.sql b/src/main/resources/data-demo.sql index 6b0608e5..a85e31b9 100644 --- a/src/main/resources/data-demo.sql +++ b/src/main/resources/data-demo.sql @@ -262,7 +262,7 @@ INSERT IGNORE INTO configuration_attribute VALUES (519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'), (520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'), - (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws:\\localhost:8706'), + (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws://localhost:8706'), (801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'), (802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'), (803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'), diff --git a/src/main/resources/data-prod.sql b/src/main/resources/data-prod.sql index 09cf6d0f..9d08c5fe 100644 --- a/src/main/resources/data-prod.sql +++ b/src/main/resources/data-prod.sql @@ -235,7 +235,7 @@ INSERT IGNORE INTO configuration_attribute VALUES (519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'), (520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'), - (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws:\\localhost:8706'), + (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws://localhost:8706'), (801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'), (802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'), (803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'), diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index a2ad5ef3..34058dd0 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -301,6 +301,8 @@ sebserver.exam.action.import=Import From Quizzes sebserver.exam.action.save=Save Exam sebserver.exam.action.activate=Activate Exam sebserver.exam.action.deactivate=Deactivate Exam +sebserver.exam.action.sebrestriction.enable=Enable SEB Restriction +sebserver.exam.action.sebrestriction.disable=Disable SEB Restriction sebserver.exam.info.pleaseSelect=Please Select an Exam first diff --git a/src/test/resources/data-test-additional.sql b/src/test/resources/data-test-additional.sql index 52db757f..d0433e83 100644 --- a/src/test/resources/data-test-additional.sql +++ b/src/test/resources/data-test-additional.sql @@ -243,7 +243,7 @@ INSERT IGNORE INTO configuration_attribute VALUES (519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'), (520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'), - (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws:\\localhost:8706'), + (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws://localhost:8706'), (801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'), (802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'), (803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'),