diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Chapters.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Chapters.java new file mode 100644 index 00000000..6c49357e --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Chapters.java @@ -0,0 +1,81 @@ +/* + * 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; + +@JsonIgnoreProperties(ignoreUnknown = true) +public final class Chapters { + + public static final String ATTR_CHAPTERS = "chapters"; + + public final Collection chapters; + + @JsonCreator + public Chapters(@JsonProperty(ATTR_CHAPTERS) final Collection chapters) { + this.chapters = chapters; + } + + public Collection getChapters() { + return this.chapters; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Chapters [chapters="); + builder.append(this.chapters); + builder.append("]"); + return builder.toString(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Chapter { + + public static final String ATTR_NAME = "name"; + public static final String ATTR_ID = "id"; + + public final String name; + public final String id; + + @JsonCreator + protected Chapter( + @JsonProperty(ATTR_NAME) final String name, + @JsonProperty(ATTR_ID) final String id) { + + this.name = name; + this.id = id; + } + + public String getName() { + return this.name; + } + + public String getId() { + return this.id; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Chapter [name="); + builder.append(this.name); + builder.append(", id="); + builder.append(this.id); + builder.append("]"); + return builder.toString(); + } + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java index 17008b77..e8a15ea4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java @@ -19,6 +19,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import ch.ethz.seb.sebserver.gbl.api.EntityType; +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.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction; @@ -108,6 +109,8 @@ public interface LmsAPITemplate { // examinee identifier received by on SEB-Client connection. //Result getExamineeAccountDetails(String examineeUserId); + Result getCourseChapters(String courseId); + /** Get SEB restriction data form LMS within a SebRestrictionData instance if available * or a ResourceNotFoundException if not yet available or restricted * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/CourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/CourseAccess.java index 426aea50..131959bd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/CourseAccess.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/CourseAccess.java @@ -19,7 +19,9 @@ import java.util.stream.Collectors; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.async.AsyncService; +import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker; import ch.ethz.seb.sebserver.gbl.async.MemoizingCircuitBreaker; +import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; @@ -27,20 +29,26 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; public abstract class CourseAccess { - protected final MemoizingCircuitBreaker> allQuizzesSupplier; + protected final MemoizingCircuitBreaker> allQuizzesRequest; + protected final CircuitBreaker chaptersRequest; protected CourseAccess(final AsyncService asyncService) { - this.allQuizzesSupplier = asyncService.createMemoizingCircuitBreaker( + this.allQuizzesRequest = asyncService.createMemoizingCircuitBreaker( allQuizzesSupplier(), 3, Constants.MINUTE_IN_MILLIS, Constants.MINUTE_IN_MILLIS, true, Constants.HOUR_IN_MILLIS); + + this.chaptersRequest = asyncService.createCircuitBreaker( + 3, + Constants.MINUTE_IN_MILLIS, + Constants.MINUTE_IN_MILLIS); } public Result getQuizFromCache(final String id) { - return Result.tryCatch(() -> this.allQuizzesSupplier + return Result.tryCatch(() -> this.allQuizzesRequest .getCached() .stream() .filter(qd -> id.equals(qd.id)) @@ -50,7 +58,7 @@ public abstract class CourseAccess { public Result>> getQuizzesFromCache(final Set ids) { return Result.tryCatch(() -> { - final List cached = this.allQuizzesSupplier.getCached(); + final List cached = this.allQuizzesRequest.getCached(); if (cached == null) { throw new RuntimeException("No cached quizzes"); } @@ -76,10 +84,16 @@ public abstract class CourseAccess { } public Result> getQuizzes(final FilterMap filterMap) { - return this.allQuizzesSupplier.get() + return this.allQuizzesRequest.get() .map(LmsAPIService.quizzesFilterFunction(filterMap)); } + protected Result getCourseChapters(final String courseId) { + return null; + } + protected abstract Supplier> allQuizzesSupplier(); + protected abstract Supplier getCourseChaptersSupplier(final String courseId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java index ae19d474..c0509bdc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java @@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP; +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.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction; @@ -177,6 +178,11 @@ final class MockupLmsAPITemplate implements LmsAPITemplate { return getQuizzes(ids); } + @Override + public Result getCourseChapters(final String courseId) { + return Result.ofError(new UnsupportedOperationException()); + } + @Override public Result getSebClientRestriction(final Exam exam) { log.info("Apply SEB Client restriction for Exam: {}", exam); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java index ae8bd0fb..9629e391 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx; import java.net.URL; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,6 +35,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.async.AsyncService; +import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; 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.LmsSetupTestResult; @@ -49,6 +51,8 @@ final class OpenEdxCourseAccess extends CourseAccess { private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseAccess.class); private static final String OPEN_EDX_DEFAULT_COURSE_ENDPOINT = "/api/courses/v1/courses/"; + private static final String OPEN_EDX_DEFAULT_BLOCKS_ENDPOINT = + "/api/courses/v1/blocks/?depth=1&all_blocks=true&course_id="; private static final String OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX = "/courses/"; private final LmsSetup lmsSetup; @@ -110,6 +114,11 @@ final class OpenEdxCourseAccess extends CourseAccess { .getOrThrow(); } + @Override + protected Supplier getCourseChaptersSupplier(final String courseId) { + throw new UnsupportedOperationException("not available yet"); + } + private ArrayList collectAllQuizzes(final OAuth2RestTemplate restTemplate) { final String externalStartURI = getExternalLMSServerAddress(this.lmsSetup); return collectAllCourses( @@ -218,6 +227,18 @@ final class OpenEdxCourseAccess extends CourseAccess { public String end; } + @JsonIgnoreProperties(ignoreUnknown = true) + static final class Blocks { + public String root; + public Collection blocks; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class Block { + public String block_id; + public String display_name; + } + private static final class EdxOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator { @Override 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 32711cdf..4390e623 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 @@ -17,6 +17,7 @@ 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.OpenEdxSebRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; @@ -78,6 +79,13 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { .getOrElse(() -> getQuizzes(ids)); } + @Override + public Result getCourseChapters(final String courseId) { + return Result.tryCatch(() -> this.openEdxCourseAccess + .getCourseChaptersSupplier(courseId) + .get()); + } + @Override public Result getSebClientRestriction(final Exam exam) { if (log.isDebugEnabled()) { 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 df699ca7..c276480c 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 @@ -28,6 +28,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.async.AsyncService; +import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; 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.LmsSetupTestResult; @@ -102,6 +103,11 @@ public class MoodleCourseAccess extends CourseAccess { .getOrThrow(); } + @Override + protected Supplier getCourseChaptersSupplier(final String courseId) { + throw new UnsupportedOperationException("not available yet"); + } + private ArrayList collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) { final String urlPrefix = this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH; return collectAllCourses( @@ -165,6 +171,7 @@ public class MoodleCourseAccess extends CourseAccess { } static Map additionalAttrs = new HashMap<>(); + private static List quizDataOf( final LmsSetup lmsSetup, final CourseData courseData, @@ -219,8 +226,8 @@ public class MoodleCourseAccess extends CourseAccess { final String full_name; final String display_name; final String summary; - final Long start_date; // unix-time milliseconds UTC - final Long end_date; // unix-time milliseconds UTC + final Long start_date; // unix-time milliseconds UTC + final Long end_date; // unix-time milliseconds UTC final Long time_created; // unix-time milliseconds UTC final Collection quizzes = new ArrayList<>(); 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 6587bdec..8c94442b 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,6 +12,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; +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.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction; @@ -66,9 +67,16 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate { .orElse(() -> getQuiz(id)); } + @Override + public Result getCourseChapters(final String courseId) { + return Result.tryCatch(() -> this.moodleCourseAccess + .getCourseChaptersSupplier(courseId) + .get()); + } + @Override public Result getSebClientRestriction(final Exam exam) { - throw new UnsupportedOperationException("SEB Restriction API not available yet"); + return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); } @Override @@ -76,12 +84,12 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate { final String externalExamId, final SebRestriction sebRestrictionData) { - throw new UnsupportedOperationException("SEB Restriction API not available yet"); + return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); } @Override public Result releaseSebClientRestriction(final Exam exam) { - throw new UnsupportedOperationException("SEB Restriction API not available yet"); + return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet")); } }