From db4411c52e3a4ba689f21bc0b05bd53f2d472459 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 5 Jun 2024 08:44:46 +0200 Subject: [PATCH] SEBSERV-417 new Moodle POST API calls --- .../bulkaction/impl/DeleteExamAction.java | 26 +- .../exam/impl/ExamAdminServiceImpl.java | 20 +- .../exam/impl/ExamImportServiceImpl.java | 11 +- .../servicelayer/lms/LmsAPIService.java | 11 - .../lms/LmsAPITemplateCacheService.java | 39 +++ .../servicelayer/lms/LmsTestService.java | 28 ++ .../lms/SEBRestrictionService.java | 4 +- .../impl/FullLmsIntegrationServiceImpl.java | 58 ++-- .../lms/impl/LmsAPIServiceImpl.java | 297 +----------------- .../impl/LmsAPITemplateCacheServiceImpl.java | 275 ++++++++++++++++ .../lms/impl/LmsTestServiceImpl.java | 122 +++++++ .../lms/impl/SEBRestrictionServiceImpl.java | 25 +- .../impl/moodle/MoodleAPIRestTemplate.java | 7 +- .../moodle/MoodleRestTemplateFactoryImpl.java | 27 +- .../plugin/MoodlePluginFullIntegration.java | 44 ++- .../session/impl/ExamSessionCacheService.java | 8 - .../proctoring/JitsiProctoringService.java | 31 +- .../proctoring/ZoomProctoringService.java | 23 +- .../weblayer/api/LmsSetupController.java | 8 +- .../MoodleMockupRestTemplateFactory.java | 6 +- .../ExamJITSIProctoringServiceTest.java | 10 +- 21 files changed, 674 insertions(+), 406 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplateCacheService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsTestService.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateCacheServiceImpl.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsTestServiceImpl.java diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamAction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamAction.java index e98db5f6..28b2b461 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamAction.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/impl/DeleteExamAction.java @@ -37,7 +37,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; @Lazy @Component @@ -53,7 +52,6 @@ public class DeleteExamAction implements BatchActionExec { private final IndicatorDAO indicatorDAO; private final AuthorizationService authorization; private final UserActivityLogDAO userActivityLogDAO; - private final ExamSessionService examSessionService; public DeleteExamAction( final ExamDAO examDAO, @@ -62,8 +60,7 @@ public class DeleteExamAction implements BatchActionExec { final ClientGroupDAO clientGroupDAO, final IndicatorDAO indicatorDAO, final AuthorizationService authorization, - final UserActivityLogDAO userActivityLogDAO, - final ExamSessionService examSessionService) { + final UserActivityLogDAO userActivityLogDAO) { this.examDAO = examDAO; this.clientConnectionDAO = clientConnectionDAO; @@ -72,7 +69,6 @@ public class DeleteExamAction implements BatchActionExec { this.indicatorDAO = indicatorDAO; this.authorization = authorization; this.userActivityLogDAO = userActivityLogDAO; - this.examSessionService = examSessionService; } @Override @@ -149,13 +145,23 @@ public class DeleteExamAction implements BatchActionExec { } private Result checkNoActiveSEBClientConnections(final Exam exam) { - if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) { - return Result.ofError(new APIMessageException( - APIMessage.ErrorMessage.INTEGRITY_VALIDATION - .of("Exam currently has active SEB Client connections."))); + if (exam.status != Exam.ExamStatus.RUNNING) { + return Result.of(exam); } - return Result.of(exam); + final Integer active = this.clientConnectionDAO + .getAllActiveConnectionTokens(exam.id) + .map(Collection::size) + .onError(error -> log.warn("Failed to get active access tokens for exam: {}", error.getMessage())) + .getOr(1); + + if (active == null || active == 0) { + return Result.of(exam); + } + + return Result.ofError(new APIMessageException( + APIMessage.ErrorMessage.INTEGRITY_VALIDATION + .of("Exam currently has active SEB Client connections."))); } private Result logDeleted(final Exam entity, final BatchAction batchAction) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index fc2cd92e..00baddc5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -48,7 +48,6 @@ public class ExamAdminServiceImpl implements ExamAdminService { private final ConfigurationNodeDAO configurationNodeDAO; private final ExamConfigurationMapDAO examConfigurationMapDAO; private final LmsAPIService lmsAPIService; - private final ExamConfigurationValueService examConfigurationValueService; private final SEBRestrictionService sebRestrictionService; @@ -248,26 +247,9 @@ public class ExamAdminServiceImpl implements ExamAdminService { @Override public Result applyQuitPassword(final Exam exam) { - return this.examConfigurationValueService - .applyQuitPasswordToConfigs(exam.id, exam.quitPassword) - .map(id -> applySEBRestrictionIfExamRunning(exam)) - .onError(t -> log.error("Failed to quit password for Exam: {}", exam, t)); + return this.sebRestrictionService.applyQuitPassword(exam); } - private Exam applySEBRestrictionIfExamRunning(final Exam exam) { - if (exam.status != ExamStatus.RUNNING) { - return exam; - } - - return this.sebRestrictionService - .applySEBClientRestriction(exam) - .flatMap(e -> this.examDAO.setSEBRestriction(e.id, true)) - .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t)) - .getOr(exam); - } - - - @Override public Result archiveExam(final Exam exam) { return Result.tryCatch(() -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamImportServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamImportServiceImpl.java index e49b8916..ea787942 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamImportServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamImportServiceImpl.java @@ -27,10 +27,8 @@ import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; -import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService; -import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; @@ -51,16 +49,15 @@ public class ExamImportServiceImpl implements ExamImportService { private static final Logger log = LoggerFactory.getLogger(ExamImportServiceImpl.class); private final ExamDAO examDAO; - // private final FullLmsIntegrationService fullLmsIntegrationService; private final ExamTemplateService examTemplateService; - private final ExamAdminService examAdminService; + private final SEBRestrictionService sebRestrictionService; private final boolean appSignatureKeyEnabled; private final int defaultNumericalTrustThreshold; public ExamImportServiceImpl( final ExamDAO examDAO, final ExamTemplateService examTemplateService, - final ExamAdminService examAdminService, + final SEBRestrictionService sebRestrictionService, final AdditionalAttributesDAO additionalAttributesDAO, final LmsAPIService lmsAPIService, final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled, @@ -68,7 +65,7 @@ public class ExamImportServiceImpl implements ExamImportService { this.examDAO = examDAO; this.examTemplateService = examTemplateService; - this.examAdminService = examAdminService; + this.sebRestrictionService = sebRestrictionService; this.additionalAttributesDAO = additionalAttributesDAO; this.lmsAPIService = lmsAPIService; this.appSignatureKeyEnabled = appSignatureKeyEnabled; @@ -102,7 +99,7 @@ public class ExamImportServiceImpl implements ExamImportService { }) .flatMap(this::applyAdditionalSEBRestrictions) .onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_RESTRICTION.of(error))) - .flatMap(examAdminService::applyQuitPassword) + .flatMap(sebRestrictionService::applyQuitPassword) .onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_QUIT_PASSWORD.of(error))) .flatMap(examTemplateService::applyScreenProctoringSettingsForExam) .onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_SCREEN_PROCTORING_SETTINGS.of(error))); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPIService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPIService.java index abeb31f5..2b06ca3a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPIService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPIService.java @@ -72,18 +72,7 @@ public interface LmsAPIService { * @return LmsAPITemplate for specified LmsSetup configuration */ Result getLmsAPITemplate(String lmsSetupId); - /** use this to the specified LmsAPITemplate. - * - * @param template the LmsAPITemplate - * @return LmsSetupTestResult containing list of errors if happened */ - LmsSetupTestResult test(LmsAPITemplate template); - /** This can be used to test an LmsSetup connection parameter without saving or heaving - * an already persistent version of an LmsSetup. - * - * @param lmsSetup the LmsSetup instance - * @return LmsSetupTestResult containing list of errors if happened */ - LmsSetupTestResult testAdHoc(LmsSetup lmsSetup); /** Get a LmsAPITemplate for specified LmsSetup configuration by primary key * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplateCacheService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplateCacheService.java new file mode 100644 index 00000000..5a1fe459 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplateCacheService.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.lms; + +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + + +public interface LmsAPITemplateCacheService { + + /** Get a LmsAPITemplate for specified LmsSetup configuration by model identifier. + * Get it from cache if available of not, create new one and put it in cache if okay. + * + * @param lmsSetupId the identifier of LmsSetup + * @return LmsAPITemplate for specified LmsSetup configuration */ + Result getLmsAPITemplate(Long lmsSetupId); + + /** Get a LmsAPITemplate for specified LmsSetup configuration by model identifier. + * Get it from cache if available of not, create new one and put it in cache if okay. + * + * @param lmsSetupId the identifier of LmsSetup + * @return LmsAPITemplate for specified LmsSetup configuration */ + Result getLmsAPITemplate(String lmsSetupId); + + Result getLmsAPITemplateForTesting(String lmsSetupId); + + void clearCache(String lmsSetupId); + + /** Reset and cleanup the caches if there are some */ + void cleanup(); +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsTestService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsTestService.java new file mode 100644 index 00000000..d64cbd70 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsTestService.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.lms; + +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; + +public interface LmsTestService { + + /** use this to the specified LmsAPITemplate. + * + * @param template the LmsAPITemplate + * @return LmsSetupTestResult containing list of errors if happened */ + LmsSetupTestResult test(LmsAPITemplate template); + + /** This can be used to test an LmsSetup connection parameter without saving or heaving + * an already persistent version of an LmsSetup. + * + * @param lmsSetup the LmsSetup instance + * @return LmsSetupTestResult containing list of errors if happened */ + LmsSetupTestResult testAdHoc(LmsSetup lmsSetup); +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionService.java index 4f1a8e0d..385e47c3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionService.java @@ -31,7 +31,7 @@ public interface SEBRestrictionService { Result getSEBRestrictionFromExam(Exam exam); /** Saves the given SEBRestriction for the given Exam. - * + *

* The webservice saves the given browser Exam keys within the Exam record * and given additional restriction properties within the Additional attributes linked * to the given Exam. @@ -65,4 +65,6 @@ public interface SEBRestrictionService { * to the LMS */ boolean checkSebRestrictionSet(Exam exam); + Result applyQuitPassword(final Exam exam); + } 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 b7541bfd..222a7391 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 @@ -44,8 +44,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValue import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamImportService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateChangeEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; -import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateCacheService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService; @@ -76,9 +76,9 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService private final SEBClientConfigDAO sebClientConfigDAO; private final ConnectionConfigurationService connectionConfigurationService; private final DeleteExamAction deleteExamAction; - private final LmsAPIService lmsAPIService; + private final LmsAPITemplateCacheService lmsAPITemplateCacheService; private final ExamImportService examImportService; - private final ExamSessionService examSessionService; + private final ClientConnectionDAO clientConnectionDAO; private final ExamConfigurationValueService examConfigurationValueService; private final ExamDAO examDAO; private final ExamTemplateDAO examTemplateDAO; @@ -96,16 +96,16 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService final ScreenProctoringService screenProctoringService, final ConnectionConfigurationService connectionConfigurationService, final DeleteExamAction deleteExamAction, - final LmsAPIService lmsAPIService, - final ExamSessionService examSessionService, final ExamConfigurationValueService examConfigurationValueService, final ExamDAO examDAO, + final ClientConnectionDAO clientConnectionDAO, final ExamImportService examImportService, final ExamTemplateDAO examTemplateDAO, final WebserviceInfo webserviceInfo, final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final UserService userService, final TeacherAccountServiceImpl teacherAccountServiceImpl, + final LmsAPITemplateCacheService lmsAPITemplateCacheService, @Value("${sebserver.webservice.lms.api.endpoint}") final String lmsAPIEndpoint, @Value("${sebserver.webservice.lms.api.clientId}") final String clientId, @Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret) { @@ -116,8 +116,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService this.sebClientConfigDAO = sebClientConfigDAO; this.connectionConfigurationService = connectionConfigurationService; this.deleteExamAction = deleteExamAction; - this.lmsAPIService = lmsAPIService; - this.examSessionService = examSessionService; + this.lmsAPITemplateCacheService = lmsAPITemplateCacheService; this.examDAO = examDAO; this.examTemplateDAO = examTemplateDAO; this.webserviceInfo = webserviceInfo; @@ -125,6 +124,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService this.userService = userService; this.examConfigurationValueService = examConfigurationValueService; this.examImportService = examImportService; + this.clientConnectionDAO = clientConnectionDAO; resource = new ClientCredentialsResourceDetails(); resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI()); @@ -246,7 +246,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService this.getIntegrationTemplates(lmsSetup.institutionId) ); - return lmsAPIService.getLmsAPITemplate(lmsSetupId) + return lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId) .getOrThrow() .applyConnectionDetails(data) .onError(error -> lmsSetupDAO @@ -271,7 +271,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService return false; } - lmsAPIService.getLmsAPITemplate(lmsSetupId) + lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId) .getOrThrow() .deleteConnectionDetails() .onError(error -> lmsSetupDAO @@ -297,7 +297,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService return lmsSetupDAO .getLmsSetupIdByConnectionId(lmsUUID) - .flatMap(lmsAPIService::getLmsAPITemplate) + .flatMap(lmsAPITemplateCacheService::getLmsAPITemplate) .map(findQuizData(courseId, quizId)) .map(createExam(examTemplateId, quitPassword)) .map(exam -> applyExamData(exam, false)) @@ -312,7 +312,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService return lmsSetupDAO .getLmsSetupIdByConnectionId(lmsUUID) - .flatMap(lmsAPIService::getLmsAPITemplate) + .flatMap(lmsAPITemplateCacheService::getLmsAPITemplate) .map(findQuizData(courseId, quizId)) .flatMap(this::findExam) .map(this::checkDeletion) @@ -384,7 +384,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService return lmsSetupDAO .getLmsSetupIdByConnectionId(lmsUUID) - .flatMap(lmsAPIService::getLmsAPITemplate) + .flatMap(lmsAPITemplateCacheService::getLmsAPITemplate) .map(findQuizData(courseId, quizId)) .flatMap(this::findExam) .flatMap(exam -> this.teacherAccountServiceImpl @@ -470,14 +470,32 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService private Exam checkDeletion(final Exam exam) { // TODO check if Exam can be deleted according to the Spec - // check if there are no active SEB client connections - if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) { - throw new APIMessage.APIMessageException( - APIMessage.ErrorMessage.INTEGRITY_VALIDATION - .of("Exam currently has active SEB Client connections.")); + if (exam.status != Exam.ExamStatus.RUNNING) { + return exam; } - return exam; + final Integer active = this.clientConnectionDAO + .getAllActiveConnectionTokens(exam.id) + .map(Collection::size) + .onError(error -> log.warn("Failed to get active access tokens for exam: {}", error.getMessage())) + .getOr(1); + + if (active == null || active == 0) { + return exam; + } + + throw new APIMessage.APIMessageException( + APIMessage.ErrorMessage.INTEGRITY_VALIDATION + .of("Exam currently has active SEB Client connections.")); + + // check if there are no active SEB client connections +// if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) { +// throw new APIMessage.APIMessageException( +// APIMessage.ErrorMessage.INTEGRITY_VALIDATION +// .of("Exam currently has active SEB Client connections.")); +// } +// +// return exam; } @@ -497,7 +515,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService private Exam applyExamData(final Exam exam, final boolean deletion) { try { - final LmsAPITemplate lmsAPITemplate = lmsAPIService + final LmsAPITemplate lmsAPITemplate = lmsAPITemplateCacheService .getLmsAPITemplate(exam.lmsSetupId) .getOrThrow(); final String lmsUUID = lmsAPITemplate.lmsSetup().connectionId; @@ -526,7 +544,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService } private Exam applyConnectionConfiguration(final Exam exam) { - return lmsAPIService + return lmsAPITemplateCacheService .getLmsAPITemplate(exam.lmsSetupId) .flatMap(template -> { final String connectionConfigId = getConnectionConfigurationId(exam); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java index 80bba86c..2037129c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java @@ -8,36 +8,20 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; -import java.util.ArrayList; import java.util.Collection; -import java.util.EnumMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.stream.Collectors; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.*; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import ch.ethz.seb.sebserver.gbl.Constants; -import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService; -import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; -import ch.ethz.seb.sebserver.gbl.client.ProxyData; import ch.ethz.seb.sebserver.gbl.model.Page; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; @@ -48,32 +32,20 @@ public class LmsAPIServiceImpl implements LmsAPIService { private static final Logger log = LoggerFactory.getLogger(LmsAPIServiceImpl.class); - private final WebserviceInfo webserviceInfo; private final LmsSetupDAO lmsSetupDAO; - private final ClientCredentialService clientCredentialService; private final QuizLookupService quizLookupService; - private final EnumMap templateFactories; - - private final Map cache = new ConcurrentHashMap<>(); + private final LmsAPITemplateCacheService lmsAPITemplateCacheService; public LmsAPIServiceImpl( - final WebserviceInfo webserviceInfo, final LmsSetupDAO lmsSetupDAO, - final ClientCredentialService clientCredentialService, final QuizLookupService quizLookupService, - final Collection lmsAPITemplateFactories) { + final LmsAPITemplateCacheService lmsAPITemplateCacheService, + final Collection lmsAPITemplateFactories + ) { - this.webserviceInfo = webserviceInfo; this.lmsSetupDAO = lmsSetupDAO; - this.clientCredentialService = clientCredentialService; this.quizLookupService = quizLookupService; - - final Map factories = lmsAPITemplateFactories - .stream() - .collect(Collectors.toMap( - LmsAPITemplateFactory::lmsType, - Function.identity())); - this.templateFactories = new EnumMap<>(factories); + this.lmsAPITemplateCacheService = lmsAPITemplateCacheService; } /** Listen to LmsSetupChangeEvent to release an affected LmsAPITemplate from cache @@ -90,19 +62,13 @@ public class LmsAPIServiceImpl implements LmsAPIService { log.debug("LmsSetup changed. Update cache by removing eventually used references"); } - final LmsAPITemplate removedTemplate = this.cache.remove( - new CacheKey(lmsSetup.getModelId(), 0)); - - if (removedTemplate != null) { - removedTemplate.clearCourseCache(); - } + lmsAPITemplateCacheService.clearCache(lmsSetup.getModelId()); this.quizLookupService.clear(lmsSetup.institutionId); } @Override public void cleanup() { - this.cache.values().forEach(LmsAPITemplate::dispose); - this.cache.clear(); + lmsAPITemplateCacheService.cleanup(); } @Override @@ -127,258 +93,11 @@ public class LmsAPIServiceImpl implements LmsAPIService { @Override public Result getLmsAPITemplate(final String lmsSetupId) { - return Result.tryCatch(() -> { - synchronized (this) { - final LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetupId); - if (lmsAPITemplate == null) { - return createLmsSetupTemplate(lmsSetupId) - .onError(error -> log.error("Failed to create LMSSetup: ", error)) - .onSuccess(t -> this.cache.put(new CacheKey(lmsSetupId, System.currentTimeMillis()), t)) - .getOrThrow(); - } - return lmsAPITemplate; - } - }); + return lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId); } - @Override - public LmsSetupTestResult test(final LmsAPITemplate template) { - final LmsSetupTestResult testCourseAccessAPI = template.testCourseAccessAPI(); - if (!testCourseAccessAPI.isOk()) { - this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0)); - return testCourseAccessAPI; - } - if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) { - final LmsSetupTestResult lmsSetupTestResult = template.testCourseRestrictionAPI(); - if (!lmsSetupTestResult.isOk()) { - this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0)); - return lmsSetupTestResult; - } - } - if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { - final Long lmsSetupId = template.lmsSetup().id; - final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI(); - if (!lmsSetupTestResult.isOk()) { - this.cache.remove(new CacheKey(template.lmsSetup().getModelId(), 0)); - this.lmsSetupDAO - .setIntegrationActive(lmsSetupId, false) - .onError(er -> log.error("Failed to mark LMS integration inactive", er)); - return lmsSetupTestResult; - } else { -// TODO -// final Result integrationDataResult = fullLmsIntegrationService -// .applyFullLmsIntegration(template.lmsSetup().id); -// -// if (integrationDataResult.hasError()) { -// return LmsSetupTestResult.ofFullIntegrationAPIError( -// template.lmsSetup().lmsType, -// "Failed to apply full LMS integration"); -// } - } - } - return LmsSetupTestResult.ofOkay(template.lmsSetup().getLmsType()); - } - - @Override - public LmsSetupTestResult testAdHoc(final LmsSetup lmsSetup) { - final AdHocAPITemplateDataSupplier apiTemplateDataSupplier = new AdHocAPITemplateDataSupplier( - lmsSetup, - this.clientCredentialService); - - final Result createLmsSetupTemplate = createLmsSetupTemplate(apiTemplateDataSupplier); - if (createLmsSetupTemplate.hasError()) { - return new LmsSetupTestResult( - lmsSetup.lmsType, - new LmsSetupTestResult.Error(ErrorType.TEMPLATE_CREATION, - createLmsSetupTemplate.getError().getMessage())); - - } - final LmsAPITemplate lmsSetupTemplate = createLmsSetupTemplate.get(); - - final LmsSetupTestResult testCourseAccessAPI = lmsSetupTemplate.testCourseAccessAPI(); - if (!testCourseAccessAPI.isOk()) { - return testCourseAccessAPI; - } - - final LmsType lmsType = lmsSetupTemplate.lmsSetup().getLmsType(); - if (lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) { - final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testCourseRestrictionAPI(); - if (!lmsSetupTestResult.isOk()) { - return lmsSetupTestResult; - } - } - - if (lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { - final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testFullIntegrationAPI(); - if (!lmsSetupTestResult.isOk()) { - return lmsSetupTestResult; - } - } - - return LmsSetupTestResult.ofOkay(lmsSetupTemplate.lmsSetup().getLmsType()); - } - - private LmsAPITemplate getFromCache(final String lmsSetupId) { - // first cleanup the cache by removing old instances - final long currentTimeMillis = System.currentTimeMillis(); - new ArrayList<>(this.cache.keySet()) - .stream() - .filter(key -> key.creationTimestamp - currentTimeMillis > Constants.DAY_IN_MILLIS) - .forEach(this.cache::remove); - // get from cache - final CacheKey cacheKey = new CacheKey(lmsSetupId, 0); - final LmsAPITemplate lmsAPITemplate = this.cache.get(cacheKey); - - // in distributed setup, check if lmsSetup is up to date - if (this.webserviceInfo.isDistributed()) { - if (lmsAPITemplate == null) { - return null; - } - - final LmsSetup lmsSetup = lmsAPITemplate.lmsSetup(); - if (!this.lmsSetupDAO.isUpToDate(lmsSetup)) { - this.cache.remove(cacheKey); - this.quizLookupService.clear(lmsSetup.institutionId); - return null; - } - } - - return lmsAPITemplate; - } - - private Result createLmsSetupTemplate(final String lmsSetupId) { - - if (log.isDebugEnabled()) { - log.debug("Create new LmsAPITemplate for id: {}", lmsSetupId); - } - - return createLmsSetupTemplate(new PersistentAPITemplateDataSupplier( - lmsSetupId, - this.lmsSetupDAO)); - } - - private Result createLmsSetupTemplate(final APITemplateDataSupplier apiTemplateDataSupplier) { - - final LmsType lmsType = apiTemplateDataSupplier.getLmsSetup().lmsType; - - if (!this.templateFactories.containsKey(lmsType)) { - throw new UnsupportedOperationException("No support for LMS Type: " + lmsType); - } - - final LmsAPITemplateFactory lmsAPITemplateFactory = this.templateFactories - .get(lmsType); - - return lmsAPITemplateFactory - .create(apiTemplateDataSupplier); - } - - /** Used to always get the actual LMS connection data from persistent */ - private static final class PersistentAPITemplateDataSupplier implements APITemplateDataSupplier { - - private final String lmsSetupId; - private final LmsSetupDAO lmsSetupDAO; - - public PersistentAPITemplateDataSupplier(final String lmsSetupId, final LmsSetupDAO lmsSetupDAO) { - this.lmsSetupId = lmsSetupId; - this.lmsSetupDAO = lmsSetupDAO; - } - - @Override - public LmsSetup getLmsSetup() { - return this.lmsSetupDAO.byModelId(this.lmsSetupId).getOrThrow(); - } - - @Override - public ClientCredentials getLmsClientCredentials() { - return this.lmsSetupDAO.getLmsAPIAccessCredentials(this.lmsSetupId).getOrThrow(); - } - - @Override - public ProxyData getProxyData() { - return this.lmsSetupDAO.getLmsAPIAccessProxyData(this.lmsSetupId).getOr(null); - } - } - - /** Used to test LMS connection data that are not yet persistently stored */ - private static final class AdHocAPITemplateDataSupplier implements APITemplateDataSupplier { - - private final LmsSetup lmsSetup; - private final ClientCredentialService clientCredentialService; - - public AdHocAPITemplateDataSupplier( - final LmsSetup lmsSetup, - final ClientCredentialService clientCredentialService) { - this.lmsSetup = lmsSetup; - this.clientCredentialService = clientCredentialService; - } - - @Override - public LmsSetup getLmsSetup() { - return this.lmsSetup; - } - - @Override - public ClientCredentials getLmsClientCredentials() { - return this.clientCredentialService.encryptClientCredentials( - this.lmsSetup.getLmsAuthName(), - this.lmsSetup.getLmsAuthSecret(), - this.lmsSetup.lmsRestApiToken) - .getOrThrow(); - } - - @Override - public ProxyData getProxyData() { - return (StringUtils.isNoneBlank(this.lmsSetup.proxyHost)) - ? new ProxyData( - this.lmsSetup.proxyHost, - this.lmsSetup.proxyPort, - this.clientCredentialService.encryptClientCredentials( - this.lmsSetup.proxyAuthUsername, - this.lmsSetup.proxyAuthSecret, - this.lmsSetup.lmsRestApiToken) - .getOrThrow()) - : null; - } - } - - private static final class CacheKey { - final String lmsSetupId; - final long creationTimestamp; - final int hash; - - CacheKey(final String lmsSetupId, final long creationTimestamp) { - this.lmsSetupId = lmsSetupId; - this.creationTimestamp = creationTimestamp; - final int prime = 31; - int result = 1; - result = prime * result + ((lmsSetupId == null) ? 0 : lmsSetupId.hashCode()); - this.hash = result; - } - - @Override - public int hashCode() { - return this.hash; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final CacheKey other = (CacheKey) obj; - if (this.lmsSetupId == null) { - if (other.lmsSetupId != null) - return false; - } else if (!this.lmsSetupId.equals(other.lmsSetupId)) - return false; - return true; - } - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateCacheServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateCacheServiceImpl.java new file mode 100644 index 00000000..7b8d3818 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateCacheServiceImpl.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.lms.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService; +import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; +import ch.ethz.seb.sebserver.gbl.client.ProxyData; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +@Lazy +@Service +@WebServiceProfile +public class LmsAPITemplateCacheServiceImpl implements LmsAPITemplateCacheService { + + private static final Logger log = LoggerFactory.getLogger(LmsAPITemplateCacheServiceImpl.class); + private final WebserviceInfo webserviceInfo; + + private final LmsSetupDAO lmsSetupDAO; + private final QuizLookupService quizLookupService; + private final ClientCredentialService clientCredentialService; + private final EnumMap templateFactories; + + private final Map cache = new ConcurrentHashMap<>(); + + public LmsAPITemplateCacheServiceImpl( + final WebserviceInfo webserviceInfo, + final LmsSetupDAO lmsSetupDAO, + final QuizLookupService quizLookupService, + final ClientCredentialService clientCredentialService, + final Collection lmsAPITemplateFactories) { + + this.webserviceInfo = webserviceInfo; + this.lmsSetupDAO = lmsSetupDAO; + this.quizLookupService = quizLookupService; + this.clientCredentialService = clientCredentialService; + final Map factories = lmsAPITemplateFactories + .stream() + .collect(Collectors.toMap( + LmsAPITemplateFactory::lmsType, + Function.identity())); + this.templateFactories = new EnumMap<>(factories); + } + + @Override + public void cleanup() { + this.cache.values().forEach(LmsAPITemplate::dispose); + this.cache.clear(); + } + + @Override + public Result getLmsAPITemplate(final Long lmsSetupId) { + return getLmsAPITemplate(String.valueOf(lmsSetupId)); + } + + @Override + public Result getLmsAPITemplate(final String lmsSetupId) { + return Result.tryCatch(() -> { + synchronized (this) { + final LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetupId); + if (lmsAPITemplate == null) { + return createLmsSetupTemplate(lmsSetupId) + .onError(error -> log.error("Failed to create LMSSetup: ", error)) + .onSuccess(t -> this.cache.put(new CacheKey(lmsSetupId, System.currentTimeMillis()), t)) + .getOrThrow(); + } + return lmsAPITemplate; + } + }); + } + + @Override + public Result getLmsAPITemplateForTesting(final String lmsSetupId) { + return lmsSetupDAO.byModelId(lmsSetupId) + .map(lmsSetup -> new AdHocAPITemplateDataSupplier( + lmsSetup, + this.clientCredentialService)) + .flatMap(this::createLmsSetupTemplate); + } + + @Override + public void clearCache(final String lmsSetupId) { + final LmsAPITemplate removedTemplate = this.cache.remove( + new CacheKey(lmsSetupId, 0)); + + if (removedTemplate != null) { + log.info("Removed LmsAPITemplate from cache: {}", removedTemplate); + removedTemplate.clearCourseCache(); + } + } + + private LmsAPITemplate getFromCache(final String lmsSetupId) { + // first cleanup the cache by removing old instances + final long currentTimeMillis = System.currentTimeMillis(); + new ArrayList<>(this.cache.keySet()) + .stream() + .filter(key -> key.creationTimestamp - currentTimeMillis > Constants.DAY_IN_MILLIS) + .forEach(this.cache::remove); + // get from cache + final CacheKey cacheKey = new CacheKey(lmsSetupId, 0); + final LmsAPITemplate lmsAPITemplate = this.cache.get(cacheKey); + + // in distributed setup, check if lmsSetup is up to date + if (this.webserviceInfo.isDistributed()) { + if (lmsAPITemplate == null) { + return null; + } + + final LmsSetup lmsSetup = lmsAPITemplate.lmsSetup(); + if (!this.lmsSetupDAO.isUpToDate(lmsSetup)) { + this.cache.remove(cacheKey); + this.quizLookupService.clear(lmsSetup.institutionId); + return null; + } + } + + return lmsAPITemplate; + } + + private Result createLmsSetupTemplate(final String lmsSetupId) { + + if (log.isDebugEnabled()) { + log.debug("Create new LmsAPITemplate for id: {}", lmsSetupId); + } + + return createLmsSetupTemplate(new PersistentAPITemplateDataSupplier( + lmsSetupId, + this.lmsSetupDAO)); + } + + private Result createLmsSetupTemplate(final APITemplateDataSupplier apiTemplateDataSupplier) { + + final LmsSetup.LmsType lmsType = apiTemplateDataSupplier.getLmsSetup().lmsType; + + if (!this.templateFactories.containsKey(lmsType)) { + throw new UnsupportedOperationException("No support for LMS Type: " + lmsType); + } + + final LmsAPITemplateFactory lmsAPITemplateFactory = this.templateFactories + .get(lmsType); + + return lmsAPITemplateFactory + .create(apiTemplateDataSupplier); + } + + /** Used to always get the actual LMS connection data from persistent */ + private static final class PersistentAPITemplateDataSupplier implements APITemplateDataSupplier { + + private final String lmsSetupId; + private final LmsSetupDAO lmsSetupDAO; + + public PersistentAPITemplateDataSupplier(final String lmsSetupId, final LmsSetupDAO lmsSetupDAO) { + this.lmsSetupId = lmsSetupId; + this.lmsSetupDAO = lmsSetupDAO; + } + + @Override + public LmsSetup getLmsSetup() { + return this.lmsSetupDAO.byModelId(this.lmsSetupId).getOrThrow(); + } + + @Override + public ClientCredentials getLmsClientCredentials() { + return this.lmsSetupDAO.getLmsAPIAccessCredentials(this.lmsSetupId).getOrThrow(); + } + + @Override + public ProxyData getProxyData() { + return this.lmsSetupDAO.getLmsAPIAccessProxyData(this.lmsSetupId).getOr(null); + } + } + + /** Used to test LMS connection data that are not yet persistently stored */ + private static final class AdHocAPITemplateDataSupplier implements APITemplateDataSupplier { + + private final LmsSetup lmsSetup; + private final ClientCredentialService clientCredentialService; + + public AdHocAPITemplateDataSupplier( + final LmsSetup lmsSetup, + final ClientCredentialService clientCredentialService) { + this.lmsSetup = lmsSetup; + this.clientCredentialService = clientCredentialService; + } + + @Override + public LmsSetup getLmsSetup() { + return this.lmsSetup; + } + + @Override + public ClientCredentials getLmsClientCredentials() { + return this.clientCredentialService.encryptClientCredentials( + this.lmsSetup.getLmsAuthName(), + this.lmsSetup.getLmsAuthSecret(), + this.lmsSetup.lmsRestApiToken) + .getOrThrow(); + } + + @Override + public ProxyData getProxyData() { + return (StringUtils.isNoneBlank(this.lmsSetup.proxyHost)) + ? new ProxyData( + this.lmsSetup.proxyHost, + this.lmsSetup.proxyPort, + this.clientCredentialService.encryptClientCredentials( + this.lmsSetup.proxyAuthUsername, + this.lmsSetup.proxyAuthSecret, + this.lmsSetup.lmsRestApiToken) + .getOrThrow()) + : null; + } + } + + private static final class CacheKey { + final String lmsSetupId; + final long creationTimestamp; + final int hash; + + CacheKey(final String lmsSetupId, final long creationTimestamp) { + this.lmsSetupId = lmsSetupId; + this.creationTimestamp = creationTimestamp; + final int prime = 31; + int result = 1; + result = prime * result + ((lmsSetupId == null) ? 0 : lmsSetupId.hashCode()); + this.hash = result; + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final CacheKey other = (CacheKey) obj; + if (this.lmsSetupId == null) { + if (other.lmsSetupId != null) + return false; + } else if (!this.lmsSetupId.equals(other.lmsSetupId)) + return false; + return true; + } + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsTestServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsTestServiceImpl.java new file mode 100644 index 00000000..43f3404c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsTestServiceImpl.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 ETH Zürich, IT Services + * + * 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.lms.impl; + +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateCacheService; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +@Lazy +@Service +@WebServiceProfile +public class LmsTestServiceImpl implements LmsTestService { + + private static final Logger log = LoggerFactory.getLogger(LmsTestServiceImpl.class); + private final LmsSetupDAO lmsSetupDAO; + private final LmsAPITemplateCacheService lmsAPITemplateCacheService; + private final FullLmsIntegrationService fullLmsIntegrationService; + + public LmsTestServiceImpl( + final LmsSetupDAO lmsSetupDAO, + final LmsAPITemplateCacheService lmsAPITemplateCacheService, + final FullLmsIntegrationService fullLmsIntegrationService) { + + this.lmsSetupDAO = lmsSetupDAO; + this.lmsAPITemplateCacheService = lmsAPITemplateCacheService; + this.fullLmsIntegrationService = fullLmsIntegrationService; + } + + @Override + public LmsSetupTestResult test(final LmsAPITemplate template) { + final LmsSetupTestResult testCourseAccessAPI = template.testCourseAccessAPI(); + if (!testCourseAccessAPI.isOk()) { + lmsAPITemplateCacheService.clearCache(template.lmsSetup().getModelId()); + return testCourseAccessAPI; + } + + if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) { + final LmsSetupTestResult lmsSetupTestResult = template.testCourseRestrictionAPI(); + if (!lmsSetupTestResult.isOk()) { + lmsAPITemplateCacheService.clearCache(template.lmsSetup().getModelId()); + return lmsSetupTestResult; + } + } + + if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { + final Long lmsSetupId = template.lmsSetup().id; + final LmsSetupTestResult lmsSetupTestResult = template.testFullIntegrationAPI(); + if (!lmsSetupTestResult.isOk()) { + lmsAPITemplateCacheService.clearCache(template.lmsSetup().getModelId()); + this.lmsSetupDAO + .setIntegrationActive(lmsSetupId, false) + .onError(er -> log.error("Failed to mark LMS integration inactive", er)); + return lmsSetupTestResult; + } else { + + final Result integrationDataResult = fullLmsIntegrationService + .applyFullLmsIntegration(template.lmsSetup().id); + + if (integrationDataResult.hasError()) { + return LmsSetupTestResult.ofFullIntegrationAPIError( + template.lmsSetup().lmsType, + "Failed to apply full LMS integration"); + } + } + } + + return LmsSetupTestResult.ofOkay(template.lmsSetup().getLmsType()); + } + + @Override + public LmsSetupTestResult testAdHoc(final LmsSetup lmsSetup) { + + final Result createLmsSetupTemplate = lmsAPITemplateCacheService + .getLmsAPITemplateForTesting(lmsSetup.getModelId()); + if (createLmsSetupTemplate.hasError()) { + return new LmsSetupTestResult( + lmsSetup.lmsType, + new LmsSetupTestResult.Error(LmsSetupTestResult.ErrorType.TEMPLATE_CREATION, + createLmsSetupTemplate.getError().getMessage())); + + } + final LmsAPITemplate lmsSetupTemplate = createLmsSetupTemplate.get(); + + final LmsSetupTestResult testCourseAccessAPI = lmsSetupTemplate.testCourseAccessAPI(); + if (!testCourseAccessAPI.isOk()) { + return testCourseAccessAPI; + } + + final LmsSetup.LmsType lmsType = lmsSetupTemplate.lmsSetup().getLmsType(); + if (lmsType.features.contains(LmsSetup.Features.SEB_RESTRICTION)) { + final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testCourseRestrictionAPI(); + if (!lmsSetupTestResult.isOk()) { + return lmsSetupTestResult; + } + } + + if (lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) { + final LmsSetupTestResult lmsSetupTestResult = lmsSetupTemplate.testFullIntegrationAPI(); + if (!lmsSetupTestResult.isOk()) { + return lmsSetupTestResult; + } + } + + return LmsSetupTestResult.ofOkay(lmsSetupTemplate.lmsSetup().getLmsType()); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java index f9d42be0..770e27ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java @@ -19,6 +19,7 @@ import java.util.Set; import java.util.stream.Collectors; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,17 +59,20 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService { private final LmsAPIService lmsAPIService; private final AdditionalAttributesDAO additionalAttributesDAO; private final ExamConfigService examConfigService; + private final ExamConfigurationValueService examConfigurationValueService; protected SEBRestrictionServiceImpl( final ExamDAO examDAO, final LmsAPIService lmsAPIService, final AdditionalAttributesDAO additionalAttributesDAO, - final ExamConfigService examConfigService) { + final ExamConfigService examConfigService, + final ExamConfigurationValueService examConfigurationValueService) { this.examDAO = examDAO; this.lmsAPIService = lmsAPIService; this.additionalAttributesDAO = additionalAttributesDAO; this.examConfigService = examConfigService; + this.examConfigurationValueService = examConfigurationValueService; } @Override @@ -94,6 +98,25 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService { return true; } + @Override + public Result applyQuitPassword(final Exam exam) { + return this.examConfigurationValueService + .applyQuitPasswordToConfigs(exam.id, exam.quitPassword) + .map(id -> applySEBRestrictionIfExamRunning(exam)) + .onError(t -> log.error("Failed to quit password for Exam: {}", exam, t)); + } + + private Exam applySEBRestrictionIfExamRunning(final Exam exam) { + if (exam.status != Exam.ExamStatus.RUNNING) { + return exam; + } + + return this.applySEBClientRestriction(exam) + .flatMap(e -> this.examDAO.setSEBRestriction(e.id, true)) + .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t)) + .getOr(exam); + } + @Override @Transactional public Result getSEBRestrictionFromExam(final Exam exam) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleAPIRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleAPIRestTemplate.java index 46d7ae6d..6dfb31d1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleAPIRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleAPIRestTemplate.java @@ -8,6 +8,8 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle; +import java.util.Map; + import org.springframework.util.MultiValueMap; import com.fasterxml.jackson.annotation.JsonCreator; @@ -37,7 +39,10 @@ public interface MoodleAPIRestTemplate { String callMoodleAPIFunction(String functionName); - String postToMoodleAPIFunction(String functionName, String body); + String postToMoodleAPIFunction( + String functionName, + MultiValueMap queryParams, + Map> queryAttributes); String callMoodleAPIFunction( String functionName, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactoryImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactoryImpl.java index 0b36cac5..1a26770b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactoryImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactoryImpl.java @@ -320,7 +320,10 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory } @Override - public String postToMoodleAPIFunction(final String functionName, final String body) { + public String postToMoodleAPIFunction( + final String functionName, + final MultiValueMap queryParams, + final Map> queryAttributes) { getAccessToken(); final UriComponentsBuilder queryParam = UriComponentsBuilder @@ -329,15 +332,35 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory .queryParam(REST_REQUEST_FUNCTION_NAME, functionName) .queryParam(REST_REQUEST_FORMAT_NAME, "json"); + if (queryParams != null && !queryParams.isEmpty()) { + queryParam.queryParams(queryParams); + } + + final String body = createMoodleFormPostBody(queryAttributes); + final HttpHeaders headers = new HttpHeaders(); headers.set( HttpHeaders.CONTENT_TYPE, - MediaType.APPLICATION_JSON_VALUE); + MediaType.APPLICATION_FORM_URLENCODED_VALUE); final HttpEntity httpEntity = new HttpEntity<>(body, headers); return doRequest(functionName, queryParam, true, httpEntity); } + private String createMoodleFormPostBody(final Map> queryAttributes) { + if (queryAttributes == null) { + return null; + } + + final StringBuffer sb = new StringBuffer(); + queryAttributes.forEach( + (name1, value1) -> value1.forEach( + (key, value) -> sb.append(name1).append("[").append(key).append("]=").append(value).append("&"))); + + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + @Override public String callMoodleAPIFunction( final String functionName, 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 bc93b77c..2daca0f5 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 @@ -8,6 +8,9 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin; +import java.util.HashMap; +import java.util.Map; + import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; @@ -21,6 +24,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRe import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleResponseException; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +39,8 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { private static final String FUNCTION_NAME_SEBSERVER_CONNECTION = "quizaccess_sebserver_connection"; private static final String FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE = "quizaccess_sebserver_connection_delete"; private static final String FUNCTION_NAME_SET_EXAM_DATA = "quizaccess_sebserver_set_exam_data"; + private static final String ATTRIBUTE_CONNECTION = "connection"; + private static final String ATTRIBUTE_EXAM_DATA = "data"; private static final String UPLOAD_ENDPOINT = "/mod/quiz/accessrule/sebserver/uploadconfig.php"; @@ -94,14 +100,20 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { // apply final LmsSetup lmsSetup = this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup(); - final String jsonPayload = jsonMapper.writeValueAsString(data); + final String connectionJSON = jsonMapper.writeValueAsString(data); final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow(); //if (log.isDebugEnabled()) { - log.info("Try to connect to Moodle Plugin 2.0 with: {}", jsonPayload); + log.info("Try to connect to Moodle Plugin 2.0 with: {}", connectionJSON); //} - final String response = rest.postToMoodleAPIFunction(FUNCTION_NAME_SEBSERVER_CONNECTION, jsonPayload); + final MultiValueMap queryAttributes = new LinkedMultiValueMap<>(); + queryAttributes.add(ATTRIBUTE_CONNECTION, connectionJSON); + + final String response = rest.postToMoodleAPIFunction( + FUNCTION_NAME_SEBSERVER_CONNECTION, + queryAttributes, + null); if (response != null && response.startsWith("{\"exception\":")) { // Seems there was an error response from Moodle side. @@ -141,9 +153,31 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI { } final LmsSetup lmsSetup = this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup(); - final String jsonPayload = jsonMapper.writeValueAsString(examData); final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow(); - final String response = rest.postToMoodleAPIFunction(FUNCTION_NAME_SET_EXAM_DATA, jsonPayload); + + final Map> attributes = new HashMap<>(); + final Map data_mapping = new HashMap<>(); + attributes.put(ATTRIBUTE_EXAM_DATA, data_mapping); + + // data[quizid]= int + // data[addordelete]= int + // data[templateid]= int + // data[showquitlink]= int + // 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" : "2"); + data_mapping.put("quitsecret", examData.quit_password); + } else { + data_mapping.put("addordelete", "0"); + } + + final String response = rest.postToMoodleAPIFunction( + FUNCTION_NAME_SET_EXAM_DATA, + null, + attributes); if (response != null && (response.startsWith("{\"exception\":") || response.startsWith("0"))) { log.warn("Failed to apply Exam data to moodle: {}", examData); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java index d0f3f557..4d379f5b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java @@ -126,14 +126,6 @@ public class ExamSessionCacheService { case RUNNING: { return true; } - case UP_COMING: - case FINISHED: { - return false; - // TODO do we really need to double-check here? -// return this.examUpdateHandler.updateRunning(exam.id) -// .map(e -> e.status == ExamStatus.RUNNING) -// .getOr(false); - } default: { return false; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java index 12180649..62e72c07 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java @@ -23,6 +23,8 @@ import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -52,7 +54,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @@ -62,7 +63,6 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; -import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringService; @Lazy @@ -104,7 +104,8 @@ public class JitsiProctoringService implements RemoteProctoringService { .stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2))); private final AuthorizationService authorizationService; - private final ExamSessionService examSessionService; + private final ClientConnectionDAO clientConnectionDAO; + private final ExamSessionCacheService examSessionCacheService; private final Cryptor cryptor; private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; private final JSONMapper jsonMapper; @@ -112,14 +113,16 @@ public class JitsiProctoringService implements RemoteProctoringService { protected JitsiProctoringService( final AuthorizationService authorizationService, - final ExamSessionService examSessionService, + final ClientConnectionDAO clientConnectionDAO, + final ExamSessionCacheService examSessionCacheService, final Cryptor cryptor, final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final JSONMapper jsonMapper, final WebserviceInfo webserviceInfo) { this.authorizationService = authorizationService; - this.examSessionService = examSessionService; + this.clientConnectionDAO = clientConnectionDAO; + this.examSessionCacheService = examSessionCacheService; this.cryptor = cryptor; this.clientHttpRequestFactoryService = clientHttpRequestFactoryService; this.jsonMapper = jsonMapper; @@ -310,8 +313,9 @@ public class JitsiProctoringService implements RemoteProctoringService { final String subject) { return Result.tryCatch(() -> { - final ClientConnectionData clientConnection = this.examSessionService - .getConnectionData(connectionToken) + + final ClientConnection clientConnection = clientConnectionDAO + .byConnectionToken(connectionToken) .getOrThrow(); return createProctoringConnection( @@ -319,7 +323,7 @@ public class JitsiProctoringService implements RemoteProctoringService { proctoringSettings.serverURL, proctoringSettings.appKey, proctoringSettings.getAppSecret(), - clientConnection.clientConnection.userSessionId, + clientConnection.userSessionId, SEB_CLIENT_KEY, roomName, subject, @@ -476,13 +480,12 @@ public class JitsiProctoringService implements RemoteProctoringService { } long expTime = System.currentTimeMillis() + Constants.DAY_IN_MILLIS; - if (this.examSessionService.isExamRunning(examProctoring.examId)) { - final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId) - .getOrThrow(); - if (exam.endTime != null) { - expTime = exam.endTime.getMillis(); - } + + final Exam runningExam = examSessionCacheService.getRunningExam(examProctoring.examId); + if (runningExam != null && runningExam.endTime != null) { + expTime = runningExam.endTime.getMillis(); } + return expTime; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java index 00daba5a..d46a6141 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java @@ -26,6 +26,8 @@ import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -134,7 +136,7 @@ public class ZoomProctoringService implements RemoteProctoringService { Constants.FALSE_STRING)) .stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2))); - private final ExamSessionService examSessionService; + private final ExamSessionCacheService examSessionCacheService; private final Cryptor cryptor; private final AsyncService asyncService; private final JSONMapper jsonMapper; @@ -146,7 +148,7 @@ public class ZoomProctoringService implements RemoteProctoringService { private final int tokenExpirySeconds; public ZoomProctoringService( - final ExamSessionService examSessionService, + final ExamSessionCacheService examSessionCacheService, final Cryptor cryptor, final AsyncService asyncService, final JSONMapper jsonMapper, @@ -157,7 +159,7 @@ public class ZoomProctoringService implements RemoteProctoringService { @Value("${sebserver.webservice.proctoring.sendRejoinForCollectingRoom:false}") final boolean sendRejoinForCollectingRoom, @Value("${sebserver.webservice.proctoring.zoom.tokenexpiry.seconds:86400}") final int tokenExpirySeconds) { - this.examSessionService = examSessionService; + this.examSessionCacheService = examSessionCacheService; this.cryptor = cryptor; this.asyncService = asyncService; this.jsonMapper = jsonMapper; @@ -326,9 +328,7 @@ public class ZoomProctoringService implements RemoteProctoringService { remoteProctoringRoom.additionalRoomData, AdditionalZoomRoomData.class); - final ClientConnectionData clientConnection = this.examSessionService - .getConnectionData(connectionToken) - .getOrThrow(); + final ClientConnectionData clientConnection = this.examSessionCacheService.getClientConnection(connectionToken); // Note: since SEB Server version 1.5 we work only with SDKKey instead of AppKey which is deprecated final ClientCredentials sdkCredentials = new ClientCredentials( @@ -354,7 +354,7 @@ public class ZoomProctoringService implements RemoteProctoringService { sdkCredentials.accessToken, sdkCredentials.clientId, String.valueOf(additionalZoomRoomData.meeting_id), - clientConnection.clientConnection.userSessionId, + (clientConnection != null) ? clientConnection.clientConnection.userSessionId : "Unknown", remoteProctoringRoom.additionalRoomData); }); } @@ -405,10 +405,11 @@ public class ZoomProctoringService implements RemoteProctoringService { private int getMeetingDuration(final Long examId) { try { - final DateTime endTime = this.examSessionService - .getRunningExam(examId) - .getOrThrow() - .getEndTime(); + + ; + + final DateTime endTime = examSessionCacheService + .getRunningExam(examId).endTime; final Long result = new Interval(DateTime.now(DateTimeZone.UTC), endTime) .toDurationMillis() / Constants.MINUTE_IN_MILLIS; return result.intValue(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java index 9e2753aa..1b2c0588 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import javax.validation.Valid; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService; import org.mybatis.dynamic.sql.SqlTable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.MediaType; @@ -50,6 +51,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe public class LmsSetupController extends ActivatableEntityController { private final LmsAPIService lmsAPIService; + private final LmsTestService lmsTestService; final ApplicationEventPublisher applicationEventPublisher; public LmsSetupController( @@ -60,6 +62,7 @@ public class LmsSetupController extends ActivatableEntityController { final LmsType lmsType = this.entityDAO.byPK(modelId).get().lmsType; return new LmsSetupTestResult( @@ -122,7 +126,7 @@ public class LmsSetupController extends ActivatableEntityController queryParams, + final Map> queryAttributes) { return null; } + @Override public String callMoodleAPIFunction( final String functionName, diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java index afd9a656..747f44fc 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamJITSIProctoringServiceTest.java @@ -14,6 +14,7 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Collections; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService; import org.junit.Test; import org.mockito.Mockito; @@ -84,7 +85,7 @@ public class ExamJITSIProctoringServiceTest { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); final JitsiProctoringService examJITSIProctoringService = - new JitsiProctoringService(null, null, cryptorMock, null, new JSONMapper(), null); + new JitsiProctoringService(null, null, null, cryptorMock, null, new JSONMapper(), null); String accessToken = examJITSIProctoringService.createPayload( "test-app", @@ -118,7 +119,7 @@ public class ExamJITSIProctoringServiceTest { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123")); final JitsiProctoringService examJITSIProctoringService = - new JitsiProctoringService(null, null, cryptorMock, null, new JSONMapper(), null); + new JitsiProctoringService(null, null, null, cryptorMock, null, new JSONMapper(), null); final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection( "connectionToken", "https://seb-jitsi.example.ch", @@ -154,7 +155,7 @@ public class ExamJITSIProctoringServiceTest { final AuthorizationService authorizationService = Mockito.mock(AuthorizationService.class); Mockito.when(authorizationService.getUserService()).thenReturn(userService); - final ExamSessionService examSessionService = Mockito.mock(ExamSessionService.class); + final ExamSessionCacheService examSessionCacheService = Mockito.mock(ExamSessionCacheService.class); final Cryptor cryptor = Mockito.mock(Cryptor.class); Mockito.when(cryptor.decrypt(Mockito.any())).thenReturn(Result.of("pwd")); @@ -164,7 +165,8 @@ public class ExamJITSIProctoringServiceTest { return new JitsiProctoringService( authorizationService, - examSessionService, + null, + examSessionCacheService, cryptor, clientHttpRequestFactoryService, jsonMapper, null);