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