added OlatLmsAPITemplate skeleton ready to be implemented
This commit is contained in:
parent
213cf443e1
commit
89c306e35a
7 changed files with 430 additions and 116 deletions
|
@ -15,19 +15,19 @@ import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Service
|
@Service
|
||||||
/** Implements a asynchronous service to manly support CircuitBreaker and MemoizingCircuitBreaker functionality. */
|
/** Implements a asynchronous service to manly support CircuitBreaker and MemoizingCircuitBreaker functionality. */
|
||||||
public class AsyncService {
|
public class AsyncService {
|
||||||
|
|
||||||
private final AsyncRunner asyncRunner;
|
private final AsyncRunner asyncRunner;
|
||||||
|
|
||||||
protected AsyncService(final AsyncRunner asyncRunner) {
|
public AsyncService(final AsyncRunner asyncRunner) {
|
||||||
this.asyncRunner = asyncRunner;
|
this.asyncRunner = asyncRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class
|
/** Create a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class
|
||||||
*
|
*
|
||||||
* @param <T> the type of the CircuitBreaker
|
* @param <T> the type of the CircuitBreaker
|
||||||
* @return a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class */
|
* @return a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class */
|
||||||
public <T> CircuitBreaker<T> createCircuitBreaker() {
|
public <T> CircuitBreaker<T> createCircuitBreaker() {
|
||||||
return new CircuitBreaker<>(this.asyncRunner);
|
return new CircuitBreaker<>(this.asyncRunner);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ public class AsyncService {
|
||||||
* @param maxBlockingTime maximal time since call CircuitBreaker waits for a response before going onto open state.
|
* @param maxBlockingTime maximal time since call CircuitBreaker waits for a response before going onto open state.
|
||||||
* @param timeToRecover the time the CircuitBreaker takes to recover form open state.
|
* @param timeToRecover the time the CircuitBreaker takes to recover form open state.
|
||||||
* @param <T> the type of the CircuitBreaker
|
* @param <T> the type of the CircuitBreaker
|
||||||
* @return a CircuitBreaker of specified type */
|
* @return a CircuitBreaker of specified type */
|
||||||
public <T> CircuitBreaker<T> createCircuitBreaker(
|
public <T> CircuitBreaker<T> createCircuitBreaker(
|
||||||
final int maxFailingAttempts,
|
final int maxFailingAttempts,
|
||||||
final long maxBlockingTime,
|
final long maxBlockingTime,
|
||||||
|
@ -52,7 +52,7 @@ public class AsyncService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a MemoizingCircuitBreaker of specified type that memoize a successful result and return the last
|
/** Create a MemoizingCircuitBreaker of specified type that memoize a successful result and return the last
|
||||||
* successful result on fail as long as maxMemoizingTime is not exceeded.
|
* successful result on fail as long as maxMemoizingTime is not exceeded.
|
||||||
*
|
*
|
||||||
* @param blockingSupplier the blocking result supplier that the MemoizingCircuitBreaker must call
|
* @param blockingSupplier the blocking result supplier that the MemoizingCircuitBreaker must call
|
||||||
* @param maxFailingAttempts maximal number of attempts the CircuitBreaker allows before going onto open state.
|
* @param maxFailingAttempts maximal number of attempts the CircuitBreaker allows before going onto open state.
|
||||||
|
@ -61,7 +61,7 @@ public class AsyncService {
|
||||||
* @param momoized whether the memoizing functionality is on or off
|
* @param momoized whether the memoizing functionality is on or off
|
||||||
* @param maxMemoizingTime the maximal time memorized data is valid
|
* @param maxMemoizingTime the maximal time memorized data is valid
|
||||||
* @param <T> the type of the CircuitBreaker
|
* @param <T> the type of the CircuitBreaker
|
||||||
* @return a CircuitBreaker of specified type */
|
* @return a CircuitBreaker of specified type */
|
||||||
public <T> MemoizingCircuitBreaker<T> createMemoizingCircuitBreaker(
|
public <T> MemoizingCircuitBreaker<T> createMemoizingCircuitBreaker(
|
||||||
final Supplier<T> blockingSupplier,
|
final Supplier<T> blockingSupplier,
|
||||||
final int maxFailingAttempts,
|
final int maxFailingAttempts,
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -134,10 +133,8 @@ public abstract class AbstractCourseAccess {
|
||||||
return this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap));
|
return this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<QuizData> protectedQuizzesRequest(final Set<String> ids) {
|
public Result<Collection<QuizData>> protectedQuizzesRequest(final Set<String> ids) {
|
||||||
return this.quizzesRequest.protectedRun(quizzesSupplier(ids))
|
return this.quizzesRequest.protectedRun(quizzesSupplier(ids));
|
||||||
.onError(error -> log.error("Failed to get QuizData for ids: ", error))
|
|
||||||
.getOrElse(() -> Collections.emptyList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<QuizData> protectedQuizRequest(final String id) {
|
public Result<QuizData> protectedQuizRequest(final String id) {
|
||||||
|
@ -145,7 +142,8 @@ public abstract class AbstractCourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
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
|
/** Default implementation that uses getExamineeAccountDetails to geht the examinee name
|
||||||
|
@ -163,19 +161,7 @@ public abstract class AbstractCourseAccess {
|
||||||
return this.chaptersRequest.protectedRun(getCourseChaptersSupplier(courseId));
|
return this.chaptersRequest.protectedRun(getCourseChaptersSupplier(courseId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** NOTE: this returns a ExamineeAccountDetails with given examineeSessionId for default.
|
protected abstract Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId);
|
||||||
* 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Provides a supplier to supply request to use within the circuit breaker */
|
/** Provides a supplier to supply request to use within the circuit breaker */
|
||||||
protected abstract Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap);
|
protected abstract Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap);
|
||||||
|
|
|
@ -141,52 +141,56 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) {
|
||||||
return Result.tryCatch(() -> {
|
return () -> {
|
||||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
try {
|
||||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
final OAuth2RestTemplate template = getRestTemplate()
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
.getOrThrow();
|
final OAuth2RestTemplate template = getRestTemplate()
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
final String externalStartURI = this.webserviceInfo
|
final String externalStartURI = this.webserviceInfo
|
||||||
.getLmsExternalAddressAlias(lmsSetup.lmsApiUrl);
|
.getLmsExternalAddressAlias(lmsSetup.lmsApiUrl);
|
||||||
|
|
||||||
final String uri = (externalStartURI != null)
|
final String uri = (externalStartURI != null)
|
||||||
? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId
|
? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId
|
||||||
: lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
|
: lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
|
||||||
|
|
||||||
final String responseJSON = template.exchange(
|
final String responseJSON = template.exchange(
|
||||||
uri,
|
uri,
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
new HttpEntity<>(httpHeaders),
|
new HttpEntity<>(httpHeaders),
|
||||||
String.class)
|
String.class)
|
||||||
.getBody();
|
.getBody();
|
||||||
|
|
||||||
final EdxUserDetails[] userDetails = this.jsonMapper.<EdxUserDetails[]> readValue(
|
final EdxUserDetails[] userDetails = this.jsonMapper.<EdxUserDetails[]> readValue(
|
||||||
responseJSON,
|
responseJSON,
|
||||||
new TypeReference<EdxUserDetails[]>() {
|
new TypeReference<EdxUserDetails[]>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userDetails == null || userDetails.length <= 0) {
|
if (userDetails == null || userDetails.length <= 0) {
|
||||||
throw new RuntimeException("No user details on Open edX API request");
|
throw new RuntimeException("No user details on Open edX API request");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> additionalAttributes = new HashMap<>();
|
||||||
|
additionalAttributes.put("bio", userDetails[0].bio);
|
||||||
|
additionalAttributes.put("country", userDetails[0].country);
|
||||||
|
additionalAttributes.put("date_joined", userDetails[0].date_joined);
|
||||||
|
additionalAttributes.put("gender", userDetails[0].gender);
|
||||||
|
additionalAttributes.put("is_active", String.valueOf(userDetails[0].is_active));
|
||||||
|
additionalAttributes.put("mailing_address", userDetails[0].mailing_address);
|
||||||
|
additionalAttributes.put("secondary_email", userDetails[0].secondary_email);
|
||||||
|
|
||||||
|
return new ExamineeAccountDetails(
|
||||||
|
userDetails[0].username,
|
||||||
|
userDetails[0].name,
|
||||||
|
userDetails[0].username,
|
||||||
|
userDetails[0].email,
|
||||||
|
additionalAttributes);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
final Map<String, String> additionalAttributes = new HashMap<>();
|
|
||||||
additionalAttributes.put("bio", userDetails[0].bio);
|
|
||||||
additionalAttributes.put("country", userDetails[0].country);
|
|
||||||
additionalAttributes.put("date_joined", userDetails[0].date_joined);
|
|
||||||
additionalAttributes.put("gender", userDetails[0].gender);
|
|
||||||
additionalAttributes.put("is_active", String.valueOf(userDetails[0].is_active));
|
|
||||||
additionalAttributes.put("mailing_address", userDetails[0].mailing_address);
|
|
||||||
additionalAttributes.put("secondary_email", userDetails[0].secondary_email);
|
|
||||||
|
|
||||||
return new ExamineeAccountDetails(
|
|
||||||
userDetails[0].username,
|
|
||||||
userDetails[0].name,
|
|
||||||
userDetails[0].username,
|
|
||||||
userDetails[0].email,
|
|
||||||
additionalAttributes);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -275,7 +279,7 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!leftIds.isEmpty()) {
|
if (!leftIds.isEmpty()) {
|
||||||
result.addAll(super.protectedQuizzesRequest(leftIds));
|
result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -129,59 +129,63 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) {
|
||||||
return Result.tryCatch(() -> {
|
return () -> {
|
||||||
final MoodleAPIRestTemplate template = getRestTemplate()
|
try {
|
||||||
.getOrThrow();
|
final MoodleAPIRestTemplate template = getRestTemplate()
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
|
final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
|
||||||
queryAttributes.add("field", "id");
|
queryAttributes.add("field", "id");
|
||||||
queryAttributes.add("values[0]", examineeSessionId);
|
queryAttributes.add("values[0]", examineeSessionId);
|
||||||
|
|
||||||
final String userDetailsJSON = template.callMoodleAPIFunction(
|
final String userDetailsJSON = template.callMoodleAPIFunction(
|
||||||
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
|
||||||
queryAttributes);
|
|
||||||
|
|
||||||
if (checkAccessDeniedError(userDetailsJSON)) {
|
|
||||||
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
|
||||||
log.error("Get access denied error from Moodle: {} for API call: {}, response: {}",
|
|
||||||
lmsSetup,
|
|
||||||
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
||||||
Utils.truncateText(userDetailsJSON, 2000));
|
queryAttributes);
|
||||||
throw new RuntimeException("No user details on Moodle API request (access-denied)");
|
|
||||||
|
if (checkAccessDeniedError(userDetailsJSON)) {
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
log.error("Get access denied error from Moodle: {} for API call: {}, response: {}",
|
||||||
|
lmsSetup,
|
||||||
|
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
||||||
|
Utils.truncateText(userDetailsJSON, 2000));
|
||||||
|
throw new RuntimeException("No user details on Moodle API request (access-denied)");
|
||||||
|
}
|
||||||
|
|
||||||
|
final MoodleUserDetails[] userDetails = this.jsonMapper.<MoodleUserDetails[]> readValue(
|
||||||
|
userDetailsJSON,
|
||||||
|
new TypeReference<MoodleUserDetails[]>() {
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userDetails == null || userDetails.length <= 0) {
|
||||||
|
throw new RuntimeException("No user details on Moodle API request");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> additionalAttributes = new HashMap<>();
|
||||||
|
additionalAttributes.put("firstname", userDetails[0].firstname);
|
||||||
|
additionalAttributes.put("lastname", userDetails[0].lastname);
|
||||||
|
additionalAttributes.put("department", userDetails[0].department);
|
||||||
|
additionalAttributes.put("firstaccess", String.valueOf(userDetails[0].firstaccess));
|
||||||
|
additionalAttributes.put("lastaccess", String.valueOf(userDetails[0].lastaccess));
|
||||||
|
additionalAttributes.put("auth", userDetails[0].auth);
|
||||||
|
additionalAttributes.put("suspended", String.valueOf(userDetails[0].suspended));
|
||||||
|
additionalAttributes.put("confirmed", String.valueOf(userDetails[0].confirmed));
|
||||||
|
additionalAttributes.put("lang", userDetails[0].lang);
|
||||||
|
additionalAttributes.put("theme", userDetails[0].theme);
|
||||||
|
additionalAttributes.put("timezone", userDetails[0].timezone);
|
||||||
|
additionalAttributes.put("description", userDetails[0].description);
|
||||||
|
additionalAttributes.put("mailformat", String.valueOf(userDetails[0].mailformat));
|
||||||
|
additionalAttributes.put("descriptionformat", String.valueOf(userDetails[0].descriptionformat));
|
||||||
|
return new ExamineeAccountDetails(
|
||||||
|
userDetails[0].id,
|
||||||
|
userDetails[0].fullname,
|
||||||
|
userDetails[0].username,
|
||||||
|
userDetails[0].email,
|
||||||
|
additionalAttributes);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
final MoodleUserDetails[] userDetails = this.jsonMapper.<MoodleUserDetails[]> readValue(
|
|
||||||
userDetailsJSON,
|
|
||||||
new TypeReference<MoodleUserDetails[]>() {
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userDetails == null || userDetails.length <= 0) {
|
|
||||||
throw new RuntimeException("No user details on Moodle API request");
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, String> additionalAttributes = new HashMap<>();
|
|
||||||
additionalAttributes.put("firstname", userDetails[0].firstname);
|
|
||||||
additionalAttributes.put("lastname", userDetails[0].lastname);
|
|
||||||
additionalAttributes.put("department", userDetails[0].department);
|
|
||||||
additionalAttributes.put("firstaccess", String.valueOf(userDetails[0].firstaccess));
|
|
||||||
additionalAttributes.put("lastaccess", String.valueOf(userDetails[0].lastaccess));
|
|
||||||
additionalAttributes.put("auth", userDetails[0].auth);
|
|
||||||
additionalAttributes.put("suspended", String.valueOf(userDetails[0].suspended));
|
|
||||||
additionalAttributes.put("confirmed", String.valueOf(userDetails[0].confirmed));
|
|
||||||
additionalAttributes.put("lang", userDetails[0].lang);
|
|
||||||
additionalAttributes.put("theme", userDetails[0].theme);
|
|
||||||
additionalAttributes.put("timezone", userDetails[0].timezone);
|
|
||||||
additionalAttributes.put("description", userDetails[0].description);
|
|
||||||
additionalAttributes.put("mailformat", String.valueOf(userDetails[0].mailformat));
|
|
||||||
additionalAttributes.put("descriptionformat", String.valueOf(userDetails[0].descriptionformat));
|
|
||||||
return new ExamineeAccountDetails(
|
|
||||||
userDetails[0].id,
|
|
||||||
userDetails[0].fullname,
|
|
||||||
userDetails[0].username,
|
|
||||||
userDetails[0].email,
|
|
||||||
additionalAttributes);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LmsSetupTestResult initAPIAccess() {
|
LmsSetupTestResult initAPIAccess() {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
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.async.AsyncService;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
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.institution.LmsSetupTestResult;
|
||||||
|
@ -74,7 +75,7 @@ public class MoodleCourseAccessTest {
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class),
|
new AsyncService(new AsyncRunner()),
|
||||||
this.env);
|
this.env);
|
||||||
|
|
||||||
final String examId = "123";
|
final String examId = "123";
|
||||||
|
|
Loading…
Reference in a new issue