better Moodle course access (background fetch and caching)
This commit is contained in:
parent
5aca3bc5b5
commit
3cefcbe3f3
6 changed files with 512 additions and 233 deletions
|
@ -516,6 +516,10 @@ public final class Utils {
|
||||||
return DateTime.now(DateTimeZone.UTC).getMillis();
|
return DateTime.now(DateTimeZone.UTC).getMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long getSecondsNow() {
|
||||||
|
return DateTime.now(DateTimeZone.UTC).getMillis() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
public static RGB toRGB(final String rgbString) {
|
public static RGB toRGB(final String rgbString) {
|
||||||
if (StringUtils.isNotBlank(rgbString)) {
|
if (StringUtils.isNotBlank(rgbString)) {
|
||||||
return new RGB(
|
return new RGB(
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -17,13 +16,10 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.joda.time.DateTimeZone;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
@ -32,9 +28,7 @@ import org.springframework.util.MultiValueMap;
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.core.JsonParseException;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
@ -44,7 +38,6 @@ 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.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.Pair;
|
|
||||||
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.impl.CourseAccess;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.CourseAccess;
|
||||||
|
@ -59,23 +52,24 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
private static final String MOODLE_QUIZ_START_URL_PATH = "mod/quiz/view.php?id=";
|
private static final String MOODLE_QUIZ_START_URL_PATH = "mod/quiz/view.php?id=";
|
||||||
|
|
||||||
private static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
|
static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
|
||||||
private static final String MOODLE_USER_PROFILE_API_FUNCTION_NAME = "core_user_get_users_by_field";
|
static final String MOODLE_USER_PROFILE_API_FUNCTION_NAME = "core_user_get_users_by_field";
|
||||||
private static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
|
static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
|
||||||
private static final String MOODLE_COURSE_API_COURSE_IDS = "courseids";
|
static final String MOODLE_COURSE_API_COURSE_IDS = "courseids";
|
||||||
private static final String MOODLE_COURSE_API_IDS = "ids";
|
static final String MOODLE_COURSE_API_IDS = "ids";
|
||||||
private static final String MOODLE_COURSE_SEARCH_API_FUNCTION_NAME = "core_course_search_courses";
|
static final String MOODLE_COURSE_SEARCH_API_FUNCTION_NAME = "core_course_search_courses";
|
||||||
private static final String MOODLE_COURSE_BY_FIELD_API_FUNCTION_NAME = "core_course_get_courses_by_field";
|
static final String MOODLE_COURSE_BY_FIELD_API_FUNCTION_NAME = "core_course_get_courses_by_field";
|
||||||
private static final String MOODLE_COURSE_API_FIELD_NAME = "field";
|
static final String MOODLE_COURSE_API_FIELD_NAME = "field";
|
||||||
private static final String MOODLE_COURSE_API_FIELD_VALUE = "value";
|
static final String MOODLE_COURSE_API_FIELD_VALUE = "value";
|
||||||
private static final String MOODLE_COURSE_API_SEARCH_CRITERIA_NAME = "criterianame";
|
static final String MOODLE_COURSE_API_SEARCH_CRITERIA_NAME = "criterianame";
|
||||||
private static final String MOODLE_COURSE_API_SEARCH_CRITERIA_VALUE = "criteriavalue";
|
static final String MOODLE_COURSE_API_SEARCH_CRITERIA_VALUE = "criteriavalue";
|
||||||
private static final String MOODLE_COURSE_API_SEARCH_PAGE = "page";
|
static final String MOODLE_COURSE_API_SEARCH_PAGE = "page";
|
||||||
private 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 LmsSetup lmsSetup;
|
||||||
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
|
||||||
|
private final MoodleCourseDataLazyLoader moodleCourseDataLazyLoader;
|
||||||
|
|
||||||
private MoodleAPIRestTemplate restTemplate;
|
private MoodleAPIRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@ -83,11 +77,13 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final LmsSetup lmsSetup,
|
final LmsSetup lmsSetup,
|
||||||
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
final MoodleRestTemplateFactory moodleRestTemplateFactory,
|
||||||
|
final MoodleCourseDataLazyLoader moodleCourseDataLazyLoader,
|
||||||
final AsyncService asyncService) {
|
final AsyncService asyncService) {
|
||||||
|
|
||||||
super(asyncService);
|
super(asyncService);
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.lmsSetup = lmsSetup;
|
this.lmsSetup = lmsSetup;
|
||||||
|
this.moodleCourseDataLazyLoader = moodleCourseDataLazyLoader;
|
||||||
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,12 +183,43 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
throw new UnsupportedOperationException("not available yet");
|
throw new UnsupportedOperationException("not available yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<QuizData> collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
|
private List<QuizData> collectAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
|
||||||
|
|
||||||
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||||
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||||
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||||
return getAllQuizzes(restTemplate)
|
|
||||||
|
Collection<CourseData> courseQuizData = Collections.emptyList();
|
||||||
|
if (this.moodleCourseDataLazyLoader.isRunning()) {
|
||||||
|
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
||||||
|
} else if (this.moodleCourseDataLazyLoader.getLastRunTime() <= 0) {
|
||||||
|
// first run async and wait some time, get what is there
|
||||||
|
this.moodleCourseDataLazyLoader.loadAsync(restTemplate);
|
||||||
|
try {
|
||||||
|
Thread.sleep(5 * Constants.SECOND_IN_MILLIS);
|
||||||
|
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to wait for first load run: ", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
} else if (this.moodleCourseDataLazyLoader.isLongRunningTask()) {
|
||||||
|
// kick off the task again when old asynchronously and take back what is there instantly
|
||||||
|
if (Utils.getMillisecondsNow() - this.moodleCourseDataLazyLoader.getLastRunTime() > 10
|
||||||
|
* Constants.MINUTE_IN_MILLIS) {
|
||||||
|
this.moodleCourseDataLazyLoader.loadAsync(restTemplate);
|
||||||
|
}
|
||||||
|
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
||||||
|
} else {
|
||||||
|
// just run the task in sync
|
||||||
|
this.moodleCourseDataLazyLoader.loadSync(restTemplate);
|
||||||
|
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (courseQuizData.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return courseQuizData
|
||||||
.stream()
|
.stream()
|
||||||
.reduce(
|
.reduce(
|
||||||
new ArrayList<>(),
|
new ArrayList<>(),
|
||||||
|
@ -209,169 +236,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CourseData> getAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
|
|
||||||
|
|
||||||
final List<CourseData> result = new ArrayList<>();
|
|
||||||
|
|
||||||
int page = 0;
|
|
||||||
Pair<List<CourseData>, Integer> quizzesBatch = getQuizzesBatch(restTemplate, page);
|
|
||||||
result.addAll(quizzesBatch.a);
|
|
||||||
|
|
||||||
log.info("Got quiz page batch for page {} of size {} with {} items",
|
|
||||||
page,
|
|
||||||
quizzesBatch.b,
|
|
||||||
quizzesBatch.a.size());
|
|
||||||
|
|
||||||
if (quizzesBatch.b != null && quizzesBatch.b > 0) {
|
|
||||||
page++;
|
|
||||||
quizzesBatch = getQuizzesBatch(restTemplate, page);
|
|
||||||
result.addAll(quizzesBatch.a);
|
|
||||||
|
|
||||||
log.info("Got quiz page batch for page {} of size {} with {} items",
|
|
||||||
page,
|
|
||||||
quizzesBatch.b,
|
|
||||||
quizzesBatch.a.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// while (quizzesBatch.b != null && quizzesBatch.b > 0) {
|
|
||||||
// page++;
|
|
||||||
// quizzesBatch = getQuizzesBatch(restTemplate, page);
|
|
||||||
// result.addAll(quizzesBatch.a);
|
|
||||||
//
|
|
||||||
// log.info("Got quiz page batch for page {} of size {} with {} items",
|
|
||||||
// page,
|
|
||||||
// quizzesBatch.b,
|
|
||||||
// quizzesBatch.a.size());
|
|
||||||
// }
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pair<List<CourseData>, Integer> getQuizzesBatch(
|
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
|
||||||
final int page) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// first get courses from Moodle for page
|
|
||||||
final Map<String, CourseData> courseData = new HashMap<>();
|
|
||||||
final Collection<CourseData> coursesPage = getCoursesPage(restTemplate, page, 1000);
|
|
||||||
|
|
||||||
if (coursesPage.isEmpty()) {
|
|
||||||
return new Pair<>(Collections.emptyList(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
courseData.putAll(coursesPage
|
|
||||||
.stream()
|
|
||||||
.collect(Collectors.toMap(cd -> cd.id, Function.identity())));
|
|
||||||
|
|
||||||
// then get all quizzes of courses and filter
|
|
||||||
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
|
||||||
attributes.put(MOODLE_COURSE_API_COURSE_IDS, new ArrayList<>(courseData.keySet()));
|
|
||||||
|
|
||||||
final String quizzesJSON = restTemplate.callMoodleAPIFunction(
|
|
||||||
MOODLE_QUIZ_API_FUNCTION_NAME,
|
|
||||||
attributes);
|
|
||||||
|
|
||||||
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
|
||||||
quizzesJSON,
|
|
||||||
CourseQuizData.class);
|
|
||||||
|
|
||||||
final Map<String, CourseData> finalCourseDataRef = courseData;
|
|
||||||
if (courseQuizData.quizzes != null) {
|
|
||||||
courseQuizData.quizzes
|
|
||||||
.stream()
|
|
||||||
.filter(getQuizFilter())
|
|
||||||
.forEach(quiz -> {
|
|
||||||
final CourseData course = finalCourseDataRef.get(quiz.course);
|
|
||||||
if (course != null) {
|
|
||||||
course.quizzes.add(quiz);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Pair<>(courseData.values()
|
|
||||||
.stream()
|
|
||||||
.filter(c -> !c.quizzes.isEmpty())
|
|
||||||
.collect(Collectors.toList()),
|
|
||||||
coursesPage.size());
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Unexpected exception while trying to get course data: ", e);
|
|
||||||
return new Pair<>(Collections.emptyList(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate<CourseQuiz> getQuizFilter() {
|
|
||||||
final long now = DateTime.now(DateTimeZone.UTC).getMillis() / 1000;
|
|
||||||
return quiz -> {
|
|
||||||
if (quiz.time_close == null || quiz.time_close == 0 || quiz.time_close > now) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("remove quiz {} end_time {} now {}", quiz.name, quiz.time_close, now);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Predicate<CourseData> getCourseFilter() {
|
|
||||||
final long now = DateTime.now(DateTimeZone.UTC).getMillis() / 1000;
|
|
||||||
return course -> {
|
|
||||||
if (course.end_date == null || course.end_date == 0 || course.end_date > now) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("remove course {} end_time {} now {}", course.short_name, course.end_date, now);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<CourseData> getCoursesPage(
|
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
|
||||||
final int page,
|
|
||||||
final int size) throws JsonParseException, JsonMappingException, IOException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// get course ids per page
|
|
||||||
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
|
||||||
attributes.add(MOODLE_COURSE_API_SEARCH_CRITERIA_NAME, "search");
|
|
||||||
attributes.add(MOODLE_COURSE_API_SEARCH_CRITERIA_VALUE, "");
|
|
||||||
attributes.add(MOODLE_COURSE_API_SEARCH_PAGE, String.valueOf(page));
|
|
||||||
attributes.add(MOODLE_COURSE_API_SEARCH_PAGE_SIZE, String.valueOf(size));
|
|
||||||
|
|
||||||
final String courseKeyPageJSON = restTemplate.callMoodleAPIFunction(
|
|
||||||
MOODLE_COURSE_SEARCH_API_FUNCTION_NAME,
|
|
||||||
attributes);
|
|
||||||
|
|
||||||
final CoursePage keysPage = this.jsonMapper.readValue(
|
|
||||||
courseKeyPageJSON,
|
|
||||||
CoursePage.class);
|
|
||||||
|
|
||||||
if (keysPage == null || keysPage.courseKeys == null || keysPage.courseKeys.isEmpty()) {
|
|
||||||
log.info("No courses found on page: {}", page);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// get courses
|
|
||||||
final Set<String> ids = keysPage.courseKeys
|
|
||||||
.stream()
|
|
||||||
.map(key -> key.id)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
// return getCoursesForIds(restTemplate, ids);
|
|
||||||
|
|
||||||
final Collection<CourseData> result = getCoursesForIds(restTemplate, ids)
|
|
||||||
.stream()
|
|
||||||
.filter(getCourseFilter())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// log.info("course page with {} courses, after filtering {} left", keysPage.courseKeys, result.size());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Unexpected error while trying to get courses page: ", e);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<QuizData> getQuizzesForIds(
|
private List<QuizData> getQuizzesForIds(
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
final Set<String> quizIds) {
|
final Set<String> quizIds) {
|
||||||
|
@ -595,54 +459,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
// ---- Mapping Classes ---
|
// ---- Mapping Classes ---
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
static final class CoursePage {
|
|
||||||
final Collection<CourseKey> courseKeys;
|
|
||||||
|
|
||||||
public CoursePage(
|
|
||||||
@JsonProperty(value = "courses") final Collection<CourseKey> courseKeys) {
|
|
||||||
|
|
||||||
this.courseKeys = courseKeys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
static final class CourseKey {
|
|
||||||
final String id;
|
|
||||||
final String short_name;
|
|
||||||
final String category_name;
|
|
||||||
final String sort_order;
|
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
protected CourseKey(
|
|
||||||
@JsonProperty(value = "id") final String id,
|
|
||||||
@JsonProperty(value = "shortname") final String short_name,
|
|
||||||
@JsonProperty(value = "categoryname") final String category_name,
|
|
||||||
@JsonProperty(value = "sortorder") final String sort_order) {
|
|
||||||
|
|
||||||
this.id = id;
|
|
||||||
this.short_name = short_name;
|
|
||||||
this.category_name = category_name;
|
|
||||||
this.sort_order = sort_order;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("CourseKey [id=");
|
|
||||||
builder.append(this.id);
|
|
||||||
builder.append(", short_name=");
|
|
||||||
builder.append(this.short_name);
|
|
||||||
builder.append(", category_name=");
|
|
||||||
builder.append(this.category_name);
|
|
||||||
builder.append(", sort_order=");
|
|
||||||
builder.append(this.sort_order);
|
|
||||||
builder.append("]");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Maps the Moodle course API course data */
|
/** Maps the Moodle course API course data */
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static final class CourseData {
|
static final class CourseData {
|
||||||
|
@ -680,6 +496,30 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
this.time_created = time_created;
|
this.time_created = time_created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
final CourseData other = (CourseData) obj;
|
||||||
|
if (this.id == null) {
|
||||||
|
if (other.id != null)
|
||||||
|
return false;
|
||||||
|
} else if (!this.id.equals(other.id))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -0,0 +1,423 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.async.AsyncRunner;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseAccess.CourseData;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseAccess.CourseQuiz;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseAccess.CourseQuizData;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseAccess.Courses;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@WebServiceProfile
|
||||||
|
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||||
|
public class MoodleCourseDataLazyLoader {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MoodleCourseDataLazyLoader.class);
|
||||||
|
|
||||||
|
private final JSONMapper jsonMapper;
|
||||||
|
private final AsyncRunner asyncRunner;
|
||||||
|
|
||||||
|
private final Set<CourseData> preFilteredCourseIds = new HashSet<>();
|
||||||
|
|
||||||
|
private long lastRunTime = 0;
|
||||||
|
private long lastLoadTime = 0;
|
||||||
|
private boolean running = false;
|
||||||
|
|
||||||
|
private final long fromCutTime = DateTime.now(DateTimeZone.UTC).minusYears(3).getMillis() / 1000;
|
||||||
|
|
||||||
|
public MoodleCourseDataLazyLoader(
|
||||||
|
final JSONMapper jsonMapper,
|
||||||
|
final AsyncRunner asyncRunner) {
|
||||||
|
|
||||||
|
this.jsonMapper = jsonMapper;
|
||||||
|
this.asyncRunner = asyncRunner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<CourseData> getPreFilteredCourseIds() {
|
||||||
|
return this.preFilteredCourseIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastRunTime() {
|
||||||
|
return this.lastRunTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return this.running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLongRunningTask() {
|
||||||
|
return this.lastLoadTime > 30 * Constants.SECOND_IN_MILLIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<CourseData> loadSync(final MoodleAPIRestTemplate restTemplate) {
|
||||||
|
if (this.running) {
|
||||||
|
throw new IllegalStateException("Is already running asynchronously");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.running = true;
|
||||||
|
loadAndCache(restTemplate).run();
|
||||||
|
this.lastRunTime = Utils.getMillisecondsNow();
|
||||||
|
|
||||||
|
log.info("Loaded {} courses synchronously", this.preFilteredCourseIds.size());
|
||||||
|
|
||||||
|
return this.preFilteredCourseIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadAsync(final MoodleAPIRestTemplate restTemplate) {
|
||||||
|
if (this.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.running = true;
|
||||||
|
this.asyncRunner.runAsync(loadAndCache(restTemplate));
|
||||||
|
this.lastRunTime = Utils.getMillisecondsNow();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable loadAndCache(final MoodleAPIRestTemplate restTemplate) {
|
||||||
|
return () -> {
|
||||||
|
final long startTime = Utils.getMillisecondsNow();
|
||||||
|
|
||||||
|
loadAllQuizzes(restTemplate);
|
||||||
|
|
||||||
|
this.lastLoadTime = Utils.getMillisecondsNow() - startTime;
|
||||||
|
this.running = false;
|
||||||
|
|
||||||
|
log.info("Loaded {} courses asynchronously", this.preFilteredCourseIds.size());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAllQuizzes(final MoodleAPIRestTemplate restTemplate) {
|
||||||
|
int page = 0;
|
||||||
|
while (getQuizzesBatch(restTemplate, page)) {
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getQuizzesBatch(
|
||||||
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
|
final int page) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// first get courses from Moodle for page
|
||||||
|
final Map<String, CourseData> courseData = new HashMap<>();
|
||||||
|
final Collection<CourseData> coursesPage = getCoursesPage(restTemplate, page, 1000);
|
||||||
|
|
||||||
|
if (coursesPage == null || coursesPage.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
courseData.putAll(coursesPage
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(cd -> cd.id, Function.identity())));
|
||||||
|
|
||||||
|
// then get all quizzes of courses and filter
|
||||||
|
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
||||||
|
attributes.put(
|
||||||
|
MoodleCourseAccess.MOODLE_COURSE_API_COURSE_IDS,
|
||||||
|
new ArrayList<>(courseData.keySet()));
|
||||||
|
|
||||||
|
final String quizzesJSON = restTemplate.callMoodleAPIFunction(
|
||||||
|
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||||
|
attributes);
|
||||||
|
|
||||||
|
final CourseQuizData courseQuizData = this.jsonMapper.readValue(
|
||||||
|
quizzesJSON,
|
||||||
|
CourseQuizData.class);
|
||||||
|
|
||||||
|
if (courseQuizData == null || courseQuizData.quizzes == null || courseQuizData.quizzes.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (courseQuizData.quizzes != null) {
|
||||||
|
courseQuizData.quizzes
|
||||||
|
.stream()
|
||||||
|
.filter(getQuizFilter())
|
||||||
|
.forEach(quiz -> {
|
||||||
|
final CourseData data = courseData.get(quiz.course);
|
||||||
|
if (data != null) {
|
||||||
|
data.quizzes.add(quiz);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.preFilteredCourseIds.addAll(
|
||||||
|
courseData.values().stream()
|
||||||
|
.filter(c -> !c.quizzes.isEmpty())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Unexpected exception while trying to get course data: ", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<CourseData> getCoursesPage(
|
||||||
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
|
final int page,
|
||||||
|
final int size) throws JsonParseException, JsonMappingException, IOException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// get course ids per page
|
||||||
|
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
||||||
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_CRITERIA_NAME, "search");
|
||||||
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_CRITERIA_VALUE, "");
|
||||||
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_PAGE, String.valueOf(page));
|
||||||
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_PAGE_SIZE, String.valueOf(size));
|
||||||
|
|
||||||
|
final String courseKeyPageJSON = restTemplate.callMoodleAPIFunction(
|
||||||
|
MoodleCourseAccess.MOODLE_COURSE_SEARCH_API_FUNCTION_NAME,
|
||||||
|
attributes);
|
||||||
|
|
||||||
|
final CoursePage keysPage = this.jsonMapper.readValue(
|
||||||
|
courseKeyPageJSON,
|
||||||
|
CoursePage.class);
|
||||||
|
|
||||||
|
if (keysPage == null || keysPage.courseKeys == null || keysPage.courseKeys.isEmpty()) {
|
||||||
|
log.info("No courses found on page: {}", page);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get courses
|
||||||
|
final Set<String> ids = keysPage.courseKeys
|
||||||
|
.stream()
|
||||||
|
.map(key -> key.id)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
final Collection<CourseData> result = getCoursesForIds(restTemplate, ids)
|
||||||
|
.stream()
|
||||||
|
.filter(getCourseFilter())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// log.info("course page with {} courses, after filtering {} left", keysPage.courseKeys, result.size());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Unexpected error while trying to get courses page: ", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<CourseData> getCoursesForIds(
|
||||||
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
|
final Set<String> ids) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Get courses for ids: {}", ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String joinedIds = StringUtils.join(ids, Constants.COMMA);
|
||||||
|
|
||||||
|
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
||||||
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_FIELD_NAME, MoodleCourseAccess.MOODLE_COURSE_API_IDS);
|
||||||
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_FIELD_VALUE, joinedIds);
|
||||||
|
final String coursePageJSON = restTemplate.callMoodleAPIFunction(
|
||||||
|
MoodleCourseAccess.MOODLE_COURSE_BY_FIELD_API_FUNCTION_NAME,
|
||||||
|
attributes);
|
||||||
|
|
||||||
|
return this.jsonMapper.<Courses> readValue(
|
||||||
|
coursePageJSON,
|
||||||
|
Courses.class).courses;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Unexpected error while trying to get courses for ids", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<CourseQuiz> getQuizFilter() {
|
||||||
|
final long now = Utils.getSecondsNow();
|
||||||
|
return quiz -> {
|
||||||
|
if (quiz.time_close == null || quiz.time_close == 0 || quiz.time_close > now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("remove quiz {} end_time {} now {}", quiz.name, quiz.time_close, now);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<CourseData> getCourseFilter() {
|
||||||
|
final long now = Utils.getSecondsNow();
|
||||||
|
return course -> {
|
||||||
|
if (course.start_date < this.fromCutTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (course.end_date == null || course.end_date == 0 || course.end_date > now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("remove course {} end_time {} now {}", course.short_name, course.end_date, now);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
static final class CoursePage {
|
||||||
|
final Collection<CourseKey> courseKeys;
|
||||||
|
|
||||||
|
public CoursePage(
|
||||||
|
@JsonProperty(value = "courses") final Collection<CourseKey> courseKeys) {
|
||||||
|
|
||||||
|
this.courseKeys = courseKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
static final class CourseKey {
|
||||||
|
final String id;
|
||||||
|
final String short_name;
|
||||||
|
final String category_name;
|
||||||
|
final String sort_order;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
protected CourseKey(
|
||||||
|
@JsonProperty(value = "id") final String id,
|
||||||
|
@JsonProperty(value = "shortname") final String short_name,
|
||||||
|
@JsonProperty(value = "categoryname") final String category_name,
|
||||||
|
@JsonProperty(value = "sortorder") final String sort_order) {
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.short_name = short_name;
|
||||||
|
this.category_name = category_name;
|
||||||
|
this.sort_order = sort_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("CourseKey [id=");
|
||||||
|
builder.append(this.id);
|
||||||
|
builder.append(", short_name=");
|
||||||
|
builder.append(this.short_name);
|
||||||
|
builder.append(", category_name=");
|
||||||
|
builder.append(this.category_name);
|
||||||
|
builder.append(", sort_order=");
|
||||||
|
builder.append(this.sort_order);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// @JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
// static final class CourseKeys {
|
||||||
|
// final Collection<CourseDataKey> courses;
|
||||||
|
//
|
||||||
|
// @JsonCreator
|
||||||
|
// protected CourseKeys(
|
||||||
|
// @JsonProperty(value = "courses") final Collection<CourseDataKey> courses) {
|
||||||
|
// this.courses = courses;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /** Maps the Moodle course API course data */
|
||||||
|
// @JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
// static final class CourseDataKey {
|
||||||
|
// final String id;
|
||||||
|
// final String short_name;
|
||||||
|
// final Long start_date; // unix-time seconds UTC
|
||||||
|
// final Long end_date; // unix-time seconds UTC
|
||||||
|
// final Long time_created; // unix-time seconds UTC
|
||||||
|
// final Collection<CourseQuizKey> quizzes = new ArrayList<>();
|
||||||
|
//
|
||||||
|
// @JsonCreator
|
||||||
|
// protected CourseDataKey(
|
||||||
|
// @JsonProperty(value = "id") final String id,
|
||||||
|
// @JsonProperty(value = "shortname") final String short_name,
|
||||||
|
// @JsonProperty(value = "startdate") final Long start_date,
|
||||||
|
// @JsonProperty(value = "enddate") final Long end_date,
|
||||||
|
// @JsonProperty(value = "timecreated") final Long time_created) {
|
||||||
|
//
|
||||||
|
// this.id = id;
|
||||||
|
// this.short_name = short_name;
|
||||||
|
// this.start_date = start_date;
|
||||||
|
// this.end_date = end_date;
|
||||||
|
// this.time_created = time_created;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
// static final class CourseQuizKeys {
|
||||||
|
// final Collection<CourseQuizKey> quizzes;
|
||||||
|
//
|
||||||
|
// @JsonCreator
|
||||||
|
// protected CourseQuizKeys(
|
||||||
|
// @JsonProperty(value = "quizzes") final Collection<CourseQuizKey> quizzes) {
|
||||||
|
// this.quizzes = quizzes;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
// static final class CourseQuizKey {
|
||||||
|
// final String id;
|
||||||
|
// final String course;
|
||||||
|
// final String name;
|
||||||
|
// final Long time_open; // unix-time seconds UTC
|
||||||
|
// final Long time_close; // unix-time seconds UTC
|
||||||
|
//
|
||||||
|
// @JsonCreator
|
||||||
|
// protected CourseQuizKey(
|
||||||
|
// @JsonProperty(value = "id") final String id,
|
||||||
|
// @JsonProperty(value = "course") final String course,
|
||||||
|
// @JsonProperty(value = "name") final String name,
|
||||||
|
// @JsonProperty(value = "timeopen") final Long time_open,
|
||||||
|
// @JsonProperty(value = "timeclose") final Long time_close) {
|
||||||
|
//
|
||||||
|
// this.id = id;
|
||||||
|
// this.course = course;
|
||||||
|
// this.name = name;
|
||||||
|
// this.time_open = time_open;
|
||||||
|
// this.time_close = time_close;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
private final AsyncService asyncService;
|
private final AsyncService asyncService;
|
||||||
private final ClientCredentialService clientCredentialService;
|
private final ClientCredentialService clientCredentialService;
|
||||||
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||||
|
private final MoodleCourseDataLazyLoader moodleCourseDataLazyLoader;
|
||||||
private final String[] alternativeTokenRequestPaths;
|
private final String[] alternativeTokenRequestPaths;
|
||||||
|
|
||||||
protected MoodleLmsAPITemplateFactory(
|
protected MoodleLmsAPITemplateFactory(
|
||||||
|
@ -40,12 +41,14 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
final ClientCredentialService clientCredentialService,
|
final ClientCredentialService clientCredentialService,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
|
final MoodleCourseDataLazyLoader moodleCourseDataLazyLoader,
|
||||||
@Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths) {
|
@Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths) {
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.asyncService = asyncService;
|
this.asyncService = asyncService;
|
||||||
this.clientCredentialService = clientCredentialService;
|
this.clientCredentialService = clientCredentialService;
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
|
this.moodleCourseDataLazyLoader = moodleCourseDataLazyLoader;
|
||||||
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
|
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
|
||||||
? StringUtils.split(alternativeTokenRequestPaths, Constants.LIST_SEPARATOR)
|
? StringUtils.split(alternativeTokenRequestPaths, Constants.LIST_SEPARATOR)
|
||||||
: null;
|
: null;
|
||||||
|
@ -71,6 +74,7 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
lmsSetup,
|
lmsSetup,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
|
this.moodleCourseDataLazyLoader,
|
||||||
this.asyncService);
|
this.asyncService);
|
||||||
|
|
||||||
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
|
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
|
||||||
|
|
|
@ -7,3 +7,7 @@ server.tomcat.uri-encoding=UTF-8
|
||||||
|
|
||||||
logging.level.ch=INFO
|
logging.level.ch=INFO
|
||||||
|
|
||||||
|
sebserver.http.client.connect-timeout=150000
|
||||||
|
sebserver.http.client.connection-request-timeout=100000
|
||||||
|
sebserver.http.client.read-timeout=200000
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ public class MoodleCourseAccessTest {
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class));
|
||||||
|
|
||||||
final String examId = "123";
|
final String examId = "123";
|
||||||
|
@ -114,6 +115,7 @@ public class MoodleCourseAccessTest {
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class));
|
||||||
|
|
||||||
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
||||||
|
@ -135,6 +137,7 @@ public class MoodleCourseAccessTest {
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class));
|
||||||
|
|
||||||
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
||||||
|
@ -155,6 +158,7 @@ public class MoodleCourseAccessTest {
|
||||||
new JSONMapper(),
|
new JSONMapper(),
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class));
|
||||||
|
|
||||||
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
||||||
|
|
Loading…
Add table
Reference in a new issue