SEBSERV-121 start implementation

This commit is contained in:
anhefti 2020-04-20 09:02:08 +02:00
parent 9d614fdf6e
commit 68e48caafb
8 changed files with 158 additions and 10 deletions

View file

@ -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<Chapter> chapters;
@JsonCreator
public Chapters(@JsonProperty(ATTR_CHAPTERS) final Collection<Chapter> chapters) {
this.chapters = chapters;
}
public Collection<Chapter> 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();
}
}
}

View file

@ -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<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
Result<Chapters> getCourseChapters(String courseId);
/** Get SEB restriction data form LMS within a SebRestrictionData instance if available
* or a ResourceNotFoundException if not yet available or restricted
*

View file

@ -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<List<QuizData>> allQuizzesSupplier;
protected final MemoizingCircuitBreaker<List<QuizData>> allQuizzesRequest;
protected final CircuitBreaker<Chapters> 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<QuizData> 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<Collection<Result<QuizData>>> getQuizzesFromCache(final Set<String> ids) {
return Result.tryCatch(() -> {
final List<QuizData> cached = this.allQuizzesSupplier.getCached();
final List<QuizData> cached = this.allQuizzesRequest.getCached();
if (cached == null) {
throw new RuntimeException("No cached quizzes");
}
@ -76,10 +84,16 @@ public abstract class CourseAccess {
}
public Result<List<QuizData>> getQuizzes(final FilterMap filterMap) {
return this.allQuizzesSupplier.get()
return this.allQuizzesRequest.get()
.map(LmsAPIService.quizzesFilterFunction(filterMap));
}
protected Result<Chapters> getCourseChapters(final String courseId) {
return null;
}
protected abstract Supplier<List<QuizData>> allQuizzesSupplier();
protected abstract Supplier<Chapters> getCourseChaptersSupplier(final String courseId);
}

View file

@ -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<Chapters> getCourseChapters(final String courseId) {
return Result.ofError(new UnsupportedOperationException());
}
@Override
public Result<SebRestriction> getSebClientRestriction(final Exam exam) {
log.info("Apply SEB Client restriction for Exam: {}", exam);

View file

@ -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<Chapters> getCourseChaptersSupplier(final String courseId) {
throw new UnsupportedOperationException("not available yet");
}
private ArrayList<QuizData> 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<Block> blocks;
}
@JsonIgnoreProperties(ignoreUnknown = true)
static final class Block {
public String block_id;
public String display_name;
}
private static final class EdxOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator {
@Override

View file

@ -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<Chapters> getCourseChapters(final String courseId) {
return Result.tryCatch(() -> this.openEdxCourseAccess
.getCourseChaptersSupplier(courseId)
.get());
}
@Override
public Result<SebRestriction> getSebClientRestriction(final Exam exam) {
if (log.isDebugEnabled()) {

View file

@ -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<Chapters> getCourseChaptersSupplier(final String courseId) {
throw new UnsupportedOperationException("not available yet");
}
private ArrayList<QuizData> 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<String, String> additionalAttrs = new HashMap<>();
private static List<QuizData> 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<CourseQuiz> quizzes = new ArrayList<>();

View file

@ -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<Chapters> getCourseChapters(final String courseId) {
return Result.tryCatch(() -> this.moodleCourseAccess
.getCourseChaptersSupplier(courseId)
.get());
}
@Override
public Result<SebRestriction> 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<Exam> releaseSebClientRestriction(final Exam exam) {
throw new UnsupportedOperationException("SEB Restriction API not available yet");
return Result.ofError(new UnsupportedOperationException("SEB Restriction API not available yet"));
}
}