SEBSERV-417 fix check full integration available and fix Exam Config change apply

This commit is contained in:
anhefti 2024-06-20 09:40:28 +02:00
parent e0952da7f3
commit b4907fcda9
11 changed files with 120 additions and 16 deletions

View file

@ -18,6 +18,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationServi
public interface FullLmsIntegrationAPI {
boolean fullIntegrationActive();
/** Performs a test for the underling {@link LmsSetup } configuration and checks if the
* LMS and the full LMS integration API of the LMS can be accessed or if there are some difficulties,
* missing API functions
@ -34,4 +36,6 @@ public interface FullLmsIntegrationAPI {
Result<String> deleteConnectionDetails();
Result<QuizData> getQuizDataForRemoteImport(String examData);
}

View file

@ -21,6 +21,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateChangeEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConnectionConfigurationChangeEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateEvent;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@ -37,6 +38,8 @@ public interface FullLmsIntegrationService {
void notifyConnectionConfigurationChange(ConnectionConfigurationChangeEvent event);
@EventListener(ExamDeletionEvent.class)
void notifyExamDeletion(ExamDeletionEvent event);
@EventListener(ExamConfigUpdateEvent.class)
void notifyExamConfigChange(ExamConfigUpdateEvent event);
/** Applies the exam data to LMS to inform the LMS that the exam exists on SEB Server site.
* @param exam The Exam
@ -75,6 +78,7 @@ public interface FullLmsIntegrationService {
String quizId,
AdHocAccountData adHocAccountData);
final class AdHocAccountData {
public final String userId;
public final String username;

View file

@ -51,6 +51,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
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;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -149,12 +150,10 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
@Override
public Result<Exam> applyExamDataToLMS(final Exam exam) {
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = lmsSetupDAO.byPK(exam.lmsSetupId).getOrThrow();
if (lmsSetup.lmsType.features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
if (hasFullIntegration(exam.lmsSetupId)) {
this.applyExamData(exam, !exam.active);
this.applyConnectionConfiguration(exam);
}
return exam;
});
@ -168,10 +167,29 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
);
}
@Override
public void notifyExamConfigChange(final ExamConfigUpdateEvent event) {
try {
final Exam exam = examDAO.byPK(event.examId).getOrThrow();
if (!hasFullIntegration(exam.lmsSetupId)) {
return;
}
this.applyExamData(exam, !exam.active);
} catch (final Exception e) {
log.error(
"Failed to apply Exam Configuration change to fully integrated LMS for exam: {}",
event.examId,
e);
}
}
@Override
public void notifyLmsSetupChange(final LmsSetupChangeEvent event) {
final LmsSetup lmsSetup = event.getLmsSetup();
if (!lmsSetup.getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
if (!hasFullIntegration(lmsSetup.id)) {
return;
}
@ -206,6 +224,7 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
lmsSetupDAO.idsOfActiveWithFullIntegration(examTemplate.institutionId)
.onSuccess(all -> all.stream()
.filter(this::hasFullIntegration)
.map(this::applyFullLmsIntegration)
.forEach(res ->
res.onError(error -> log.warn(
@ -229,15 +248,6 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.forEach(this::applyConnectionConfiguration);
}
private boolean needsConnectionConfigurationChange(final Exam exam, final Long ccId) {
if (exam.status == Exam.ExamStatus.ARCHIVED) {
return false;
}
final String configId = getConnectionConfigurationId(exam);
return StringUtils.isNotBlank(configId) && configId.equals(String.valueOf(ccId));
}
@Override
public Result<IntegrationData> applyFullLmsIntegration(final Long lmsSetupId) {
return lmsSetupDAO
@ -544,6 +554,9 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
}
private Exam applyExamData(final Exam exam, final boolean deletion) {
if (!hasFullIntegration(exam.lmsSetupId)) {
return exam;
}
if (exam.examTemplateId == null) {
throw new IllegalStateException("Exam has no template id: " + exam.getName());
}
@ -611,6 +624,27 @@ public class FullLmsIntegrationServiceImpl implements FullLmsIntegrationService
.getOr(exam);
}
private boolean hasFullIntegration(final Long lmsSetupId) {
final LmsAPITemplate lmsAPITemplate = this.lmsAPITemplateCacheService
.getLmsAPITemplate(lmsSetupId)
.getOrThrow();
final LmsSetup lmsSetup = lmsAPITemplate.lmsSetup();
if (!lmsSetup.getLmsType().features.contains(LmsSetup.Features.LMS_FULL_INTEGRATION)) {
return false;
}
return lmsAPITemplate.fullIntegrationActive();
}
private boolean needsConnectionConfigurationChange(final Exam exam, final Long ccId) {
if (exam.status == Exam.ExamStatus.ARCHIVED) {
return false;
}
final String configId = getConnectionConfigurationId(exam);
return StringUtils.isNotBlank(configId) && configId.equals(String.valueOf(ccId));
}
private String getAPIRootURL() {
return webserviceInfo.getExternalServerURL() + lmsAPIEndpoint;
}

View file

@ -500,6 +500,10 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
return protectedRun;
}
@Override
public boolean fullIntegrationActive() {
return this.lmsIntegrationAPI.fullIntegrationActive();
}
@Override
public LmsSetupTestResult testFullIntegrationAPI() {

View file

@ -422,6 +422,13 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
.map(x -> exam);
}
/// Full Integration API - Not integrated yet
@Override
public boolean fullIntegrationActive() {
return false;
}
@Override
public LmsSetupTestResult testFullIntegrationAPI() {
return LmsSetupTestResult.ofAPINotSupported(LmsType.ANS_DELFT);

View file

@ -19,6 +19,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationServi
public class MockupFullIntegration implements FullLmsIntegrationAPI {
@Override
public boolean fullIntegrationActive() {
return true;
}
@Override
public LmsSetupTestResult testFullIntegrationAPI() {
return LmsSetupTestResult.ofAPINotSupported(LmsSetup.LmsType.MOODLE_PLUGIN);

View file

@ -64,6 +64,11 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
Constants.TRUE_STRING));
}
@Override
public boolean fullIntegrationActive() {
return false;
}
@Override
public LmsSetupTestResult testFullIntegrationAPI() {
final LmsSetupTestResult attributesCheck = this.restTemplateFactory.test();
@ -80,7 +85,12 @@ public class MoodlePluginFullIntegration implements FullLmsIntegrationAPI {
}
try {
final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
if (restTemplate.getMoodlePluginVersion() != MoodleAPIRestTemplate.MoodlePluginVersion.V2_0) {
throw new RuntimeException("Old Moodle Plugin Version: " + restTemplate.getMoodlePluginVersion().name());
}
restTemplate.testAPIConnection(
FUNCTION_NAME_SEBSERVER_CONNECTION,
FUNCTION_NAME_SEBSERVER_CONNECTION_DELETE,

View file

@ -409,6 +409,13 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
.map(x -> exam);
}
/// Full Integration API - Not integrated yet
@Override
public boolean fullIntegrationActive() {
return false;
}
@Override
public LmsSetupTestResult testFullIntegrationAPI() {
return LmsSetupTestResult.ofAPINotSupported(LmsType.OPEN_OLAT);

View file

@ -0,0 +1,20 @@
/*
* 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.session;
import org.springframework.context.ApplicationEvent;
public class ExamConfigUpdateEvent extends ApplicationEvent {
public final Long examId;
public ExamConfigUpdateEvent(final Long examId) {
super(examId);
this.examId = examId;
}
}

View file

@ -14,9 +14,12 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.FullLmsIntegrationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateEvent;
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.stereotype.Service;
@ -49,6 +52,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
private final ExamUpdateHandler examUpdateHandler;
private final ExamAdminService examAdminService;
private final ExamConfigurationValueService examConfigurationValueService;
private final ApplicationEventPublisher applicationEventPublisher;
protected ExamConfigUpdateServiceImpl(
@ -58,7 +62,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
final ExamSessionService examSessionService,
final ExamUpdateHandler examUpdateHandler,
final ExamAdminService examAdminService,
final ExamConfigurationValueService examConfigurationValueService) {
final ExamConfigurationValueService examConfigurationValueService,
final ApplicationEventPublisher applicationEventPublisher) {
this.examDAO = examDAO;
this.configurationDAO = configurationDAO;
@ -67,6 +72,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
this.examUpdateHandler = examUpdateHandler;
this.examAdminService = examAdminService;
this.examConfigurationValueService = examConfigurationValueService;
this.applicationEventPublisher = applicationEventPublisher;
}
// processing:
@ -102,7 +108,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
final Collection<Exam> exams = lockForUpdate(examIdsFirstCheck, updateId)
.stream()
.map(Result::getOrThrow)
.collect(Collectors.toList());
.toList();
final Collection<Long> examsIds = exams
.stream()
@ -243,6 +249,9 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
.releaseLock(exam.id, updateId)
.onError(t -> log.error("Failed to release lock for exam: {}", exam));
// notify...
applicationEventPublisher.publishEvent(new ExamConfigUpdateEvent(exam.id));
return result;
})
.onError(t -> this.examDAO.forceUnlock(mapping.examId));

View file

@ -193,7 +193,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
final PermissionDeniedException ex,
final WebRequest request) {
log.info("Permission Denied Exception: ", ex);
log.info("Permission Denied Exception: {}", ex.getMessage());
return APIMessage.ErrorMessage.FORBIDDEN
.createErrorResponse(ex.getMessage());
}