added OlatLmsAPITemplate skeleton ready to be implemented

This commit is contained in:
anhefti 2021-05-17 22:43:42 +02:00
parent 213cf443e1
commit 89c306e35a
7 changed files with 430 additions and 116 deletions

View file

@ -20,7 +20,7 @@ public class AsyncService {
private final AsyncRunner asyncRunner;
protected AsyncService(final AsyncRunner asyncRunner) {
public AsyncService(final AsyncRunner asyncRunner) {
this.asyncRunner = asyncRunner;
}

View file

@ -9,7 +9,6 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
@ -134,10 +133,8 @@ public abstract class AbstractCourseAccess {
return this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap));
}
public Collection<QuizData> protectedQuizzesRequest(final Set<String> ids) {
return this.quizzesRequest.protectedRun(quizzesSupplier(ids))
.onError(error -> log.error("Failed to get QuizData for ids: ", error))
.getOrElse(() -> Collections.emptyList());
public Result<Collection<QuizData>> protectedQuizzesRequest(final Set<String> ids) {
return this.quizzesRequest.protectedRun(quizzesSupplier(ids));
}
public Result<QuizData> protectedQuizRequest(final String id) {
@ -145,7 +142,8 @@ public abstract class AbstractCourseAccess {
}
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
return this.accountDetailRequest.protectedRun(accountDetailsSupplier(examineeSessionId));
final Supplier<ExamineeAccountDetails> accountDetailsSupplier = accountDetailsSupplier(examineeSessionId);
return this.accountDetailRequest.protectedRun(accountDetailsSupplier);
}
/** Default implementation that uses getExamineeAccountDetails to geht the examinee name
@ -163,19 +161,7 @@ public abstract class AbstractCourseAccess {
return this.chaptersRequest.protectedRun(getCourseChaptersSupplier(courseId));
}
/** NOTE: this returns a ExamineeAccountDetails with given examineeSessionId for default.
* Override this if requesting account details is supported for specified LMS access.
*
* @param examineeSessionId
* @return this returns a ExamineeAccountDetails with given examineeSessionId for default */
protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) {
return () -> new ExamineeAccountDetails(
examineeSessionId,
examineeSessionId,
examineeSessionId,
examineeSessionId,
Collections.emptyMap());
}
protected abstract Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId);
/** Provides a supplier to supply request to use within the circuit breaker */
protected abstract Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap);

View file

@ -141,8 +141,9 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
}
@Override
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
return Result.tryCatch(() -> {
protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) {
return () -> {
try {
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
final HttpHeaders httpHeaders = new HttpHeaders();
final OAuth2RestTemplate template = getRestTemplate()
@ -186,7 +187,10 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
userDetails[0].username,
userDetails[0].email,
additionalAttributes);
});
} catch (final Exception e) {
throw new RuntimeException(e);
}
};
}
@Override
@ -275,7 +279,7 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
});
if (!leftIds.isEmpty()) {
result.addAll(super.protectedQuizzesRequest(leftIds));
result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow());
}
return result;

View file

