diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java index 7f99b148..0cec3bc0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationAPI.java @@ -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 deleteConnectionDetails(); Result getQuizDataForRemoteImport(String examData); + + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java index 4a4ca461..d89911d1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/FullLmsIntegrationService.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java index 2e03df52..af592a66 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/FullLmsIntegrationServiceImpl.java @@ -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 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 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; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java index cdc07aac..b08ae3b9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java @@ -500,6 +500,10 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate { return protectedRun; } + @Override + public boolean fullIntegrationActive() { + return this.lmsIntegrationAPI.fullIntegrationActive(); + } @Override public LmsSetupTestResult testFullIntegrationAPI() { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java index 424648ec..7ab90bd5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java @@ -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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java index 1d5abf99..4bc628fc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupFullIntegration.java @@ -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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java index 37c2e0cc..3c6a14c8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginFullIntegration.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 6ddc5c8b..16787900 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamConfigUpdateEvent.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamConfigUpdateEvent.java new file mode 100644 index 00000000..e0731d5d --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamConfigUpdateEvent.java @@ -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; + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java index df1c8a99..37699fe8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java @@ -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 exams = lockForUpdate(examIdsFirstCheck, updateId) .stream() .map(Result::getOrThrow) - .collect(Collectors.toList()); + .toList(); final Collection 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)); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java index bb1c1ed5..c6ec90f9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java @@ -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()); }