SEBSERV-417 new Moodle POST API calls
This commit is contained in:
parent
17400023f3
commit
db4411c52e
21 changed files with 674 additions and 406 deletions
|
@ -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.IndicatorDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
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.dao.UserActivityLogDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
|
@ -53,7 +52,6 @@ public class DeleteExamAction implements BatchActionExec {
|
||||||
private final IndicatorDAO indicatorDAO;
|
private final IndicatorDAO indicatorDAO;
|
||||||
private final AuthorizationService authorization;
|
private final AuthorizationService authorization;
|
||||||
private final UserActivityLogDAO userActivityLogDAO;
|
private final UserActivityLogDAO userActivityLogDAO;
|
||||||
private final ExamSessionService examSessionService;
|
|
||||||
|
|
||||||
public DeleteExamAction(
|
public DeleteExamAction(
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
|
@ -62,8 +60,7 @@ public class DeleteExamAction implements BatchActionExec {
|
||||||
final ClientGroupDAO clientGroupDAO,
|
final ClientGroupDAO clientGroupDAO,
|
||||||
final IndicatorDAO indicatorDAO,
|
final IndicatorDAO indicatorDAO,
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
final UserActivityLogDAO userActivityLogDAO,
|
final UserActivityLogDAO userActivityLogDAO) {
|
||||||
final ExamSessionService examSessionService) {
|
|
||||||
|
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.clientConnectionDAO = clientConnectionDAO;
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
|
@ -72,7 +69,6 @@ public class DeleteExamAction implements BatchActionExec {
|
||||||
this.indicatorDAO = indicatorDAO;
|
this.indicatorDAO = indicatorDAO;
|
||||||
this.authorization = authorization;
|
this.authorization = authorization;
|
||||||
this.userActivityLogDAO = userActivityLogDAO;
|
this.userActivityLogDAO = userActivityLogDAO;
|
||||||
this.examSessionService = examSessionService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,13 +145,23 @@ public class DeleteExamAction implements BatchActionExec {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Exam> checkNoActiveSEBClientConnections(final Exam exam) {
|
private Result<Exam> checkNoActiveSEBClientConnections(final Exam exam) {
|
||||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
if (exam.status != Exam.ExamStatus.RUNNING) {
|
||||||
return Result.ofError(new APIMessageException(
|
return Result.of(exam);
|
||||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
|
||||||
.of("Exam currently has active SEB Client connections.")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<Exam> logDeleted(final Exam entity, final BatchAction batchAction) {
|
private Result<Exam> logDeleted(final Exam entity, final BatchAction batchAction) {
|
||||||
|
|
|
@ -48,7 +48,6 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
|
|
||||||
private final ExamConfigurationValueService examConfigurationValueService;
|
private final ExamConfigurationValueService examConfigurationValueService;
|
||||||
private final SEBRestrictionService sebRestrictionService;
|
private final SEBRestrictionService sebRestrictionService;
|
||||||
|
|
||||||
|
@ -248,26 +247,9 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> applyQuitPassword(final Exam exam) {
|
public Result<Exam> applyQuitPassword(final Exam exam) {
|
||||||
return this.examConfigurationValueService
|
return this.sebRestrictionService.applyQuitPassword(exam);
|
||||||
.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 != 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
|
@Override
|
||||||
public Result<Exam> archiveExam(final Exam exam) {
|
public Result<Exam> archiveExam(final Exam exam) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
|
@ -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.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
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.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.ExamImportService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
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.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
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 static final Logger log = LoggerFactory.getLogger(ExamImportServiceImpl.class);
|
||||||
|
|
||||||
private final ExamDAO examDAO;
|
private final ExamDAO examDAO;
|
||||||
// private final FullLmsIntegrationService fullLmsIntegrationService;
|
|
||||||
private final ExamTemplateService examTemplateService;
|
private final ExamTemplateService examTemplateService;
|
||||||
private final ExamAdminService examAdminService;
|
private final SEBRestrictionService sebRestrictionService;
|
||||||
private final boolean appSignatureKeyEnabled;
|
private final boolean appSignatureKeyEnabled;
|
||||||
private final int defaultNumericalTrustThreshold;
|
private final int defaultNumericalTrustThreshold;
|
||||||
|
|
||||||
public ExamImportServiceImpl(
|
public ExamImportServiceImpl(
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
final ExamTemplateService examTemplateService,
|
final ExamTemplateService examTemplateService,
|
||||||
final ExamAdminService examAdminService,
|
final SEBRestrictionService sebRestrictionService,
|
||||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||||
final LmsAPIService lmsAPIService,
|
final LmsAPIService lmsAPIService,
|
||||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled,
|
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.examDAO = examDAO;
|
||||||
this.examTemplateService = examTemplateService;
|
this.examTemplateService = examTemplateService;
|
||||||
this.examAdminService = examAdminService;
|
this.sebRestrictionService = sebRestrictionService;
|
||||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
this.appSignatureKeyEnabled = appSignatureKeyEnabled;
|
this.appSignatureKeyEnabled = appSignatureKeyEnabled;
|
||||||
|
@ -102,7 +99,7 @@ public class ExamImportServiceImpl implements ExamImportService {
|
||||||
})
|
})
|
||||||
.flatMap(this::applyAdditionalSEBRestrictions)
|
.flatMap(this::applyAdditionalSEBRestrictions)
|
||||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_AUTO_RESTRICTION.of(error)))
|
.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)))
|
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_QUIT_PASSWORD.of(error)))
|
||||||
.flatMap(examTemplateService::applyScreenProctoringSettingsForExam)
|
.flatMap(examTemplateService::applyScreenProctoringSettingsForExam)
|
||||||
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_SCREEN_PROCTORING_SETTINGS.of(error)));
|
.onError(error -> errors.add(APIMessage.ErrorMessage.EXAM_IMPORT_ERROR_SCREEN_PROCTORING_SETTINGS.of(error)));
|
||||||
|
|
|
@ -72,18 +72,7 @@ public interface LmsAPIService {
|
||||||
* @return LmsAPITemplate for specified LmsSetup configuration */
|
* @return LmsAPITemplate for specified LmsSetup configuration */
|
||||||
Result<LmsAPITemplate> getLmsAPITemplate(String lmsSetupId);
|
Result<LmsAPITemplate> 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
|
/** Get a LmsAPITemplate for specified LmsSetup configuration by primary key
|
||||||
*
|
*
|
||||||
|
|
|
@ -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<LmsAPITemplate> 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<LmsAPITemplate> getLmsAPITemplate(String lmsSetupId);
|
||||||
|
|
||||||
|
Result<LmsAPITemplate> getLmsAPITemplateForTesting(String lmsSetupId);
|
||||||
|
|
||||||
|
void clearCache(String lmsSetupId);
|
||||||
|
|
||||||
|
/** Reset and cleanup the caches if there are some */
|
||||||
|
void cleanup();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ public interface SEBRestrictionService {
|
||||||
Result<SEBRestriction> getSEBRestrictionFromExam(Exam exam);
|
Result<SEBRestriction> getSEBRestrictionFromExam(Exam exam);
|
||||||
|
|
||||||
/** Saves the given SEBRestriction for the given Exam.
|
/** Saves the given SEBRestriction for the given Exam.
|
||||||
*
|
* <p>
|
||||||
* The webservice saves the given browser Exam keys within the Exam record
|
* The webservice saves the given browser Exam keys within the Exam record
|
||||||
* and given additional restriction properties within the Additional attributes linked
|
* and given additional restriction properties within the Additional attributes linked
|
||||||
* to the given Exam.
|
* to the given Exam.
|
||||||
|
@ -65,4 +65,6 @@ public interface SEBRestrictionService {
|
||||||
* to the LMS */
|
* to the LMS */
|
||||||
boolean checkSebRestrictionSet(Exam exam);
|
boolean checkSebRestrictionSet(Exam exam);
|
||||||
|
|
||||||
|
Result<Exam> applyQuitPassword(final Exam exam);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.ExamImportService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateChangeEvent;
|
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.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.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.lms.impl.moodle.MoodleUtils;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationService;
|
||||||
|
@ -76,9 +76,9 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
private final SEBClientConfigDAO sebClientConfigDAO;
|
private final SEBClientConfigDAO sebClientConfigDAO;
|
||||||
private final ConnectionConfigurationService connectionConfigurationService;
|
private final ConnectionConfigurationService connectionConfigurationService;
|
||||||
private final DeleteExamAction deleteExamAction;
|
private final DeleteExamAction deleteExamAction;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPITemplateCacheService lmsAPITemplateCacheService;
|
||||||
private final ExamImportService examImportService;
|
private final ExamImportService examImportService;
|
||||||
private final ExamSessionService examSessionService;
|
private final ClientConnectionDAO clientConnectionDAO;
|
||||||
private final ExamConfigurationValueService examConfigurationValueService;
|
private final ExamConfigurationValueService examConfigurationValueService;
|
||||||
private final ExamDAO examDAO;
|
private final ExamDAO examDAO;
|
||||||
private final ExamTemplateDAO examTemplateDAO;
|
private final ExamTemplateDAO examTemplateDAO;
|
||||||
|
@ -96,16 +96,16 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
final ScreenProctoringService screenProctoringService,
|
final ScreenProctoringService screenProctoringService,
|
||||||
final ConnectionConfigurationService connectionConfigurationService,
|
final ConnectionConfigurationService connectionConfigurationService,
|
||||||
final DeleteExamAction deleteExamAction,
|
final DeleteExamAction deleteExamAction,
|
||||||
final LmsAPIService lmsAPIService,
|
|
||||||
final ExamSessionService examSessionService,
|
|
||||||
final ExamConfigurationValueService examConfigurationValueService,
|
final ExamConfigurationValueService examConfigurationValueService,
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
final ExamImportService examImportService,
|
final ExamImportService examImportService,
|
||||||
final ExamTemplateDAO examTemplateDAO,
|
final ExamTemplateDAO examTemplateDAO,
|
||||||
final WebserviceInfo webserviceInfo,
|
final WebserviceInfo webserviceInfo,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
final UserService userService,
|
final UserService userService,
|
||||||
final TeacherAccountServiceImpl teacherAccountServiceImpl,
|
final TeacherAccountServiceImpl teacherAccountServiceImpl,
|
||||||
|
final LmsAPITemplateCacheService lmsAPITemplateCacheService,
|
||||||
@Value("${sebserver.webservice.lms.api.endpoint}") final String lmsAPIEndpoint,
|
@Value("${sebserver.webservice.lms.api.endpoint}") final String lmsAPIEndpoint,
|
||||||
@Value("${sebserver.webservice.lms.api.clientId}") final String clientId,
|
@Value("${sebserver.webservice.lms.api.clientId}") final String clientId,
|
||||||
@Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret) {
|
@Value("${sebserver.webservice.api.admin.clientSecret}") final String clientSecret) {
|
||||||
|
@ -116,8 +116,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
this.sebClientConfigDAO = sebClientConfigDAO;
|
this.sebClientConfigDAO = sebClientConfigDAO;
|
||||||
this.connectionConfigurationService = connectionConfigurationService;
|
this.connectionConfigurationService = connectionConfigurationService;
|
||||||
this.deleteExamAction = deleteExamAction;
|
this.deleteExamAction = deleteExamAction;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPITemplateCacheService = lmsAPITemplateCacheService;
|
||||||
this.examSessionService = examSessionService;
|
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.examTemplateDAO = examTemplateDAO;
|
this.examTemplateDAO = examTemplateDAO;
|
||||||
this.webserviceInfo = webserviceInfo;
|
this.webserviceInfo = webserviceInfo;
|
||||||
|
@ -125,6 +124,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.examConfigurationValueService = examConfigurationValueService;
|
this.examConfigurationValueService = examConfigurationValueService;
|
||||||
this.examImportService = examImportService;
|
this.examImportService = examImportService;
|
||||||
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
|
|
||||||
resource = new ClientCredentialsResourceDetails();
|
resource = new ClientCredentialsResourceDetails();
|
||||||
resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI());
|
resource.setAccessTokenUri(webserviceInfo.getOAuthTokenURI());
|
||||||
|
@ -246,7 +246,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
this.getIntegrationTemplates(lmsSetup.institutionId)
|
this.getIntegrationTemplates(lmsSetup.institutionId)
|
||||||
);
|
);
|
||||||
|
|
||||||
return lmsAPIService.getLmsAPITemplate(lmsSetupId)
|
return lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.applyConnectionDetails(data)
|
.applyConnectionDetails(data)
|
||||||
.onError(error -> lmsSetupDAO
|
.onError(error -> lmsSetupDAO
|
||||||
|
@ -271,7 +271,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lmsAPIService.getLmsAPITemplate(lmsSetupId)
|
lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.deleteConnectionDetails()
|
.deleteConnectionDetails()
|
||||||
.onError(error -> lmsSetupDAO
|
.onError(error -> lmsSetupDAO
|
||||||
|
@ -297,7 +297,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
|
|
||||||
return lmsSetupDAO
|
return lmsSetupDAO
|
||||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
.flatMap(lmsAPITemplateCacheService::getLmsAPITemplate)
|
||||||
.map(findQuizData(courseId, quizId))
|
.map(findQuizData(courseId, quizId))
|
||||||
.map(createExam(examTemplateId, quitPassword))
|
.map(createExam(examTemplateId, quitPassword))
|
||||||
.map(exam -> applyExamData(exam, false))
|
.map(exam -> applyExamData(exam, false))
|
||||||
|
@ -312,7 +312,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
|
|
||||||
return lmsSetupDAO
|
return lmsSetupDAO
|
||||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
.flatMap(lmsAPITemplateCacheService::getLmsAPITemplate)
|
||||||
.map(findQuizData(courseId, quizId))
|
.map(findQuizData(courseId, quizId))
|
||||||
.flatMap(this::findExam)
|
.flatMap(this::findExam)
|
||||||
.map(this::checkDeletion)
|
.map(this::checkDeletion)
|
||||||
|
@ -384,7 +384,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
|
|
||||||
return lmsSetupDAO
|
return lmsSetupDAO
|
||||||
.getLmsSetupIdByConnectionId(lmsUUID)
|
.getLmsSetupIdByConnectionId(lmsUUID)
|
||||||
.flatMap(lmsAPIService::getLmsAPITemplate)
|
.flatMap(lmsAPITemplateCacheService::getLmsAPITemplate)
|
||||||
.map(findQuizData(courseId, quizId))
|
.map(findQuizData(courseId, quizId))
|
||||||
.flatMap(this::findExam)
|
.flatMap(this::findExam)
|
||||||
.flatMap(exam -> this.teacherAccountServiceImpl
|
.flatMap(exam -> this.teacherAccountServiceImpl
|
||||||
|
@ -470,14 +470,32 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
private Exam checkDeletion(final Exam exam) {
|
private Exam checkDeletion(final Exam exam) {
|
||||||
// TODO check if Exam can be deleted according to the Spec
|
// TODO check if Exam can be deleted according to the Spec
|
||||||
|
|
||||||
// check if there are no active SEB client connections
|
if (exam.status != Exam.ExamStatus.RUNNING) {
|
||||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
return exam;
|
||||||
throw new APIMessage.APIMessageException(
|
|
||||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
|
||||||
.of("Exam currently has active SEB Client connections."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
private Exam applyExamData(final Exam exam, final boolean deletion) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final LmsAPITemplate lmsAPITemplate = lmsAPIService
|
final LmsAPITemplate lmsAPITemplate = lmsAPITemplateCacheService
|
||||||
.getLmsAPITemplate(exam.lmsSetupId)
|
.getLmsAPITemplate(exam.lmsSetupId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
final String lmsUUID = lmsAPITemplate.lmsSetup().connectionId;
|
final String lmsUUID = lmsAPITemplate.lmsSetup().connectionId;
|
||||||
|
@ -526,7 +544,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
|
||||||
}
|
}
|
||||||
|
|
||||||
private Exam applyConnectionConfiguration(final Exam exam) {
|
private Exam applyConnectionConfiguration(final Exam exam) {
|
||||||
return lmsAPIService
|
return lmsAPITemplateCacheService
|
||||||
.getLmsAPITemplate(exam.lmsSetupId)
|
.getLmsAPITemplate(exam.lmsSetupId)
|
||||||
.flatMap(template -> {
|
.flatMap(template -> {
|
||||||
final String connectionConfigId = getConnectionConfigurationId(exam);
|
final String connectionConfigId = getConnectionConfigurationId(exam);
|
||||||
|
|
|
@ -8,36 +8,20 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
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 ch.ethz.seb.sebserver.webservice.servicelayer.lms.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Service;
|
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.Page;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
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;
|
||||||
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.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
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 static final Logger log = LoggerFactory.getLogger(LmsAPIServiceImpl.class);
|
||||||
|
|
||||||
private final WebserviceInfo webserviceInfo;
|
|
||||||
private final LmsSetupDAO lmsSetupDAO;
|
private final LmsSetupDAO lmsSetupDAO;
|
||||||
private final ClientCredentialService clientCredentialService;
|
|
||||||
private final QuizLookupService quizLookupService;
|
private final QuizLookupService quizLookupService;
|
||||||
private final EnumMap<LmsType, LmsAPITemplateFactory> templateFactories;
|
private final LmsAPITemplateCacheService lmsAPITemplateCacheService;
|
||||||
|
|
||||||
private final Map<CacheKey, LmsAPITemplate> cache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public LmsAPIServiceImpl(
|
public LmsAPIServiceImpl(
|
||||||
final WebserviceInfo webserviceInfo,
|
|
||||||
final LmsSetupDAO lmsSetupDAO,
|
final LmsSetupDAO lmsSetupDAO,
|
||||||
final ClientCredentialService clientCredentialService,
|
|
||||||
final QuizLookupService quizLookupService,
|
final QuizLookupService quizLookupService,
|
||||||
final Collection<LmsAPITemplateFactory> lmsAPITemplateFactories) {
|
final LmsAPITemplateCacheService lmsAPITemplateCacheService,
|
||||||
|
final Collection<LmsAPITemplateFactory> lmsAPITemplateFactories
|
||||||
|
) {
|
||||||
|
|
||||||
this.webserviceInfo = webserviceInfo;
|
|
||||||
this.lmsSetupDAO = lmsSetupDAO;
|
this.lmsSetupDAO = lmsSetupDAO;
|
||||||
this.clientCredentialService = clientCredentialService;
|
|
||||||
this.quizLookupService = quizLookupService;
|
this.quizLookupService = quizLookupService;
|
||||||
|
this.lmsAPITemplateCacheService = lmsAPITemplateCacheService;
|
||||||
final Map<LmsType, LmsAPITemplateFactory> factories = lmsAPITemplateFactories
|
|
||||||
.stream()
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
LmsAPITemplateFactory::lmsType,
|
|
||||||
Function.identity()));
|
|
||||||
this.templateFactories = new EnumMap<>(factories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Listen to LmsSetupChangeEvent to release an affected LmsAPITemplate from cache
|
/** 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");
|
log.debug("LmsSetup changed. Update cache by removing eventually used references");
|
||||||
}
|
}
|
||||||
|
|
||||||
final LmsAPITemplate removedTemplate = this.cache.remove(
|
lmsAPITemplateCacheService.clearCache(lmsSetup.getModelId());
|
||||||
new CacheKey(lmsSetup.getModelId(), 0));
|
|
||||||
|
|
||||||
if (removedTemplate != null) {
|
|
||||||
removedTemplate.clearCourseCache();
|
|
||||||
}
|
|
||||||
this.quizLookupService.clear(lmsSetup.institutionId);
|
this.quizLookupService.clear(lmsSetup.institutionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
this.cache.values().forEach(LmsAPITemplate::dispose);
|
lmsAPITemplateCacheService.cleanup();
|
||||||
this.cache.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -127,258 +93,11 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsSetupId) {
|
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsSetupId) {
|
||||||
return Result.tryCatch(() -> {
|
return lmsAPITemplateCacheService.getLmsAPITemplate(lmsSetupId);
|
||||||
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 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<FullLmsIntegrationService.IntegrationData> 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<LmsAPITemplate> 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<LmsAPITemplate> createLmsSetupTemplate(final String lmsSetupId) {
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Create new LmsAPITemplate for id: {}", lmsSetupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createLmsSetupTemplate(new PersistentAPITemplateDataSupplier(
|
|
||||||
lmsSetupId,
|
|
||||||
this.lmsSetupDAO));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result<LmsAPITemplate> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<LmsSetup.LmsType, LmsAPITemplateFactory> templateFactories;
|
||||||
|
|
||||||
|
private final Map<CacheKey, LmsAPITemplate> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public LmsAPITemplateCacheServiceImpl(
|
||||||
|
final WebserviceInfo webserviceInfo,
|
||||||
|
final LmsSetupDAO lmsSetupDAO,
|
||||||
|
final QuizLookupService quizLookupService,
|
||||||
|
final ClientCredentialService clientCredentialService,
|
||||||
|
final Collection<LmsAPITemplateFactory> lmsAPITemplateFactories) {
|
||||||
|
|
||||||
|
this.webserviceInfo = webserviceInfo;
|
||||||
|
this.lmsSetupDAO = lmsSetupDAO;
|
||||||
|
this.quizLookupService = quizLookupService;
|
||||||
|
this.clientCredentialService = clientCredentialService;
|
||||||
|
final Map<LmsSetup.LmsType, LmsAPITemplateFactory> 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<LmsAPITemplate> getLmsAPITemplate(final Long lmsSetupId) {
|
||||||
|
return getLmsAPITemplate(String.valueOf(lmsSetupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<LmsAPITemplate> 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<LmsAPITemplate> 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<LmsAPITemplate> createLmsSetupTemplate(final String lmsSetupId) {
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Create new LmsAPITemplate for id: {}", lmsSetupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLmsSetupTemplate(new PersistentAPITemplateDataSupplier(
|
||||||
|
lmsSetupId,
|
||||||
|
this.lmsSetupDAO));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<LmsAPITemplate> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FullLmsIntegrationService.IntegrationData> 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<LmsAPITemplate> 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -58,17 +59,20 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||||
private final ExamConfigService examConfigService;
|
private final ExamConfigService examConfigService;
|
||||||
|
private final ExamConfigurationValueService examConfigurationValueService;
|
||||||
|
|
||||||
protected SEBRestrictionServiceImpl(
|
protected SEBRestrictionServiceImpl(
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
final LmsAPIService lmsAPIService,
|
final LmsAPIService lmsAPIService,
|
||||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||||
final ExamConfigService examConfigService) {
|
final ExamConfigService examConfigService,
|
||||||
|
final ExamConfigurationValueService examConfigurationValueService) {
|
||||||
|
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||||
this.examConfigService = examConfigService;
|
this.examConfigService = examConfigService;
|
||||||
|
this.examConfigurationValueService = examConfigurationValueService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -94,6 +98,25 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Exam> 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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<SEBRestriction> getSEBRestrictionFromExam(final Exam exam) {
|
public Result<SEBRestriction> getSEBRestrictionFromExam(final Exam exam) {
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
@ -37,7 +39,10 @@ public interface MoodleAPIRestTemplate {
|
||||||
|
|
||||||
String callMoodleAPIFunction(String functionName);
|
String callMoodleAPIFunction(String functionName);
|
||||||
|
|
||||||
String postToMoodleAPIFunction(String functionName, String body);
|
String postToMoodleAPIFunction(
|
||||||
|
String functionName,
|
||||||
|
MultiValueMap<String, String> queryParams,
|
||||||
|
Map<String, Map<String, String>> queryAttributes);
|
||||||
|
|
||||||
String callMoodleAPIFunction(
|
String callMoodleAPIFunction(
|
||||||
String functionName,
|
String functionName,
|
||||||
|
|
|
@ -320,7 +320,10 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String postToMoodleAPIFunction(final String functionName, final String body) {
|
public String postToMoodleAPIFunction(
|
||||||
|
final String functionName,
|
||||||
|
final MultiValueMap<String, String> queryParams,
|
||||||
|
final Map<String, Map<String, String>> queryAttributes) {
|
||||||
getAccessToken();
|
getAccessToken();
|
||||||
|
|
||||||
final UriComponentsBuilder queryParam = UriComponentsBuilder
|
final UriComponentsBuilder queryParam = UriComponentsBuilder
|
||||||
|
@ -329,15 +332,35 @@ public class MoodleRestTemplateFactoryImpl implements MoodleRestTemplateFactory
|
||||||
.queryParam(REST_REQUEST_FUNCTION_NAME, functionName)
|
.queryParam(REST_REQUEST_FUNCTION_NAME, functionName)
|
||||||
.queryParam(REST_REQUEST_FORMAT_NAME, "json");
|
.queryParam(REST_REQUEST_FORMAT_NAME, "json");
|
||||||
|
|
||||||
|
if (queryParams != null && !queryParams.isEmpty()) {
|
||||||
|
queryParam.queryParams(queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String body = createMoodleFormPostBody(queryAttributes);
|
||||||
|
|
||||||
final HttpHeaders headers = new HttpHeaders();
|
final HttpHeaders headers = new HttpHeaders();
|
||||||
headers.set(
|
headers.set(
|
||||||
HttpHeaders.CONTENT_TYPE,
|
HttpHeaders.CONTENT_TYPE,
|
||||||
MediaType.APPLICATION_JSON_VALUE);
|
MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
final HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
|
final HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
|
||||||
|
|
||||||
return doRequest(functionName, queryParam, true, httpEntity);
|
return doRequest(functionName, queryParam, true, httpEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String createMoodleFormPostBody(final Map<String, Map<String, String>> 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
|
@Override
|
||||||
public String callMoodleAPIFunction(
|
public String callMoodleAPIFunction(
|
||||||
final String functionName,
|
final String functionName,
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin;
|
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.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
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.MoodleResponseException;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleUtils;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 = "quizaccess_sebserver_connection";
|
||||||
private static final String FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE = "quizaccess_sebserver_connection_delete";
|
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 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";
|
private static final String UPLOAD_ENDPOINT = "/mod/quiz/accessrule/sebserver/uploadconfig.php";
|
||||||
|
|
||||||
|
@ -94,14 +100,20 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
|
||||||
|
|
||||||
// apply
|
// apply
|
||||||
final LmsSetup lmsSetup = this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup();
|
final LmsSetup lmsSetup = this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup();
|
||||||
final String jsonPayload = jsonMapper.writeValueAsString(data);
|
final String connectionJSON = jsonMapper.writeValueAsString(data);
|
||||||
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
||||||
|
|
||||||
//if (log.isDebugEnabled()) {
|
//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<String, String> 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\":")) {
|
if (response != null && response.startsWith("{\"exception\":")) {
|
||||||
// Seems there was an error response from Moodle side.
|
// 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 LmsSetup lmsSetup = this.restTemplateFactory.getApiTemplateDataSupplier().getLmsSetup();
|
||||||
final String jsonPayload = jsonMapper.writeValueAsString(examData);
|
|
||||||
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
final MoodleAPIRestTemplate rest = getRestTemplate().getOrThrow();
|
||||||
final String response = rest.postToMoodleAPIFunction(FUNCTION_NAME_SET_EXAM_DATA, jsonPayload);
|
|
||||||
|
final Map<String, Map<String, String>> attributes = new HashMap<>();
|
||||||
|
final Map<String, String> 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"))) {
|
if (response != null && (response.startsWith("{\"exception\":") || response.startsWith("0"))) {
|
||||||
log.warn("Failed to apply Exam data to moodle: {}", examData);
|
log.warn("Failed to apply Exam data to moodle: {}", examData);
|
||||||
|
|
|
@ -126,14 +126,6 @@ public class ExamSessionCacheService {
|
||||||
case RUNNING: {
|
case RUNNING: {
|
||||||
return true;
|
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: {
|
default: {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.util.stream.Collectors;
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
|
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.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.ClientInstruction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
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.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
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;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringService;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -104,7 +104,8 @@ public class JitsiProctoringService implements RemoteProctoringService {
|
||||||
.stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2)));
|
.stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2)));
|
||||||
|
|
||||||
private final AuthorizationService authorizationService;
|
private final AuthorizationService authorizationService;
|
||||||
private final ExamSessionService examSessionService;
|
private final ClientConnectionDAO clientConnectionDAO;
|
||||||
|
private final ExamSessionCacheService examSessionCacheService;
|
||||||
private final Cryptor cryptor;
|
private final Cryptor cryptor;
|
||||||
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
|
@ -112,14 +113,16 @@ public class JitsiProctoringService implements RemoteProctoringService {
|
||||||
|
|
||||||
protected JitsiProctoringService(
|
protected JitsiProctoringService(
|
||||||
final AuthorizationService authorizationService,
|
final AuthorizationService authorizationService,
|
||||||
final ExamSessionService examSessionService,
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
|
final ExamSessionCacheService examSessionCacheService,
|
||||||
final Cryptor cryptor,
|
final Cryptor cryptor,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final WebserviceInfo webserviceInfo) {
|
final WebserviceInfo webserviceInfo) {
|
||||||
|
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.examSessionService = examSessionService;
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
|
this.examSessionCacheService = examSessionCacheService;
|
||||||
this.cryptor = cryptor;
|
this.cryptor = cryptor;
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
|
@ -310,8 +313,9 @@ public class JitsiProctoringService implements RemoteProctoringService {
|
||||||
final String subject) {
|
final String subject) {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final ClientConnectionData clientConnection = this.examSessionService
|
|
||||||
.getConnectionData(connectionToken)
|
final ClientConnection clientConnection = clientConnectionDAO
|
||||||
|
.byConnectionToken(connectionToken)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
return createProctoringConnection(
|
return createProctoringConnection(
|
||||||
|
@ -319,7 +323,7 @@ public class JitsiProctoringService implements RemoteProctoringService {
|
||||||
proctoringSettings.serverURL,
|
proctoringSettings.serverURL,
|
||||||
proctoringSettings.appKey,
|
proctoringSettings.appKey,
|
||||||
proctoringSettings.getAppSecret(),
|
proctoringSettings.getAppSecret(),
|
||||||
clientConnection.clientConnection.userSessionId,
|
clientConnection.userSessionId,
|
||||||
SEB_CLIENT_KEY,
|
SEB_CLIENT_KEY,
|
||||||
roomName,
|
roomName,
|
||||||
subject,
|
subject,
|
||||||
|
@ -476,13 +480,12 @@ public class JitsiProctoringService implements RemoteProctoringService {
|
||||||
}
|
}
|
||||||
|
|
||||||
long expTime = System.currentTimeMillis() + Constants.DAY_IN_MILLIS;
|
long expTime = System.currentTimeMillis() + Constants.DAY_IN_MILLIS;
|
||||||
if (this.examSessionService.isExamRunning(examProctoring.examId)) {
|
|
||||||
final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
|
final Exam runningExam = examSessionCacheService.getRunningExam(examProctoring.examId);
|
||||||
.getOrThrow();
|
if (runningExam != null && runningExam.endTime != null) {
|
||||||
if (exam.endTime != null) {
|
expTime = runningExam.endTime.getMillis();
|
||||||
expTime = exam.endTime.getMillis();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return expTime;
|
return expTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ import java.util.stream.Collectors;
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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.commons.lang3.StringUtils;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
@ -134,7 +136,7 @@ public class ZoomProctoringService implements RemoteProctoringService {
|
||||||
Constants.FALSE_STRING))
|
Constants.FALSE_STRING))
|
||||||
.stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2)));
|
.stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2)));
|
||||||
|
|
||||||
private final ExamSessionService examSessionService;
|
private final ExamSessionCacheService examSessionCacheService;
|
||||||
private final Cryptor cryptor;
|
private final Cryptor cryptor;
|
||||||
private final AsyncService asyncService;
|
private final AsyncService asyncService;
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
|
@ -146,7 +148,7 @@ public class ZoomProctoringService implements RemoteProctoringService {
|
||||||
private final int tokenExpirySeconds;
|
private final int tokenExpirySeconds;
|
||||||
|
|
||||||
public ZoomProctoringService(
|
public ZoomProctoringService(
|
||||||
final ExamSessionService examSessionService,
|
final ExamSessionCacheService examSessionCacheService,
|
||||||
final Cryptor cryptor,
|
final Cryptor cryptor,
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
final JSONMapper jsonMapper,
|
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.sendRejoinForCollectingRoom:false}") final boolean sendRejoinForCollectingRoom,
|
||||||
@Value("${sebserver.webservice.proctoring.zoom.tokenexpiry.seconds:86400}") final int tokenExpirySeconds) {
|
@Value("${sebserver.webservice.proctoring.zoom.tokenexpiry.seconds:86400}") final int tokenExpirySeconds) {
|
||||||
|
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionCacheService = examSessionCacheService;
|
||||||
this.cryptor = cryptor;
|
this.cryptor = cryptor;
|
||||||
this.asyncService = asyncService;
|
this.asyncService = asyncService;
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
|
@ -326,9 +328,7 @@ public class ZoomProctoringService implements RemoteProctoringService {
|
||||||
remoteProctoringRoom.additionalRoomData,
|
remoteProctoringRoom.additionalRoomData,
|
||||||
AdditionalZoomRoomData.class);
|
AdditionalZoomRoomData.class);
|
||||||
|
|
||||||
final ClientConnectionData clientConnection = this.examSessionService
|
final ClientConnectionData clientConnection = this.examSessionCacheService.getClientConnection(connectionToken);
|
||||||
.getConnectionData(connectionToken)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
// Note: since SEB Server version 1.5 we work only with SDKKey instead of AppKey which is deprecated
|
// Note: since SEB Server version 1.5 we work only with SDKKey instead of AppKey which is deprecated
|
||||||
final ClientCredentials sdkCredentials = new ClientCredentials(
|
final ClientCredentials sdkCredentials = new ClientCredentials(
|
||||||
|
@ -354,7 +354,7 @@ public class ZoomProctoringService implements RemoteProctoringService {
|
||||||
sdkCredentials.accessToken,
|
sdkCredentials.accessToken,
|
||||||
sdkCredentials.clientId,
|
sdkCredentials.clientId,
|
||||||
String.valueOf(additionalZoomRoomData.meeting_id),
|
String.valueOf(additionalZoomRoomData.meeting_id),
|
||||||
clientConnection.clientConnection.userSessionId,
|
(clientConnection != null) ? clientConnection.clientConnection.userSessionId : "Unknown",
|
||||||
remoteProctoringRoom.additionalRoomData);
|
remoteProctoringRoom.additionalRoomData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -405,10 +405,11 @@ public class ZoomProctoringService implements RemoteProctoringService {
|
||||||
|
|
||||||
private int getMeetingDuration(final Long examId) {
|
private int getMeetingDuration(final Long examId) {
|
||||||
try {
|
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)
|
final Long result = new Interval(DateTime.now(DateTimeZone.UTC), endTime)
|
||||||
.toDurationMillis() / Constants.MINUTE_IN_MILLIS;
|
.toDurationMillis() / Constants.MINUTE_IN_MILLIS;
|
||||||
return result.intValue();
|
return result.intValue();
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsTestService;
|
||||||
import org.mybatis.dynamic.sql.SqlTable;
|
import org.mybatis.dynamic.sql.SqlTable;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
@ -50,6 +51,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
||||||
public class LmsSetupController extends ActivatableEntityController<LmsSetup, LmsSetup> {
|
public class LmsSetupController extends ActivatableEntityController<LmsSetup, LmsSetup> {
|
||||||
|
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
|
private final LmsTestService lmsTestService;
|
||||||
final ApplicationEventPublisher applicationEventPublisher;
|
final ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
public LmsSetupController(
|
public LmsSetupController(
|
||||||
|
@ -60,6 +62,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
||||||
final LmsAPIService lmsAPIService,
|
final LmsAPIService lmsAPIService,
|
||||||
final PaginationService paginationService,
|
final PaginationService paginationService,
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
|
final LmsTestService lmsTestService,
|
||||||
final ApplicationEventPublisher applicationEventPublisher) {
|
final ApplicationEventPublisher applicationEventPublisher) {
|
||||||
|
|
||||||
super(authorization,
|
super(authorization,
|
||||||
|
@ -70,6 +73,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
||||||
beanValidationService);
|
beanValidationService);
|
||||||
|
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
|
this.lmsTestService = lmsTestService;
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +101,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
||||||
|
|
||||||
final LmsSetupTestResult result = this.lmsAPIService
|
final LmsSetupTestResult result = this.lmsAPIService
|
||||||
.getLmsAPITemplate(modelId)
|
.getLmsAPITemplate(modelId)
|
||||||
.map(this.lmsAPIService::test)
|
.map(this.lmsTestService::test)
|
||||||
.onErrorDo(error -> {
|
.onErrorDo(error -> {
|
||||||
final LmsType lmsType = this.entityDAO.byPK(modelId).get().lmsType;
|
final LmsType lmsType = this.entityDAO.byPK(modelId).get().lmsType;
|
||||||
return new LmsSetupTestResult(
|
return new LmsSetupTestResult(
|
||||||
|
@ -122,7 +126,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
||||||
|
|
||||||
this.authorization.checkModify(lmsSetup);
|
this.authorization.checkModify(lmsSetup);
|
||||||
|
|
||||||
final LmsSetupTestResult result = this.lmsAPIService.testAdHoc(lmsSetup);
|
final LmsSetupTestResult result = this.lmsTestService.testAdHoc(lmsSetup);
|
||||||
if (result.missingLMSSetupAttribute != null && !result.missingLMSSetupAttribute.isEmpty()) {
|
if (result.missingLMSSetupAttribute != null && !result.missingLMSSetupAttribute.isEmpty()) {
|
||||||
throw new APIMessageException(result.missingLMSSetupAttribute);
|
throw new APIMessageException(result.missingLMSSetupAttribute);
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,10 +130,14 @@ public class MoodleMockupRestTemplateFactory implements MoodleRestTemplateFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String postToMoodleAPIFunction(String functionName, String body) {
|
public String postToMoodleAPIFunction(
|
||||||
|
final String functionName,
|
||||||
|
final MultiValueMap<String, String> queryParams,
|
||||||
|
final Map<String, Map<String, String>> queryAttributes) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String callMoodleAPIFunction(
|
public String callMoodleAPIFunction(
|
||||||
final String functionName,
|
final String functionName,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@ public class ExamJITSIProctoringServiceTest {
|
||||||
final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
||||||
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123"));
|
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123"));
|
||||||
final JitsiProctoringService examJITSIProctoringService =
|
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(
|
String accessToken = examJITSIProctoringService.createPayload(
|
||||||
"test-app",
|
"test-app",
|
||||||
|
@ -118,7 +119,7 @@ public class ExamJITSIProctoringServiceTest {
|
||||||
final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
||||||
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123"));
|
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn(Result.of("fbvgeghergrgrthrehreg123"));
|
||||||
final JitsiProctoringService examJITSIProctoringService =
|
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(
|
final ProctoringRoomConnection data = examJITSIProctoringService.createProctoringConnection(
|
||||||
"connectionToken",
|
"connectionToken",
|
||||||
"https://seb-jitsi.example.ch",
|
"https://seb-jitsi.example.ch",
|
||||||
|
@ -154,7 +155,7 @@ public class ExamJITSIProctoringServiceTest {
|
||||||
|
|
||||||
final AuthorizationService authorizationService = Mockito.mock(AuthorizationService.class);
|
final AuthorizationService authorizationService = Mockito.mock(AuthorizationService.class);
|
||||||
Mockito.when(authorizationService.getUserService()).thenReturn(userService);
|
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);
|
final Cryptor cryptor = Mockito.mock(Cryptor.class);
|
||||||
Mockito.when(cryptor.decrypt(Mockito.any())).thenReturn(Result.of("pwd"));
|
Mockito.when(cryptor.decrypt(Mockito.any())).thenReturn(Result.of("pwd"));
|
||||||
|
|
||||||
|
@ -164,7 +165,8 @@ public class ExamJITSIProctoringServiceTest {
|
||||||
|
|
||||||
return new JitsiProctoringService(
|
return new JitsiProctoringService(
|
||||||
authorizationService,
|
authorizationService,
|
||||||
examSessionService,
|
null,
|
||||||
|
examSessionCacheService,
|
||||||
cryptor,
|
cryptor,
|
||||||
clientHttpRequestFactoryService,
|
clientHttpRequestFactoryService,
|
||||||
jsonMapper, null);
|
jsonMapper, null);
|
||||||
|
|
Loading…
Reference in a new issue