refactoring and improvement of LMS binding API
This commit is contained in:
parent
0bda2cb292
commit
dd6150ec2a
36 changed files with 790 additions and 544 deletions
|
@ -129,6 +129,7 @@ public final class API {
|
||||||
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
||||||
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
|
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
|
||||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
|
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
|
||||||
|
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_INCLUDE_RESTRICTION = "include-restriction";
|
||||||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
|
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
|
||||||
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
|
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
|
||||||
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
|
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
|
||||||
|
|
|
@ -47,7 +47,7 @@ public final class Exam implements GrantEntity {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
ExamStatus.FINISHED,
|
ExamStatus.FINISHED,
|
||||||
// Boolean.FALSE,
|
Boolean.FALSE,
|
||||||
null,
|
null,
|
||||||
Boolean.FALSE,
|
Boolean.FALSE,
|
||||||
null);
|
null);
|
||||||
|
@ -115,6 +115,9 @@ public final class Exam implements GrantEntity {
|
||||||
@JsonProperty(EXAM.ATTR_STATUS)
|
@JsonProperty(EXAM.ATTR_STATUS)
|
||||||
public final ExamStatus status;
|
public final ExamStatus status;
|
||||||
|
|
||||||
|
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION)
|
||||||
|
public final Boolean sebRestriction;
|
||||||
|
|
||||||
@JsonProperty(EXAM.ATTR_BROWSER_KEYS)
|
@JsonProperty(EXAM.ATTR_BROWSER_KEYS)
|
||||||
public final String browserExamKeys;
|
public final String browserExamKeys;
|
||||||
|
|
||||||
|
@ -139,6 +142,7 @@ public final class Exam implements GrantEntity {
|
||||||
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
||||||
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
|
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
|
||||||
@JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status,
|
@JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status,
|
||||||
|
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION) final Boolean sebRestriction,
|
||||||
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
|
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
|
||||||
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
|
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
|
||||||
@JsonProperty(EXAM.ATTR_LASTUPDATE) final String lastUpdate) {
|
@JsonProperty(EXAM.ATTR_LASTUPDATE) final String lastUpdate) {
|
||||||
|
@ -155,6 +159,7 @@ public final class Exam implements GrantEntity {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
|
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
|
||||||
|
this.sebRestriction = sebRestriction;
|
||||||
this.browserExamKeys = browserExamKeys;
|
this.browserExamKeys = browserExamKeys;
|
||||||
this.active = (active != null) ? active : Boolean.TRUE;
|
this.active = (active != null) ? active : Boolean.TRUE;
|
||||||
this.lastUpdate = lastUpdate;
|
this.lastUpdate = lastUpdate;
|
||||||
|
@ -181,6 +186,7 @@ public final class Exam implements GrantEntity {
|
||||||
EXAM.ATTR_STATUS,
|
EXAM.ATTR_STATUS,
|
||||||
ExamStatus.class,
|
ExamStatus.class,
|
||||||
getStatusFromDate(this.startTime, this.endTime));
|
getStatusFromDate(this.startTime, this.endTime));
|
||||||
|
this.sebRestriction = null;
|
||||||
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
|
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
|
||||||
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
|
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
|
||||||
this.supporter = mapper.getStringSet(EXAM.ATTR_SUPPORTER);
|
this.supporter = mapper.getStringSet(EXAM.ATTR_SUPPORTER);
|
||||||
|
@ -204,6 +210,7 @@ public final class Exam implements GrantEntity {
|
||||||
this.type = null;
|
this.type = null;
|
||||||
this.owner = null;
|
this.owner = null;
|
||||||
this.status = (status != null) ? status : getStatusFromDate(this.startTime, this.endTime);
|
this.status = (status != null) ? status : getStatusFromDate(this.startTime, this.endTime);
|
||||||
|
this.sebRestriction = null;
|
||||||
this.browserExamKeys = null;
|
this.browserExamKeys = null;
|
||||||
this.active = null;
|
this.active = null;
|
||||||
this.supporter = null;
|
this.supporter = null;
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
|
||||||
public final class QuizData implements GrantEntity {
|
public final class QuizData implements GrantEntity {
|
||||||
|
|
||||||
|
public static final String FILTER_ATTR_QUIZ_NAME = "quiz_name";
|
||||||
public static final String FILTER_ATTR_START_TIME = "start_timestamp";
|
public static final String FILTER_ATTR_START_TIME = "start_timestamp";
|
||||||
|
|
||||||
public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
|
public static final String ATTR_ADDITIONAL_ATTRIBUTES = "ADDITIONAL_ATTRIBUTES";
|
||||||
|
|
|
@ -37,16 +37,32 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
||||||
public static final String FILTER_ATTR_LMS_SETUP = "lms_setup";
|
public static final String FILTER_ATTR_LMS_SETUP = "lms_setup";
|
||||||
public static final String FILTER_ATTR_LMS_TYPE = "lms_type";
|
public static final String FILTER_ATTR_LMS_TYPE = "lms_type";
|
||||||
|
|
||||||
|
/** LMS binding and API features */
|
||||||
public enum Features {
|
public enum Features {
|
||||||
|
/** The course API allows the application to securely connect to a LMS service
|
||||||
|
* and request course or quiz data from that LMS as well as requesting some
|
||||||
|
* limited LMS user account data like user name or display name. */
|
||||||
COURSE_API,
|
COURSE_API,
|
||||||
|
/** The SEB restriction API allows the application to securely connect to a LMS service
|
||||||
|
* and place or release SEB restrictions, for a particular course or quiz, on the LMS.
|
||||||
|
* The SEB restriciton is usually in the form of certain hash keys and addition
|
||||||
|
* restriction settings that prompt the LMS to check access on course/quiz connection and
|
||||||
|
* allow only access for a dedicated SEB client with the right configuration in place. */
|
||||||
SEB_RESTRICTION
|
SEB_RESTRICTION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines the supported types if LMS bindings.
|
||||||
|
* Also defines the supports feature(s) for each type of LMS binding. */
|
||||||
public enum LmsType {
|
public enum LmsType {
|
||||||
|
/** Mockup LMS type used to create test setups */
|
||||||
MOCKUP(Features.COURSE_API),
|
MOCKUP(Features.COURSE_API),
|
||||||
|
/** The Open edX LMS binding features both APIs, course access as well as SEB restrcition */
|
||||||
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
|
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 */),
|
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
|
||||||
|
/** The Ans Delft binding is on the way */
|
||||||
ANS_DELFT(/* Features.COURSE_API , Features.SEB_RESTRICTION */),
|
ANS_DELFT(/* Features.COURSE_API , Features.SEB_RESTRICTION */),
|
||||||
|
/** The OpenOLAT binding is on the way */
|
||||||
OPEN_OLAT(/* Features.COURSE_API , Features.SEB_RESTRICTION */);
|
OPEN_OLAT(/* Features.COURSE_API , Features.SEB_RESTRICTION */);
|
||||||
|
|
||||||
public final EnumSet<Features> features;
|
public final EnumSet<Features> features;
|
||||||
|
|
|
@ -226,6 +226,8 @@ public class ExamForm implements TemplateComposer {
|
||||||
final ExamStatus examStatus = exam.getStatus();
|
final ExamStatus examStatus = exam.getStatus();
|
||||||
final boolean editable = modifyGrant && (examStatus == ExamStatus.UP_COMING ||
|
final boolean editable = modifyGrant && (examStatus == ExamStatus.UP_COMING ||
|
||||||
examStatus == ExamStatus.RUNNING);
|
examStatus == ExamStatus.RUNNING);
|
||||||
|
|
||||||
|
// TODO this is not performat try to improve by doing one check with the CheckExamConsistency above
|
||||||
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
||||||
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
||||||
.getBuilder(CheckSEBRestriction.class)
|
.getBuilder(CheckSEBRestriction.class)
|
||||||
|
|
|
@ -29,4 +29,5 @@ public interface LmsSetupDAO extends ActivatableEntityDAO<LmsSetup, LmsSetup>, B
|
||||||
* @param lmsSetupId the LMS Setup identifier
|
* @param lmsSetupId the LMS Setup identifier
|
||||||
* @return Result refer to the proxy data or to an error if happened */
|
* @return Result refer to the proxy data or to an error if happened */
|
||||||
Result<ProxyData> getLmsAPIAccessProxyData(String lmsSetupId);
|
Result<ProxyData> getLmsAPIAccessProxyData(String lmsSetupId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1010,6 +1010,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
record.getOwner(),
|
record.getOwner(),
|
||||||
supporter,
|
supporter,
|
||||||
(quizData != null) ? status : (statusOverride != null) ? statusOverride : status,
|
(quizData != null) ? status : (statusOverride != null) ? statusOverride : status,
|
||||||
|
BooleanUtils.toBooleanObject(record.getLmsSebRestriction()),
|
||||||
record.getBrowserKeys(),
|
record.getBrowserKeys(),
|
||||||
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null),
|
BooleanUtils.toBooleanObject((quizData != null) ? record.getActive() : null),
|
||||||
record.getLastupdate());
|
record.getLastupdate());
|
||||||
|
|
|
@ -41,17 +41,6 @@ public interface ExamAdminService {
|
||||||
* @return Result refer to the restriction flag or to an error when happened */
|
* @return Result refer to the restriction flag or to an error when happened */
|
||||||
Result<Boolean> isRestricted(Exam exam);
|
Result<Boolean> isRestricted(Exam exam);
|
||||||
|
|
||||||
// /** Get the proctoring service settings for a certain exam to an error when happened.
|
|
||||||
// *
|
|
||||||
// * @param examId the exam instance
|
|
||||||
// * @return Result refer to proctoring service settings for the exam. */
|
|
||||||
// default Result<ProctoringServiceSettings> getProctoringServiceSettings(final Exam exam) {
|
|
||||||
// if (exam == null || exam.id == null) {
|
|
||||||
// return Result.ofRuntimeError("Invalid Exam model");
|
|
||||||
// }
|
|
||||||
// return getProctoringServiceSettings(exam.id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** Get proctoring service settings for a certain exam to an error when happened.
|
/** Get proctoring service settings for a certain exam to an error when happened.
|
||||||
*
|
*
|
||||||
* @param examId the exam identifier
|
* @param examId the exam identifier
|
||||||
|
@ -99,29 +88,4 @@ public interface ExamAdminService {
|
||||||
.flatMap(settings -> getExamProctoringService(settings.serverType));
|
.flatMap(settings -> getExamProctoringService(settings.serverType));
|
||||||
}
|
}
|
||||||
|
|
||||||
// /** Get the exam proctoring service implementation of specified type.
|
|
||||||
// *
|
|
||||||
// * @param settings the ProctoringSettings that defines the ProctoringServerType
|
|
||||||
// * @return ExamProctoringService instance */
|
|
||||||
// default Result<ExamProctoringService> getExamProctoringService(final ProctoringServiceSettings settings) {
|
|
||||||
// return Result.tryCatch(() -> getExamProctoringService(settings.serverType).getOrThrow());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /** Get the exam proctoring service implementation for specified exam.
|
|
||||||
// *
|
|
||||||
// * @param exam the exam instance
|
|
||||||
// * @return ExamProctoringService instance */
|
|
||||||
// default Result<ExamProctoringService> getExamProctoringService(final Exam exam) {
|
|
||||||
// return Result.tryCatch(() -> getExamProctoringService(exam.id).getOrThrow());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /** Get the exam proctoring service implementation for specified exam.
|
|
||||||
// *
|
|
||||||
// * @param examId the exam identifier
|
|
||||||
// * @return ExamProctoringService instance */
|
|
||||||
// default Result<ExamProctoringService> getExamProctoringService(final Long examId) {
|
|
||||||
// return getProctoringServiceSettings(examId)
|
|
||||||
// .flatMap(this::getExamProctoringService);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
|
|
||||||
|
/** Supplier for LmsAPITemplate to supply the templat with the needed LMS connection data. */
|
||||||
|
public interface APITemplateDataSupplier {
|
||||||
|
|
||||||
|
/** Get the LmsSetup instance containing all setup attributes
|
||||||
|
*
|
||||||
|
* @return the LmsSetup instance containing all setup attributes */
|
||||||
|
LmsSetup getLmsSetup();
|
||||||
|
|
||||||
|
/** Get the encoded LMS setup client credentials needed to access the LMS API.
|
||||||
|
*
|
||||||
|
* @return the encoded LMS setup client credentials needed to access the LMS API. */
|
||||||
|
ClientCredentials getLmsClientCredentials();
|
||||||
|
|
||||||
|
/** Get the proxy data if available and if needed for LMS connection
|
||||||
|
*
|
||||||
|
* @return the proxy data if available and if needed for LMS connection */
|
||||||
|
ProxyData getProxyData();
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
* changes this service will be notifies about the change and release the related LmsAPITemplate from cache. */
|
* changes this service will be notifies about the change and release the related LmsAPITemplate from cache. */
|
||||||
public interface LmsAPIService {
|
public interface LmsAPIService {
|
||||||
|
|
||||||
|
/** Reset and cleanup the caches if there are some */
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
/** Get the specified LmsSetup model by primary key
|
/** Get the specified LmsSetup model by primary key
|
||||||
*
|
*
|
||||||
* @param id The identifier (PK) of the LmsSetup model
|
* @param id The identifier (PK) of the LmsSetup model
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Set;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
|
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.Chapters;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
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.QuizData;
|
||||||
|
@ -27,64 +28,114 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
|
||||||
|
|
||||||
/** Defines an LMS API access template to build SEB Server LMS integration.
|
/** Defines an LMS API access template to build SEB Server LMS integration.
|
||||||
*
|
* </p>
|
||||||
* A LMS integration consists of two main parts so far:
|
* A LMS integration consists of two main parts so far:
|
||||||
* - The course API to search and request course data from LMS as well as resolve some LMS account details for a given
|
* </p>
|
||||||
* examineeId
|
|
||||||
* - The SEB restriction API to apply SEB restriction data to the LMS to restrict a certain course for SEB
|
|
||||||
*
|
*
|
||||||
* A LmsAPITemplate is been constructed within a LmsSetup that defines the LMS setup data that is needed to connect to
|
* <pre>
|
||||||
* a specific LMS instance of implemented type.
|
* - The course API to search and request course data from LMS as well as resolve some
|
||||||
*
|
* LMS account details for a given examineeId.
|
||||||
* The enum LmsSetup.LmsType defines the supported LMS types and for each type the supported API part(s).
|
* - The SEB restriction API to apply SEB restriction data to the LMS to restrict a
|
||||||
*
|
* certain course for SEB.
|
||||||
* SEB Server uses the test functions that are defined for each LMS API part to test API access for a certain LMS
|
* </pre>
|
||||||
* instance respectively the underling LMSSetup. Concrete implementations can do various tests to check full
|
* </p>
|
||||||
* or partial API Access and can flag missing or wrong LMSSetup attributes with the resulting LmsSetupTestResult.
|
|
||||||
*
|
*
|
||||||
|
* <b>Course API</b></br>
|
||||||
|
* All course API requests of this template shall not block and return as fast as possible
|
||||||
|
* with the best result it can provide for the time on that the request was made.
|
||||||
|
* </p>
|
||||||
|
* Since the course API requests course data from potentially thousands of existing and
|
||||||
|
* active courses, the course API can implement some caches if needed.</br>
|
||||||
|
* A cache in the course API has manly two purposes; The first and prior purpose is to
|
||||||
|
* be able to provide course data as fast as possible even if the LMS is not available or
|
||||||
|
* busy at the time. The second purpose is to guarantee fast data access for the system
|
||||||
|
* if this is needed and data actuality has second priority.</br>
|
||||||
|
* Therefore usual get quiz data functions like {@link #getQuizzes(FilterMap filterMap) },
|
||||||
|
* {@link #getQuizzes(Set<String> ids) } and {@link #getQuiz(final String id) }
|
||||||
|
* shall always first try to connect to the LMS and request the specified data from the LMS.
|
||||||
|
* If this succeeds the cache shall be updated with the received quizzes data and return them.
|
||||||
|
* If this is not possible within a certain time, the implementation shall get as much of the
|
||||||
|
* requested data from the cache and return them to the caller to not block the call too long
|
||||||
|
* and allow the caller to return fast and present as much data as possible.</br>
|
||||||
|
* This can be done with a {@link MemoizingCircuitBreaker} or a simple {@link CircuitBreaker}
|
||||||
|
* with a separated cache, for example. The abstract implementation; {@link AbstractCourseAccess}
|
||||||
|
* provides already defined wrapped circuit breaker for each call. To use it, just extend the
|
||||||
|
* abstract class and implement the needed suppliers.</br>
|
||||||
|
* On the other hand, dedicated cache access functions like {@link #getQuizzesFromCache(Set<String> ids) }
|
||||||
|
* shall always first look into the cache to geht the requested data and if not
|
||||||
|
* available, call the LMS and request the data from the LMS. If partial data is needed to get
|
||||||
|
* be requested from the LMS, this functions shall also update the catch with the requested
|
||||||
|
* and cache missed data afterwards.
|
||||||
|
* </p>
|
||||||
|
* <b>SEB restriction API</b></br>
|
||||||
|
* For this API we need no caching since this is mostly about pushing data to the LMS for the LMS
|
||||||
|
* to use. But this calls sahl also be protected within some kind of circuit breaker pattern to
|
||||||
|
* avoid blocking on long latency.
|
||||||
|
* </p>
|
||||||
|
* </p>
|
||||||
|
* A {@link LmsAPITemplate } will be constructed within the application with a {@link LmsSetup } instances.
|
||||||
|
* The application constructs a {@link LmsAPITemplate } for each type of LMS setup when needed or requested and
|
||||||
|
* there is not already a cached template or the cached template is out of date.</br>
|
||||||
|
* The {@link LmsSetup } defines the data that is needed to connect to a specific LMS instance of implemented type
|
||||||
|
* and is wrapped within a {@link LmsAPITemplate } instance that lives as long as there are no changes to the
|
||||||
|
* {@link LmsSetup and the {@link LmsSetup } that is wrapped within the {@link LmsAPITemplate } is up to date.
|
||||||
|
* <p>
|
||||||
|
* The enum {@link LmsSetup.LmsType } defines the supported LMS types and for each type the supported API part(s).
|
||||||
|
* <p>
|
||||||
|
* The application uses the test functions that are defined for each LMS API part to test API access for a certain LMS
|
||||||
|
* instance respectively the underling {@link LmsSetup }. Concrete implementations can do various tests to check full
|
||||||
|
* or partial API Access and can flag missing or wrong {@link LmsSetup } attributes with the resulting
|
||||||
|
* {@link LmsSetupTestResult }.</br>
|
||||||
* SEB Server than uses an instance of this template to communicate with the an LMS. */
|
* SEB Server than uses an instance of this template to communicate with the an LMS. */
|
||||||
public interface LmsAPITemplate {
|
public interface LmsAPITemplate {
|
||||||
|
|
||||||
/** Get the underling LMSSetup configuration for this LmsAPITemplate
|
/** Get the LMS type of the concrete template implementation
|
||||||
*
|
*
|
||||||
* @return the underling LMSSetup configuration for this LmsAPITemplate */
|
* @return the LMS type of the concrete template implementation */
|
||||||
|
LmsSetup.LmsType getType();
|
||||||
|
|
||||||
|
/** Get the underling {@link LmsSetup } configuration for this LmsAPITemplate
|
||||||
|
*
|
||||||
|
* @return the underling {@link LmsSetup } configuration for this LmsAPITemplate */
|
||||||
LmsSetup lmsSetup();
|
LmsSetup lmsSetup();
|
||||||
|
|
||||||
/** Performs a test for the underling LmsSetup configuration and checks if the
|
// *******************************************************************
|
||||||
|
// **** Course API functions *****************************************
|
||||||
|
|
||||||
|
/** Performs a test for the underling {@link LmsSetup } configuration and checks if the
|
||||||
* LMS and the course API of the LMS can be accessed or if there are some difficulties,
|
* LMS and the course API of the LMS can be accessed or if there are some difficulties,
|
||||||
* missing configuration data or connection/authentication errors.
|
* missing configuration data or connection/authentication errors.
|
||||||
*
|
*
|
||||||
* @return LmsSetupTestResult instance with the test result report */
|
* @return {@link LmsSetupTestResult } instance with the test result report */
|
||||||
LmsSetupTestResult testCourseAccessAPI();
|
LmsSetupTestResult testCourseAccessAPI();
|
||||||
|
|
||||||
/** Performs a test for the underling LmsSetup configuration and checks if the
|
/** Get an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
|
||||||
* LMS and the course restriction API of the LMS can be accessed or if there are some difficulties,
|
|
||||||
* missing configuration data or connection/authentication errors.
|
|
||||||
*
|
*
|
||||||
* @return LmsSetupTestResult instance with the test result report */
|
* @param filterMap the {@link FilterMap } to get a filtered result. Possible filter attributes are:
|
||||||
LmsSetupTestResult testCourseRestrictionAPI();
|
|
||||||
|
|
||||||
/** Get an unsorted List of filtered QuizData from the LMS course/quiz API
|
|
||||||
*
|
*
|
||||||
* @param filterMap the FilterMap to get a filtered result. For possible filter attributes
|
* <pre>
|
||||||
* see documentation on QuizData
|
* {@link QuizData.FILTER_ATTR_QUIZ_NAME } The quiz name filter text (exclude all names that do not contain the given text)
|
||||||
* @return Result of an unsorted List of filtered QuizData from the LMS course/quiz API
|
* {@link QuizData.FILTER_ATTR_START_TIME } The quiz start time (exclude all quizzes that starts before)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return Result of an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
|
||||||
* or refer to an error when happened */
|
* or refer to an error when happened */
|
||||||
Result<List<QuizData>> getQuizzes(FilterMap filterMap);
|
Result<List<QuizData>> getQuizzes(FilterMap filterMap);
|
||||||
|
|
||||||
/** Get all QuizData for the set of QuizData identifiers from LMS API in a collection
|
/** Get all {@link QuizData } for the set of {@link QuizData } identifiers from LMS API in a collection
|
||||||
* of Result. If particular Quiz cannot be loaded because of errors or deletion,
|
* of Result. If particular quiz cannot be loaded because of errors or deletion,
|
||||||
* the Result will have an error reference.
|
* the Result will have an error reference.
|
||||||
*
|
*
|
||||||
* @param ids the Set of Quiz identifiers to get the QuizData for
|
* @param ids the Set of Quiz identifiers to get the {@link QuizData } for
|
||||||
* @return Collection of all QuizData from the given id set */
|
* @return Collection of all {@link QuizData } from the given id set */
|
||||||
Collection<Result<QuizData>> getQuizzes(Set<String> ids);
|
Collection<Result<QuizData>> getQuizzes(Set<String> ids);
|
||||||
|
|
||||||
/** Get the quiz data with specified identifier.
|
/** Get the quiz data with specified identifier.
|
||||||
*
|
*
|
||||||
* Default implementation: Uses getQuizzes(Set<String> ids) and returns the first matching or an error.
|
* Default implementation: Uses {@link #getQuizzes(Set<String> ids) } and returns the first matching or an error.
|
||||||
*
|
*
|
||||||
* @param id the quiz data identifier
|
* @param id the quiz data identifier
|
||||||
* @return Result refer to the quiz data or to an error when happened */
|
* @return Result refer to the quiz data or to an error when happened */
|
||||||
|
@ -99,26 +150,27 @@ public interface LmsAPITemplate {
|
||||||
.orElse(Result.ofError(new ResourceNotFoundException(EntityType.EXAM, id)));
|
.orElse(Result.ofError(new ResourceNotFoundException(EntityType.EXAM, id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get all QuizData for the set of QuizData-identifiers (ids) from the LMS defined within the
|
/** Get all {@link QuizData } for the set of {@link QuizData }-identifiers (ids) from the LMS defined within the
|
||||||
* underling LMSSetup, in a collection of Results.
|
* underling LmsSetup, in a collection of Results.
|
||||||
*
|
*
|
||||||
* If there is caching involved this function shall try to get the data from the cache first.
|
* If there is caching involved this function shall try to get the data from the cache first.
|
||||||
*
|
*
|
||||||
* NOTE: This function depends on the specific LMS implementation and on whether caching the quiz data
|
* NOTE: This function depends on the specific LMS implementation and on whether caching the quiz data
|
||||||
* makes sense or not. Following strategy is recommended:
|
* makes sense or not. Following strategy is recommended:
|
||||||
* Looks first in the cache if the whole set of QuizData can be get from the cache.
|
* Looks first in the cache if the whole set of {@link QuizData } can be get from the cache.
|
||||||
* If all quizzes are cached, returns all from cache.
|
* If all quizzes are cached, returns all from cache.
|
||||||
* If one or more quiz is not in the cache, requests all quizzes from the API and refreshes the cache
|
* If one or more quiz is not in the cache, requests all quizzes from the API and refreshes the cache
|
||||||
*
|
*
|
||||||
* @param ids the Set of Quiz identifiers to get the QuizData for
|
* @param ids the Set of Quiz identifiers to get the {@link QuizData } for
|
||||||
* @return Collection of all QuizData from the given id set */
|
* @return Collection of all {@link QuizData } from the given id set */
|
||||||
Collection<Result<QuizData>> getQuizzesFromCache(Set<String> ids);
|
Collection<Result<QuizData>> getQuizzesFromCache(Set<String> ids);
|
||||||
|
|
||||||
/** Convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
|
/** Convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
|
||||||
* to LMS examinee account details by requesting them on the LMS API with the given examineeUserId
|
* to LMS examinee account details by requesting them on the LMS API with the given examineeUserId
|
||||||
*
|
*
|
||||||
* @param examineeUserId the examinee user identifier derived from SEB Client
|
* @param examineeUserId the examinee user identifier derived from SEB Client
|
||||||
* @return a Result refer to the ExamineeAccountDetails instance or to an error when happened or not supported */
|
* @return a Result refer to the {@link ExamineeAccountDetails } instance or to an error when happened or not
|
||||||
|
* supported */
|
||||||
Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
|
Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
|
||||||
|
|
||||||
/** Used to convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
|
/** Used to convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
|
||||||
|
@ -142,11 +194,23 @@ public interface LmsAPITemplate {
|
||||||
* @return Result referencing to the Chapters model for the given course or to an error when happened. */
|
* @return Result referencing to the Chapters model for the given course or to an error when happened. */
|
||||||
Result<Chapters> getCourseChapters(String courseId);
|
Result<Chapters> getCourseChapters(String courseId);
|
||||||
|
|
||||||
/** Get SEB restriction data form LMS within a SEBRestrictionData instance. The available restriction details
|
// ****************************************************************************
|
||||||
|
// **** SEB restriction API functions *****************************************
|
||||||
|
|
||||||
|
/** Performs a test for the underling {@link LmsSetup } configuration and checks if the
|
||||||
|
* LMS and the course restriction API of the LMS can be accessed or if there are some difficulties,
|
||||||
|
* missing configuration data or connection/authentication errors.
|
||||||
|
*
|
||||||
|
* @return {@link LmsSetupTestResult } instance with the test result report */
|
||||||
|
LmsSetupTestResult testCourseRestrictionAPI();
|
||||||
|
|
||||||
|
/** Get SEB restriction data form LMS within a {@link SEBRestrictionData } instance. The available restriction
|
||||||
|
* details
|
||||||
* depends on the type of LMS but shall at least contains the config-key(s) and the browser-exam-key(s).
|
* depends on the type of LMS but shall at least contains the config-key(s) and the browser-exam-key(s).
|
||||||
*
|
*
|
||||||
* @param exam the exam to get the SEB restriction data for
|
* @param exam the exam to get the SEB restriction data for
|
||||||
* @return Result refer to the SEBRestrictionData instance or to an ResourceNotFoundException if the restriction is
|
* @return Result refer to the {@link SEBRestrictionData } instance or to an ResourceNotFoundException if the
|
||||||
|
* restriction is
|
||||||
* missing or to another exception on unexpected error case */
|
* missing or to another exception on unexpected error case */
|
||||||
Result<SEBRestriction> getSEBClientRestriction(Exam exam);
|
Result<SEBRestriction> getSEBClientRestriction(Exam exam);
|
||||||
|
|
||||||
|
@ -154,7 +218,8 @@ public interface LmsAPITemplate {
|
||||||
*
|
*
|
||||||
* @param externalExamId The exam/course identifier from LMS side (Exam.externalId)
|
* @param externalExamId The exam/course identifier from LMS side (Exam.externalId)
|
||||||
* @param sebRestrictionData containing all data for SEB Client restriction to apply to the LMS
|
* @param sebRestrictionData containing all data for SEB Client restriction to apply to the LMS
|
||||||
* @return Result refer to the given SEBRestrictionData if restriction was successful or to an error if not */
|
* @return Result refer to the given {@link SEBRestrictionData } if restriction was successful or to an error if
|
||||||
|
* not */
|
||||||
Result<SEBRestriction> applySEBClientRestriction(
|
Result<SEBRestriction> applySEBClientRestriction(
|
||||||
String externalExamId,
|
String externalExamId,
|
||||||
SEBRestriction sebRestrictionData);
|
SEBRestriction sebRestrictionData);
|
||||||
|
|
|
@ -8,9 +8,6 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
|
||||||
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.model.institution.LmsSetup.LmsType;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
@ -23,16 +20,10 @@ public interface LmsAPITemplateFactory {
|
||||||
* @return the LMS type if a specific implementation */
|
* @return the LMS type if a specific implementation */
|
||||||
LmsType lmsType();
|
LmsType lmsType();
|
||||||
|
|
||||||
/** Creates a LmsAPITemplate for the specific implements LMS type.
|
/** Creates a {@link LmsAPITemplate } for the specific implements LMS type
|
||||||
|
* And provides it with the needed {@link APITemplateDataSupplier }
|
||||||
*
|
*
|
||||||
* @param lmsSetup the LMS setup data to initialize the template
|
* @param apiTemplateDataSupplier supplies all needed actual LMS setup data */
|
||||||
* @param credentials the access data for accessing the LMS API. Either client credentials or access token from LMS
|
Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier);
|
||||||
* setup input
|
|
||||||
* @param proxyData The proxy data used to connect to the LMS if needed.
|
|
||||||
* @return Result refer to the LmsAPITemplate or to an error when happened */
|
|
||||||
Result<LmsAPITemplate> create(
|
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
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.exam.SEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -19,6 +21,9 @@ public interface SEBRestrictionService {
|
||||||
|
|
||||||
String SEB_RESTRICTION_ADDITIONAL_PROPERTY_CONFIG_KEY = "config_key";
|
String SEB_RESTRICTION_ADDITIONAL_PROPERTY_CONFIG_KEY = "config_key";
|
||||||
|
|
||||||
|
/** Get the LmsAPIService that is used by the SEBRestrictionService */
|
||||||
|
LmsAPIService getLmsAPIService();
|
||||||
|
|
||||||
/** Get the SEBRestriction properties for specified Exam.
|
/** Get the SEBRestriction properties for specified Exam.
|
||||||
*
|
*
|
||||||
* @param exam the Exam
|
* @param exam the Exam
|
||||||
|
@ -50,4 +55,6 @@ public interface SEBRestrictionService {
|
||||||
* @return Result refer to the Exam instance or to an error if happened */
|
* @return Result refer to the Exam instance or to an error if happened */
|
||||||
Result<Exam> releaseSEBClientRestriction(Exam exam);
|
Result<Exam> releaseSEBClientRestriction(Exam exam);
|
||||||
|
|
||||||
|
boolean checkConsistency(@NotNull Long lmsSetupId, Exam exam);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
* API requests in a protected environment.
|
* API requests in a protected environment.
|
||||||
*
|
*
|
||||||
* Extend this to implement a concrete course access API for a given type of LMS. */
|
* Extend this to implement a concrete course access API for a given type of LMS. */
|
||||||
public abstract class CourseAccess {
|
public abstract class AbstractCourseAccess {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(CourseAccess.class);
|
private static final Logger log = LoggerFactory.getLogger(AbstractCourseAccess.class);
|
||||||
|
|
||||||
/** Fetch status that indicates an asynchronous quiz data fetch status if the
|
/** Fetch status that indicates an asynchronous quiz data fetch status if the
|
||||||
* concrete implementation has such. */
|
* concrete implementation has such. */
|
||||||
|
@ -54,7 +54,7 @@ public abstract class CourseAccess {
|
||||||
/** CircuitBreaker for protected examinee account details requests */
|
/** CircuitBreaker for protected examinee account details requests */
|
||||||
protected final CircuitBreaker<ExamineeAccountDetails> accountDetailRequest;
|
protected final CircuitBreaker<ExamineeAccountDetails> accountDetailRequest;
|
||||||
|
|
||||||
protected CourseAccess(
|
protected AbstractCourseAccess(
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
final Environment environment) {
|
final Environment environment) {
|
||||||
|
|
|
@ -22,10 +22,10 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.event.EventListener;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
||||||
|
@ -38,6 +38,8 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
|
||||||
|
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.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
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.LmsAPITemplateFactory;
|
||||||
|
@ -71,21 +73,9 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
this.templateFactories = new EnumMap<>(factories);
|
this.templateFactories = new EnumMap<>(factories);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Listen to LmsSetupChangeEvent to release an affected LmsAPITemplate from cache
|
@Override
|
||||||
*
|
public void cleanup() {
|
||||||
* @param event the event holding the changed LmsSetup */
|
this.cache.clear();
|
||||||
@EventListener
|
|
||||||
public void notifyLmsSetupChange(final LmsSetupChangeEvent event) {
|
|
||||||
final LmsSetup lmsSetup = event.getLmsSetup();
|
|
||||||
if (lmsSetup == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("LmsSetup changed. Update cache by removing eventually used references");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cache.remove(new CacheKey(lmsSetup.getModelId(), 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -107,10 +97,19 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsSetupId) {
|
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsSetupId) {
|
||||||
return Result.tryCatch(() -> this.lmsSetupDAO
|
return Result.tryCatch(() -> {
|
||||||
.byModelId(lmsSetupId)
|
LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetupId);
|
||||||
.getOrThrow())
|
if (lmsAPITemplate == null) {
|
||||||
.flatMap(this::getLmsAPITemplate);
|
lmsAPITemplate = createLmsSetupTemplate(lmsSetupId);
|
||||||
|
if (lmsAPITemplate != null) {
|
||||||
|
this.cache.put(new CacheKey(lmsSetupId, System.currentTimeMillis()), lmsAPITemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lmsAPITemplate == null) {
|
||||||
|
throw new ResourceNotFoundException(EntityType.LMS_SETUP, lmsSetupId);
|
||||||
|
}
|
||||||
|
return lmsAPITemplate;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,23 +128,22 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetupTestResult testAdHoc(final LmsSetup lmsSetup) {
|
public LmsSetupTestResult testAdHoc(final LmsSetup lmsSetup) {
|
||||||
final ClientCredentials lmsCredentials = this.clientCredentialService.encryptClientCredentials(
|
final AdHocAPITemplateDataSupplier apiTemplateDataSupplier = new AdHocAPITemplateDataSupplier(
|
||||||
lmsSetup.lmsAuthName,
|
lmsSetup,
|
||||||
lmsSetup.lmsAuthSecret,
|
this.clientCredentialService);
|
||||||
lmsSetup.lmsRestApiToken)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
final ProxyData proxyData = (StringUtils.isNoneBlank(lmsSetup.proxyHost))
|
final LmsAPITemplate lmsSetupTemplate = createLmsSetupTemplate(apiTemplateDataSupplier);
|
||||||
? new ProxyData(
|
|
||||||
lmsSetup.proxyHost,
|
|
||||||
lmsSetup.proxyPort,
|
|
||||||
this.clientCredentialService.encryptClientCredentials(
|
|
||||||
lmsSetup.proxyAuthUsername,
|
|
||||||
lmsSetup.proxyAuthSecret)
|
|
||||||
.getOrThrow())
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return test(createLmsSetupTemplate(lmsSetup, lmsCredentials, proxyData));
|
final LmsSetupTestResult testCourseAccessAPI = lmsSetupTemplate.testCourseAccessAPI();
|
||||||
|
if (!testCourseAccessAPI.isOk()) {
|
||||||
|
return testCourseAccessAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lmsSetupTemplate.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
|
||||||
|
return lmsSetupTemplate.testCourseRestrictionAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LmsSetupTestResult.ofOkay();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect all QuizData from all affecting LmsSetup.
|
/** Collect all QuizData from all affecting LmsSetup.
|
||||||
|
@ -183,6 +181,7 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
return this.lmsSetupDAO.all(institutionId, true)
|
return this.lmsSetupDAO.all(institutionId, true)
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.parallelStream()
|
.parallelStream()
|
||||||
|
.map(LmsSetup::getModelId)
|
||||||
.map(this::getLmsAPITemplate)
|
.map(this::getLmsAPITemplate)
|
||||||
.flatMap(Result::onErrorLogAndSkip)
|
.flatMap(Result::onErrorLogAndSkip)
|
||||||
.map(template -> template.getQuizzes(filterMap))
|
.map(template -> template.getQuizzes(filterMap))
|
||||||
|
@ -193,18 +192,7 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<LmsAPITemplate> getLmsAPITemplate(final LmsSetup lmsSetup) {
|
private LmsAPITemplate getFromCache(final String lmsSetupId) {
|
||||||
return Result.tryCatch(() -> {
|
|
||||||
LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetup);
|
|
||||||
if (lmsAPITemplate == null) {
|
|
||||||
lmsAPITemplate = createLmsSetupTemplate(lmsSetup);
|
|
||||||
this.cache.put(new CacheKey(lmsSetup.getModelId(), System.currentTimeMillis()), lmsAPITemplate);
|
|
||||||
}
|
|
||||||
return lmsAPITemplate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private LmsAPITemplate getFromCache(final LmsSetup lmsSetup) {
|
|
||||||
// first cleanup the cache by removing old instances
|
// first cleanup the cache by removing old instances
|
||||||
final long currentTimeMillis = System.currentTimeMillis();
|
final long currentTimeMillis = System.currentTimeMillis();
|
||||||
new ArrayList<>(this.cache.keySet())
|
new ArrayList<>(this.cache.keySet())
|
||||||
|
@ -212,40 +200,103 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
||||||
.filter(key -> key.creationTimestamp - currentTimeMillis > Constants.DAY_IN_MILLIS)
|
.filter(key -> key.creationTimestamp - currentTimeMillis > Constants.DAY_IN_MILLIS)
|
||||||
.forEach(this.cache::remove);
|
.forEach(this.cache::remove);
|
||||||
// get from cache
|
// get from cache
|
||||||
return this.cache.get(new CacheKey(lmsSetup.getModelId(), 0));
|
return this.cache.get(new CacheKey(lmsSetupId, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private LmsAPITemplate createLmsSetupTemplate(final LmsSetup lmsSetup) {
|
private LmsAPITemplate createLmsSetupTemplate(final String lmsSetupId) {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Create new LmsAPITemplate for id: {}", lmsSetup.getModelId());
|
log.debug("Create new LmsAPITemplate for id: {}", lmsSetupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClientCredentials credentials = this.lmsSetupDAO
|
return createLmsSetupTemplate(new PersistentAPITemplateDataSupplier(
|
||||||
.getLmsAPIAccessCredentials(lmsSetup.getModelId())
|
lmsSetupId,
|
||||||
.getOrThrow();
|
this.lmsSetupDAO));
|
||||||
|
|
||||||
final ProxyData proxyData = this.lmsSetupDAO
|
|
||||||
.getLmsAPIAccessProxyData(lmsSetup.getModelId())
|
|
||||||
.getOr(null);
|
|
||||||
|
|
||||||
return createLmsSetupTemplate(lmsSetup, credentials, proxyData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LmsAPITemplate createLmsSetupTemplate(
|
private LmsAPITemplate createLmsSetupTemplate(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData) {
|
|
||||||
|
|
||||||
if (!this.templateFactories.containsKey(lmsSetup.lmsType)) {
|
final LmsType lmsType = apiTemplateDataSupplier.getLmsSetup().lmsType;
|
||||||
throw new UnsupportedOperationException("No support for LMS Type: " + lmsSetup.lmsType);
|
|
||||||
|
if (!this.templateFactories.containsKey(lmsType)) {
|
||||||
|
throw new UnsupportedOperationException("No support for LMS Type: " + lmsType);
|
||||||
}
|
}
|
||||||
|
|
||||||
final LmsAPITemplateFactory lmsAPITemplateFactory = this.templateFactories.get(lmsSetup.lmsType);
|
final LmsAPITemplateFactory lmsAPITemplateFactory = this.templateFactories
|
||||||
return lmsAPITemplateFactory.create(lmsSetup, credentials, proxyData)
|
.get(lmsType);
|
||||||
|
|
||||||
|
return lmsAPITemplateFactory
|
||||||
|
.create(apiTemplateDataSupplier)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Used to always get the actual LMS connection data from persistent */
|
||||||
|
private static final class PersistentAPITemplateDataSupplier implements APITemplateDataSupplier {
|
||||||
|
|
||||||
|
private final String lmsSetupId;
|
||||||
|
private final LmsSetupDAO lmsSetupDAO;
|
||||||
|
|
||||||
|
public PersistentAPITemplateDataSupplier(final String lmsSetupId, final LmsSetupDAO lmsSetupDAO) {
|
||||||
|
this.lmsSetupId = lmsSetupId;
|
||||||
|
this.lmsSetupDAO = lmsSetupDAO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LmsSetup getLmsSetup() {
|
||||||
|
return this.lmsSetupDAO.byModelId(this.lmsSetupId).getOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientCredentials getLmsClientCredentials() {
|
||||||
|
return this.lmsSetupDAO.getLmsAPIAccessCredentials(this.lmsSetupId).getOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProxyData getProxyData() {
|
||||||
|
return this.lmsSetupDAO.getLmsAPIAccessProxyData(this.lmsSetupId).getOr(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Used to test LMS connection data that are not yet persistently stored */
|
||||||
|
private static final class AdHocAPITemplateDataSupplier implements APITemplateDataSupplier {
|
||||||
|
|
||||||
|
private final LmsSetup lmsSetup;
|
||||||
|
private final ClientCredentialService clientCredentialService;
|
||||||
|
|
||||||
|
public AdHocAPITemplateDataSupplier(
|
||||||
|
final LmsSetup lmsSetup,
|
||||||
|
final ClientCredentialService clientCredentialService) {
|
||||||
|
this.lmsSetup = lmsSetup;
|
||||||
|
this.clientCredentialService = clientCredentialService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LmsSetup getLmsSetup() {
|
||||||
|
return this.lmsSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientCredentials getLmsClientCredentials() {
|
||||||
|
return this.clientCredentialService.encryptClientCredentials(
|
||||||
|
this.lmsSetup.getLmsAuthName(),
|
||||||
|
this.lmsSetup.getLmsAuthSecret())
|
||||||
|
.getOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProxyData getProxyData() {
|
||||||
|
return (StringUtils.isNoneBlank(this.lmsSetup.proxyHost))
|
||||||
|
? new ProxyData(
|
||||||
|
this.lmsSetup.proxyHost,
|
||||||
|
this.lmsSetup.proxyPort,
|
||||||
|
this.clientCredentialService.encryptClientCredentials(
|
||||||
|
this.lmsSetup.proxyAuthUsername,
|
||||||
|
this.lmsSetup.proxyAuthSecret)
|
||||||
|
.getOrThrow())
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class CacheKey {
|
private static final class CacheKey {
|
||||||
final String lmsSetupId;
|
final String lmsSetupId;
|
||||||
final long creationTimestamp;
|
final long creationTimestamp;
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 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;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
|
||||||
|
|
||||||
public class LmsSetupChangeEvent extends ApplicationEvent {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -7239994198026689531L;
|
|
||||||
|
|
||||||
public LmsSetupChangeEvent(final LmsSetup source) {
|
|
||||||
super(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LmsSetup getLmsSetup() {
|
|
||||||
return (LmsSetup) this.source;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -18,6 +18,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -29,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
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.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.Features;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -62,15 +65,38 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
this.examConfigService = examConfigService;
|
this.examConfigService = examConfigService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LmsAPIService getLmsAPIService() {
|
||||||
|
return this.lmsAPIService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkConsistency(@NotNull final Long lmsSetupId, final Exam exam) {
|
||||||
|
final LmsSetup lmsSetup = this.lmsAPIService
|
||||||
|
.getLmsSetup(exam.lmsSetupId)
|
||||||
|
.getOr(null);
|
||||||
|
|
||||||
|
// check only if SEB_RESTRICTION feature is on
|
||||||
|
if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||||
|
if (!exam.sebRestriction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<SEBRestriction> getSEBRestrictionFromExam(final Exam exam) {
|
public Result<SEBRestriction> getSEBRestrictionFromExam(final Exam exam) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
// load the config keys from restriction and merge with new generated config keys
|
// load the config keys from restriction and merge with new generated config keys
|
||||||
|
final long currentTimeMillis = System.currentTimeMillis();
|
||||||
final Set<String> configKeys = new HashSet<>();
|
final Set<String> configKeys = new HashSet<>();
|
||||||
final Collection<String> generatedKeys = this.examConfigService
|
final Collection<String> generatedKeys = this.examConfigService
|
||||||
.generateConfigKeys(exam.institutionId, exam.id)
|
.generateConfigKeys(exam.institutionId, exam.id)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
System.out.println("******* " + (System.currentTimeMillis() - currentTimeMillis));
|
||||||
|
|
||||||
configKeys.addAll(generatedKeys);
|
configKeys.addAll(generatedKeys);
|
||||||
if (generatedKeys != null && !generatedKeys.isEmpty()) {
|
if (generatedKeys != null && !generatedKeys.isEmpty()) {
|
||||||
|
@ -134,6 +160,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
null, null, null, null, null, null, null, null, null, null,
|
null, null, null, null, null, null, null, null, null, null,
|
||||||
exam.supporter,
|
exam.supporter,
|
||||||
exam.status,
|
exam.status,
|
||||||
|
null,
|
||||||
(browserExamKeys != null && !browserExamKeys.isEmpty())
|
(browserExamKeys != null && !browserExamKeys.isEmpty())
|
||||||
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
|
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
|
||||||
: StringUtils.EMPTY,
|
: StringUtils.EMPTY,
|
||||||
|
@ -167,28 +194,31 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> applySEBClientRestriction(final Exam exam) {
|
public Result<Exam> applySEBClientRestriction(final Exam exam) {
|
||||||
if (!this.lmsAPIService
|
return Result.tryCatch(() -> {
|
||||||
.getLmsSetup(exam.lmsSetupId)
|
if (!this.lmsAPIService
|
||||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
.getLmsSetup(exam.lmsSetupId)
|
||||||
|
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||||
|
|
||||||
return Result.of(exam);
|
return exam;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getSEBRestrictionFromExam(exam)
|
return this.getSEBRestrictionFromExam(exam)
|
||||||
.map(sebRestrictionData -> {
|
.map(sebRestrictionData -> {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Applying SEB Client restriction on LMS with: {}", sebRestrictionData);
|
log.debug("Applying SEB Client restriction on LMS with: {}", sebRestrictionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.lmsAPIService
|
return this.lmsAPIService
|
||||||
.getLmsAPITemplate(exam.lmsSetupId)
|
.getLmsAPITemplate(exam.lmsSetupId)
|
||||||
.flatMap(lmsTemplate -> lmsTemplate.applySEBClientRestriction(
|
.flatMap(lmsTemplate -> lmsTemplate.applySEBClientRestriction(
|
||||||
exam.externalId,
|
exam.externalId,
|
||||||
sebRestrictionData))
|
sebRestrictionData))
|
||||||
.map(data -> exam)
|
.map(data -> exam)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
});
|
})
|
||||||
|
.getOrThrow();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -53,12 +54,13 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
|
||||||
|
|
||||||
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
||||||
*
|
*
|
||||||
* See also: https://course-catalog-api-guide.readthedocs.io */
|
* See also: https://course-catalog-api-guide.readthedocs.io */
|
||||||
final class OpenEdxCourseAccess extends CourseAccess {
|
final class OpenEdxCourseAccess extends AbstractCourseAccess {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseAccess.class);
|
private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseAccess.class);
|
||||||
|
|
||||||
|
@ -70,7 +72,6 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
private static final String OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT = "/api/user/v1/accounts?username=";
|
private static final String OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT = "/api/user/v1/accounts?username=";
|
||||||
|
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final LmsSetup lmsSetup;
|
|
||||||
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
|
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
|
||||||
private final WebserviceInfo webserviceInfo;
|
private final WebserviceInfo webserviceInfo;
|
||||||
private final MemoizingCircuitBreaker<List<QuizData>> allQuizzesRequest;
|
private final MemoizingCircuitBreaker<List<QuizData>> allQuizzesRequest;
|
||||||
|
@ -80,7 +81,6 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
public OpenEdxCourseAccess(
|
public OpenEdxCourseAccess(
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
||||||
final WebserviceInfo webserviceInfo,
|
final WebserviceInfo webserviceInfo,
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
|
@ -88,7 +88,6 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
super(asyncService, environment);
|
super(asyncService, environment);
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.lmsSetup = lmsSetup;
|
|
||||||
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
||||||
this.webserviceInfo = webserviceInfo;
|
this.webserviceInfo = webserviceInfo;
|
||||||
|
|
||||||
|
@ -130,8 +129,14 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||||
|
return this.openEdxRestTemplateFactory.apiTemplateDataSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
LmsSetupTestResult initAPIAccess() {
|
LmsSetupTestResult initAPIAccess() {
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
|
||||||
final LmsSetupTestResult attributesCheck = this.openEdxRestTemplateFactory.test();
|
final LmsSetupTestResult attributesCheck = this.openEdxRestTemplateFactory.test();
|
||||||
if (!attributesCheck.isOk()) {
|
if (!attributesCheck.isOk()) {
|
||||||
return attributesCheck;
|
return attributesCheck;
|
||||||
|
@ -148,13 +153,14 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
final OAuth2RestTemplate restTemplate = restTemplateRequest.get();
|
final OAuth2RestTemplate restTemplate = restTemplateRequest.get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
restTemplate.getAccessToken();
|
||||||
|
//this.getEdxPage(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
|
|
||||||
restTemplate.setAuthenticator(new EdxOAuth2RequestAuthenticator());
|
restTemplate.setAuthenticator(new EdxOAuth2RequestAuthenticator());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.getEdxPage(this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
this.getEdxPage(lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT, restTemplate);
|
||||||
} catch (final RuntimeException ee) {
|
} catch (final RuntimeException ee) {
|
||||||
log.error("Failed to access Open edX course API: ", ee);
|
log.error("Failed to access Open edX course API: ", ee);
|
||||||
return LmsSetupTestResult.ofQuizAccessAPIError(ee.getMessage());
|
return LmsSetupTestResult.ofQuizAccessAPIError(ee.getMessage());
|
||||||
|
@ -167,14 +173,18 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
@Override
|
@Override
|
||||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
final OAuth2RestTemplate template = getRestTemplate()
|
final OAuth2RestTemplate template = getRestTemplate()
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
final String externalStartURI = this.webserviceInfo.getLmsExternalAddressAlias(this.lmsSetup.lmsApiUrl);
|
final String externalStartURI = this.webserviceInfo
|
||||||
|
.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
|
||||||
: this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
|
: lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
|
||||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
|
||||||
final String responseJSON = template.exchange(
|
final String responseJSON = template.exchange(
|
||||||
uri,
|
uri,
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
|
@ -211,9 +221,24 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids) {
|
protected Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids) {
|
||||||
return () -> getRestTemplate()
|
|
||||||
.map(template -> this.collectQuizzes(template, ids))
|
if (ids.size() == 1) {
|
||||||
.getOrThrow();
|
return () -> {
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
|
||||||
|
return Arrays.asList(quizDataOf(
|
||||||
|
lmsSetup,
|
||||||
|
getOneCourses(
|
||||||
|
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||||
|
getRestTemplate().getOrThrow(),
|
||||||
|
ids.iterator().next()),
|
||||||
|
externalStartURI));
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return () -> getRestTemplate()
|
||||||
|
.map(template -> this.collectQuizzes(template, ids))
|
||||||
|
.getOrThrow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Supplier<List<QuizData>> quizzesSupplier() {
|
private Supplier<List<QuizData>> quizzesSupplier() {
|
||||||
|
@ -225,8 +250,10 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
@Override
|
@Override
|
||||||
protected Supplier<Chapters> getCourseChaptersSupplier(final String courseId) {
|
protected Supplier<Chapters> getCourseChaptersSupplier(final String courseId) {
|
||||||
return () -> {
|
return () -> {
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
|
||||||
final String uri =
|
final String uri =
|
||||||
this.lmsSetup.lmsApiUrl +
|
lmsSetup.lmsApiUrl +
|
||||||
OPEN_EDX_DEFAULT_BLOCKS_ENDPOINT +
|
OPEN_EDX_DEFAULT_BLOCKS_ENDPOINT +
|
||||||
Utils.encodeFormURL_UTF_8(courseId);
|
Utils.encodeFormURL_UTF_8(courseId);
|
||||||
return new Chapters(getCourseBlocks(uri)
|
return new Chapters(getCourseBlocks(uri)
|
||||||
|
@ -239,16 +266,18 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<QuizData> collectQuizzes(final OAuth2RestTemplate restTemplate, final Set<String> ids) {
|
private ArrayList<QuizData> collectQuizzes(final OAuth2RestTemplate restTemplate, final Set<String> ids) {
|
||||||
final String externalStartURI = getExternalLMSServerAddress(this.lmsSetup);
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
|
||||||
|
|
||||||
return collectCourses(
|
return collectCourses(
|
||||||
this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||||
restTemplate,
|
restTemplate,
|
||||||
ids)
|
ids)
|
||||||
.stream()
|
.stream()
|
||||||
.reduce(
|
.reduce(
|
||||||
new ArrayList<>(),
|
new ArrayList<>(),
|
||||||
(list, courseData) -> {
|
(list, courseData) -> {
|
||||||
list.add(quizDataOf(this.lmsSetup, courseData, externalStartURI));
|
list.add(quizDataOf(lmsSetup, courseData, externalStartURI));
|
||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
(list1, list2) -> {
|
(list1, list2) -> {
|
||||||
|
@ -258,15 +287,16 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<QuizData> collectAllQuizzes(final OAuth2RestTemplate restTemplate) {
|
private ArrayList<QuizData> collectAllQuizzes(final OAuth2RestTemplate restTemplate) {
|
||||||
final String externalStartURI = getExternalLMSServerAddress(this.lmsSetup);
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
|
||||||
return collectAllCourses(
|
return collectAllCourses(
|
||||||
this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
|
||||||
restTemplate)
|
restTemplate)
|
||||||
.stream()
|
.stream()
|
||||||
.reduce(
|
.reduce(
|
||||||
new ArrayList<>(),
|
new ArrayList<>(),
|
||||||
(list, courseData) -> {
|
(list, courseData) -> {
|
||||||
list.add(quizDataOf(this.lmsSetup, courseData, externalStartURI));
|
list.add(quizDataOf(lmsSetup, courseData, externalStartURI));
|
||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
(list1, list2) -> {
|
(list1, list2) -> {
|
||||||
|
@ -322,6 +352,21 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
return collector;
|
return collector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CourseData getOneCourses(
|
||||||
|
final String pageURI,
|
||||||
|
final OAuth2RestTemplate restTemplate,
|
||||||
|
final String id) {
|
||||||
|
|
||||||
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
final ResponseEntity<CourseData> exchange = restTemplate.exchange(
|
||||||
|
pageURI + "/" + id,
|
||||||
|
HttpMethod.GET,
|
||||||
|
new HttpEntity<>(httpHeaders),
|
||||||
|
CourseData.class);
|
||||||
|
|
||||||
|
return exchange.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
private List<CourseData> collectAllCourses(final String pageURI, final OAuth2RestTemplate restTemplate) {
|
private List<CourseData> collectAllCourses(final String pageURI, final OAuth2RestTemplate restTemplate) {
|
||||||
final List<CourseData> collector = new ArrayList<>();
|
final List<CourseData> collector = new ArrayList<>();
|
||||||
EdXPage page = getEdxPage(pageURI, restTemplate).getBody();
|
EdXPage page = getEdxPage(pageURI, restTemplate).getBody();
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
||||||
|
|
||||||
import java.util.function.BooleanSupplier;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
|
@ -23,8 +21,6 @@ import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
|
@ -43,19 +39,16 @@ public class OpenEdxCourseRestriction {
|
||||||
private static final String OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH =
|
private static final String OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH =
|
||||||
"/seb-openedx/api/v1/course/%s/configuration/";
|
"/seb-openedx/api/v1/course/%s/configuration/";
|
||||||
|
|
||||||
private final LmsSetup lmsSetup;
|
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
|
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
|
||||||
|
|
||||||
private OAuth2RestTemplate restTemplate;
|
private OAuth2RestTemplate restTemplate;
|
||||||
|
|
||||||
protected OpenEdxCourseRestriction(
|
protected OpenEdxCourseRestriction(
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
||||||
final int restrictionAPIPushCount) {
|
final int restrictionAPIPushCount) {
|
||||||
|
|
||||||
this.lmsSetup = lmsSetup;
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
||||||
}
|
}
|
||||||
|
@ -75,15 +68,16 @@ public class OpenEdxCourseRestriction {
|
||||||
}
|
}
|
||||||
|
|
||||||
final OAuth2RestTemplate restTemplate = restTemplateRequest.get();
|
final OAuth2RestTemplate restTemplate = restTemplateRequest.get();
|
||||||
|
|
||||||
// NOTE: since the OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO endpoint is
|
|
||||||
// not accessible within OAuth2 authentication (just with user - authentication),
|
|
||||||
// we can only check if the endpoint is available for now. This is checked
|
|
||||||
// if there is no 404 response.
|
|
||||||
// TODO: Ask eduNEXT to implement also OAuth2 API access for this endpoint to be able
|
|
||||||
// to check the version of the installed plugin.
|
|
||||||
final String url = this.lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO;
|
|
||||||
try {
|
try {
|
||||||
|
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
|
||||||
|
// NOTE: since the OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO endpoint is
|
||||||
|
// not accessible within OAuth2 authentication (just with user - authentication),
|
||||||
|
// we can only check if the endpoint is available for now. This is checked
|
||||||
|
// if there is no 404 response.
|
||||||
|
// TODO: Ask eduNEXT to implement also OAuth2 API access for this endpoint to be able
|
||||||
|
// to check the version of the installed plugin.
|
||||||
|
final String url = lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_INFO;
|
||||||
|
|
||||||
restTemplate.exchange(
|
restTemplate.exchange(
|
||||||
url,
|
url,
|
||||||
|
@ -111,8 +105,10 @@ public class OpenEdxCourseRestriction {
|
||||||
log.debug("GET SEB Client restriction on course: {}", courseId);
|
log.debug("GET SEB Client restriction on course: {}", courseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final String url = this.lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
|
@ -146,9 +142,28 @@ public class OpenEdxCourseRestriction {
|
||||||
log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction);
|
log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleSEBRestriction(pushSEBRestrictionFunction(
|
return Result.tryCatch(() -> {
|
||||||
restriction,
|
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||||
courseId));
|
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||||
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
|
final OpenEdxSEBRestriction body = this
|
||||||
|
.getRestTemplate()
|
||||||
|
.getOrThrow()
|
||||||
|
.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.PUT,
|
||||||
|
new HttpEntity<>(toJson(restriction), httpHeaders),
|
||||||
|
OpenEdxSEBRestriction.class)
|
||||||
|
.getBody();
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<Boolean> deleteSEBRestriction(final String courseId) {
|
Result<Boolean> deleteSEBRestriction(final String courseId) {
|
||||||
|
@ -157,74 +172,99 @@ public class OpenEdxCourseRestriction {
|
||||||
log.debug("DELETE SEB Client restriction on course: {}", courseId);
|
log.debug("DELETE SEB Client restriction on course: {}", courseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleSEBRestriction(deleteSEBRestrictionFunction(courseId));
|
return Result.tryCatch(() -> {
|
||||||
}
|
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||||
private BooleanSupplier pushSEBRestrictionFunction(
|
|
||||||
final OpenEdxSEBRestriction restriction,
|
|
||||||
final String courseId) {
|
|
||||||
|
|
||||||
final String url = this.lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
|
||||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
|
||||||
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
|
||||||
return () -> {
|
|
||||||
final OpenEdxSEBRestriction body = this.restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.PUT,
|
|
||||||
new HttpEntity<>(toJson(restriction), httpHeaders),
|
|
||||||
OpenEdxSEBRestriction.class)
|
|
||||||
.getBody();
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private BooleanSupplier deleteSEBRestrictionFunction(final String courseId) {
|
|
||||||
|
|
||||||
final String url = this.lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
|
||||||
return () -> {
|
|
||||||
final HttpHeaders httpHeaders = new HttpHeaders();
|
final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
final ResponseEntity<Object> exchange = this.restTemplate.exchange(
|
final ResponseEntity<Object> exchange = this
|
||||||
url,
|
.getRestTemplate()
|
||||||
HttpMethod.DELETE,
|
.getOrThrow()
|
||||||
new HttpEntity<>(httpHeaders),
|
.exchange(
|
||||||
Object.class);
|
url,
|
||||||
|
HttpMethod.DELETE,
|
||||||
|
new HttpEntity<>(httpHeaders),
|
||||||
|
Object.class);
|
||||||
|
|
||||||
if (exchange.getStatusCode() == HttpStatus.NO_CONTENT) {
|
if (exchange.getStatusCode() == HttpStatus.NO_CONTENT) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
|
log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
log.error("Unexpected response for deletion: {}", exchange);
|
throw new RuntimeException("Unexpected response for deletion: " + exchange);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Boolean> handleSEBRestriction(final BooleanSupplier task) {
|
// private BooleanSupplier pushSEBRestrictionFunction(
|
||||||
return getRestTemplate()
|
// final OpenEdxSEBRestriction restriction,
|
||||||
.map(restTemplate -> {
|
// final String courseId) {
|
||||||
try {
|
//
|
||||||
return task.getAsBoolean();
|
// final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||||
} catch (final HttpClientErrorException ce) {
|
// final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||||
if (ce.getStatusCode() == HttpStatus.UNAUTHORIZED) {
|
// final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
throw new APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of(ce.getMessage()
|
// httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
+ " Unable to get access for API. Please check the corresponding LMS Setup "));
|
// httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
}
|
// return () -> {
|
||||||
throw ce;
|
// final OpenEdxSEBRestriction body = this.restTemplate.exchange(
|
||||||
} catch (final Exception e) {
|
// url,
|
||||||
throw new RuntimeException("Unexpected: ", e);
|
// HttpMethod.PUT,
|
||||||
}
|
// new HttpEntity<>(toJson(restriction), httpHeaders),
|
||||||
});
|
// OpenEdxSEBRestriction.class)
|
||||||
}
|
// .getBody();
|
||||||
|
//
|
||||||
|
// if (log.isDebugEnabled()) {
|
||||||
|
// log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private BooleanSupplier deleteSEBRestrictionFunction(final String courseId) {
|
||||||
|
//
|
||||||
|
// final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
// final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
|
||||||
|
// return () -> {
|
||||||
|
// final HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
// httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
|
// final ResponseEntity<Object> exchange = this.restTemplate.exchange(
|
||||||
|
// url,
|
||||||
|
// HttpMethod.DELETE,
|
||||||
|
// new HttpEntity<>(httpHeaders),
|
||||||
|
// Object.class);
|
||||||
|
//
|
||||||
|
// if (exchange.getStatusCode() == HttpStatus.NO_CONTENT) {
|
||||||
|
// if (log.isDebugEnabled()) {
|
||||||
|
// log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// log.error("Unexpected response for deletion: {}", exchange);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private Result<Boolean> handleSEBRestriction(final BooleanSupplier task) {
|
||||||
|
// return getRestTemplate()
|
||||||
|
// .map(restTemplate -> {
|
||||||
|
// try {
|
||||||
|
// return task.getAsBoolean();
|
||||||
|
// } catch (final HttpClientErrorException ce) {
|
||||||
|
// if (ce.getStatusCode() == HttpStatus.UNAUTHORIZED) {
|
||||||
|
// throw new APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of(ce.getMessage()
|
||||||
|
// + " Unable to get access for API. Please check the corresponding LMS Setup "));
|
||||||
|
// }
|
||||||
|
// throw ce;
|
||||||
|
// } catch (final Exception e) {
|
||||||
|
// throw new RuntimeException("Unexpected: ", e);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
private String getSEBRestrictionUrl(final String courseId) {
|
private String getSEBRestrictionUrl(final String courseId) {
|
||||||
return String.format(OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH, courseId);
|
return String.format(OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH, courseId);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
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.exam.SEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
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.model.institution.LmsSetupTestResult;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -39,23 +40,27 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(OpenEdxLmsAPITemplate.class);
|
private static final Logger log = LoggerFactory.getLogger(OpenEdxLmsAPITemplate.class);
|
||||||
|
|
||||||
private final LmsSetup lmsSetup;
|
|
||||||
private final OpenEdxCourseAccess openEdxCourseAccess;
|
private final OpenEdxCourseAccess openEdxCourseAccess;
|
||||||
private final OpenEdxCourseRestriction openEdxCourseRestriction;
|
private final OpenEdxCourseRestriction openEdxCourseRestriction;
|
||||||
|
|
||||||
OpenEdxLmsAPITemplate(
|
OpenEdxLmsAPITemplate(
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final OpenEdxCourseAccess openEdxCourseAccess,
|
final OpenEdxCourseAccess openEdxCourseAccess,
|
||||||
final OpenEdxCourseRestriction openEdxCourseRestriction) {
|
final OpenEdxCourseRestriction openEdxCourseRestriction) {
|
||||||
|
|
||||||
this.lmsSetup = lmsSetup;
|
|
||||||
this.openEdxCourseAccess = openEdxCourseAccess;
|
this.openEdxCourseAccess = openEdxCourseAccess;
|
||||||
this.openEdxCourseRestriction = openEdxCourseRestriction;
|
this.openEdxCourseRestriction = openEdxCourseRestriction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LmsType getType() {
|
||||||
|
return LmsType.OPEN_EDX;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetup lmsSetup() {
|
public LmsSetup lmsSetup() {
|
||||||
return this.lmsSetup;
|
return this.openEdxCourseAccess
|
||||||
|
.getApiTemplateDataSupplier()
|
||||||
|
.getLmsSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -150,7 +155,8 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
|
||||||
log.debug("Release SEB Client restriction for Exam: {}", exam);
|
log.debug("Release SEB Client restriction for Exam: {}", exam);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.openEdxCourseRestriction.deleteSEBRestriction(exam.externalId)
|
return this.openEdxCourseRestriction
|
||||||
|
.deleteSEBRestriction(exam.externalId)
|
||||||
.map(result -> exam);
|
.map(result -> exam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,11 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
|
||||||
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.model.institution.LmsSetup.LmsType;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
|
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.LmsAPITemplate;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||||
|
|
||||||
|
@ -71,37 +69,29 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<LmsAPITemplate> create(
|
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData) {
|
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory = new OpenEdxRestTemplateFactory(
|
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory = new OpenEdxRestTemplateFactory(
|
||||||
lmsSetup,
|
apiTemplateDataSupplier,
|
||||||
credentials,
|
|
||||||
proxyData,
|
|
||||||
this.clientCredentialService,
|
this.clientCredentialService,
|
||||||
this.clientHttpRequestFactoryService,
|
this.clientHttpRequestFactoryService,
|
||||||
this.alternativeTokenRequestPaths);
|
this.alternativeTokenRequestPaths);
|
||||||
|
|
||||||
final OpenEdxCourseAccess openEdxCourseAccess = new OpenEdxCourseAccess(
|
final OpenEdxCourseAccess openEdxCourseAccess = new OpenEdxCourseAccess(
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
lmsSetup,
|
|
||||||
openEdxRestTemplateFactory,
|
openEdxRestTemplateFactory,
|
||||||
this.webserviceInfo,
|
this.webserviceInfo,
|
||||||
this.asyncService,
|
this.asyncService,
|
||||||
this.environment);
|
this.environment);
|
||||||
|
|
||||||
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
|
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
|
||||||
lmsSetup,
|
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
openEdxRestTemplateFactory,
|
openEdxRestTemplateFactory,
|
||||||
this.restrictionAPIPushCount);
|
this.restrictionAPIPushCount);
|
||||||
|
|
||||||
return new OpenEdxLmsAPITemplate(
|
return new OpenEdxLmsAPITemplate(
|
||||||
lmsSetup,
|
|
||||||
openEdxCourseAccess,
|
openEdxCourseAccess,
|
||||||
openEdxCourseRestriction);
|
openEdxCourseRestriction);
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,30 +43,25 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||||
|
|
||||||
final class OpenEdxRestTemplateFactory {
|
final class OpenEdxRestTemplateFactory {
|
||||||
|
|
||||||
private static final String OPEN_EDX_DEFAULT_TOKEN_REQUEST_PATH = "/oauth2/access_token";
|
private static final String OPEN_EDX_DEFAULT_TOKEN_REQUEST_PATH = "/oauth2/access_token";
|
||||||
|
|
||||||
final LmsSetup lmsSetup;
|
final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||||
final ClientCredentials credentials;
|
|
||||||
final ProxyData proxyData;
|
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||||
final ClientCredentialService clientCredentialService;
|
final ClientCredentialService clientCredentialService;
|
||||||
final Set<String> knownTokenAccessPaths;
|
final Set<String> knownTokenAccessPaths;
|
||||||
|
|
||||||
OpenEdxRestTemplateFactory(
|
OpenEdxRestTemplateFactory(
|
||||||
final LmsSetup lmsSetup,
|
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData,
|
|
||||||
final ClientCredentialService clientCredentialService,
|
final ClientCredentialService clientCredentialService,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
final String[] alternativeTokenRequestPaths) {
|
final String[] alternativeTokenRequestPaths) {
|
||||||
|
|
||||||
this.lmsSetup = lmsSetup;
|
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||||
this.clientCredentialService = clientCredentialService;
|
this.clientCredentialService = clientCredentialService;
|
||||||
this.credentials = credentials;
|
|
||||||
this.proxyData = proxyData;
|
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
|
|
||||||
this.knownTokenAccessPaths = new HashSet<>();
|
this.knownTokenAccessPaths = new HashSet<>();
|
||||||
|
@ -76,26 +71,34 @@ final class OpenEdxRestTemplateFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||||
|
return this.apiTemplateDataSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
public LmsSetupTestResult test() {
|
public LmsSetupTestResult test() {
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||||
|
|
||||||
final List<APIMessage> missingAttrs = new ArrayList<>();
|
final List<APIMessage> missingAttrs = new ArrayList<>();
|
||||||
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) {
|
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_URL,
|
LMS_SETUP.ATTR_LMS_URL,
|
||||||
"lmsSetup:lmsUrl:notNull"));
|
"lmsSetup:lmsUrl:notNull"));
|
||||||
} else {
|
} else {
|
||||||
// try to connect to the url
|
// try to connect to the url
|
||||||
if (!Utils.pingHost(this.lmsSetup.lmsApiUrl)) {
|
if (!Utils.pingHost(lmsSetup.lmsApiUrl)) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_URL,
|
LMS_SETUP.ATTR_LMS_URL,
|
||||||
"lmsSetup:lmsUrl:url.invalid"));
|
"lmsSetup:lmsUrl:url.invalid"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.credentials.hasClientId()) {
|
if (!lmsClientCredentials.hasClientId()) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||||
"lmsSetup:lmsClientname:notNull"));
|
"lmsSetup:lmsClientname:notNull"));
|
||||||
}
|
}
|
||||||
if (!this.credentials.hasSecret()) {
|
if (!lmsClientCredentials.hasSecret()) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||||
"lmsSetup:lmsClientsecret:notNull"));
|
"lmsSetup:lmsClientsecret:notNull"));
|
||||||
|
@ -120,10 +123,7 @@ final class OpenEdxRestTemplateFactory {
|
||||||
|
|
||||||
Result<OAuth2RestTemplate> createOAuthRestTemplate(final String accessTokenPath) {
|
Result<OAuth2RestTemplate> createOAuthRestTemplate(final String accessTokenPath) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final OAuth2RestTemplate template = createRestTemplate(
|
final OAuth2RestTemplate template = createRestTemplate(accessTokenPath);
|
||||||
this.lmsSetup,
|
|
||||||
this.credentials,
|
|
||||||
accessTokenPath);
|
|
||||||
|
|
||||||
final OAuth2AccessToken accessToken = template.getAccessToken();
|
final OAuth2AccessToken accessToken = template.getAccessToken();
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
|
@ -134,10 +134,11 @@ final class OpenEdxRestTemplateFactory {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2RestTemplate createRestTemplate(
|
private OAuth2RestTemplate createRestTemplate(final String accessTokenRequestPath) throws URISyntaxException {
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final ClientCredentials credentials,
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
final String accessTokenRequestPath) throws URISyntaxException {
|
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||||
|
final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData();
|
||||||
|
|
||||||
final CharSequence plainClientId = credentials.clientId;
|
final CharSequence plainClientId = credentials.clientId;
|
||||||
final CharSequence plainClientSecret = this.clientCredentialService
|
final CharSequence plainClientSecret = this.clientCredentialService
|
||||||
|
@ -150,7 +151,7 @@ final class OpenEdxRestTemplateFactory {
|
||||||
details.setClientSecret(plainClientSecret.toString());
|
details.setClientSecret(plainClientSecret.toString());
|
||||||
|
|
||||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||||
.getClientHttpRequestFactory(this.proxyData)
|
.getClientHttpRequestFactory(proxyData)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
final OAuth2RestTemplate template = new OAuth2RestTemplate(details);
|
final OAuth2RestTemplate template = new OAuth2RestTemplate(details);
|
||||||
|
|
|
@ -6,18 +6,16 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
|
||||||
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.model.institution.LmsSetup.LmsType;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
|
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.LmsAPITemplate;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||||
|
|
||||||
|
@ -38,14 +36,9 @@ public class MockLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<LmsAPITemplate> create(
|
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData) {
|
|
||||||
|
|
||||||
return Result.tryCatch(() -> new MockupLmsAPITemplate(
|
return Result.tryCatch(() -> new MockupLmsAPITemplate(
|
||||||
lmsSetup,
|
apiTemplateDataSupplier,
|
||||||
credentials,
|
|
||||||
this.webserviceInfo));
|
this.webserviceInfo));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -35,31 +35,32 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
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.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
||||||
|
|
||||||
final class MockupLmsAPITemplate implements LmsAPITemplate {
|
public class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MockupLmsAPITemplate.class);
|
private static final Logger log = LoggerFactory.getLogger(MockupLmsAPITemplate.class);
|
||||||
|
|
||||||
private final LmsSetup lmsSetup;
|
|
||||||
private final ClientCredentials credentials;
|
|
||||||
private final Collection<QuizData> mockups;
|
private final Collection<QuizData> mockups;
|
||||||
private final WebserviceInfo webserviceInfo;
|
private final WebserviceInfo webserviceInfo;
|
||||||
|
private final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||||
|
|
||||||
MockupLmsAPITemplate(
|
MockupLmsAPITemplate(
|
||||||
final LmsSetup lmsSetup,
|
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||||
final ClientCredentials credentials,
|
|
||||||
final WebserviceInfo webserviceInfo) {
|
final WebserviceInfo webserviceInfo) {
|
||||||
|
|
||||||
this.lmsSetup = lmsSetup;
|
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||||
this.credentials = credentials;
|
|
||||||
this.webserviceInfo = webserviceInfo;
|
this.webserviceInfo = webserviceInfo;
|
||||||
|
this.mockups = new ArrayList<>();
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
final Long lmsSetupId = lmsSetup.id;
|
final Long lmsSetupId = lmsSetup.id;
|
||||||
final Long institutionId = lmsSetup.getInstitutionId();
|
final Long institutionId = lmsSetup.getInstitutionId();
|
||||||
final LmsType lmsType = lmsSetup.getLmsType();
|
final LmsType lmsType = lmsSetup.getLmsType();
|
||||||
this.mockups = new ArrayList<>();
|
|
||||||
this.mockups.add(new QuizData(
|
this.mockups.add(new QuizData(
|
||||||
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "<p>Demo Quiz Mockup</p>",
|
"quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "<p>Demo Quiz Mockup</p>",
|
||||||
"2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
|
"2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
|
||||||
|
@ -92,24 +93,31 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||||
"http://lms.mockup.com/api/"));
|
"http://lms.mockup.com/api/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LmsType getType() {
|
||||||
|
return LmsType.MOCKUP;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetup lmsSetup() {
|
public LmsSetup lmsSetup() {
|
||||||
return this.lmsSetup;
|
return this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<APIMessage> checkAttributes() {
|
private List<APIMessage> checkAttributes() {
|
||||||
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||||
final List<APIMessage> missingAttrs = new ArrayList<>();
|
final List<APIMessage> missingAttrs = new ArrayList<>();
|
||||||
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) {
|
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_URL,
|
LMS_SETUP.ATTR_LMS_URL,
|
||||||
"lmsSetup:lmsUrl:notNull"));
|
"lmsSetup:lmsUrl:notNull"));
|
||||||
}
|
}
|
||||||
if (!this.credentials.hasClientId()) {
|
if (!lmsClientCredentials.hasClientId()) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||||
"lmsSetup:lmsClientname:notNull"));
|
"lmsSetup:lmsClientname:notNull"));
|
||||||
}
|
}
|
||||||
if (!this.credentials.hasSecret()) {
|
if (!lmsClientCredentials.hasSecret()) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||||
"lmsSetup:lmsClientsecret:notNull"));
|
"lmsSetup:lmsClientsecret:notNull"));
|
||||||
|
@ -119,7 +127,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetupTestResult testCourseAccessAPI() {
|
public LmsSetupTestResult testCourseAccessAPI() {
|
||||||
log.info("Test Lms Binding for Mockup and LmsSetup: {}", this.lmsSetup);
|
log.info("Test Lms Binding for Mockup and LmsSetup: {}", this.apiTemplateDataSupplier.getLmsSetup());
|
||||||
|
|
||||||
final List<APIMessage> missingAttrs = checkAttributes();
|
final List<APIMessage> missingAttrs = checkAttributes();
|
||||||
|
|
||||||
|
@ -136,7 +144,6 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetupTestResult testCourseRestrictionAPI() {
|
public LmsSetupTestResult testCourseRestrictionAPI() {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return LmsSetupTestResult.ofQuizRestrictionAPIError("unsupported");
|
return LmsSetupTestResult.ofQuizRestrictionAPIError("unsupported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +246,7 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
||||||
private boolean authenticate() {
|
private boolean authenticate() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final CharSequence plainClientId = this.credentials.clientId;
|
final CharSequence plainClientId = this.apiTemplateDataSupplier.getLmsClientCredentials().clientId;
|
||||||
if (plainClientId == null || plainClientId.length() <= 0) {
|
if (plainClientId == null || plainClientId.length() <= 0) {
|
||||||
throw new IllegalAccessException("Wrong client credential");
|
throw new IllegalAccessException("Wrong client credential");
|
||||||
}
|
}
|
|
@ -45,7 +45,8 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseDataShort;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseDataShort;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
|
||||||
* background task if needed and return immediately to do not block the request.
|
* background task if needed and return immediately to do not block the request.
|
||||||
* The planed Moodle integration on moodle side also defines an improved course access API. This will
|
* The planed Moodle integration on moodle side also defines an improved course access API. This will
|
||||||
* possibly make this synchronous fetch strategy obsolete in the future. */
|
* possibly make this synchronous fetch strategy obsolete in the future. */
|
||||||
public class MoodleCourseAccess extends CourseAccess {
|
public class MoodleCourseAccess extends AbstractCourseAccess {
|
||||||
|
|
||||||
private static final long INITIAL_WAIT_TIME = 3 * Constants.SECOND_IN_MILLIS;
|
private static final long INITIAL_WAIT_TIME = 3 * Constants.SECOND_IN_MILLIS;
|
||||||
|
|
||||||
|
@ -86,7 +87,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
static final String MOODLE_COURSE_API_SEARCH_PAGE_SIZE = "perpage";
|
static final String MOODLE_COURSE_API_SEARCH_PAGE_SIZE = "perpage";
|
||||||
|
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final LmsSetup lmsSetup;
|
|
||||||
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
||||||
private final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader;
|
private final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader;
|
||||||
private final CircuitBreaker<List<QuizData>> allQuizzesRequest;
|
private final CircuitBreaker<List<QuizData>> allQuizzesRequest;
|
||||||
|
@ -96,7 +96,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
protected MoodleCourseAccess(
|
protected MoodleCourseAccess(
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
||||||
final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader,
|
final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader,
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
|
@ -104,7 +103,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
super(asyncService, environment);
|
super(asyncService, environment);
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.lmsSetup = lmsSetup;
|
|
||||||
this.moodleCourseDataAsyncLoader = moodleCourseDataAsyncLoader;
|
this.moodleCourseDataAsyncLoader = moodleCourseDataAsyncLoader;
|
||||||
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
||||||
|
|
||||||
|
@ -136,6 +134,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||||
|
return this.moodleRestTemplateFactory.apiTemplateDataSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
@ -151,8 +153,9 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
queryAttributes);
|
queryAttributes);
|
||||||
|
|
||||||
if (checkAccessDeniedError(userDetailsJSON)) {
|
if (checkAccessDeniedError(userDetailsJSON)) {
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
log.error("Get access denied error from Moodle: {} for API call: {}, response: {}",
|
log.error("Get access denied error from Moodle: {} for API call: {}, response: {}",
|
||||||
this.lmsSetup,
|
lmsSetup,
|
||||||
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
MOODLE_USER_PROFILE_API_FUNCTION_NAME,
|
||||||
Utils.truncateText(userDetailsJSON, 2000));
|
Utils.truncateText(userDetailsJSON, 2000));
|
||||||
throw new RuntimeException("No user details on Moodle API request (access-denied)");
|
throw new RuntimeException("No user details on Moodle API request (access-denied)");
|
||||||
|
@ -257,9 +260,11 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
final FilterMap filterMap) {
|
final FilterMap filterMap) {
|
||||||
|
|
||||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
|
||||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||||
|
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||||
|
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||||
|
|
||||||
final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null;
|
final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null;
|
||||||
final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1;
|
final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1;
|
||||||
|
@ -313,10 +318,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
private List<QuizData> getCached() {
|
private List<QuizData> getCached() {
|
||||||
final Collection<CourseDataShort> courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
final Collection<CourseDataShort> courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||||
|
|
||||||
return reduceCoursesToQuizzes(urlPrefix, courseQuizData);
|
return reduceCoursesToQuizzes(urlPrefix, courseQuizData);
|
||||||
}
|
}
|
||||||
|
@ -325,13 +330,14 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
final String urlPrefix,
|
final String urlPrefix,
|
||||||
final Collection<CourseDataShort> courseQuizData) {
|
final Collection<CourseDataShort> courseQuizData) {
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
return courseQuizData
|
return courseQuizData
|
||||||
.stream()
|
.stream()
|
||||||
.reduce(
|
.reduce(
|
||||||
new ArrayList<>(),
|
new ArrayList<>(),
|
||||||
(list, courseData) -> {
|
(list, courseData) -> {
|
||||||
list.addAll(quizDataOf(
|
list.addAll(quizDataOf(
|
||||||
this.lmsSetup,
|
lmsSetup,
|
||||||
courseData,
|
courseData,
|
||||||
urlPrefix));
|
urlPrefix));
|
||||||
return list;
|
return list;
|
||||||
|
@ -380,16 +386,17 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
||||||
quizzesJSON,
|
quizzesJSON,
|
||||||
CourseQuizData.class);
|
CourseQuizData.class);
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
|
||||||
if (courseQuizData == null) {
|
if (courseQuizData == null) {
|
||||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, this.lmsSetup.name);
|
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, lmsSetup.name);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
logMoodleWarnings(courseQuizData.warnings);
|
logMoodleWarnings(courseQuizData.warnings);
|
||||||
|
|
||||||
if (courseQuizData.quizzes == null || courseQuizData.quizzes.isEmpty()) {
|
if (courseQuizData.quizzes == null || courseQuizData.quizzes.isEmpty()) {
|
||||||
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, this.lmsSetup.name);
|
log.error("No quizzes found for ids: {} on LMS; {}", quizIds, lmsSetup.name);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,9 +409,9 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
: lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||||
|
|
||||||
return courseData.values()
|
return courseData.values()
|
||||||
.stream()
|
.stream()
|
||||||
|
@ -413,7 +420,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
new ArrayList<>(),
|
new ArrayList<>(),
|
||||||
(list, cd) -> {
|
(list, cd) -> {
|
||||||
list.addAll(quizDataOf(
|
list.addAll(quizDataOf(
|
||||||
this.lmsSetup,
|
lmsSetup,
|
||||||
cd,
|
cd,
|
||||||
urlPrefix));
|
urlPrefix));
|
||||||
return list;
|
return list;
|
||||||
|
@ -451,16 +458,17 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
final Courses courses = this.jsonMapper.readValue(
|
final Courses courses = this.jsonMapper.readValue(
|
||||||
coursePageJSON,
|
coursePageJSON,
|
||||||
Courses.class);
|
Courses.class);
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
|
|
||||||
if (courses == null) {
|
if (courses == null) {
|
||||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
log.error("No courses found for ids: {} on LMS: {}", ids, lmsSetup.name);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
logMoodleWarnings(courses.warnings);
|
logMoodleWarnings(courses.warnings);
|
||||||
|
|
||||||
if (courses.courses == null || courses.courses.isEmpty()) {
|
if (courses.courses == null || courses.courses.isEmpty()) {
|
||||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
log.error("No courses found for ids: {} on LMS: {}", ids, lmsSetup.name);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,9 +638,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
private void logMoodleWarnings(final Collection<Warning> warnings) {
|
private void logMoodleWarnings(final Collection<Warning> warnings) {
|
||||||
if (warnings != null && !warnings.isEmpty()) {
|
if (warnings != null && !warnings.isEmpty()) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
|
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
|
||||||
log.debug(
|
log.debug(
|
||||||
"There are warnings from Moodle response: Moodle: {} request: {} warnings: {} warning sample: {}",
|
"There are warnings from Moodle response: Moodle: {} request: {} warnings: {} warning sample: {}",
|
||||||
this.lmsSetup,
|
lmsSetup,
|
||||||
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||||
warnings.size(),
|
warnings.size(),
|
||||||
warnings.iterator().next().toString());
|
warnings.iterator().next().toString());
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
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.exam.SEBRestriction;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
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.model.institution.LmsSetupTestResult;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
@ -51,23 +52,27 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class);
|
private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class);
|
||||||
|
|
||||||
private final LmsSetup lmsSetup;
|
|
||||||
private final MoodleCourseAccess moodleCourseAccess;
|
private final MoodleCourseAccess moodleCourseAccess;
|
||||||
private final MoodleCourseRestriction moodleCourseRestriction;
|
private final MoodleCourseRestriction moodleCourseRestriction;
|
||||||
|
|
||||||
protected MoodleLmsAPITemplate(
|
protected MoodleLmsAPITemplate(
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final MoodleCourseAccess moodleCourseAccess,
|
final MoodleCourseAccess moodleCourseAccess,
|
||||||
final MoodleCourseRestriction moodleCourseRestriction) {
|
final MoodleCourseRestriction moodleCourseRestriction) {
|
||||||
|
|
||||||
this.lmsSetup = lmsSetup;
|
|
||||||
this.moodleCourseAccess = moodleCourseAccess;
|
this.moodleCourseAccess = moodleCourseAccess;
|
||||||
this.moodleCourseRestriction = moodleCourseRestriction;
|
this.moodleCourseRestriction = moodleCourseRestriction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LmsType getType() {
|
||||||
|
return LmsType.MOODLE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetup lmsSetup() {
|
public LmsSetup lmsSetup() {
|
||||||
return this.lmsSetup;
|
return this.moodleCourseAccess
|
||||||
|
.getApiTemplateDataSupplier()
|
||||||
|
.getLmsSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,12 +20,11 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
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.model.institution.LmsSetup.LmsType;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
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.LmsAPITemplate;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory;
|
||||||
|
|
||||||
|
@ -68,29 +67,25 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<LmsAPITemplate> create(
|
public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) {
|
||||||
final LmsSetup lmsSetup,
|
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData) {
|
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final MoodleCourseDataAsyncLoader asyncLoaderPrototype =
|
final LmsSetup lmsSetup = apiTemplateDataSupplier.getLmsSetup();
|
||||||
this.applicationContext.getBean(MoodleCourseDataAsyncLoader.class);
|
|
||||||
asyncLoaderPrototype.init(lmsSetup.name);
|
final MoodleCourseDataAsyncLoader asyncLoaderPrototype = this.applicationContext
|
||||||
|
.getBean(MoodleCourseDataAsyncLoader.class);
|
||||||
|
asyncLoaderPrototype.init(lmsSetup.getModelId());
|
||||||
|
|
||||||
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
|
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
lmsSetup,
|
apiTemplateDataSupplier,
|
||||||
credentials,
|
|
||||||
proxyData,
|
|
||||||
this.clientCredentialService,
|
this.clientCredentialService,
|
||||||
this.clientHttpRequestFactoryService,
|
this.clientHttpRequestFactoryService,
|
||||||
this.alternativeTokenRequestPaths);
|
this.alternativeTokenRequestPaths);
|
||||||
|
|
||||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
lmsSetup,
|
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
asyncLoaderPrototype,
|
asyncLoaderPrototype,
|
||||||
this.asyncService,
|
this.asyncService,
|
||||||
|
@ -101,7 +96,6 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
|
||||||
moodleRestTemplateFactory);
|
moodleRestTemplateFactory);
|
||||||
|
|
||||||
return new MoodleLmsAPITemplate(
|
return new MoodleLmsAPITemplate(
|
||||||
lmsSetup,
|
|
||||||
moodleCourseAccess,
|
moodleCourseAccess,
|
||||||
moodleCourseRestriction);
|
moodleCourseRestriction);
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,33 +50,28 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
|
||||||
|
|
||||||
class MoodleRestTemplateFactory {
|
class MoodleRestTemplateFactory {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
|
private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
|
||||||
|
|
||||||
final JSONMapper jsonMapper;
|
final JSONMapper jsonMapper;
|
||||||
final LmsSetup lmsSetup;
|
final APITemplateDataSupplier apiTemplateDataSupplier;
|
||||||
final ClientCredentials credentials;
|
|
||||||
final ProxyData proxyData;
|
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||||
final ClientCredentialService clientCredentialService;
|
final ClientCredentialService clientCredentialService;
|
||||||
final Set<String> knownTokenAccessPaths;
|
final Set<String> knownTokenAccessPaths;
|
||||||
|
|
||||||
public MoodleRestTemplateFactory(
|
public MoodleRestTemplateFactory(
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final LmsSetup lmsSetup,
|
final APITemplateDataSupplier apiTemplateDataSupplier,
|
||||||
final ClientCredentials credentials,
|
|
||||||
final ProxyData proxyData,
|
|
||||||
final ClientCredentialService clientCredentialService,
|
final ClientCredentialService clientCredentialService,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
final String[] alternativeTokenRequestPaths) {
|
final String[] alternativeTokenRequestPaths) {
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.lmsSetup = lmsSetup;
|
this.apiTemplateDataSupplier = apiTemplateDataSupplier;
|
||||||
this.clientCredentialService = clientCredentialService;
|
this.clientCredentialService = clientCredentialService;
|
||||||
this.credentials = credentials;
|
|
||||||
this.proxyData = proxyData;
|
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
|
|
||||||
this.knownTokenAccessPaths = new HashSet<>();
|
this.knownTokenAccessPaths = new HashSet<>();
|
||||||
|
@ -86,28 +81,36 @@ class MoodleRestTemplateFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APITemplateDataSupplier getApiTemplateDataSupplier() {
|
||||||
|
return this.apiTemplateDataSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
public LmsSetupTestResult test() {
|
public LmsSetupTestResult test() {
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||||
|
|
||||||
final List<APIMessage> missingAttrs = new ArrayList<>();
|
final List<APIMessage> missingAttrs = new ArrayList<>();
|
||||||
if (StringUtils.isBlank(this.lmsSetup.lmsApiUrl)) {
|
if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_URL,
|
LMS_SETUP.ATTR_LMS_URL,
|
||||||
"lmsSetup:lmsUrl:notNull"));
|
"lmsSetup:lmsUrl:notNull"));
|
||||||
} else {
|
} else {
|
||||||
// try to connect to the url
|
// try to connect to the url
|
||||||
if (!Utils.pingHost(this.lmsSetup.lmsApiUrl)) {
|
if (!Utils.pingHost(lmsSetup.lmsApiUrl)) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_URL,
|
LMS_SETUP.ATTR_LMS_URL,
|
||||||
"lmsSetup:lmsUrl:url.invalid"));
|
"lmsSetup:lmsUrl:url.invalid"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(this.lmsSetup.lmsRestApiToken)) {
|
if (StringUtils.isBlank(lmsSetup.lmsRestApiToken)) {
|
||||||
if (!this.credentials.hasClientId()) {
|
if (!credentials.hasClientId()) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
LMS_SETUP.ATTR_LMS_CLIENTNAME,
|
||||||
"lmsSetup:lmsClientname:notNull"));
|
"lmsSetup:lmsClientname:notNull"));
|
||||||
}
|
}
|
||||||
if (!this.credentials.hasSecret()) {
|
if (!credentials.hasSecret()) {
|
||||||
missingAttrs.add(APIMessage.fieldValidationError(
|
missingAttrs.add(APIMessage.fieldValidationError(
|
||||||
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
LMS_SETUP.ATTR_LMS_CLIENTSECRET,
|
||||||
"lmsSetup:lmsClientsecret:notNull"));
|
"lmsSetup:lmsClientsecret:notNull"));
|
||||||
|
@ -122,14 +125,17 @@ class MoodleRestTemplateFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<MoodleAPIRestTemplate> createRestTemplate() {
|
Result<MoodleAPIRestTemplate> createRestTemplate() {
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
|
||||||
return this.knownTokenAccessPaths
|
return this.knownTokenAccessPaths
|
||||||
.stream()
|
.stream()
|
||||||
.map(this::createRestTemplate)
|
.map(this::createRestTemplate)
|
||||||
.map(result -> {
|
.map(result -> {
|
||||||
if (result.hasError()) {
|
if (result.hasError()) {
|
||||||
log.warn("Failed to get access token for LMS: {}({})",
|
log.warn("Failed to get access token for LMS: {}({})",
|
||||||
this.lmsSetup.name,
|
lmsSetup.name,
|
||||||
this.lmsSetup.id,
|
lmsSetup.id,
|
||||||
result.getError());
|
result.getError());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -138,53 +144,48 @@ class MoodleRestTemplateFactory {
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(Result.ofRuntimeError(
|
.orElse(Result.ofRuntimeError(
|
||||||
"Failed to gain any access for LMS " +
|
"Failed to gain any access for LMS " +
|
||||||
this.lmsSetup.name + "(" + this.lmsSetup.id +
|
lmsSetup.name + "(" + lmsSetup.id +
|
||||||
") on paths: " + this.knownTokenAccessPaths));
|
") on paths: " + this.knownTokenAccessPaths));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
|
Result<MoodleAPIRestTemplate> createRestTemplate(final String accessTokenPath) {
|
||||||
return Result.tryCatch(() -> {
|
|
||||||
final MoodleAPIRestTemplate template = createRestTemplate(
|
|
||||||
this.credentials,
|
|
||||||
accessTokenPath);
|
|
||||||
|
|
||||||
final CharSequence accessToken = template.getAccessToken();
|
final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
|
||||||
|
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
|
||||||
|
final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData();
|
||||||
|
|
||||||
|
final CharSequence plainClientId = credentials.clientId;
|
||||||
|
final CharSequence plainClientSecret = this.clientCredentialService
|
||||||
|
.getPlainClientSecret(credentials)
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
final MoodleAPIRestTemplate restTemplate = new MoodleAPIRestTemplate(
|
||||||
|
this.jsonMapper,
|
||||||
|
lmsSetup.lmsApiUrl,
|
||||||
|
accessTokenPath,
|
||||||
|
lmsSetup.lmsRestApiToken,
|
||||||
|
plainClientId,
|
||||||
|
plainClientSecret);
|
||||||
|
|
||||||
|
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
||||||
|
.getClientHttpRequestFactory(proxyData)
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
||||||
|
final CharSequence accessToken = restTemplate.getAccessToken();
|
||||||
|
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
throw new RuntimeException("Failed to get access token for LMS " +
|
throw new RuntimeException("Failed to get access token for LMS " +
|
||||||
this.lmsSetup.name + "(" + this.lmsSetup.id +
|
lmsSetup.name + "(" + lmsSetup.id +
|
||||||
") on path: " + accessTokenPath);
|
") on path: " + accessTokenPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return template;
|
return restTemplate;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MoodleAPIRestTemplate createRestTemplate(
|
|
||||||
final ClientCredentials credentials,
|
|
||||||
final String accessTokenRequestPath) {
|
|
||||||
|
|
||||||
final CharSequence plainClientId = credentials.clientId;
|
|
||||||
final CharSequence plainClientSecret = this.clientCredentialService
|
|
||||||
.getPlainClientSecret(credentials)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
final MoodleAPIRestTemplate restTemplate = new MoodleAPIRestTemplate(
|
|
||||||
this.jsonMapper,
|
|
||||||
this.lmsSetup.lmsApiUrl,
|
|
||||||
accessTokenRequestPath,
|
|
||||||
this.lmsSetup.lmsRestApiToken,
|
|
||||||
plainClientId,
|
|
||||||
plainClientSecret);
|
|
||||||
|
|
||||||
final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService
|
|
||||||
.getClientHttpRequestFactory(this.proxyData)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
|
||||||
|
|
||||||
return restTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MoodleAPIRestTemplate extends RestTemplate {
|
public class MoodleAPIRestTemplate extends RestTemplate {
|
||||||
|
|
||||||
public static final String URI_VAR_USER_NAME = "username";
|
public static final String URI_VAR_USER_NAME = "username";
|
||||||
|
@ -210,7 +211,6 @@ class MoodleRestTemplateFactory {
|
||||||
private final HttpEntity<?> tokenReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
|
private final HttpEntity<?> tokenReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
|
||||||
|
|
||||||
protected MoodleAPIRestTemplate(
|
protected MoodleAPIRestTemplate(
|
||||||
|
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final String serverURL,
|
final String serverURL,
|
||||||
final String tokenPath,
|
final String tokenPath,
|
||||||
|
@ -318,10 +318,13 @@ class MoodleRestTemplateFactory {
|
||||||
functionReqEntity,
|
functionReqEntity,
|
||||||
String.class);
|
String.class);
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = MoodleRestTemplateFactory.this.apiTemplateDataSupplier
|
||||||
|
.getLmsSetup();
|
||||||
|
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
||||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + response.getBody());
|
lmsSetup + " response: " + response.getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
final String body = response.getBody();
|
final String body = response.getBody();
|
||||||
|
@ -335,7 +338,7 @@ class MoodleRestTemplateFactory {
|
||||||
this.accessToken = null;
|
this.accessToken = null;
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
||||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + body);
|
lmsSetup + " response: " + body);
|
||||||
}
|
}
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
|
@ -343,7 +346,11 @@ class MoodleRestTemplateFactory {
|
||||||
|
|
||||||
private void requestAccessToken() {
|
private void requestAccessToken() {
|
||||||
|
|
||||||
|
final LmsSetup lmsSetup = MoodleRestTemplateFactory.this.apiTemplateDataSupplier
|
||||||
|
.getLmsSetup();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final ResponseEntity<String> response = super.exchange(
|
final ResponseEntity<String> response = super.exchange(
|
||||||
this.serverURL + this.tokenPath,
|
this.serverURL + this.tokenPath,
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
|
@ -353,11 +360,11 @@ class MoodleRestTemplateFactory {
|
||||||
|
|
||||||
if (response.getStatusCode() != HttpStatus.OK) {
|
if (response.getStatusCode() != HttpStatus.OK) {
|
||||||
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} response: {} : {}",
|
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} response: {} : {}",
|
||||||
MoodleRestTemplateFactory.this.lmsSetup,
|
lmsSetup,
|
||||||
response.getStatusCode(),
|
response.getStatusCode(),
|
||||||
response.getBody());
|
response.getBody());
|
||||||
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
||||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + response.getBody());
|
lmsSetup + " response: " + response.getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -369,25 +376,25 @@ class MoodleRestTemplateFactory {
|
||||||
throw new RuntimeException("Access Token request with 200 but no or invalid token body");
|
throw new RuntimeException("Access Token request with 200 but no or invalid token body");
|
||||||
} else {
|
} else {
|
||||||
log.info("Successfully get access token from Moodle: {}",
|
log.info("Successfully get access token from Moodle: {}",
|
||||||
MoodleRestTemplateFactory.this.lmsSetup);
|
lmsSetup);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accessToken = moodleToken.token;
|
this.accessToken = moodleToken.token;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} response: {} : {}",
|
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} response: {} : {}",
|
||||||
MoodleRestTemplateFactory.this.lmsSetup,
|
lmsSetup,
|
||||||
response.getStatusCode(),
|
response.getStatusCode(),
|
||||||
response.getBody());
|
response.getBody());
|
||||||
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
||||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + response.getBody(), e);
|
lmsSetup + " response: " + response.getBody(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} :",
|
log.error("Failed to gain access token for LMS (Moodle): lmsSetup: {} :",
|
||||||
MoodleRestTemplateFactory.this.lmsSetup,
|
lmsSetup,
|
||||||
e);
|
e);
|
||||||
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
throw new RuntimeException("Failed to gain access token for LMS (Moodle): lmsSetup: " +
|
||||||
MoodleRestTemplateFactory.this.lmsSetup + " cause: " + e.getMessage());
|
lmsSetup + " cause: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||||
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.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
@ -49,7 +47,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.IndicatorDistributedRequestCache;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.IndicatorDistributedRequestCache;
|
||||||
|
|
||||||
|
@ -67,7 +65,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
private final ExamDAO examDAO;
|
private final ExamDAO examDAO;
|
||||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||||
private final CacheManager cacheManager;
|
private final CacheManager cacheManager;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final SEBRestrictionService sebRestrictionService;
|
||||||
private final IndicatorDistributedRequestCache indicatorDistributedRequestCache;
|
private final IndicatorDistributedRequestCache indicatorDistributedRequestCache;
|
||||||
private final boolean distributedSetup;
|
private final boolean distributedSetup;
|
||||||
|
|
||||||
|
@ -79,7 +77,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
final ClientConnectionDAO clientConnectionDAO,
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
final IndicatorDAO indicatorDAO,
|
final IndicatorDAO indicatorDAO,
|
||||||
final CacheManager cacheManager,
|
final CacheManager cacheManager,
|
||||||
final LmsAPIService lmsAPIService,
|
final SEBRestrictionService sebRestrictionService,
|
||||||
final IndicatorDistributedRequestCache indicatorDistributedRequestCache,
|
final IndicatorDistributedRequestCache indicatorDistributedRequestCache,
|
||||||
@Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup) {
|
@Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup) {
|
||||||
|
|
||||||
|
@ -90,7 +88,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
this.clientConnectionDAO = clientConnectionDAO;
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
this.cacheManager = cacheManager;
|
this.cacheManager = cacheManager;
|
||||||
this.indicatorDAO = indicatorDAO;
|
this.indicatorDAO = indicatorDAO;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.sebRestrictionService = sebRestrictionService;
|
||||||
this.indicatorDistributedRequestCache = indicatorDistributedRequestCache;
|
this.indicatorDistributedRequestCache = indicatorDistributedRequestCache;
|
||||||
this.distributedSetup = distributedSetup;
|
this.distributedSetup = distributedSetup;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +115,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsAPIService getLmsAPIService() {
|
public LmsAPIService getLmsAPIService() {
|
||||||
return this.lmsAPIService;
|
return this.sebRestrictionService.getLmsAPIService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,29 +147,10 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// check SEB restriction available and restricted
|
if (!this.sebRestrictionService.checkConsistency(exam.lmsSetupId, exam)) {
|
||||||
// if SEB restriction is not available no consistency violation message is added
|
result.add(
|
||||||
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION
|
||||||
.getOr(null);
|
.of(exam.getModelId()));
|
||||||
if (lmsSetup != null && lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
|
||||||
this.lmsAPIService.getLmsAPITemplate(exam.lmsSetupId)
|
|
||||||
.map(t -> {
|
|
||||||
if (t.testCourseRestrictionAPI().isOk()) {
|
|
||||||
return t;
|
|
||||||
} else {
|
|
||||||
throw new NoSEBRestrictionException();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatMap(t -> t.getSEBClientRestriction(exam))
|
|
||||||
.onError(error -> {
|
|
||||||
if (error instanceof NoSEBRestrictionException) {
|
|
||||||
result.add(
|
|
||||||
ErrorMessage.EXAM_CONSISTENCY_VALIDATION_SEB_RESTRICTION
|
|
||||||
.of(exam.getModelId()));
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unexpected error: ", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check indicator exists
|
// check indicator exists
|
||||||
|
|
|
@ -246,12 +246,21 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = API.PARAM_INSTITUTION_ID,
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
required = true,
|
required = true,
|
||||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||||
|
@RequestParam(
|
||||||
|
name = API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_INCLUDE_RESTRICTION,
|
||||||
|
defaultValue = "false") final boolean includeRestriction) {
|
||||||
|
|
||||||
checkReadPrivilege(institutionId);
|
checkReadPrivilege(institutionId);
|
||||||
return this.examSessionService
|
final Collection<APIMessage> result = this.examSessionService
|
||||||
.checkExamConsistency(modelId)
|
.checkExamConsistency(modelId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
if (includeRestriction) {
|
||||||
|
// TODO include seb restriction check and status
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
@ -524,37 +533,43 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Exam> applySEBRestriction(final Exam exam, final boolean restrict) {
|
private Result<Exam> applySEBRestriction(final Exam exam, final boolean restrict) {
|
||||||
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
|
||||||
.getOrThrow();
|
|
||||||
|
|
||||||
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
return Result.tryCatch(() -> {
|
||||||
return Result.ofError(new UnsupportedOperationException(
|
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
|
||||||
"SEB Restriction feature not available for LMS type: " + lmsSetup.lmsType));
|
.getOrThrow();
|
||||||
}
|
|
||||||
|
|
||||||
if (restrict) {
|
if (!lmsSetup.lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||||
if (!this.lmsAPIService
|
throw new UnsupportedOperationException(
|
||||||
.getLmsSetup(exam.lmsSetupId)
|
"SEB Restriction feature not available for LMS type: " + lmsSetup.lmsType);
|
||||||
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
|
||||||
|
|
||||||
return Result.ofError(new APIMessageException(
|
|
||||||
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT
|
|
||||||
.of("The LMS for this Exam has no SEB restriction feature")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
if (restrict) {
|
||||||
return Result.ofError(new APIMessageException(
|
if (!this.lmsAPIService
|
||||||
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
.getLmsSetup(exam.lmsSetupId)
|
||||||
.of("Exam currently has active SEB Client connections.")));
|
.getOrThrow().lmsType.features.contains(Features.SEB_RESTRICTION)) {
|
||||||
}
|
|
||||||
|
|
||||||
return this.checkNoActiveSEBClientConnections(exam)
|
throw new APIMessageException(
|
||||||
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT
|
||||||
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict));
|
.of("The LMS for this Exam has no SEB restriction feature"));
|
||||||
} else {
|
}
|
||||||
return this.sebRestrictionService.releaseSEBClientRestriction(exam)
|
|
||||||
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict));
|
if (this.examSessionService.hasActiveSEBClientConnections(exam.id)) {
|
||||||
}
|
throw new APIMessageException(
|
||||||
|
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||||
|
.of("Exam currently has active SEB Client connections."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO double check before setSEBRestriction
|
||||||
|
return this.checkNoActiveSEBClientConnections(exam)
|
||||||
|
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
||||||
|
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict))
|
||||||
|
.getOrThrow();
|
||||||
|
} else {
|
||||||
|
return this.sebRestrictionService.releaseSEBClientRestriction(exam)
|
||||||
|
.flatMap(e -> this.examDAO.setSEBRestriction(exam.id, restrict))
|
||||||
|
.getOrThrow();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) {
|
static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) {
|
||||||
|
|
|
@ -30,7 +30,6 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||||
|
@ -39,7 +38,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
|
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||||
|
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
|
@ -134,10 +132,4 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
||||||
return new LmsSetup(null, postParams);
|
return new LmsSetup(null, postParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Result<LmsSetup> notifySaved(final LmsSetup entity) {
|
|
||||||
this.applicationEventPublisher.publishEvent(new LmsSetupChangeEvent(entity));
|
|
||||||
return super.notifySaved(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,7 @@ public class ModelObjectJSONGenerator {
|
||||||
1L, 1L, 1L, "externalId", "name", "description", DateTime.now(), DateTime.now(),
|
1L, 1L, 1L, "externalId", "name", "description", DateTime.now(), DateTime.now(),
|
||||||
"startURL", ExamType.BYOD, "owner",
|
"startURL", ExamType.BYOD, "owner",
|
||||||
Arrays.asList("user1", "user2"),
|
Arrays.asList("user1", "user2"),
|
||||||
ExamStatus.RUNNING, "browserExamKeys", true, null);
|
ExamStatus.RUNNING, false, "browserExamKeys", true, null);
|
||||||
System.out.println(domainObject.getClass().getSimpleName() + ":");
|
System.out.println(domainObject.getClass().getSimpleName() + ":");
|
||||||
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
||||||
|
|
||||||
|
|
|
@ -850,6 +850,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
||||||
null,
|
null,
|
||||||
Utils.immutableCollectionOf(userId),
|
Utils.immutableCollectionOf(userId),
|
||||||
ExamStatus.RUNNING,
|
ExamStatus.RUNNING,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
null);
|
null);
|
||||||
|
|
|
@ -64,6 +64,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
||||||
exam.owner,
|
exam.owner,
|
||||||
Arrays.asList("user5"),
|
Arrays.asList("user5"),
|
||||||
null,
|
null,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
null))
|
null))
|
||||||
|
@ -94,6 +95,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
||||||
exam.owner,
|
exam.owner,
|
||||||
Arrays.asList("user2"),
|
Arrays.asList("user2"),
|
||||||
null,
|
null,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
null))
|
null))
|
||||||
|
|
|
@ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.webservice.integration.api.admin;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.test.context.jdbc.Sql;
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
@ -23,10 +26,25 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
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.LmsSetup;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
|
|
||||||
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
|
|
||||||
public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LmsAPIService lmsAPIService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
|
||||||
|
public void init() {
|
||||||
|
this.lmsAPIService.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
|
||||||
|
public void cleanup() {
|
||||||
|
this.lmsAPIService.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testImportFromQuiz() throws Exception {
|
public void testImportFromQuiz() throws Exception {
|
||||||
// create new active LmsSetup Mock with seb-admin
|
// create new active LmsSetup Mock with seb-admin
|
||||||
|
@ -57,7 +75,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
||||||
this,
|
this,
|
||||||
getSebAdminAccess(),
|
getSebAdminAccess(),
|
||||||
getSebAdminAccess(),
|
getSebAdminAccess(),
|
||||||
"LmsSetupMock",
|
"LmsSetupMock1",
|
||||||
"quiz2",
|
"quiz2",
|
||||||
ExamType.MANAGED,
|
ExamType.MANAGED,
|
||||||
"user5");
|
"user5");
|
||||||
|
@ -76,7 +94,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
||||||
this,
|
this,
|
||||||
getAdminInstitution2Access(),
|
getAdminInstitution2Access(),
|
||||||
getAdminInstitution2Access(),
|
getAdminInstitution2Access(),
|
||||||
"LmsSetupMock",
|
"LmsSetupMock2",
|
||||||
"quiz2",
|
"quiz2",
|
||||||
ExamType.MANAGED,
|
ExamType.MANAGED,
|
||||||
"user7");
|
"user7");
|
||||||
|
@ -90,7 +108,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
||||||
this,
|
this,
|
||||||
getAdminInstitution2Access(),
|
getAdminInstitution2Access(),
|
||||||
getExamAdmin1(), // this exam administrator is on Institution 2
|
getExamAdmin1(), // this exam administrator is on Institution 2
|
||||||
"LmsSetupMock2",
|
"LmsSetupMock3",
|
||||||
"quiz2",
|
"quiz2",
|
||||||
ExamType.MANAGED,
|
ExamType.MANAGED,
|
||||||
"user7");
|
"user7");
|
||||||
|
@ -108,7 +126,7 @@ public class ExamImportTest extends AdministrationAPIIntegrationTester {
|
||||||
this,
|
this,
|
||||||
getAdminInstitution1Access(),
|
getAdminInstitution1Access(),
|
||||||
getExamAdmin1(), // this exam administrator is on Institution 2
|
getExamAdmin1(), // this exam administrator is on Institution 2
|
||||||
"LmsSetupMock",
|
"LmsSetupMock4",
|
||||||
"quiz2",
|
"quiz2",
|
||||||
ExamType.MANAGED,
|
ExamType.MANAGED,
|
||||||
"user7");
|
"user7");
|
||||||
|
|
|
@ -71,7 +71,6 @@ public class MoodleCourseAccessTest {
|
||||||
|
|
||||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class),
|
mock(AsyncService.class),
|
||||||
|
@ -120,7 +119,6 @@ public class MoodleCourseAccessTest {
|
||||||
|
|
||||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class),
|
mock(AsyncService.class),
|
||||||
|
@ -143,7 +141,6 @@ public class MoodleCourseAccessTest {
|
||||||
|
|
||||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class),
|
mock(AsyncService.class),
|
||||||
|
@ -165,7 +162,6 @@ public class MoodleCourseAccessTest {
|
||||||
|
|
||||||
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class),
|
mock(AsyncService.class),
|
||||||
|
|
Loading…
Reference in a new issue