diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java index 54cf7e84..7fc91d32 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java @@ -32,6 +32,9 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException; +/** The open edX SEB course restriction API implementation. + * + * See also : https://seb-openedx.readthedocs.io/en/latest/ */ public class OpenEdxCourseRestriction { private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseRestriction.class); @@ -43,7 +46,6 @@ public class OpenEdxCourseRestriction { private final LmsSetup lmsSetup; private final JSONMapper jsonMapper; private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory; - private final int restrictionAPIPushCount; private OAuth2RestTemplate restTemplate; @@ -56,7 +58,6 @@ public class OpenEdxCourseRestriction { this.lmsSetup = lmsSetup; this.jsonMapper = jsonMapper; this.openEdxRestTemplateFactory = openEdxRestTemplateFactory; - this.restrictionAPIPushCount = restrictionAPIPushCount; } LmsSetupTestResult initAPIAccess() { @@ -145,9 +146,9 @@ public class OpenEdxCourseRestriction { log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction); } - return handleSEBRestriction(processSEBRestrictionUpdate(pushSEBRestrictionFunction( + return handleSEBRestriction(pushSEBRestrictionFunction( restriction, - courseId))); + courseId)); } Result deleteSEBRestriction(final String courseId) { @@ -156,41 +157,7 @@ public class OpenEdxCourseRestriction { log.debug("DELETE SEB Client restriction on course: {}", courseId); } - return handleSEBRestriction(processSEBRestrictionUpdate(deleteSEBRestrictionFunction(courseId))); - } - - private BooleanSupplier processSEBRestrictionUpdate(final BooleanSupplier restrictionUpdate) { - return () -> { - if (this.restrictionAPIPushCount > 0) { - // NOTE: This is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin to - // apply on load-balanced infrastructure or infrastructure that has several layers of cache. - // The reason for this is that the API (Open edX system) internally don't apply a resource-change that is - // done within HTTP API call immediately from an outside perspective. - // After a resource-change on the API is done, the system toggles between the old and the new resource - // while constantly calling GET. This usually happens for about a minute or two then it stabilizes on the new resource - // - // This may source on load-balancing or internally caching on Open edX side. - // To mitigate this effect the SEB Server can be configured to apply a resource-change on the - // API several times in a row to flush as match caches and reach as match as possible server instances. - // - // Since this is a brute-force method to mitigate the problem, this should only be a temporary - // work-around until a better solution on Open edX SEB integration side has been found and applied. - - log.warn("SEB restriction update with multiple API push " - + "(this is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin)"); - - for (int i = 0; i < this.restrictionAPIPushCount; i++) { - if (!restrictionUpdate.getAsBoolean()) { - Result.ofRuntimeError( - "Failed to process SEB restriction update. See logs for more information"); - } - } - - return true; - } else { - return restrictionUpdate.getAsBoolean(); - } - }; + return handleSEBRestriction(deleteSEBRestrictionFunction(courseId)); } private BooleanSupplier pushSEBRestrictionFunction( diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java index ccc8a806..918a0022 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java @@ -31,6 +31,10 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; +/** The OpenEdxLmsAPITemplate is separated into two parts: + * - OpenEdxCourseAccess implements the course access API + * - OpenEdxCourseRestriction implements the SEB restriction API + * - Both uses the OpenEdxRestTemplateFactory to create a spring based RestTemplate to access the LMS API */ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { private static final Logger log = LoggerFactory.getLogger(OpenEdxLmsAPITemplate.class); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java index 9da7f83e..77989adf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java @@ -51,7 +51,17 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT /** Implements the LmsAPITemplate for Open edX LMS Course API access. * - * See also: https://docs.moodle.org/dev/Web_service_API_functions */ + * See also: https://docs.moodle.org/dev/Web_service_API_functions + * + * NOTE: Because of the missing integration on Moodle side so far the MoodleCourseAccess + * needs to deal with Moodle's standard API functions that don't allow to filter and page course/quiz data + * in an easy and proper way. Therefore we have to fetch all course and quiz data from Moodle before + * filtering and paging can be applied. Since there are possibly thousands of active courses and quizzes + * this moodle course access implements an synchronous fetch as well as an asynchronous fetch strategy. + * The asynchronous fetch strategy is started within a background task and fill up a shared cache. + * A request will start the background task if needed and return immediately to do not block the request. + * The planed Moodle integration on moodle side also defines an improved course access API. This will + * possibly make this synchronous fetch strategy obsolete in the future. */ public class MoodleCourseAccess extends CourseAccess { private static final long INITIAL_WAIT_TIME = 3 * Constants.SECOND_IN_MILLIS; @@ -253,6 +263,7 @@ public class MoodleCourseAccess extends CourseAccess { final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null; final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1; + // Verify and call the proper strategy to get the course and quiz data Collection courseQuizData = Collections.emptyList(); if (this.moodleCourseDataAsyncLoader.isRunning()) { courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java index be7e510b..ede74cdb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java @@ -53,6 +53,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT @Component @WebServiceProfile @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +/** This implements the (temporary) asynchronous fetch strategy to fetch + * course and quiz data within a background task and fill up a shared cache. */ public class MoodleCourseDataAsyncLoader { private static final Logger log = LoggerFactory.getLogger(MoodleCourseDataAsyncLoader.class); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java index e37ad8a1..f9784a4f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java @@ -32,6 +32,20 @@ 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.impl.NoSEBRestrictionException; +/** The MoodleLmsAPITemplate is separated into two parts: + * - MoodleCourseAccess implements the course access API + * - MoodleCourseRestriction implements the SEB restriction API + * - Both uses the MoodleRestTemplateFactore to create a spring based RestTemplate to access the LMS API + * + * NOTE: Because of the missing integration on Moodle side so far the MoodleCourseAccess + * needs to deal with Moodle's standard API functions that don't allow to filter and page course/quiz data + * in an easy and proper way. Therefore we have to fetch all course and quiz data from Moodle before + * filtering and paging can be applied. Since there are possibly thousands of active courses and quizzes + * this moodle course access implements an synchronous fetch as well as an asynchronous fetch strategy. + * The asynchronous fetch strategy is started within a background task and fill up a shared cache. + * A request will start the background task if needed and return immediately to do not block the request. + * The planed Moodle integration on moodle side also defines an improved course access API. This will + * possibly make this synchronous fetch strategy obsolete in the future. */ public class MoodleLmsAPITemplate implements LmsAPITemplate { private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class); @@ -63,7 +77,6 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate { @Override public LmsSetupTestResult testCourseRestrictionAPI() { throw new NoSEBRestrictionException(); - //return this.moodleCourseRestriction.initAPIAccess(); } @Override