SEBSERV-301 implementation

This commit is contained in:
anhefti 2022-12-08 16:36:24 +01:00
parent a141eccfa1
commit 71ae9fc755
12 changed files with 471 additions and 108 deletions

View file

@ -270,4 +270,14 @@ public class POSTMapper {
this.params.putIfAbsent(name, Arrays.asList(value));
return (T) this;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("POSTMapper [params=");
builder.append(this.params);
builder.append("]");
return builder.toString();
}
}

View file

@ -60,6 +60,8 @@ public final class LmsSetup implements GrantEntity, Activatable {
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
/** The Moodle binding features only the course access API so far */
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
/** The Moodle binding features with SEB Server integration plugin for fully featured */
MOODLE_PLUGIN(Features.COURSE_API, Features.SEB_RESTRICTION),
/** The Ans Delft binding is on the way */
ANS_DELFT(Features.COURSE_API, Features.SEB_RESTRICTION),
/** The OpenOLAT binding is on the way */

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2022 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 org.springframework.util.MultiValueMap;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
public interface MoodleAPIRestTemplate {
String URI_VAR_USER_NAME = "username";
String URI_VAR_PASSWORD = "pwd";
String URI_VAR_SERVICE = "service";
String MOODLE_DEFAULT_TOKEN_REQUEST_PATH =
"/login/token.php?username={" + URI_VAR_USER_NAME +
"}&password={" + URI_VAR_PASSWORD + "}&service={" + URI_VAR_SERVICE + "}";
String MOODLE_DEFAULT_REST_API_PATH = "/webservice/rest/server.php";
String REST_REQUEST_TOKEN_NAME = "wstoken";
String REST_REQUEST_FUNCTION_NAME = "wsfunction";
String REST_REQUEST_FORMAT_NAME = "moodlewsrestformat";
String getService();
void setService(String service);
CharSequence getAccessToken();
void testAPIConnection(String... functions);
String callMoodleAPIFunction(String functionName);
String callMoodleAPIFunction(
final String functionName,
final MultiValueMap<String, String> queryAttributes);
String callMoodleAPIFunction(
final String functionName,
final MultiValueMap<String, String> queryParams,
final MultiValueMap<String, String> queryAttributes);
/** This maps a Moodle warning JSON object */
@JsonIgnoreProperties(ignoreUnknown = true)
static final class Warning {
final String item;
final String itemid;
final String warningcode;
final String message;
@JsonCreator
public Warning(
@JsonProperty(value = "item") final String item,
@JsonProperty(value = "itemid") final String itemid,
@JsonProperty(value = "warningcode") final String warningcode,
@JsonProperty(value = "message") final String message) {
this.item = item;
this.itemid = itemid;
this.warningcode = warningcode;
this.message = message;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Warning [item=");
builder.append(this.item);
builder.append(", itemid=");
builder.append(this.itemid);
builder.append(", warningcode=");
builder.append(this.warningcode);
builder.append(", message=");
builder.append(this.message);
builder.append("]");
return builder.toString();
}
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
import java.util.ArrayList;
import java.util.Arrays;
@ -57,11 +57,11 @@ public class MoodleRestTemplateFactory {
private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
final JSONMapper jsonMapper;
final APITemplateDataSupplier apiTemplateDataSupplier;
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
final ClientCredentialService clientCredentialService;
final Set<String> knownTokenAccessPaths;
public final JSONMapper jsonMapper;
public final APITemplateDataSupplier apiTemplateDataSupplier;
public final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
public final ClientCredentialService clientCredentialService;
public final Set<String> knownTokenAccessPaths;
public MoodleRestTemplateFactory(
final JSONMapper jsonMapper,
@ -75,11 +75,12 @@ public class MoodleRestTemplateFactory {
this.clientCredentialService = clientCredentialService;
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.knownTokenAccessPaths = new HashSet<>();
this.knownTokenAccessPaths.add(MoodleAPIRestTemplate.MOODLE_DEFAULT_TOKEN_REQUEST_PATH);
final Set<String> paths = new HashSet<>();
paths.add(MoodleAPIRestTemplate.MOODLE_DEFAULT_TOKEN_REQUEST_PATH);
if (alternativeTokenRequestPaths != null) {
this.knownTokenAccessPaths.addAll(Arrays.asList(alternativeTokenRequestPaths));
paths.addAll(Arrays.asList(alternativeTokenRequestPaths));
}
this.knownTokenAccessPaths = Utils.immutableSetOf(paths);
}
APITemplateDataSupplier getApiTemplateDataSupplier() {
@ -125,7 +126,7 @@ public class MoodleRestTemplateFactory {
return LmsSetupTestResult.ofOkay(LmsType.MOODLE);
}
Result<MoodleAPIRestTemplate> createRestTemplate() {
public Result<MoodleAPIRestTemplate> createRestTemplate() {
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
@ -149,7 +150,7 @@ public class MoodleRestTemplateFactory {
") on paths: " + this.knownTokenAccessPaths));
}
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
public Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
@ -162,8 +163,9 @@ public class MoodleRestTemplateFactory {
.getPlainClientSecret(credentials)
.getOrThrow();
final MoodleAPIRestTemplate restTemplate = new MoodleAPIRestTemplate(
final MoodleAPIRestTemplateImpl restTemplate = new MoodleAPIRestTemplateImpl(
this.jsonMapper,
this.apiTemplateDataSupplier,
lmsSetup.lmsApiUrl,
accessTokenPath,
lmsSetup.lmsRestApiToken,
@ -187,22 +189,13 @@ public class MoodleRestTemplateFactory {
});
}
public class MoodleAPIRestTemplate extends RestTemplate {
public static class MoodleAPIRestTemplateImpl extends RestTemplate implements MoodleAPIRestTemplate {
public static final String URI_VAR_USER_NAME = "username";
public static final String URI_VAR_PASSWORD = "pwd";
public static final String URI_VAR_SERVICE = "service";
private static final String MOODLE_DEFAULT_TOKEN_REQUEST_PATH =
"/login/token.php?username={" + URI_VAR_USER_NAME +
"}&password={" + URI_VAR_PASSWORD + "}&service={" + URI_VAR_SERVICE + "}";
private static final String MOODLE_DEFAULT_REST_API_PATH = "/webservice/rest/server.php";
private static final String REST_REQUEST_TOKEN_NAME = "wstoken";
private static final String REST_REQUEST_FUNCTION_NAME = "wsfunction";
private static final String REST_REQUEST_FORMAT_NAME = "moodlewsrestformat";
private static final String REST_API_TEST_FUNCTION = "core_webservice_get_site_info";
final JSONMapper jsonMapper;
final APITemplateDataSupplier apiTemplateDataSupplier;
private final String serverURL;
private final String tokenPath;
@ -211,14 +204,18 @@ public class MoodleRestTemplateFactory {
private final Map<String, String> tokenReqURIVars;
private final HttpEntity<?> tokenReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
protected MoodleAPIRestTemplate(
protected MoodleAPIRestTemplateImpl(
final JSONMapper jsonMapper,
final APITemplateDataSupplier apiTemplateDataSupplier,
final String serverURL,
final String tokenPath,
final CharSequence accessToken,
final CharSequence username,
final CharSequence password) {
this.jsonMapper = jsonMapper;
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
this.serverURL = serverURL;
this.tokenPath = tokenPath;
this.accessToken = StringUtils.isNotBlank(accessToken) ? accessToken : null;
@ -230,14 +227,17 @@ public class MoodleRestTemplateFactory {
}
@Override
public String getService() {
return this.tokenReqURIVars.get(URI_VAR_SERVICE);
}
@Override
public void setService(final String service) {
this.tokenReqURIVars.put(URI_VAR_SERVICE, service);
}
@Override
public CharSequence getAccessToken() {
if (this.accessToken == null) {
requestAccessToken();
@ -246,22 +246,27 @@ public class MoodleRestTemplateFactory {
return this.accessToken;
}
@Override
public void testAPIConnection(final String... functions) {
try {
final String apiInfo = this.callMoodleAPIFunction(REST_API_TEST_FUNCTION);
final WebserviceInfo webserviceInfo =
MoodleRestTemplateFactory.this.jsonMapper.readValue(apiInfo, WebserviceInfo.class);
final WebserviceInfo webserviceInfo = this.jsonMapper.readValue(
apiInfo,
WebserviceInfo.class);
if (StringUtils.isBlank(webserviceInfo.username) || StringUtils.isBlank(webserviceInfo.userid)) {
throw new RuntimeException("Invalid WebserviceInfo: " + webserviceInfo);
}
final List<String> missingAPIFunctions = Arrays.stream(functions)
.filter(f -> !webserviceInfo.functions.containsKey(f))
.collect(Collectors.toList());
if (functions != null) {
if (!missingAPIFunctions.isEmpty()) {
throw new RuntimeException("Missing Moodle Webservice API functions: " + missingAPIFunctions);
final List<String> missingAPIFunctions = Arrays.stream(functions)
.filter(f -> !webserviceInfo.functions.containsKey(f))
.collect(Collectors.toList());
if (!missingAPIFunctions.isEmpty()) {
throw new RuntimeException("Missing Moodle Webservice API functions: " + missingAPIFunctions);
}
}
} catch (final RuntimeException re) {
@ -271,16 +276,19 @@ public class MoodleRestTemplateFactory {
}
}
@Override
public String callMoodleAPIFunction(final String functionName) {
return callMoodleAPIFunction(functionName, null, null);
}
@Override
public String callMoodleAPIFunction(
final String functionName,
final MultiValueMap<String, String> queryAttributes) {
return callMoodleAPIFunction(functionName, null, queryAttributes);
}
@Override
public String callMoodleAPIFunction(
final String functionName,
final MultiValueMap<String, String> queryParams,
@ -319,9 +327,7 @@ public class MoodleRestTemplateFactory {
functionReqEntity,
String.class);
final LmsSetup lmsSetup = MoodleRestTemplateFactory.this.apiTemplateDataSupplier
.getLmsSetup();
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
if (response.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException(
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
@ -347,9 +353,7 @@ public class MoodleRestTemplateFactory {
private void requestAccessToken() {
final LmsSetup lmsSetup = MoodleRestTemplateFactory.this.apiTemplateDataSupplier
.getLmsSetup();
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
try {
final ResponseEntity<String> response = super.exchange(
@ -369,7 +373,7 @@ public class MoodleRestTemplateFactory {
}
try {
final MoodleToken moodleToken = MoodleRestTemplateFactory.this.jsonMapper.readValue(
final MoodleToken moodleToken = this.jsonMapper.readValue(
response.getBody(),
MoodleToken.class);

View file

@ -48,9 +48,11 @@ 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.CourseAccessAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate.Warning;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader.CourseDataShort;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader.CourseQuizShort;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
*
@ -885,40 +887,4 @@ public class MoodleCourseAccess implements CourseAccessAPI {
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
static final class Warning {
final String item;
final String itemid;
final String warningcode;
final String message;
@JsonCreator
public Warning(
@JsonProperty(value = "item") final String item,
@JsonProperty(value = "itemid") final String itemid,
@JsonProperty(value = "warningcode") final String warningcode,
@JsonProperty(value = "message") final String message) {
this.item = item;
this.itemid = itemid;
this.warningcode = warningcode;
this.message = message;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Warning [item=");
builder.append(this.item);
builder.append(", itemid=");
builder.append(this.itemid);
builder.append(", warningcode=");
builder.append(this.warningcode);
builder.append(", message=");
builder.append(this.message);
builder.append("]");
return builder.toString();
}
}
}

View file

@ -47,8 +47,8 @@ import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseAccess.Warning;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate.Warning;
@Lazy
@Component

View file

@ -30,7 +30,8 @@ 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.SEBRestrictionAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
/** GET:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123

View file

@ -6,10 +6,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
@ -28,10 +29,7 @@ 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;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseAccess;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseRestriction;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MoodlePluginCheck;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MoodlePluginCourseAccess;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MoodlePluginCourseRestriction;
@ -43,6 +41,7 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
private final MoodlePluginCheck moodlePluginCheck;
private final JSONMapper jsonMapper;
private final CacheManager cacheManager;
private final AsyncService asyncService;
private final Environment environment;
private final ClientCredentialService clientCredentialService;
@ -53,6 +52,7 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
protected MoodleLmsAPITemplateFactory(
final MoodlePluginCheck moodlePluginCheck,
final JSONMapper jsonMapper,
final CacheManager cacheManager,
final AsyncService asyncService,
final Environment environment,
final ClientCredentialService clientCredentialService,
@ -62,6 +62,7 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
this.moodlePluginCheck = moodlePluginCheck;
this.jsonMapper = jsonMapper;
this.cacheManager = cacheManager;
this.asyncService = asyncService;
this.environment = environment;
this.clientCredentialService = clientCredentialService;
@ -87,9 +88,19 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
.getBean(MoodleCourseDataAsyncLoader.class);
asyncLoaderPrototype.init(lmsSetup.getModelId());
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
this.jsonMapper,
apiTemplateDataSupplier,
this.clientCredentialService,
this.clientHttpRequestFactoryService,
this.alternativeTokenRequestPaths);
if (this.moodlePluginCheck.checkPluginAvailable(lmsSetup)) {
final MoodlePluginCourseAccess moodlePluginCourseAccess = new MoodlePluginCourseAccess();
final MoodlePluginCourseAccess moodlePluginCourseAccess = new MoodlePluginCourseAccess(
this.jsonMapper,
moodleRestTemplateFactory,
this.cacheManager);
final MoodlePluginCourseRestriction moodlePluginCourseRestriction = new MoodlePluginCourseRestriction();
return new LmsAPITemplateAdapter(
@ -101,13 +112,6 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
} else {
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
this.jsonMapper,
apiTemplateDataSupplier,
this.clientCredentialService,
this.clientHttpRequestFactoryService,
this.alternativeTokenRequestPaths);
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
this.jsonMapper,
moodleRestTemplateFactory,

View file

@ -8,30 +8,96 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
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.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.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.CourseAccessAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleAPIRestTemplate.Warning;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
public class MoodlePluginCourseAccess implements CourseAccessAPI {
public class MoodlePluginCourseAccess extends AbstractCachedCourseAccess implements CourseAccessAPI {
private static final Logger log = LoggerFactory.getLogger(MoodlePluginCourseAccess.class);
static final String COURSES_API_FUNCTION_NAME = "local_sebserver_get_courses";
static final String QUIZZES_BY_COURSES_API_FUNCTION_NAME = "local_sebserver_get_quizzes_by_courses";
static final String USERS_API_FUNCTION_NAME = "local_sebserver_get_users";
static final String CRITERIA_FROM_DATE = "from_date";
static final String CRITERIA_TO_DATE = "to_date";
static final String CRITERIA_LIMIT_FROM = "limitfrom";
static final String CRITERIA_LIMIT_NUM = "limitnum";
private final JSONMapper jsonMapper;
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
private MoodleAPIRestTemplate restTemplate;
public MoodlePluginCourseAccess(
final JSONMapper jsonMapper,
final MoodleRestTemplateFactory moodleRestTemplateFactory,
final CacheManager cacheManager) {
super(cacheManager);
this.jsonMapper = jsonMapper;
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
}
@Override
public LmsSetupTestResult testCourseAccessAPI() {
// TODO Auto-generated method stub
return null;
final LmsSetupTestResult attributesCheck = this.moodleRestTemplateFactory.test();
if (!attributesCheck.isOk()) {
return attributesCheck;
}
final Result<MoodleAPIRestTemplate> restTemplateRequest = getRestTemplate();
if (restTemplateRequest.hasError()) {
final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
this.moodleRestTemplateFactory.knownTokenAccessPaths;
log.error(message + " cause: {}", restTemplateRequest.getError().getMessage());
return LmsSetupTestResult.ofTokenRequestError(LmsType.MOODLE_PLUGIN, message);
}
final MoodleAPIRestTemplate restTemplate = restTemplateRequest.get();
// try {
// restTemplate.testAPIConnection(
// COURSES_API_FUNCTION_NAME,
// QUIZZES_BY_COURSES_API_FUNCTION_NAME,
// USERS_API_FUNCTION_NAME);
// } catch (final RuntimeException e) {
// log.error("Failed to access Moodle course API: ", e);
// return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.MOODLE_PLUGIN, e.getMessage());
// }
return LmsSetupTestResult.ofOkay(LmsType.MOODLE_PLUGIN);
}
@Override
public Result<List<QuizData>> getQuizzes(final FilterMap filterMap) {
System.out.println("***************** filterMap: " + filterMap);
// TODO Auto-generated method stub
return null;
return Result.of(Collections.emptyList());
}
@Override
@ -46,12 +112,6 @@ public class MoodlePluginCourseAccess implements CourseAccessAPI {
return null;
}
@Override
public void clearCourseCache() {
// TODO Auto-generated method stub
}
@Override
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeUserId) {
// TODO Auto-generated method stub
@ -70,4 +130,119 @@ public class MoodlePluginCourseAccess implements CourseAccessAPI {
return null;
}
@Override
protected Long getLmsSetupId() {
// TODO Auto-generated method stub
return null;
}
private Result<MoodleAPIRestTemplate> getRestTemplate() {
if (this.restTemplate == null) {
final Result<MoodleAPIRestTemplate> templateRequest = this.moodleRestTemplateFactory
.createRestTemplate();
if (templateRequest.hasError()) {
return templateRequest;
} else {
this.restTemplate = templateRequest.get();
}
}
return Result.of(this.restTemplate);
}
// ---- Mapping Classes ---
@JsonIgnoreProperties(ignoreUnknown = true)
private static final class Courses {
final Collection<CourseData> courses;
final Collection<Warning> warnings;
@JsonCreator
protected Courses(
@JsonProperty(value = "courses") final Collection<CourseData> courses,
@JsonProperty(value = "warnings") final Collection<Warning> warnings) {
this.courses = courses;
this.warnings = warnings;
}
}
/** Maps the Moodle course API course data */
@JsonIgnoreProperties(ignoreUnknown = true)
private static final class CourseData {
final String id;
final String short_name;
final String idnumber;
final String full_name;
final String display_name;
final Long start_date; // unix-time seconds UTC
final Long end_date; // unix-time seconds UTC
final Long time_created; // unix-time seconds UTC
final Collection<CourseQuiz> quizzes = new ArrayList<>();
@JsonCreator
protected CourseData(
@JsonProperty(value = "id") final String id,
@JsonProperty(value = "shortname") final String short_name,
@JsonProperty(value = "idnumber") final String idnumber,
@JsonProperty(value = "fullname") final String full_name,
@JsonProperty(value = "displayname") final String display_name,
@JsonProperty(value = "startdate") final Long start_date,
@JsonProperty(value = "enddate") final Long end_date,
@JsonProperty(value = "timecreated") final Long time_created) {
this.id = id;
this.short_name = short_name;
this.idnumber = idnumber;
this.full_name = full_name;
this.display_name = display_name;
this.start_date = start_date;
this.end_date = end_date;
this.time_created = time_created;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
private static final class CourseQuizData {
final Collection<CourseQuiz> quizzes;
final Collection<Warning> warnings;
@JsonCreator
protected CourseQuizData(
@JsonProperty(value = "quizzes") final Collection<CourseQuiz> quizzes,
@JsonProperty(value = "warnings") final Collection<Warning> warnings) {
this.quizzes = quizzes;
this.warnings = warnings;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
static final class CourseQuiz {
final String id;
final String course;
final String course_module;
final String name;
final String intro; // HTML
final Long time_open; // unix-time seconds UTC
final Long time_close; // unix-time seconds UTC
@JsonCreator
protected CourseQuiz(
@JsonProperty(value = "id") final String id,
@JsonProperty(value = "course") final String course,
@JsonProperty(value = "coursemodule") final String course_module,
@JsonProperty(value = "name") final String name,
@JsonProperty(value = "intro") final String intro,
@JsonProperty(value = "timeopen") final Long time_open,
@JsonProperty(value = "timeclose") final Long time_close) {
this.id = id;
this.course = course;
this.course_module = course_module;
this.name = name;
this.intro = intro;
this.time_open = time_open;
this.time_close = time_close;
}
}
}

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
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.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
@ -19,7 +20,7 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
@Override
public LmsSetupTestResult testCourseRestrictionAPI() {
// TODO Auto-generated method stub
return null;
return LmsSetupTestResult.ofOkay(LmsType.MOODLE_PLUGIN);
}
@Override

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2022 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.plugin;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
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;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader;
@Lazy
@Service
@WebServiceProfile
public class MooldePluginLmsAPITemplateFactory implements LmsAPITemplateFactory {
private final MoodlePluginCheck moodlePluginCheck;
private final JSONMapper jsonMapper;
private final CacheManager cacheManager;
private final AsyncService asyncService;
private final Environment environment;
private final ClientCredentialService clientCredentialService;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final ApplicationContext applicationContext;
private final String[] alternativeTokenRequestPaths;
protected MooldePluginLmsAPITemplateFactory(
final MoodlePluginCheck moodlePluginCheck,
final JSONMapper jsonMapper,
final CacheManager cacheManager,
final AsyncService asyncService,
final Environment environment,
final ClientCredentialService clientCredentialService,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
final ApplicationContext applicationContext,
@Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths) {
this.moodlePluginCheck = moodlePluginCheck;
this.jsonMapper = jsonMapper;
this.cacheManager = cacheManager;
this.asyncService = asyncService;
this.environment = environment;
this.clientCredentialService = clientCredentialService;
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.applicationContext = applicationContext;
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
? StringUtils.split(alternativeTokenRequestPaths, Constants.LIST_SEPARATOR)
: null;
}
@Override
public LmsType lmsType() {
return LmsType.MOODLE_PLUGIN;
}
@Override
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = apiTemplateDataSupplier.getLmsSetup();
final MoodleCourseDataAsyncLoader asyncLoaderPrototype = this.applicationContext
.getBean(MoodleCourseDataAsyncLoader.class);
asyncLoaderPrototype.init(lmsSetup.getModelId());
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
this.jsonMapper,
apiTemplateDataSupplier,
this.clientCredentialService,
this.clientHttpRequestFactoryService,
this.alternativeTokenRequestPaths);
final MoodlePluginCourseAccess moodlePluginCourseAccess = new MoodlePluginCourseAccess(
this.jsonMapper,
moodleRestTemplateFactory,
this.cacheManager);
final MoodlePluginCourseRestriction moodlePluginCourseRestriction = new MoodlePluginCourseRestriction();
return new LmsAPITemplateAdapter(
this.asyncService,
this.environment,
apiTemplateDataSupplier,
moodlePluginCourseAccess,
moodlePluginCourseRestriction);
});
}
}

View file

@ -27,7 +27,8 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplateImpl;
public class MoodleCourseAccessTest {
@ -38,7 +39,7 @@ public class MoodleCourseAccessTest {
public void testGetExamineeAccountDetails() {
final MoodleRestTemplateFactory moodleRestTemplateFactory = mock(MoodleRestTemplateFactory.class);
final MoodleAPIRestTemplate moodleAPIRestTemplate = mock(MoodleAPIRestTemplate.class);
final MoodleAPIRestTemplateImpl moodleAPIRestTemplate = mock(MoodleAPIRestTemplateImpl.class);
when(moodleRestTemplateFactory.createRestTemplate()).thenReturn(Result.of(moodleAPIRestTemplate));
when(moodleAPIRestTemplate.callMoodleAPIFunction(
anyString(),
@ -132,7 +133,7 @@ public class MoodleCourseAccessTest {
@Test
public void testInitAPIAccessError2() {
final MoodleRestTemplateFactory moodleRestTemplateFactory = mock(MoodleRestTemplateFactory.class);
final MoodleAPIRestTemplate moodleAPIRestTemplate = mock(MoodleAPIRestTemplate.class);
final MoodleAPIRestTemplateImpl moodleAPIRestTemplate = mock(MoodleAPIRestTemplateImpl.class);
when(moodleRestTemplateFactory.createRestTemplate()).thenReturn(Result.of(moodleAPIRestTemplate));
doThrow(RuntimeException.class).when(moodleAPIRestTemplate).testAPIConnection(any());
when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay(LmsType.MOODLE));
@ -153,7 +154,7 @@ public class MoodleCourseAccessTest {
@Test
public void testInitAPIAccessOK() {
final MoodleRestTemplateFactory moodleRestTemplateFactory = mock(MoodleRestTemplateFactory.class);
final MoodleAPIRestTemplate moodleAPIRestTemplate = mock(MoodleAPIRestTemplate.class);
final MoodleAPIRestTemplateImpl moodleAPIRestTemplate = mock(MoodleAPIRestTemplateImpl.class);
when(moodleRestTemplateFactory.createRestTemplate()).thenReturn(Result.of(moodleAPIRestTemplate));
when(moodleRestTemplateFactory.test()).thenReturn(LmsSetupTestResult.ofOkay(LmsType.MOODLE));