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 733ae229f..67c28a85e 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 @@ -178,7 +178,7 @@ private BatchActionType(final EntityType entityType) { public static final String LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID = "exam_template_id"; public static final String LMS_FULL_INTEGRATION_EXAM_DATA = "exam_data"; public static final String LMS_FULL_INTEGRATION_QUIT_PASSWORD = "quit_password"; - public static final String LMS_FULL_INTEGRATION_QUIT_LINK = "quit_link"; + public static final String LMS_FULL_INTEGRATION_QUIT_LINK = "show_quit_link"; public static final String LMS_FULL_INTEGRATION_USER_ID = "user_id"; public static final String LMS_FULL_INTEGRATION_USER_NAME = "user_username"; public static final String LMS_FULL_INTEGRATION_USER_EMAIL = "user_email"; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java index ce74c3d9c..96f2755c6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java @@ -89,4 +89,6 @@ Result> setDefaultValues( * @param pwd The hashed quit password * @return Result refer to void or to an error when happened*/ Result saveQuitPassword(Long configurationId, String pwd); + + Result saveForce(ConfigurationValue configurationValue); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java index f3f47fa7f..442e598d0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java @@ -268,41 +268,17 @@ public Result createNew(final ConfigurationValue data) { public Result save(final ConfigurationValue data) { return checkInstitutionalIntegrity(data) .map(this::checkFollowUpIntegrity) - .flatMap(this::attributeRecord) - .map(attributeRecord -> { - - final Long id; - if (data.id == null) { - - id = getByProperties(data) - .orElseGet(() -> { - log.debug("Missing SEB exam configuration attrribute value for: {}", data); - log.debug("Use self-healing strategy to recover from missing SEB exam " - + "configuration attrribute value\n**** Create new AttributeValue for: {}", - data); + .map(this::saveData) + .flatMap(ConfigurationValueDAOImpl::toDomainModel) + .onError(TransactionHandler::rollback); + } - createNew(data); - return getByProperties(data) - .orElseThrow(() -> new ResourceNotFoundException( - EntityType.CONFIGURATION_VALUE, - String.valueOf(data.attributeId))); - }); - } else { - id = data.id; - } - final ConfigurationValueRecord newRecord = new ConfigurationValueRecord( - id, - null, - null, - null, - data.listIndex, - data.value); - - this.configurationValueRecordMapper.updateByPrimaryKeySelective(newRecord); - return this.configurationValueRecordMapper.selectByPrimaryKey(id); - }) + @Override + public Result saveForce(final ConfigurationValue data) { + return checkInstitutionalIntegrity(data) + .map(this::saveData) .flatMap(ConfigurationValueDAOImpl::toDomainModel) .onError(TransactionHandler::rollback); } @@ -708,4 +684,38 @@ private Optional getByProperties(final ConfigurationValue data) { .findFirst(); } + private ConfigurationValueRecord saveData(final ConfigurationValue data) { + final Long id; + if (data.id == null) { + + id = getByProperties(data) + .orElseGet(() -> { + log.debug("Missing SEB exam configuration attrribute value for: {}", data); + log.debug("Use self-healing strategy to recover from missing SEB exam " + + "configuration attrribute value\n**** Create new AttributeValue for: {}", + data); + + createNew(data); + return getByProperties(data) + .orElseThrow(() -> new ResourceNotFoundException( + EntityType.CONFIGURATION_VALUE, + String.valueOf(data.attributeId))); + + }); + } else { + id = data.id; + } + + final ConfigurationValueRecord newRecord = new ConfigurationValueRecord( + id, + null, + null, + null, + data.listIndex, + data.value); + + this.configurationValueRecordMapper.updateByPrimaryKeySelective(newRecord); + return this.configurationValueRecordMapper.selectByPrimaryKey(id); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index 86fa0d4c9..e84c06638 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -95,25 +95,25 @@ default boolean isScreenProctoringEnabled(final Exam exam) { /** Updates needed additional attributes from assigned exam configuration for the exam * * @param examId The exam identifier */ - void updateAdditionalExamConfigAttributes(final Long examId); + void updateAdditionalExamConfigAttributes(Long examId); /** This indicates if proctoring is set and enabled for a certain exam. * * @param examId the exam identifier * @return Result refer to proctoring is enabled flag or to an error when happened. */ - Result isProctoringEnabled(final Long examId); + Result isProctoringEnabled(Long examId); /** This indicates if screen proctoring is set and enabled for a certain exam. * * @param examId the exam identifier * @return Result refer to screen proctoring is enabled flag or to an error when happened. */ - Result isScreenProctoringEnabled(final Long examId); + Result isScreenProctoringEnabled(Long examId); /** Get the exam proctoring service implementation for specified exam. * * @param examId the exam identifier * @return ExamProctoringService instance */ - Result getExamProctoringService(final Long examId); + Result getExamProctoringService(Long examId); /** This resets the proctoring settings for a given exam and stores the default settings. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java index d2b82a004..9e0b7b5f8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java @@ -54,6 +54,14 @@ String getMappedDefaultConfigAttributeValue( */ Result applyQuitPasswordToConfigs(Long examId, String quitPassword); + /** Used to apply the quit pass given from the exam to all exam configuration for the exam. + * + * @param examId The exam identifier + * @param quitLink The quit link to set to all exam configuration of the given exam + * @return Result to the given exam id or to an error when happened + */ + Result applyQuitURLToConfigs(Long examId, String quitLink); + /** Get the quitLink SEB Setting from the Exam Configuration that is applied to the given exam. * * @param examId Exam identifier diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java index ef95884e7..226bd39fd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java @@ -152,52 +152,24 @@ public Result applyQuitPasswordToConfigs(final Long examId, final String q return examId; } - final Long configNodeId = this.examConfigurationMapDAO - .getDefaultConfigurationNode(examId) - .getOr(null); + return saveSEBAttributeValueToConfig(examId, CONFIG_ATTR_NAME_QUIT_SECRET, quitSecret); + }); + } - if (configNodeId == null) { - log.info("No Exam Configuration found for exam {} to apply quitPassword", examId); - return examId; - } + @Override + public Result applyQuitURLToConfigs(final Long examId, final String quitLink) { + return Result.tryCatch(() -> { - final Long attrId = getAttributeId(CONFIG_ATTR_NAME_QUIT_SECRET); - if (attrId == null) { + final String oldQuitLink = this.getQuitLink(examId); + if (Objects.equals(oldQuitLink, quitLink)) { return examId; } - final Configuration followupConfig = this.configurationDAO.getFollowupConfiguration(configNodeId) - .onError(error -> log.warn("Failed to get followup config for {} cause {}", - configNodeId, - error.getMessage())) - .getOr(null); - - final ConfigurationValue configurationValue = new ConfigurationValue( - null, - followupConfig.institutionId, - followupConfig.id, - attrId, - 0, - quitSecret - ); - - this.configurationValueDAO - .save(configurationValue) - .onError(err -> log.error( - "Failed to save quit password to config value: {}", - configurationValue, - err)); - - // TODO possible without save to history? - this.configurationDAO - .saveToHistory(configNodeId) - .onError(error -> log.warn("Failed to save to history for exam: {} cause: {}", - examId, error.getMessage())); - - return examId; + return saveSEBAttributeValueToConfig(examId, CONFIG_ATTR_NAME_QUIT_LINK, quitLink); }); } + @Override public String getQuitLink(final Long examId) { try { @@ -236,4 +208,67 @@ private Long getAttributeId(final String configAttributeName) { .getOr(null); } + private Long saveSEBAttributeValueToConfig( + final Long examId, + final String attrName, + final String attrValue) { + + final Long configNodeId = this.examConfigurationMapDAO + .getDefaultConfigurationNode(examId) + .getOr(null); + + if (configNodeId == null) { + log.info("No Exam Configuration found for exam {} to apply SEB Setting: {}", examId, attrName); + return examId; + } + + final Long attrId = getAttributeId(attrName); + if (attrId == null) { + return examId; + } + + final Configuration lastStable = this.configurationDAO + .getConfigurationLastStableVersion(configNodeId) + .getOrThrow(); + final Long followupId = configurationDAO + .getFollowupConfigurationId(configNodeId) + .getOrThrow(); + + // save to last sable version + this.configurationValueDAO + .saveForce(new ConfigurationValue( + null, + lastStable.institutionId, + lastStable.id, + attrId, + 0, + attrValue + )) + .onError(err -> log.error( + "Failed to save SEB Setting: {} to config: {}", + attrName, + lastStable, + err)); + + if (!Objects.equals(followupId, lastStable.id)) { + // save also to followup version + this.configurationValueDAO + .saveForce(new ConfigurationValue( + null, + lastStable.institutionId, + followupId, + attrId, + 0, + attrValue + )) + .onError(err -> log.error( + "Failed to save SEB Setting: {} to config: {}", + attrName, + lastStable, + err)); + } + + return examId; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java index d89911d13..6d4372093 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java @@ -56,7 +56,7 @@ Result importExam( String quizId, String examTemplateId, String quitPassword, - String quitLink, + boolean showQuitLink, final String examData); Result deleteExam( @@ -117,7 +117,7 @@ final class ExamData { @JsonProperty("template_id") public final String template_id; @JsonProperty("show_quit_link") - public final Boolean show_quit_link; + public final String quit_link; @JsonProperty("quit_password") public final String quit_password; @@ -127,7 +127,7 @@ public ExamData( final String quiz_id, final Boolean exam_created, final String template_id, - final Boolean show_quit_link, + final String quit_link, final String quit_password) { this.id = id; @@ -135,7 +135,7 @@ public ExamData( this.quiz_id = quiz_id; this.exam_created = exam_created; this.template_id = template_id; - this.show_quit_link = show_quit_link; + this.quit_link = quit_link; this.quit_password = quit_password; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java index af592a667..3857d5b50 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java @@ -323,19 +323,28 @@ public Result importExam( final String quizId, final String examTemplateId, final String quitPassword, - final String quitLink, + final boolean showQuitLink, final String examData) { return lmsSetupDAO .getLmsSetupIdByConnectionId(lmsUUID) .flatMap(lmsAPITemplateCacheService::getLmsAPITemplate) .map(template -> getQuizData(template, courseId, quizId, examData)) - .map(createExam(examTemplateId, quitPassword)) + .map(createExam(examTemplateId, showQuitLink, quitPassword)) .map(exam -> applyExamData(exam, false)) - .flatMap(sebRestrictionService::applySEBClientRestriction) + .map(this::applySEBClientRestrictionIfRunning) .map(this::applyConnectionConfiguration); } + private Exam applySEBClientRestrictionIfRunning(final Exam exam) { + if (exam.status == Exam.ExamStatus.RUNNING) { + return sebRestrictionService + .applySEBClientRestriction(exam) + .getOrThrow(); + } + return exam; + } + @Override public Result deleteExam( final String lmsUUID, @@ -368,6 +377,7 @@ public Result streamConnectionConfiguration( .flatMap(this::findExam); if (examResult.hasError()) { + log.error("Failed to find exam for SEB Connection Configuration download: ", examResult.getError()); throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("Exam not found")); } @@ -375,10 +385,15 @@ public Result streamConnectionConfiguration( final String connectionConfigId = getConnectionConfigurationId(exam); if (StringUtils.isBlank(connectionConfigId)) { + log.error("Failed to verify SEB Connection Configuration id for exam: {}", exam.name); throw new APIMessage.APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("No active Connection Configuration found")); } - this.connectionConfigurationService.exportSEBClientConfiguration(out, connectionConfigId, exam.id); + this.connectionConfigurationService.exportSEBClientConfiguration( + out, + connectionConfigId, + exam.id); + return Result.EMPTY; } catch (final Exception e) { @@ -469,6 +484,7 @@ private Result findExam(final QuizData quizData) { private Function createExam( final String examTemplateId, + final boolean showQuitLink, final String quitPassword) { return quizData -> { @@ -503,6 +519,7 @@ private Function createExam( return examDAO .createNew(exam) .flatMap(examImportService::applyExamImportInitialization) + .map( e -> this.applyQuitLinkToSEBConfig(e, showQuitLink)) .map(this::logExamCreated) .getOrThrow(); }; @@ -572,7 +589,7 @@ private Exam applyExamData(final Exam exam, final boolean deletion) { final String templateId = deletion ? null : String.valueOf(exam.examTemplateId); final String quitPassword = deletion ? null : examConfigurationValueService.getQuitPassword(exam.id); - final Boolean quitLink = deletion ? null : StringUtils.isNotBlank(examConfigurationValueService.getQuitLink(exam.id)); + final String quitLink = deletion ? null : examConfigurationValueService.getQuitLink(exam.id); final ExamData examData = new ExamData( lmsUUID, @@ -591,6 +608,35 @@ private Exam applyExamData(final Exam exam, final boolean deletion) { return exam; } + private Exam applyQuitLinkToSEBConfig(final Exam exam, final boolean showQuitLink) { + try { + + if (!showQuitLink) { + // check set no quit link to SEB config + examConfigurationValueService + .applyQuitURLToConfigs(exam.id, "") + .getOrThrow(); + } else { + // check if in config quit link is set, if so nothing to do, if not generate one and apply + String quitLink = examConfigurationValueService.getQuitLink(exam.id); + if (StringUtils.isNotBlank(quitLink)) { + return exam; + } + + quitLink = "http://quit_seb"; + + examConfigurationValueService + .applyQuitURLToConfigs(exam.id, quitLink) + .getOrThrow(); + } + + return exam; + } catch (final Exception e) { + log.error("Failed to apply quit link to SEB Exam Configuration: ", e); + return exam; + } + } + private Exam applyConnectionConfiguration(final Exam exam) { return lmsAPITemplateCacheService .getLmsAPITemplate(exam.lmsSetupId) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java index 53f1ed7c2..23cfb44a5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java @@ -209,12 +209,14 @@ public Result applyExamData(final ExamData examData) { // data[addordelete]= int // data[templateid]= int // data[showquitlink]= int + // data[quitlink]=string // data[quitsecret]= string data_mapping.put("quizid", examData.quiz_id); if (BooleanUtils.isTrue(examData.exam_created)) { data_mapping.put("addordelete", "1"); data_mapping.put("templateid", examData.template_id); - data_mapping.put("showquitlink", BooleanUtils.isTrue(examData.show_quit_link) ? "1" : "0"); + data_mapping.put("showquitlink", StringUtils.isNotBlank(examData.quit_link) ? "1" : "0"); + data_mapping.put("quitlink", examData.quit_link); data_mapping.put("quitsecret", examData.quit_password); } else { data_mapping.put("addordelete", "0"); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsIntegrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsIntegrationController.java index a3e631557..cf2288539 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsIntegrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsIntegrationController.java @@ -14,17 +14,15 @@ import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; -import java.util.Arrays; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; -import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.webservice.WebserviceInfo; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.BooleanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -43,16 +41,13 @@ public class LmsIntegrationController { private final FullLmsIntegrationService fullLmsIntegrationService; private final WebserviceInfo webserviceInfo; - private final ExamDAO examDAO; public LmsIntegrationController( final FullLmsIntegrationService fullLmsIntegrationService, - final WebserviceInfo webserviceInfo, - final ExamDAO examDAO) { + final WebserviceInfo webserviceInfo) { this.fullLmsIntegrationService = fullLmsIntegrationService; this.webserviceInfo = webserviceInfo; - this.examDAO = examDAO; } @RequestMapping( @@ -66,7 +61,7 @@ public void createExam( @RequestParam(name = API.LMS_FULL_INTEGRATION_EXAM_TEMPLATE_ID) final String templateId, @RequestParam(name = API.LMS_FULL_INTEGRATION_EXAM_DATA, required = false) final String examData, @RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_PASSWORD, required = false) final String quitPassword, - @RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_LINK, required = false) final String quitLink, + @RequestParam(name = API.LMS_FULL_INTEGRATION_QUIT_LINK, required = false) final int quitLink, final HttpServletResponse response) { final Exam exam = fullLmsIntegrationService.importExam( @@ -75,7 +70,7 @@ public void createExam( quizId, templateId, quitPassword, - quitLink, + BooleanUtils.toBoolean(quitLink), examData) .onError(e -> { log.error(