diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java
index 059fda0d..2ab1481f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java
@@ -59,6 +59,7 @@ public final class Constants {
public static final Character DOUBLE_QUOTE = '"';
public static final Character COMMA = ',';
public static final Character PIPE = '|';
+ public static final Character UNDERLINE = '_';
public static final Character AMPERSAND = '&';
public static final Character EQUALITY_SIGN = '=';
public static final Character LIST_SEPARATOR_CHAR = COMMA;
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java
index f651b48f..4b76abec 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamDAO.java
@@ -37,6 +37,8 @@ public interface ExamDAO extends ActivatableEntityDAO, BulkActionSup
* happened */
Result examGrantEntityByClientConnection(Long connectionId);
+ Result getWithQuizDataFromCache(Long id);
+
/** Get all active Exams for a given institution.
*
* @param institutionId the identifier of the institution
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java
index 5391b966..59a919fd 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamConfigurationMapDAOImpl.java
@@ -377,6 +377,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
private Result> toDomainModel(
final Collection records) {
+
return Result.tryCatch(() -> records
.stream()
.map(model -> this.toDomainModel(model).getOrThrow())
@@ -390,7 +391,8 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
.selectByPrimaryKey(record.getConfigurationNodeId());
final String status = config.getStatus();
- final Exam exam = this.examDAO.byPK(record.getExamId())
+ final Exam exam = this.examDAO
+ .getWithQuizDataFromCache(record.getExamId())
.getOr(null);
return new ExamConfigurationMap(
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
index 99ce6240..1e6920c5 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
@@ -119,6 +119,13 @@ public class ExamDAOImpl implements ExamDAO {
.map(record -> toDomainModel(record, null, null).getOrThrow());
}
+ @Override
+ @Transactional(readOnly = true)
+ public Result getWithQuizDataFromCache(final Long id) {
+ return recordById(id)
+ .flatMap(this::toDomainModelFromCache);
+ }
+
@Override
@Transactional(readOnly = true)
public Result> all(final Long institutionId, final Boolean active) {
@@ -761,6 +768,17 @@ public class ExamDAOImpl implements ExamDAO {
exam.getDescription());
}
+ private Result toDomainModelFromCache(final ExamRecord record) {
+
+ return this.lmsAPIService
+ .getLmsAPITemplate(record.getLmsSetupId())
+ .flatMap(template -> this.toDomainModel(
+ record,
+ template.getQuizFromCache(record.getExternalId())
+ .getOrThrow(),
+ null));
+ }
+
private Result toDomainModel(final ExamRecord record) {
return toDomainModel(
record.getLmsSetupId(),
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java
index aec35ad1..2a40847e 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java
@@ -165,6 +165,15 @@ public interface LmsAPITemplate {
* @return Collection of all {@link QuizData } from the given id set */
Collection> getQuizzesFromCache(Set ids);
+ /** Get a particular quiz data from cache if available. If not, tries to get it from the LMS.
+ *
+ * @param id the quiz identifier, external identifier of the exam.
+ * @return Result refer to the {@link QuizData } or to an error when happended */
+ Result getQuizFromCache(String id);
+
+ /** Clears the underling caches if there are some for a particular implementation. */
+ void clearCache();
+
/** 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
*
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java
new file mode 100644
index 00000000..15473d59
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.core.env.Environment;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.async.AsyncService;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+
+public abstract class AbstractCachedCourseAccess extends AbstractCourseAccess {
+
+ public static final String CACHE_NAME_QUIZ_DATA = "QUIZ_DATA_CACHE";
+
+ private static final String NO_QUIZ_DATA_FOUND_ERROR = "NO_QUIZ_DATA_FOUND_ERROR";
+
+ private final Cache cache;
+
+ protected AbstractCachedCourseAccess(
+ final AsyncService asyncService,
+ final Environment environment,
+ final CacheManager cacheManager) {
+
+ super(asyncService, environment);
+ this.cache = cacheManager.getCache(CACHE_NAME_QUIZ_DATA);
+ }
+
+ public void clearCache() {
+ this.cache.clear();
+ }
+
+ protected QuizData getFromCache(final String id) {
+ return this.cache.get(createCacheKey(id), QuizData.class);
+ }
+
+ protected void putToCache(final QuizData quizData) {
+ this.cache.put(createCacheKey(quizData.id), quizData);
+ }
+
+ protected void putToCache(final Collection quizData) {
+ quizData.stream().forEach(q -> this.cache.put(createCacheKey(q.id), q));
+ }
+
+ protected void evict(final String id) {
+ this.cache.evict(createCacheKey(id));
+ }
+
+ @Override
+ public Result getQuizFromCache(final String id) {
+ final QuizData fromCache = getFromCache(id);
+ if (fromCache != null) {
+ return Result.of(fromCache);
+ } else {
+ return Result.ofRuntimeError(NO_QUIZ_DATA_FOUND_ERROR);
+ }
+ }
+
+ @Override
+ public Result>> getQuizzesFromCache(final Set ids) {
+ return Result.of(ids.stream().map(this::getQuizFromCache).collect(Collectors.toList()));
+ }
+
+ protected abstract Long getLmsSetupId();
+
+ private final String createCacheKey(final String id) {
+ return id + Constants.UNDERLINE + getLmsSetupId();
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java
index b43fb874..0b0afa2c 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java
@@ -11,12 +11,8 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
import java.util.Set;
-import java.util.function.Function;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,6 +21,7 @@ import org.springframework.core.env.Environment;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
+import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker.State;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
@@ -101,42 +98,8 @@ public abstract class AbstractCourseAccess {
Constants.SECOND_IN_MILLIS * 10));
}
- public Result>> getQuizzesFromCache(final Set ids) {
- return Result.tryCatch(() -> {
- final List cached = allQuizzesSupplier().getAllCached();
- final List available = (cached != null)
- ? cached
- : Collections.emptyList();
-
- final Map quizMapping = available
- .stream()
- .collect(Collectors.toMap(q -> q.id, Function.identity()));
-
- if (!quizMapping.keySet().containsAll(ids)) {
-
- final Map collect = quizzesSupplier(ids).get()
- .stream()
- .collect(Collectors.toMap(qd -> qd.id, Function.identity()));
- if (collect != null) {
- quizMapping.clear();
- quizMapping.putAll(collect);
- }
- }
-
- return ids
- .stream()
- .map(id -> {
- final QuizData q = quizMapping.get(id);
- return (q == null)
- ? Result. ofError(new NoSuchElementException("Quiz with id: " + id))
- : Result.of(q);
- })
- .collect(Collectors.toList());
- });
- }
-
public Result> getQuizzes(final FilterMap filterMap) {
- return allQuizzesSupplier().getAll(filterMap);
+ return this.quizzesRequest.protectedRun(allQuizzesSupplier(filterMap));
}
public Result getExamineeAccountDetails(final String examineeSessionId) {
@@ -172,30 +135,41 @@ public abstract class AbstractCourseAccess {
Collections.emptyMap());
}
+ /** This abstraction has no cache implementation and therefore this returns a Result
+ * with an "No cache supported error.
+ *
+ * To implement and use caching, this must be overridden and implemented
+ *
+ * @param id The identifier of the QuizData to get from cache
+ * @return Result with an "No cache supported error */
+ public Result getQuizFromCache(final String id) {
+ return Result.ofRuntimeError("No cache supported");
+ }
+
+ /** This abstraction has no cache implementation and therefore this returns a Result
+ * with an "No cache supported error.
+ *
+ * To implement and use caching, this must be overridden and implemented
+ *
+ * @param ids Collection of quiz data identifier to get from the cache
+ * @return Result with an "No cache supported error */
+ public Result>> getQuizzesFromCache(final Set ids) {
+ return Result.ofRuntimeError("No cache supported");
+ }
+
/** Provides a supplier for the quiz data request to use within the circuit breaker */
protected abstract Supplier> quizzesSupplier(final Set ids);
- /** Provides a AllQuizzesSupplier to supply quiz data either form cache or from LMS */
- protected abstract AllQuizzesSupplier allQuizzesSupplier();
+ /** Provides a supplier to supply request to use within the circuit breaker */
+ protected abstract Supplier> allQuizzesSupplier(final FilterMap filterMap);
/** Provides a supplier for the course chapter data request to use within the circuit breaker */
protected abstract Supplier getCourseChaptersSupplier(final String courseId);
- /** Gives a fetch status if asynchronous quiz data fetching is part of the underling implementation */
- protected abstract FetchStatus getFetchStatus();
-
- /** Uses to supply quiz data */
- protected interface AllQuizzesSupplier {
- /** Get all currently cached quiz data if supported by the underling implementation
- *
- * @return List containing all cached quiz data objects */
- List getAllCached();
-
- /** Get a list of all quiz data filtered by the given filter map from LMS.
- *
- * @param filterMap Map containing the filter criteria
- * @return Result refer to the list of filtered quiz data or to an error when happened */
- Result> getAll(final FilterMap filterMap);
+ protected FetchStatus getFetchStatus() {
+ if (this.quizzesRequest.getState() != State.CLOSED) {
+ return FetchStatus.FETCH_ERROR;
+ }
+ return FetchStatus.ALL_FETCHED;
}
-
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java
index 407bcd40..05b1e7fb 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java
@@ -98,17 +98,20 @@ public class LmsAPIServiceImpl implements LmsAPIService {
@Override
public Result getLmsAPITemplate(final String lmsSetupId) {
return Result.tryCatch(() -> {
- LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetupId);
- if (lmsAPITemplate == null) {
- lmsAPITemplate = createLmsSetupTemplate(lmsSetupId);
- if (lmsAPITemplate != null) {
- this.cache.put(new CacheKey(lmsSetupId, System.currentTimeMillis()), lmsAPITemplate);
+ synchronized (this) {
+ LmsAPITemplate lmsAPITemplate = getFromCache(lmsSetupId);
+ if (lmsAPITemplate == null) {
+ 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;
}
- if (lmsAPITemplate == null) {
- throw new ResourceNotFoundException(EntityType.LMS_SETUP, lmsSetupId);
- }
- return lmsAPITemplate;
});
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java
index 0c2c85a8..081e0ad0 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java
@@ -91,12 +91,10 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
public Result getSEBRestrictionFromExam(final Exam exam) {
return Result.tryCatch(() -> {
// load the config keys from restriction and merge with new generated config keys
- final long currentTimeMillis = System.currentTimeMillis();
final Set configKeys = new HashSet<>();
final Collection generatedKeys = this.examConfigService
.generateConfigKeys(exam.institutionId, exam.id)
.getOrThrow();
- System.out.println("******* " + (System.currentTimeMillis() - currentTimeMillis));
configKeys.addAll(generatedKeys);
if (generatedKeys != null && !generatedKeys.isEmpty()) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java
index 54d36cd3..29f705c5 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java
@@ -21,6 +21,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -43,8 +44,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
-import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker.State;
-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.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@@ -55,12 +54,12 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
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.impl.AbstractCourseAccess;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess;
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
*
* See also: https://course-catalog-api-guide.readthedocs.io */
-final class OpenEdxCourseAccess extends AbstractCourseAccess {
+final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseAccess.class);
@@ -74,65 +73,34 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
private final JSONMapper jsonMapper;
private final OpenEdxRestTemplateFactory openEdxRestTemplateFactory;
private final WebserviceInfo webserviceInfo;
- private final MemoizingCircuitBreaker> allQuizzesRequest;
- private final AllQuizzesSupplier allQuizzesSupplier;
private OAuth2RestTemplate restTemplate;
+ private final Long lmsSetupId;
public OpenEdxCourseAccess(
final JSONMapper jsonMapper,
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
final WebserviceInfo webserviceInfo,
final AsyncService asyncService,
- final Environment environment) {
+ final Environment environment,
+ final CacheManager cacheManager) {
- super(asyncService, environment);
+ super(asyncService, environment, cacheManager);
this.jsonMapper = jsonMapper;
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
this.webserviceInfo = webserviceInfo;
-
- this.allQuizzesRequest = asyncService.createMemoizingCircuitBreaker(
- quizzesSupplier(),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.attempts",
- Integer.class,
- 3),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.blockingTime",
- Long.class,
- Constants.MINUTE_IN_MILLIS),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.timeToRecover",
- Long.class,
- Constants.MINUTE_IN_MILLIS),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.memoize",
- Boolean.class,
- true),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.memoizingTime",
- Long.class,
- Constants.HOUR_IN_MILLIS));
-
- this.allQuizzesSupplier = new AllQuizzesSupplier() {
-
- @Override
- public List getAllCached() {
- return OpenEdxCourseAccess.this.allQuizzesRequest.getCached();
- }
-
- @Override
- public Result> getAll(final FilterMap filterMap) {
- return OpenEdxCourseAccess.this.allQuizzesRequest.get();
- }
-
- };
+ this.lmsSetupId = openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup().id;
}
APITemplateDataSupplier getApiTemplateDataSupplier() {
return this.openEdxRestTemplateFactory.apiTemplateDataSupplier;
}
+ @Override
+ protected Long getLmsSetupId() {
+ return this.lmsSetupId;
+ }
+
LmsSetupTestResult initAPIAccess() {
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
@@ -219,6 +187,13 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
});
}
+ @Override
+ protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
+ return () -> getRestTemplate()
+ .map(this::collectAllQuizzes)
+ .getOrThrow();
+ }
+
@Override
protected Supplier> quizzesSupplier(final Set ids) {
@@ -231,7 +206,7 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
getOneCourses(
lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_ENDPOINT,
getRestTemplate().getOrThrow(),
- ids.iterator().next()),
+ ids),
externalStartURI));
};
} else {
@@ -241,12 +216,6 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
}
}
- private Supplier> quizzesSupplier() {
- return () -> getRestTemplate()
- .map(this::collectAllQuizzes)
- .getOrThrow();
- }
-
@Override
protected Supplier getCourseChaptersSupplier(final String courseId) {
return () -> {
@@ -337,7 +306,10 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
final List collector = new ArrayList<>();
EdXPage page = getEdxPage(pageURI, restTemplate).getBody();
if (page != null) {
- collector.addAll(page.results);
+ page.results
+ .stream()
+ .filter(cd -> ids.contains(cd.id))
+ .forEach(collector::add);
while (page != null && StringUtils.isNotBlank(page.next)) {
page = getEdxPage(page.next, restTemplate).getBody();
if (page != null) {
@@ -355,16 +327,33 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
private CourseData getOneCourses(
final String pageURI,
final OAuth2RestTemplate restTemplate,
- final String id) {
+ final Set ids) {
- final HttpHeaders httpHeaders = new HttpHeaders();
- final ResponseEntity exchange = restTemplate.exchange(
- pageURI + "/" + id,
- HttpMethod.GET,
- new HttpEntity<>(httpHeaders),
- CourseData.class);
+ System.out.println("********************");
- return exchange.getBody();
+ // NOTE: try first to get the course data by id. This seems to be possible
+ // when the SEB restriction is not set. Once the SEB restriction is set,
+ // this gives a 403 response.
+ // We haven't found another way to get course data by id in this case so far
+ // Workaround is to search the course by paging (slow)
+ try {
+ final HttpHeaders httpHeaders = new HttpHeaders();
+ final String uri = pageURI + ids.iterator().next();
+ final ResponseEntity exchange = restTemplate.exchange(
+ uri,
+ HttpMethod.GET,
+ new HttpEntity<>(httpHeaders),
+ CourseData.class);
+
+ return exchange.getBody();
+ } catch (final Exception e) {
+ // try with paging
+ final List collectCourses = collectCourses(pageURI, restTemplate, ids);
+ if (collectCourses.isEmpty()) {
+ return null;
+ }
+ return collectCourses.get(0);
+ }
}
private List collectAllCourses(final String pageURI, final OAuth2RestTemplate restTemplate) {
@@ -403,7 +392,7 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
Blocks.class);
}
- private static QuizData quizDataOf(
+ private QuizData quizDataOf(
final LmsSetup lmsSetup,
final CourseData courseData,
final String uriPrefix) {
@@ -411,7 +400,7 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
final String startURI = uriPrefix + courseData.id;
final Map additionalAttrs = new HashMap<>();
additionalAttrs.put("blocks_url", courseData.blocks_url);
- return new QuizData(
+ final QuizData quizData = new QuizData(
courseData.id,
lmsSetup.getInstitutionId(),
lmsSetup.id,
@@ -421,6 +410,10 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
courseData.start,
courseData.end,
startURI);
+
+ super.putToCache(quizData);
+
+ return quizData;
}
/** Maps a OpenEdX course API course page */
@@ -538,17 +531,4 @@ final class OpenEdxCourseAccess extends AbstractCourseAccess {
return Result.of(this.restTemplate);
}
- @Override
- protected FetchStatus getFetchStatus() {
- if (this.allQuizzesRequest.getState() != State.CLOSED) {
- return FetchStatus.FETCH_ERROR;
- }
- return FetchStatus.ALL_FETCHED;
- }
-
- @Override
- protected AllQuizzesSupplier allQuizzesSupplier() {
- return this.allQuizzesSupplier;
- }
-
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java
index 47993903..7a1c068e 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java
@@ -98,12 +98,24 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
.collect(Collectors.toList());
}
+ @Override
+ public Result getQuizFromCache(final String id) {
+ return this.openEdxCourseAccess
+ .getQuizFromCache(id)
+ .orElse(() -> getQuiz(id));
+ }
+
@Override
public Collection> getQuizzesFromCache(final Set ids) {
return this.openEdxCourseAccess.getQuizzesFromCache(ids)
.getOrElse(() -> getQuizzes(ids));
}
+ @Override
+ public void clearCache() {
+ this.openEdxCourseAccess.clearCache();
+ }
+
@Override
public Result getCourseChapters(final String courseId) {
return Result.tryCatch(() -> this.openEdxCourseAccess
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java
index 1ad00b7b..a3609f16 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java
@@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@@ -36,6 +37,7 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
private final WebserviceInfo webserviceInfo;
private final AsyncService asyncService;
private final Environment environment;
+ private final CacheManager cacheManager;
private final ClientCredentialService clientCredentialService;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final String[] alternativeTokenRequestPaths;
@@ -46,6 +48,7 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
final WebserviceInfo webserviceInfo,
final AsyncService asyncService,
final Environment environment,
+ final CacheManager cacheManager,
final ClientCredentialService clientCredentialService,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
@Value("${sebserver.webservice.lms.openedx.api.token.request.paths}") final String alternativeTokenRequestPaths,
@@ -55,6 +58,7 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
this.webserviceInfo = webserviceInfo;
this.asyncService = asyncService;
this.environment = environment;
+ this.cacheManager = cacheManager;
this.clientCredentialService = clientCredentialService;
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
@@ -84,7 +88,8 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
openEdxRestTemplateFactory,
this.webserviceInfo,
this.asyncService,
- this.environment);
+ this.environment,
+ this.cacheManager);
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
this.jsonMapper,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
index cb1d22c9..90f014c8 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
@@ -9,7 +9,9 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.mockup;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -181,6 +183,16 @@ public class MockupLmsAPITemplate implements LmsAPITemplate {
return getQuizzes(ids);
}
+ @Override
+ public Result getQuizFromCache(final String id) {
+ return getQuizzes(new HashSet<>(Arrays.asList(id))).iterator().next();
+ }
+
+ @Override
+ public void clearCache() {
+
+ }
+
@Override
public Result getCourseChapters(final String courseId) {
return Result.ofError(new UnsupportedOperationException());
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
index 7d898892..4a3d8fc8 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
@@ -14,6 +14,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -37,6 +38,7 @@ import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
+import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker.State;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@@ -48,6 +50,7 @@ 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.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.CourseQuizShort;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
@@ -90,7 +93,6 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
private final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader;
private final CircuitBreaker> allQuizzesRequest;
- private final AllQuizzesSupplier allQuizzesSupplier;
private MoodleAPIRestTemplate restTemplate;
@@ -119,19 +121,6 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
"sebserver.webservice.circuitbreaker.allQuizzesRequest.timeToRecover",
Long.class,
Constants.MINUTE_IN_MILLIS));
-
- this.allQuizzesSupplier = new AllQuizzesSupplier() {
-
- @Override
- public List getAllCached() {
- return getCached();
- }
-
- @Override
- public Result> getAll(final FilterMap filterMap) {
- return MoodleCourseAccess.this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap));
- }
- };
}
APITemplateDataSupplier getApiTemplateDataSupplier() {
@@ -223,6 +212,77 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
return LmsSetupTestResult.ofOkay();
}
+ @Override
+ public Result getQuizFromCache(final String id) {
+ return Result.tryCatch(() -> {
+
+ final Map cachedCourseData = this.moodleCourseDataAsyncLoader
+ .getCachedCourseData();
+
+ final String courseId = getCourseId(id);
+ final String quizId = getQuizId(id);
+ if (cachedCourseData.containsKey(courseId)) {
+ final CourseDataShort courseData = cachedCourseData.get(courseId);
+ final CourseQuizShort quiz = courseData.quizzes
+ .stream()
+ .filter(q -> q.id.equals(quizId))
+ .findFirst()
+ .orElse(null);
+
+ if (quiz != null) {
+ final Map additionalAttrs = new HashMap<>();
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_CREATION_TIME,
+ String.valueOf(courseData.time_created));
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_SHORT_NAME, courseData.short_name);
+ additionalAttrs.put(QuizData.ATTR_ADDITIONAL_ID_NUMBER, courseData.idnumber);
+ final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
+ 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;
+
+ return createQuizData(lmsSetup, courseData, urlPrefix, additionalAttrs, quiz);
+ }
+ }
+
+ throw new RuntimeException("No quiz found in cache");
+ });
+ }
+
+ @Override
+ public Result>> getQuizzesFromCache(final Set ids) {
+ return Result.tryCatch(() -> {
+ final List cached = getCached();
+ final List available = (cached != null)
+ ? cached
+ : Collections.emptyList();
+
+ final Map quizMapping = available
+ .stream()
+ .collect(Collectors.toMap(q -> q.id, Function.identity()));
+
+ if (!quizMapping.keySet().containsAll(ids)) {
+
+ final Map collect = quizzesSupplier(ids).get()
+ .stream()
+ .collect(Collectors.toMap(qd -> qd.id, Function.identity()));
+ if (collect != null) {
+ quizMapping.clear();
+ quizMapping.putAll(collect);
+ }
+ }
+
+ return ids
+ .stream()
+ .map(id -> {
+ final QuizData q = quizMapping.get(id);
+ return (q == null)
+ ? Result. ofError(new NoSuchElementException("Quiz with id: " + id))
+ : Result.of(q);
+ })
+ .collect(Collectors.toList());
+ });
+ }
+
@Override
protected Supplier> quizzesSupplier(final Set ids) {
return () -> getRestTemplate()
@@ -231,6 +291,7 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
}
+ @Override
protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
return () -> getRestTemplate()
.map(template -> collectAllQuizzes(template, filterMap))
@@ -244,6 +305,9 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
@Override
protected FetchStatus getFetchStatus() {
+ if (this.allQuizzesRequest.getState() != State.CLOSED) {
+ return FetchStatus.FETCH_ERROR;
+ }
if (this.moodleCourseDataAsyncLoader.isRunning()) {
return FetchStatus.ASYNC_FETCH_RUNNING;
}
@@ -251,9 +315,8 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
return FetchStatus.ALL_FETCHED;
}
- @Override
- protected AllQuizzesSupplier allQuizzesSupplier() {
- return this.allQuizzesSupplier;
+ public void clearCache() {
+ this.moodleCourseDataAsyncLoader.clearCache();
}
private List collectAllQuizzes(
@@ -261,7 +324,6 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
final FilterMap filterMap) {
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
-
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;
@@ -272,7 +334,7 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
// Verify and call the proper strategy to get the course and quiz data
Collection courseQuizData = Collections.emptyList();
if (this.moodleCourseDataAsyncLoader.isRunning()) {
- courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
+ courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData().values();
} else if (this.moodleCourseDataAsyncLoader.getLastRunTime() <= 0) {
// set cut time if available
if (fromCutTime >= 0) {
@@ -282,7 +344,7 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
this.moodleCourseDataAsyncLoader.loadAsync(restTemplate);
try {
Thread.sleep(INITIAL_WAIT_TIME);
- courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
+ courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData().values();
} catch (final Exception e) {
log.error("Failed to wait for first load run: ", e);
return Collections.emptyList();
@@ -290,7 +352,7 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
} else if (this.moodleCourseDataAsyncLoader.isLongRunningTask()) {
// on long running tasks if we have a different fromCutTime as before
// kick off the lazy loading task immediately with the new time filter
- courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
+ courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData().values();
if (fromCutTime > 0 && fromCutTime != this.moodleCourseDataAsyncLoader.getFromCutTime()) {
this.moodleCourseDataAsyncLoader.setFromCutTime(fromCutTime);
this.moodleCourseDataAsyncLoader.loadAsync(restTemplate);
@@ -306,7 +368,7 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
this.moodleCourseDataAsyncLoader.setFromCutTime(fromCutTime);
}
this.moodleCourseDataAsyncLoader.loadSync(restTemplate);
- courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
+ courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData().values();
}
if (courseQuizData.isEmpty()) {
@@ -317,7 +379,8 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
}
private List getCached() {
- final Collection courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
+ final Collection courseQuizData =
+ this.moodleCourseDataAsyncLoader.getCachedCourseData().values();
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
final String urlPrefix = (lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
? lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
@@ -534,34 +597,41 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
final List courseAndQuiz = courseData.quizzes
.stream()
- .map(courseQuizData -> {
- final String startURI = uriPrefix + courseQuizData.course_module;
- //additionalAttrs.put(QuizData.ATTR_ADDITIONAL_TIME_LIMIT, String.valueOf(courseQuizData.time_limit));
- return new QuizData(
- getInternalQuizId(
- courseQuizData.course_module,
- courseData.id,
- courseData.short_name,
- courseData.idnumber),
- lmsSetup.getInstitutionId(),
- lmsSetup.id,
- lmsSetup.getLmsType(),
- courseQuizData.name,
- Constants.EMPTY_NOTE,
- (courseQuizData.time_open != null && courseQuizData.time_open > 0)
- ? Utils.toDateTimeUTCUnix(courseQuizData.time_open)
- : Utils.toDateTimeUTCUnix(courseData.start_date),
- (courseQuizData.time_close != null && courseQuizData.time_close > 0)
- ? Utils.toDateTimeUTCUnix(courseQuizData.time_close)
- : Utils.toDateTimeUTCUnix(courseData.end_date),
- startURI,
- additionalAttrs);
- })
+ .map(courseQuizData -> createQuizData(lmsSetup, courseData, uriPrefix, additionalAttrs, courseQuizData))
.collect(Collectors.toList());
return courseAndQuiz;
}
+ private QuizData createQuizData(
+ final LmsSetup lmsSetup,
+ final CourseDataShort courseData,
+ final String uriPrefix,
+ final Map additionalAttrs,
+ final CourseQuizShort courseQuizData) {
+
+ final String startURI = uriPrefix + courseQuizData.course_module;
+ return new QuizData(
+ getInternalQuizId(
+ courseQuizData.course_module,
+ courseData.id,
+ courseData.short_name,
+ courseData.idnumber),
+ lmsSetup.getInstitutionId(),
+ lmsSetup.id,
+ lmsSetup.getLmsType(),
+ courseQuizData.name,
+ Constants.EMPTY_NOTE,
+ (courseQuizData.time_open != null && courseQuizData.time_open > 0)
+ ? Utils.toDateTimeUTCUnix(courseQuizData.time_open)
+ : Utils.toDateTimeUTCUnix(courseData.start_date),
+ (courseQuizData.time_close != null && courseQuizData.time_close > 0)
+ ? Utils.toDateTimeUTCUnix(courseQuizData.time_close)
+ : Utils.toDateTimeUTCUnix(courseData.end_date),
+ startURI,
+ additionalAttrs);
+ }
+
private Result getRestTemplate() {
if (this.restTemplate == null) {
final Result templateRequest = this.moodleRestTemplateFactory
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java
index ede74cdb..4d3a9730 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java
@@ -65,7 +65,7 @@ public class MoodleCourseDataAsyncLoader {
private final int maxSize;
private final int pageSize;
- private final Set cachedCourseData = new HashSet<>();
+ private final Map cachedCourseData = new HashMap<>();
private final Set newIds = new HashSet<>();
private String lmsSetup = Constants.EMPTY_NOTE;
@@ -122,8 +122,8 @@ public class MoodleCourseDataAsyncLoader {
this.fromCutTime = fromCutTime;
}
- public Set getCachedCourseData() {
- return new HashSet<>(this.cachedCourseData);
+ public Map getCachedCourseData() {
+ return new HashMap<>(this.cachedCourseData);
}
public long getLastRunTime() {
@@ -138,7 +138,7 @@ public class MoodleCourseDataAsyncLoader {
return this.lastLoadTime > 3 * Constants.SECOND_IN_MILLIS;
}
- public Set loadSync(final MoodleAPIRestTemplate restTemplate) {
+ public Map loadSync(final MoodleAPIRestTemplate restTemplate) {
if (this.running) {
throw new IllegalStateException("Is already running asynchronously");
}
@@ -168,14 +168,20 @@ public class MoodleCourseDataAsyncLoader {
}
+ public void clearCache() {
+ if (!isRunning()) {
+ this.cachedCourseData.clear();
+ this.newIds.clear();
+ }
+ }
+
private Runnable loadAndCache(final MoodleAPIRestTemplate restTemplate) {
return () -> {
- //this.cachedCourseData.clear();
this.newIds.clear();
final long startTime = Utils.getMillisecondsNow();
loadAllQuizzes(restTemplate);
- syncCache();
+ this.syncCache();
this.lastLoadTime = Utils.getMillisecondsNow() - startTime;
this.running = false;
@@ -263,7 +269,7 @@ public class MoodleCourseDataAsyncLoader {
this.lmsSetup,
c);
} else {
- this.cachedCourseData.add(c);
+ this.cachedCourseData.put(c.id, c);
this.newIds.add(c.id);
}
});
@@ -461,13 +467,15 @@ public class MoodleCourseDataAsyncLoader {
private void syncCache() {
if (!this.cachedCourseData.isEmpty()) {
- final Set newData = this.cachedCourseData.stream()
- .filter(data -> this.newIds.contains(data.id))
+
+ final Set oldData = this.cachedCourseData
+ .keySet()
+ .stream()
+ .filter(id -> !this.newIds.contains(id))
.collect(Collectors.toSet());
synchronized (this.cachedCourseData) {
- this.cachedCourseData.clear();
- this.cachedCourseData.addAll(newData);
+ oldData.stream().forEach(this.cachedCourseData::remove);
}
}
this.newIds.clear();
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java
index 18560d03..f4079e82 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java
@@ -110,6 +110,17 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
.collect(Collectors.toList());
}
+ @Override
+ public Result getQuizFromCache(final String id) {
+ return this.moodleCourseAccess.getQuizFromCache(id)
+ .orElse(() -> getQuiz(id));
+ }
+
+ @Override
+ public void clearCache() {
+ this.moodleCourseAccess.clearCache();
+ }
+
@Override
public Collection> getQuizzesFromCache(final Set ids) {
return this.moodleCourseAccess.getQuizzesFromCache(ids)
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index c65af50f..1d01a7df 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -123,7 +123,8 @@ public class ExamSessionServiceImpl implements ExamSessionService {
return Result.tryCatch(() -> {
final Collection result = new ArrayList<>();
- final Exam exam = this.examDAO.byPK(examId)
+ final Exam exam = this.examDAO
+ .getWithQuizDataFromCache(examId)
.getOrThrow();
// check lms connection
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java
index 9da295d5..6efd89c4 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java
@@ -279,7 +279,8 @@ public class ExamAdministrationController extends EntityController {
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
checkReadPrivilege(institutionId);
- return this.examDAO.byPK(modelId)
+ return this.examDAO
+ .getWithQuizDataFromCache(modelId)
.flatMap(this.examAdminService::isRestricted)
.getOrThrow();
}
@@ -407,9 +408,7 @@ public class ExamAdministrationController extends EntityController {
@PathVariable final Long modelId) {
checkReadPrivilege(institutionId);
- return this.entityDAO.byPK(modelId)
- .flatMap(this.authorization::checkRead)
- .flatMap(exam -> this.examAdminService.getProctoringServiceSettings(exam.id))
+ return this.examAdminService.getProctoringServiceSettings(modelId)
.getOrThrow();
}
diff --git a/src/main/resources/config/ehcache.xml b/src/main/resources/config/ehcache.xml
index 4d6312cd..b212c3e2 100644
--- a/src/main/resources/config/ehcache.xml
+++ b/src/main/resources/config/ehcache.xml
@@ -23,7 +23,7 @@
24
- 2000
+ 3000
@@ -93,7 +93,16 @@
-
+
+ java.lang.String
+ ch.ethz.seb.sebserver.gbl.model.exam.QuizData
+
+ 10
+
+
+ 10000
+
+
\ No newline at end of file