@ -129,8 +129,9 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
}
@Override
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
return Result.tryCatch(() -> {
protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) {
return () -> {
try {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
@ -181,7 +182,10 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
userDetails[0].username,
userDetails[0].email,
additionalAttributes);
});
} catch (final Exception e) {
throw new RuntimeException(e);
}
};
}
LmsSetupTestResult initAPIAccess() {

View file

@ -0,0 +1,282 @@
/*
* Copyright (c) 2021 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.olat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.cache.CacheManager;
import org.springframework.core.env.Environment;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
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;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
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.AbstractCachedCourseAccess;
public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements LmsAPITemplate {
// TODO add needed dependencies here
private final APITemplateDataSupplier apiTemplateDataSupplier;
private final Long lmsSetupId;
protected OlatLmsAPITemplate(
// TODO if you need more dependencies inject them here and set the reference
final APITemplateDataSupplier apiTemplateDataSupplier,
final AsyncService asyncService,
final Environment environment,
final CacheManager cacheManager) {
super(asyncService, environment, cacheManager);
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
this.lmsSetupId = apiTemplateDataSupplier.getLmsSetup().id;
}
@Override
public LmsType getType() {
return LmsType.OPEN_OLAT;
}
@Override
public LmsSetup lmsSetup() {
return this.apiTemplateDataSupplier.getLmsSetup();
}
@Override
protected Long getLmsSetupId() {
return this.lmsSetupId;
}
@Override
public LmsSetupTestResult testCourseAccessAPI() {
final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings();
if (testLmsSetupSettings.hasAnyError()) {
return testLmsSetupSettings;
}
// TODO check if the course API of the remote LMS is available
// if not, create corresponding LmsSetupTestResult error
return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT);
}
@Override
public LmsSetupTestResult testCourseRestrictionAPI() {
final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings();
if (testLmsSetupSettings.hasAnyError()) {
return testLmsSetupSettings;
}
if (LmsType.OPEN_OLAT.features.contains(Features.SEB_RESTRICTION)) {
// TODO check if the course API of the remote LMS is available
// if not, create corresponding LmsSetupTestResult error
}
return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT);
}
private LmsSetupTestResult testLmsSetupSettings() {
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
final List<APIMessage> missingAttrs = new ArrayList<>();
// Check given LMS URL
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_URL,
"lmsSetup:lmsUrl:notNull"));
} else {
// Try to connect to the URL
if (!Utils.pingHost(lmsSetup.lmsApiUrl)) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_URL,
"lmsSetup:lmsUrl:url.invalid"));
}
}
// Client id is mandatory
if (!lmsClientCredentials.hasClientId()) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_CLIENTNAME,
"lmsSetup:lmsClientname:notNull"));
}
// Client secret is mandatory
if (!lmsClientCredentials.hasSecret()) {
missingAttrs.add(APIMessage.fieldValidationError(
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
"lmsSetup:lmsClientsecret:notNull"));
}
if (!missingAttrs.isEmpty()) {
return LmsSetupTestResult.ofMissingAttributes(LmsType.OPEN_OLAT, missingAttrs);
}
return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT);
}
@Override
public Result<List<QuizData>> getQuizzes(final FilterMap filterMap) {
return this
.protectedQuizzesRequest(filterMap)
.map(quizzes -> quizzes.stream()
.filter(LmsAPIService.quizFilterPredicate(filterMap))
.collect(Collectors.toList()));
}
@Override
public Result<Collection<QuizData>> getQuizzes(final Set<String> ids) {
return Result.tryCatch(() -> {
final HashSet<String> leftIds = new HashSet<>(ids);
final Collection<QuizData> result = new ArrayList<>();
ids.stream()
.map(super::getFromCache)
.forEach(q -> {
if (q != null) {
leftIds.remove(q.id);
result.add(q);
}
});
if (!leftIds.isEmpty()) {
result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow());
}
return result;
});
}
@Override
public Result<QuizData> getQuiz(final String id) {
final QuizData fromCache = super.getFromCache(id);
if (fromCache != null) {
return Result.of(fromCache);
}
return super.protectedQuizRequest(id);
}
@Override
protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) {
final String quizName = filterMap.getString(QuizData.FILTER_ATTR_QUIZ_NAME);
final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null;
// TODO get all course / quiz data from remote LMS that matches the filter criteria.
// put loaded QuizData to the cache: super.putToCache(quizDataCollection);
// before returning it.
return () -> {
throw new RuntimeException("TODO");
};
}
@Override
protected Supplier<Collection<QuizData>> quizzesSupplier(final Set<String> ids) {
// TODO get all quiz / course data for specified identifiers from remote LMS
// and put it to the cache: super.putToCache(quizDataCollection);
// before returning it.
return () -> {
throw new RuntimeException("TODO");
};
}
@Override
protected Supplier<QuizData> quizSupplier(final String id) {
// TODO get the specified quiz / course data for specified identifier from remote LMS
// and put it to the cache: super.putToCache(quizDataCollection);
// before returning it.
return () -> {
throw new RuntimeException("TODO");
};
}
@Override
protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) {
// TODO get the examinee's account details by the given examineeSessionId from remote LMS.
// Currently only the name is needed to display on monitoring view.
return () -> {
throw new RuntimeException("TODO");
};
}
@Override
protected Supplier<Chapters> getCourseChaptersSupplier(final String courseId) {
return () -> {
throw new UnsupportedOperationException("not available yet");
};
}
@Override
public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) {
final String quizId = exam.externalId;
// TODO get the SEB client restrictions that are currently set on the remote LMS for
// the given quiz / course derived from the given exam
return Result.ofRuntimeError("TODO");
}
@Override
public Result<SEBRestriction> applySEBClientRestriction(
final String externalExamId,
final SEBRestriction sebRestrictionData) {
// TODO apply the given sebRestrictionData settings as current SEB client restriction setting
// to the remote LMS for the given quiz / course.
// Mainly SEBRestriction.configKeys and SEBRestriction.browserExamKeys
return Result.ofRuntimeError("TODO");
}
@Override
public Result<Exam> releaseSEBClientRestriction(final Exam exam) {
final String quizId = exam.externalId;
// TODO Release respectively delete all SEB client restrictions for the given
// course / quize on the remote LMS.
return Result.ofRuntimeError("TODO");
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2021 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.olat;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
@Lazy
@Service
@WebServiceProfile
public class OlatLmsAPITemplateFactory implements LmsAPITemplateFactory {
@Override
public LmsType lmsType() {
return LmsType.OPEN_OLAT;
}
@Override
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
// TODO Auto-generated method stub
return null;
}
}

View file

@ -22,6 +22,7 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.async.AsyncRunner;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
@ -74,7 +75,7 @@ public class MoodleCourseAccessTest {
new JSONMapper(),
moodleRestTemplateFactory,
null,
mock(AsyncService.class),
new AsyncService(new AsyncRunner()),
this.env);
final String examId = "123";