From 532ca816bc84291bc1c706864517d5f302032a41 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 23 Jul 2020 16:11:40 +0200 Subject: [PATCH] SEBSERV-75 added Moodle course restriction on SEB Server side --- .../gbl/model/exam/MoodleSEBRestriction.java | 65 +++++++ .../gbl/model/exam/SEBRestriction.java | 8 + .../lms/impl/edx/OpenEdxLmsAPITemplate.java | 1 - .../impl/moodle/MoodleCourseRestriction.java | 168 ++++++++++++++++++ .../lms/impl/moodle/MoodleLmsAPITemplate.java | 36 +++- .../moodle/MoodleLmsAPITemplateFactory.java | 8 +- .../moodle/MoodleRestTemplateFactory.java | 62 ++++--- 7 files changed, 317 insertions(+), 31 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/MoodleSEBRestriction.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/MoodleSEBRestriction.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/MoodleSEBRestriction.java new file mode 100644 index 00000000..eb9210cb --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/MoodleSEBRestriction.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET) + * + * 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.gbl.model.exam; + +import java.util.Collection; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import ch.ethz.seb.sebserver.gbl.util.Utils; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class MoodleSEBRestriction { + + public static final String ATTR_BROWSER_KEYS = "BROWSER_KEYS"; + public static final String ATTR_CONFIG_KEYS = "CONFIG_KEYS"; + + @JsonProperty(ATTR_CONFIG_KEYS) + public final Collection configKeys; + + @JsonProperty(ATTR_BROWSER_KEYS) + public final Collection browserExamKeys; + + @JsonCreator + protected MoodleSEBRestriction( + @JsonProperty(ATTR_CONFIG_KEYS) final Collection configKeys, + @JsonProperty(ATTR_BROWSER_KEYS) final Collection browserExamKeys) { + + this.configKeys = Utils.immutableCollectionOf(configKeys); + this.browserExamKeys = Utils.immutableCollectionOf(browserExamKeys); + } + + public Collection getConfigKeys() { + return this.configKeys; + } + + public Collection getBrowserExamKeys() { + return this.browserExamKeys; + } + + public static MoodleSEBRestriction from(final SEBRestriction sebRestrictionData) { + return new MoodleSEBRestriction( + sebRestrictionData.configKeys, + sebRestrictionData.browserExamKeys); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("MoodleSEBRestriction [configKeys="); + builder.append(this.configKeys); + builder.append(", browserExamKeys="); + builder.append(this.browserExamKeys); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBRestriction.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBRestriction.java index 96acd23c..c0a525dc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBRestriction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBRestriction.java @@ -173,4 +173,12 @@ public final class SEBRestriction implements Entity { attrs); } + public static SEBRestriction from(final Long examId, final MoodleSEBRestriction restriction) { + return new SEBRestriction( + examId, + restriction.configKeys, + restriction.browserExamKeys, + null); + } + } 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 51814386..81d432f7 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 @@ -133,7 +133,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { return this.openEdxCourseRestriction.deleteSEBRestriction(exam.externalId) .map(result -> exam); - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java new file mode 100644 index 00000000..8b21501f --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET) + * + * 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.moodle; + +import java.util.ArrayList; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +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.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate; + +/** GET: + * http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123 + * + * Response (JSON): + * {"courseId"="123", "configKeys"=["key1","key2","key3",...], "browserKeys"=["bkey1", "bkey2", "bkey3",...]} + * + * Set keys: + * POST: + * http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123&configKey[0]=key1&configKey[1]=key2&browserKey[0]=bkey1&browserKey[1]=bkey2 + * + * Delete all key (and remove restrictions): + * POST: + * http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction_delete&courseId=123 */ +public class MoodleCourseRestriction { + + private static final Logger log = LoggerFactory.getLogger(MoodleCourseRestriction.class); + + private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION = "seb_restriction"; + private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE = "seb_restriction_delete"; + private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID = "courseId"; + private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY = "configKey"; + private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY = "browserKey"; + + private final JSONMapper jsonMapper; + private final LmsSetup lmsSetup; + private final MoodleRestTemplateFactory moodleRestTemplateFactory; + + private MoodleAPIRestTemplate restTemplate; + + protected MoodleCourseRestriction( + final JSONMapper jsonMapper, + final LmsSetup lmsSetup, + final MoodleRestTemplateFactory moodleRestTemplateFactory) { + + this.jsonMapper = jsonMapper; + this.lmsSetup = lmsSetup; + this.moodleRestTemplateFactory = moodleRestTemplateFactory; + } + + LmsSetupTestResult initAPIAccess() { + // TODO test availability + return LmsSetupTestResult.ofQuizAccessAPIError("not available yet"); + } + + Result getSEBRestriction(final String courseId) { + + if (log.isDebugEnabled()) { + log.debug("GET SEB Client restriction on course: {}", courseId); + } + + return Result.tryCatch(() -> { + + final MoodleAPIRestTemplate template = getRestTemplate() + .getOrThrow(); + + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId); + + final String resultJSON = template.callMoodleAPIFunction( + MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION, + queryParams, + null); + + final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue( + resultJSON, + new TypeReference() { + }); + + return restrictiondata; + }); + } + + Result putSEBRestriction( + final String courseId, + final MoodleSEBRestriction restriction) { + + if (log.isDebugEnabled()) { + log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction); + } + + return Result.tryCatch(() -> { + + final MoodleAPIRestTemplate template = getRestTemplate() + .getOrThrow(); + + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId); + + final MultiValueMap queryAttributes = new LinkedMultiValueMap<>(); + queryAttributes.addAll( + MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY, + new ArrayList<>(restriction.configKeys)); + queryAttributes.addAll( + MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY, + new ArrayList<>(restriction.browserExamKeys)); + + template.callMoodleAPIFunction( + MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION, + queryParams, + queryAttributes); + + return true; + }); + } + + Result deleteSEBRestriction(final String courseId) { + + if (log.isDebugEnabled()) { + log.debug("DELETE SEB Client restriction on course: {}", courseId); + } + + return Result.tryCatch(() -> { + final MoodleAPIRestTemplate template = getRestTemplate() + .getOrThrow(); + + final MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId); + + template.callMoodleAPIFunction( + MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE, + queryParams, + null); + + return true; + }); + } + + private Result getRestTemplate() { + if (this.restTemplate == null) { + final Result templateRequest = this.moodleRestTemplateFactory + .createRestTemplate(); + if (templateRequest.hasError()) { + return templateRequest; + } else { + this.restTemplate = templateRequest.get(); + } + } + + return Result.of(this.restTemplate); + } + +} 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 ed12153b..bbfd2a0b 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 @@ -12,8 +12,12 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; @@ -25,15 +29,20 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; public class MoodleLmsAPITemplate implements LmsAPITemplate { + private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class); + private final LmsSetup lmsSetup; private final MoodleCourseAccess moodleCourseAccess; + private final MoodleCourseRestriction moodleCourseRestriction; protected MoodleLmsAPITemplate( final LmsSetup lmsSetup, - final MoodleCourseAccess moodleCourseAccess) { + final MoodleCourseAccess moodleCourseAccess, + final MoodleCourseRestriction moodleCourseRestriction) { this.lmsSetup = lmsSetup; this.moodleCourseAccess = moodleCourseAccess; + this.moodleCourseRestriction = moodleCourseRestriction; } @Override @@ -87,7 +96,13 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate { @Override public Result getSEBClientRestriction(final Exam exam) { - return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); + if (log.isDebugEnabled()) { + log.debug("Get SEB Client restriction for Exam: {}", exam); + } + + return this.moodleCourseRestriction + .getSEBRestriction(exam.externalId) + .map(restriction -> SEBRestriction.from(exam.id, restriction)); } @Override @@ -95,12 +110,25 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate { final String externalExamId, final SEBRestriction sebRestrictionData) { - return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); + if (log.isDebugEnabled()) { + log.debug("Apply SEB Client restriction: {}", sebRestrictionData); + } + + return this.moodleCourseRestriction + .putSEBRestriction( + externalExamId, + MoodleSEBRestriction.from(sebRestrictionData)) + .map(result -> sebRestrictionData); } @Override public Result releaseSEBClientRestriction(final Exam exam) { - return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); + if (log.isDebugEnabled()) { + log.debug("Release SEB Client restriction for Exam: {}", exam); + } + + return this.moodleCourseRestriction.deleteSEBRestriction(exam.externalId) + .map(result -> exam); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java index b6b1114a..f31cd03e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java @@ -73,9 +73,15 @@ public class MoodleLmsAPITemplateFactory { moodleRestTemplateFactory, this.asyncService); + final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction( + this.jsonMapper, + lmsSetup, + moodleRestTemplateFactory); + return new MoodleLmsAPITemplate( lmsSetup, - moodleCourseAccess); + moodleCourseAccess, + moodleCourseRestriction); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java index 6dfbc0c2..0c073b89 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java @@ -8,21 +8,17 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle; -import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; -import ch.ethz.seb.sebserver.gbl.api.APIMessage; -import ch.ethz.seb.sebserver.gbl.api.JSONMapper; -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.Domain.LMS_SETUP; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; -import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; -import ch.ethz.seb.sebserver.gbl.util.Result; -import ch.ethz.seb.sebserver.gbl.util.Utils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -36,16 +32,21 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +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.Domain.LMS_SETUP; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; +import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; final class MoodleRestTemplateFactory { @@ -243,12 +244,19 @@ final class MoodleRestTemplateFactory { } public String callMoodleAPIFunction(final String functionName) { - return callMoodleAPIFunction(functionName, null); + return callMoodleAPIFunction(functionName, null, null); } public String callMoodleAPIFunction( final String functionName, final MultiValueMap queryAttributes) { + return callMoodleAPIFunction(functionName, null, queryAttributes); + } + + public String callMoodleAPIFunction( + final String functionName, + final MultiValueMap queryParams, + final MultiValueMap queryAttributes) { getAccessToken(); @@ -258,6 +266,10 @@ final class MoodleRestTemplateFactory { .queryParam(REST_REQUEST_FUNCTION_NAME, functionName) .queryParam(REST_REQUEST_FORMAT_NAME, "json"); + if (queryParams != null && !queryParams.isEmpty()) { + queryParam.queryParams(queryParams); + } + final boolean usePOST = queryAttributes != null && !queryAttributes.isEmpty(); HttpEntity functionReqEntity; if (usePOST) {