Added EHcache for caching and improved Moodle asnyc loading
This commit is contained in:
parent
5f30aa9c2e
commit
eec4392f78
20 changed files with 685 additions and 218 deletions
14
pom.xml
14
pom.xml
|
@ -290,11 +290,15 @@
|
||||||
<artifactId>flyway-core</artifactId>
|
<artifactId>flyway-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JMX -->
|
<!-- EHCache -->
|
||||||
<!-- <dependency> -->
|
<dependency>
|
||||||
<!-- <groupId>org.jolokia</groupId> -->
|
<groupId>org.ehcache</groupId>
|
||||||
<!-- <artifactId>jolokia-core</artifactId> -->
|
<artifactId>ehcache</artifactId>
|
||||||
<!-- </dependency> -->
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.cache</groupId>
|
||||||
|
<artifactId>cache-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache HTTP -->
|
<!-- Apache HTTP -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -15,7 +15,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
|
||||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
@ -40,7 +39,6 @@ import ch.ethz.seb.sebserver.gbl.profile.ProdWebServiceProfile;
|
||||||
@SpringBootApplication(exclude = {
|
@SpringBootApplication(exclude = {
|
||||||
UserDetailsServiceAutoConfiguration.class,
|
UserDetailsServiceAutoConfiguration.class,
|
||||||
})
|
})
|
||||||
@EnableCaching
|
|
||||||
public class SEBServer {
|
public class SEBServer {
|
||||||
|
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamImported;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamImported;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizPage;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizPage;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||||
|
@ -297,7 +298,11 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
final QuizData quizData,
|
final QuizData quizData,
|
||||||
final Function<String, String> institutionNameFunction) {
|
final Function<String, String> institutionNameFunction) {
|
||||||
|
|
||||||
action.getSingleSelection();
|
final QuizData fullQuizData = this.pageService.getRestService().getBuilder(GetQuizData.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, quizData.getModelId())
|
||||||
|
.withQueryParam(QuizData.QUIZ_ATTR_LMS_SETUP_ID, String.valueOf(quizData.lmsSetupId))
|
||||||
|
.call()
|
||||||
|
.getOr(quizData);
|
||||||
|
|
||||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<Void>(
|
final ModalInputDialog<Void> dialog = new ModalInputDialog<Void>(
|
||||||
action.pageContext().getParent().getShell(),
|
action.pageContext().getParent().getShell(),
|
||||||
|
@ -307,7 +312,7 @@ public class QuizLookupList implements TemplateComposer {
|
||||||
dialog.open(
|
dialog.open(
|
||||||
DETAILS_TITLE_TEXT_KEY,
|
DETAILS_TITLE_TEXT_KEY,
|
||||||
action.pageContext(),
|
action.pageContext(),
|
||||||
pc -> createDetailsForm(quizData, pc, institutionNameFunction));
|
pc -> createDetailsForm(fullQuizData, pc, institutionNameFunction));
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.cache.Caching;
|
||||||
|
import javax.cache.spi.CachingProvider;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||||
|
import org.springframework.cache.jcache.JCacheCacheManager;
|
||||||
|
import org.springframework.cache.jcache.config.JCacheConfigurerSupport;
|
||||||
|
import org.springframework.cache.support.CompositeCacheManager;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
|
||||||
|
@EnableCaching
|
||||||
|
@WebServiceProfile
|
||||||
|
@Configuration
|
||||||
|
public class CacheConfig extends JCacheConfigurerSupport {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CacheConfig.class);
|
||||||
|
|
||||||
|
@Value("${spring.cache.jcache.config}")
|
||||||
|
String jCacheConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Bean
|
||||||
|
public CacheManager cacheManager() {
|
||||||
|
try {
|
||||||
|
final CachingProvider cachingProvider = Caching.getCachingProvider();
|
||||||
|
final javax.cache.CacheManager cacheManager =
|
||||||
|
cachingProvider.getCacheManager(new URI(this.jCacheConfig), this.getClass().getClassLoader());
|
||||||
|
System.out.println("cacheManager:" + cacheManager);
|
||||||
|
|
||||||
|
final CompositeCacheManager composite = new CompositeCacheManager();
|
||||||
|
composite.setCacheManagers(Arrays.asList(
|
||||||
|
new JCacheCacheManager(cacheManager),
|
||||||
|
new ConcurrentMapCacheManager()));
|
||||||
|
composite.setFallbackToNoOpCache(true);
|
||||||
|
|
||||||
|
return composite;
|
||||||
|
|
||||||
|
} catch (final URISyntaxException e) {
|
||||||
|
log.error("Failed to initialize caching with EHCache. Fallback to simple caching");
|
||||||
|
return new ConcurrentMapCacheManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,55 +20,81 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
||||||
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.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
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;
|
||||||
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.LmsAPIService;
|
|
||||||
|
|
||||||
public abstract class CourseAccess {
|
public abstract class CourseAccess {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(CourseAccess.class);
|
private static final Logger log = LoggerFactory.getLogger(CourseAccess.class);
|
||||||
|
|
||||||
protected final MemoizingCircuitBreaker<List<QuizData>> allQuizzesRequest;
|
public enum FetchStatus {
|
||||||
|
ALL_FETCHED,
|
||||||
|
ASYNC_FETCH_RUNNING,
|
||||||
|
FETCH_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
protected final CircuitBreaker<List<QuizData>> quizzesRequest;
|
protected final CircuitBreaker<List<QuizData>> quizzesRequest;
|
||||||
protected final CircuitBreaker<Chapters> chaptersRequest;
|
protected final CircuitBreaker<Chapters> chaptersRequest;
|
||||||
protected final CircuitBreaker<ExamineeAccountDetails> accountDetailRequest;
|
protected final CircuitBreaker<ExamineeAccountDetails> accountDetailRequest;
|
||||||
|
|
||||||
protected CourseAccess(final AsyncService asyncService) {
|
protected CourseAccess(
|
||||||
this.allQuizzesRequest = asyncService.createMemoizingCircuitBreaker(
|
final AsyncService asyncService,
|
||||||
allQuizzesSupplier(null),
|
final Environment environment) {
|
||||||
3,
|
|
||||||
Constants.MINUTE_IN_MILLIS,
|
|
||||||
Constants.MINUTE_IN_MILLIS,
|
|
||||||
true,
|
|
||||||
Constants.HOUR_IN_MILLIS);
|
|
||||||
|
|
||||||
this.quizzesRequest = asyncService.createCircuitBreaker(
|
this.quizzesRequest = asyncService.createCircuitBreaker(
|
||||||
3,
|
environment.getProperty(
|
||||||
Constants.MINUTE_IN_MILLIS,
|
"sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
|
||||||
Constants.MINUTE_IN_MILLIS);
|
Integer.class,
|
||||||
|
3),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
|
||||||
|
Long.class,
|
||||||
|
Constants.MINUTE_IN_MILLIS),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.quizzesRequest.timeToRecover",
|
||||||
|
Long.class,
|
||||||
|
Constants.MINUTE_IN_MILLIS));
|
||||||
|
|
||||||
this.chaptersRequest = asyncService.createCircuitBreaker(
|
this.chaptersRequest = asyncService.createCircuitBreaker(
|
||||||
3,
|
environment.getProperty(
|
||||||
Constants.SECOND_IN_MILLIS * 10,
|
"sebserver.webservice.circuitbreaker.chaptersRequest.attempts",
|
||||||
Constants.MINUTE_IN_MILLIS);
|
Integer.class,
|
||||||
|
3),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.chaptersRequest.blockingTime",
|
||||||
|
Long.class,
|
||||||
|
Constants.SECOND_IN_MILLIS * 10),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.chaptersRequest.timeToRecover",
|
||||||
|
Long.class,
|
||||||
|
Constants.MINUTE_IN_MILLIS));
|
||||||
|
|
||||||
this.accountDetailRequest = asyncService.createCircuitBreaker(
|
this.accountDetailRequest = asyncService.createCircuitBreaker(
|
||||||
1,
|
environment.getProperty(
|
||||||
Constants.SECOND_IN_MILLIS * 10,
|
"sebserver.webservice.circuitbreaker.accountDetailRequest.attempts",
|
||||||
Constants.SECOND_IN_MILLIS * 10);
|
Integer.class,
|
||||||
|
2),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.accountDetailRequest.blockingTime",
|
||||||
|
Long.class,
|
||||||
|
Constants.SECOND_IN_MILLIS * 10),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.accountDetailRequest.timeToRecover",
|
||||||
|
Long.class,
|
||||||
|
Constants.SECOND_IN_MILLIS * 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<Collection<Result<QuizData>>> getQuizzesFromCache(final Set<String> ids) {
|
public Result<Collection<Result<QuizData>>> getQuizzesFromCache(final Set<String> ids) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final List<QuizData> cached = this.allQuizzesRequest.getCached();
|
final List<QuizData> cached = allQuizzesSupplier().getAllCached();
|
||||||
final List<QuizData> available = (cached != null)
|
final List<QuizData> available = (cached != null)
|
||||||
? cached
|
? cached
|
||||||
: quizzesSupplier(ids).get();
|
: quizzesSupplier(ids).get();
|
||||||
|
@ -101,11 +127,7 @@ public abstract class CourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<List<QuizData>> getQuizzes(final FilterMap filterMap) {
|
public Result<List<QuizData>> getQuizzes(final FilterMap filterMap) {
|
||||||
if (filterMap != null) {
|
return allQuizzesSupplier().getAll(filterMap);
|
||||||
this.allQuizzesRequest.setSupplier(allQuizzesSupplier(filterMap));
|
|
||||||
}
|
|
||||||
return this.allQuizzesRequest.get()
|
|
||||||
.map(LmsAPIService.quizzesFilterFunction(filterMap));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) {
|
||||||
|
@ -139,8 +161,16 @@ public abstract class CourseAccess {
|
||||||
|
|
||||||
protected abstract Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids);
|
protected abstract Supplier<List<QuizData>> quizzesSupplier(final Set<String> ids);
|
||||||
|
|
||||||
protected abstract Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap);
|
protected abstract AllQuizzesSupplier allQuizzesSupplier();
|
||||||
|
|
||||||
protected abstract Supplier<Chapters> getCourseChaptersSupplier(final String courseId);
|
protected abstract Supplier<Chapters> getCourseChaptersSupplier(final String courseId);
|
||||||
|
|
||||||
|
protected abstract FetchStatus getFetchStatus();
|
||||||
|
|
||||||
|
protected interface AllQuizzesSupplier {
|
||||||
|
List<QuizData> getAllCached();
|
||||||
|
|
||||||
|
Result<List<QuizData>> getAll(final FilterMap filterMap);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.util.stream.Collectors;
|
||||||
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;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
@ -41,6 +42,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
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.Chapters;
|
||||||
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;
|
||||||
|
@ -70,6 +73,8 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
private final LmsSetup lmsSetup;
|
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 AllQuizzesSupplier allQuizzesSupplier;
|
||||||
|
|
||||||
private OAuth2RestTemplate restTemplate;
|
private OAuth2RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@ -78,13 +83,51 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
final LmsSetup lmsSetup,
|
final LmsSetup lmsSetup,
|
||||||
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
|
||||||
final WebserviceInfo webserviceInfo,
|
final WebserviceInfo webserviceInfo,
|
||||||
final AsyncService asyncService) {
|
final AsyncService asyncService,
|
||||||
|
final Environment environment) {
|
||||||
|
|
||||||
super(asyncService);
|
super(asyncService, environment);
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.lmsSetup = lmsSetup;
|
this.lmsSetup = lmsSetup;
|
||||||
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
|
||||||
this.webserviceInfo = webserviceInfo;
|
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<QuizData> getAllCached() {
|
||||||
|
return OpenEdxCourseAccess.this.allQuizzesRequest.getCached();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<List<QuizData>> getAll(final FilterMap filterMap) {
|
||||||
|
return OpenEdxCourseAccess.this.allQuizzesRequest.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
LmsSetupTestResult initAPIAccess() {
|
LmsSetupTestResult initAPIAccess() {
|
||||||
|
@ -173,8 +216,7 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Supplier<List<QuizData>> quizzesSupplier() {
|
||||||
protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) {
|
|
||||||
return () -> getRestTemplate()
|
return () -> getRestTemplate()
|
||||||
.map(this::collectAllQuizzes)
|
.map(this::collectAllQuizzes)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
@ -451,4 +493,17 @@ final class OpenEdxCourseAccess extends CourseAccess {
|
||||||
return Result.of(this.restTemplate);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.edx;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||||
|
@ -33,6 +34,7 @@ public class OpenEdxLmsAPITemplateFactory {
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final WebserviceInfo webserviceInfo;
|
private final WebserviceInfo webserviceInfo;
|
||||||
private final AsyncService asyncService;
|
private final AsyncService asyncService;
|
||||||
|
private final Environment environment;
|
||||||
private final ClientCredentialService clientCredentialService;
|
private final ClientCredentialService clientCredentialService;
|
||||||
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||||
private final String[] alternativeTokenRequestPaths;
|
private final String[] alternativeTokenRequestPaths;
|
||||||
|
@ -42,6 +44,7 @@ public class OpenEdxLmsAPITemplateFactory {
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final WebserviceInfo webserviceInfo,
|
final WebserviceInfo webserviceInfo,
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
|
final Environment environment,
|
||||||
final ClientCredentialService clientCredentialService,
|
final ClientCredentialService clientCredentialService,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
@Value("${sebserver.webservice.lms.openedx.api.token.request.paths}") final String alternativeTokenRequestPaths,
|
@Value("${sebserver.webservice.lms.openedx.api.token.request.paths}") final String alternativeTokenRequestPaths,
|
||||||
|
@ -50,6 +53,7 @@ public class OpenEdxLmsAPITemplateFactory {
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.webserviceInfo = webserviceInfo;
|
this.webserviceInfo = webserviceInfo;
|
||||||
this.asyncService = asyncService;
|
this.asyncService = asyncService;
|
||||||
|
this.environment = environment;
|
||||||
this.clientCredentialService = clientCredentialService;
|
this.clientCredentialService = clientCredentialService;
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
|
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
|
||||||
|
@ -78,7 +82,8 @@ public class OpenEdxLmsAPITemplateFactory {
|
||||||
lmsSetup,
|
lmsSetup,
|
||||||
openEdxRestTemplateFactory,
|
openEdxRestTemplateFactory,
|
||||||
this.webserviceInfo,
|
this.webserviceInfo,
|
||||||
this.asyncService);
|
this.asyncService,
|
||||||
|
this.environment);
|
||||||
|
|
||||||
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
|
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
|
||||||
lmsSetup,
|
lmsSetup,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
||||||
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.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;
|
||||||
|
@ -43,6 +45,7 @@ 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.impl.CourseAccess;
|
||||||
|
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;
|
||||||
|
|
||||||
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
|
||||||
|
@ -73,7 +76,9 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
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 final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader;
|
||||||
|
private final CircuitBreaker<List<QuizData>> allQuizzesRequest;
|
||||||
|
private final AllQuizzesSupplier allQuizzesSupplier;
|
||||||
|
|
||||||
private MoodleAPIRestTemplate restTemplate;
|
private MoodleAPIRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@ -81,14 +86,42 @@ 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 MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader,
|
||||||
final AsyncService asyncService) {
|
final AsyncService asyncService,
|
||||||
|
final Environment environment) {
|
||||||
|
|
||||||
super(asyncService);
|
super(asyncService, environment);
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.lmsSetup = lmsSetup;
|
this.lmsSetup = lmsSetup;
|
||||||
this.moodleCourseDataLazyLoader = moodleCourseDataLazyLoader;
|
this.moodleCourseDataAsyncLoader = moodleCourseDataAsyncLoader;
|
||||||
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
|
||||||
|
|
||||||
|
this.allQuizzesRequest = asyncService.createCircuitBreaker(
|
||||||
|
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));
|
||||||
|
|
||||||
|
this.allQuizzesSupplier = new AllQuizzesSupplier() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<QuizData> getAllCached() {
|
||||||
|
return getCached();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<List<QuizData>> getAll(final FilterMap filterMap) {
|
||||||
|
return MoodleCourseAccess.this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,7 +208,6 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) {
|
protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) {
|
||||||
return () -> getRestTemplate()
|
return () -> getRestTemplate()
|
||||||
.map(template -> collectAllQuizzes(template, filterMap))
|
.map(template -> collectAllQuizzes(template, filterMap))
|
||||||
|
@ -187,6 +219,20 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
throw new UnsupportedOperationException("not available yet");
|
throw new UnsupportedOperationException("not available yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FetchStatus getFetchStatus() {
|
||||||
|
if (this.moodleCourseDataAsyncLoader.isRunning()) {
|
||||||
|
return FetchStatus.ASYNC_FETCH_RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FetchStatus.ALL_FETCHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AllQuizzesSupplier allQuizzesSupplier() {
|
||||||
|
return this.allQuizzesSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
private List<QuizData> collectAllQuizzes(
|
private List<QuizData> collectAllQuizzes(
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
final FilterMap filterMap) {
|
final FilterMap filterMap) {
|
||||||
|
@ -198,42 +244,42 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
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;
|
||||||
|
|
||||||
Collection<CourseData> courseQuizData = Collections.emptyList();
|
Collection<CourseDataShort> courseQuizData = Collections.emptyList();
|
||||||
if (this.moodleCourseDataLazyLoader.isRunning()) {
|
if (this.moodleCourseDataAsyncLoader.isRunning()) {
|
||||||
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||||
} else if (this.moodleCourseDataLazyLoader.getLastRunTime() <= 0) {
|
} else if (this.moodleCourseDataAsyncLoader.getLastRunTime() <= 0) {
|
||||||
// set cut time if available
|
// set cut time if available
|
||||||
if (fromCutTime >= 0) {
|
if (fromCutTime >= 0) {
|
||||||
this.moodleCourseDataLazyLoader.setFromCutTime(fromCutTime);
|
this.moodleCourseDataAsyncLoader.setFromCutTime(fromCutTime);
|
||||||
}
|
}
|
||||||
// first run async and wait some time, get what is there
|
// first run async and wait some time, get what is there
|
||||||
this.moodleCourseDataLazyLoader.loadAsync(restTemplate);
|
this.moodleCourseDataAsyncLoader.loadAsync(restTemplate);
|
||||||
try {
|
try {
|
||||||
Thread.sleep(INITIAL_WAIT_TIME);
|
Thread.sleep(INITIAL_WAIT_TIME);
|
||||||
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to wait for first load run: ", e);
|
log.error("Failed to wait for first load run: ", e);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
} else if (this.moodleCourseDataLazyLoader.isLongRunningTask()) {
|
} else if (this.moodleCourseDataAsyncLoader.isLongRunningTask()) {
|
||||||
// on long running tasks if we have a different fromCutTime as before
|
// on long running tasks if we have a different fromCutTime as before
|
||||||
// kick off the lazy loading task immediately with the new time filter
|
// kick off the lazy loading task immediately with the new time filter
|
||||||
if (fromCutTime > 0 && fromCutTime != this.moodleCourseDataLazyLoader.getFromCutTime()) {
|
if (fromCutTime > 0 && fromCutTime != this.moodleCourseDataAsyncLoader.getFromCutTime()) {
|
||||||
this.moodleCourseDataLazyLoader.setFromCutTime(fromCutTime);
|
this.moodleCourseDataAsyncLoader.setFromCutTime(fromCutTime);
|
||||||
this.moodleCourseDataLazyLoader.loadAsync(restTemplate);
|
this.moodleCourseDataAsyncLoader.loadAsync(restTemplate);
|
||||||
// otherwise kick off only if the last fetch task was then minutes ago
|
// otherwise kick off only if the last fetch task was then minutes ago
|
||||||
} else if (Utils.getMillisecondsNow() - this.moodleCourseDataLazyLoader.getLastRunTime() > 10
|
} else if (Utils.getMillisecondsNow() - this.moodleCourseDataAsyncLoader.getLastRunTime() > 10
|
||||||
* Constants.MINUTE_IN_MILLIS) {
|
* Constants.MINUTE_IN_MILLIS) {
|
||||||
this.moodleCourseDataLazyLoader.loadAsync(restTemplate);
|
this.moodleCourseDataAsyncLoader.loadAsync(restTemplate);
|
||||||
}
|
}
|
||||||
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||||
} else {
|
} else {
|
||||||
// just run the task in sync
|
// just run the task in sync
|
||||||
if (fromCutTime >= 0) {
|
if (fromCutTime >= 0) {
|
||||||
this.moodleCourseDataLazyLoader.setFromCutTime(fromCutTime);
|
this.moodleCourseDataAsyncLoader.setFromCutTime(fromCutTime);
|
||||||
}
|
}
|
||||||
this.moodleCourseDataLazyLoader.loadSync(restTemplate);
|
this.moodleCourseDataAsyncLoader.loadSync(restTemplate);
|
||||||
courseQuizData = this.moodleCourseDataLazyLoader.getPreFilteredCourseIds();
|
courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (courseQuizData.isEmpty()) {
|
if (courseQuizData.isEmpty()) {
|
||||||
|
@ -257,6 +303,29 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<QuizData> getCached() {
|
||||||
|
final Collection<CourseDataShort> courseQuizData = this.moodleCourseDataAsyncLoader.getCachedCourseData();
|
||||||
|
|
||||||
|
final String urlPrefix = (this.lmsSetup.lmsApiUrl.endsWith(Constants.URL_PATH_SEPARATOR))
|
||||||
|
? this.lmsSetup.lmsApiUrl + MOODLE_QUIZ_START_URL_PATH
|
||||||
|
: this.lmsSetup.lmsApiUrl + Constants.URL_PATH_SEPARATOR + MOODLE_QUIZ_START_URL_PATH;
|
||||||
|
return courseQuizData
|
||||||
|
.stream()
|
||||||
|
.reduce(
|
||||||
|
new ArrayList<>(),
|
||||||
|
(list, courseData) -> {
|
||||||
|
list.addAll(quizDataOf(
|
||||||
|
this.lmsSetup,
|
||||||
|
courseData,
|
||||||
|
urlPrefix));
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
(list1, list2) -> {
|
||||||
|
list1.addAll(list2);
|
||||||
|
return list1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private List<QuizData> getQuizzesForIds(
|
private List<QuizData> getQuizzesForIds(
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
final Set<String> quizIds) {
|
final Set<String> quizIds) {
|
||||||
|
@ -405,6 +474,46 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
return courseAndQuiz;
|
return courseAndQuiz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<QuizData> quizDataOf(
|
||||||
|
final LmsSetup lmsSetup,
|
||||||
|
final CourseDataShort courseData,
|
||||||
|
final String uriPrefix) {
|
||||||
|
|
||||||
|
additionalAttrs.clear();
|
||||||
|
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 List<QuizData> 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);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return courseAndQuiz;
|
||||||
|
}
|
||||||
|
|
||||||
private Result<MoodleAPIRestTemplate> getRestTemplate() {
|
private Result<MoodleAPIRestTemplate> getRestTemplate() {
|
||||||
if (this.restTemplate == null) {
|
if (this.restTemplate == null) {
|
||||||
final Result<MoodleAPIRestTemplate> templateRequest = this.moodleRestTemplateFactory
|
final Result<MoodleAPIRestTemplate> templateRequest = this.moodleRestTemplateFactory
|
||||||
|
@ -482,7 +591,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
|
|
||||||
/** Maps the Moodle course API course data */
|
/** Maps the Moodle course API course data */
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static final class CourseData {
|
private static final class CourseData {
|
||||||
final String id;
|
final String id;
|
||||||
final String short_name;
|
final String short_name;
|
||||||
final String idnumber;
|
final String idnumber;
|
||||||
|
@ -516,35 +625,10 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
this.end_date = end_date;
|
this.end_date = end_date;
|
||||||
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)
|
||||||
static final class Courses {
|
private static final class Courses {
|
||||||
final Collection<CourseData> courses;
|
final Collection<CourseData> courses;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
|
@ -555,7 +639,7 @@ public class MoodleCourseAccess extends CourseAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static final class CourseQuizData {
|
private static final class CourseQuizData {
|
||||||
final Collection<CourseQuiz> quizzes;
|
final Collection<CourseQuiz> quizzes;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
|
|
|
@ -28,8 +28,10 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
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;
|
||||||
|
@ -40,40 +42,72 @@ 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;
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncRunner;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncRunner;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
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.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;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||||
public class MoodleCourseDataLazyLoader {
|
public class MoodleCourseDataAsyncLoader {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(MoodleCourseDataLazyLoader.class);
|
private static final Logger log = LoggerFactory.getLogger(MoodleCourseDataAsyncLoader.class);
|
||||||
|
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final AsyncRunner asyncRunner;
|
private final AsyncRunner asyncRunner;
|
||||||
|
private final CircuitBreaker<String> moodleRestCall;
|
||||||
|
private final int maxSize;
|
||||||
|
private final int pageSize;
|
||||||
|
|
||||||
private final Set<CourseData> preFilteredCourseIds = new HashSet<>();
|
private final Set<CourseDataShort> cachedCourseData = new HashSet<>();
|
||||||
|
|
||||||
|
private String lmsSetup = Constants.EMPTY_NOTE;
|
||||||
private long lastRunTime = 0;
|
private long lastRunTime = 0;
|
||||||
private long lastLoadTime = 0;
|
private long lastLoadTime = 0;
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
|
|
||||||
private long fromCutTime;
|
private long fromCutTime;
|
||||||
|
|
||||||
public MoodleCourseDataLazyLoader(
|
public MoodleCourseDataAsyncLoader(
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final AsyncRunner asyncRunner) {
|
final AsyncService asyncService,
|
||||||
|
final AsyncRunner asyncRunner,
|
||||||
|
final Environment environment) {
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.asyncRunner = asyncRunner;
|
|
||||||
this.fromCutTime = Utils.toUnixTimeInSeconds(DateTime.now(DateTimeZone.UTC).minusYears(3));
|
this.fromCutTime = Utils.toUnixTimeInSeconds(DateTime.now(DateTimeZone.UTC).minusYears(3));
|
||||||
|
this.asyncRunner = asyncRunner;
|
||||||
|
|
||||||
|
this.moodleRestCall = asyncService.createCircuitBreaker(
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.moodleRestCall.attempts",
|
||||||
|
Integer.class,
|
||||||
|
2),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.moodleRestCall.blockingTime",
|
||||||
|
Long.class,
|
||||||
|
Constants.SECOND_IN_MILLIS * 20),
|
||||||
|
environment.getProperty(
|
||||||
|
"sebserver.webservice.circuitbreaker.moodleRestCall.timeToRecover",
|
||||||
|
Long.class,
|
||||||
|
Constants.MINUTE_IN_MILLIS));
|
||||||
|
|
||||||
|
this.maxSize =
|
||||||
|
environment.getProperty("sebserver.webservice.cache.moodle.course.maxSize", Integer.class, 10000);
|
||||||
|
this.pageSize =
|
||||||
|
environment.getProperty("sebserver.webservice.cache.moodle.course.pageSize", Integer.class, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(final String lmsSetupName) {
|
||||||
|
if (Constants.EMPTY_NOTE.equals(this.lmsSetup)) {
|
||||||
|
this.lmsSetup = lmsSetupName;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Invalid initialization of MoodleCourseDataAsyncLoader. It has already been initialized yet");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getFromCutTime() {
|
public long getFromCutTime() {
|
||||||
|
@ -84,8 +118,8 @@ public class MoodleCourseDataLazyLoader {
|
||||||
this.fromCutTime = fromCutTime;
|
this.fromCutTime = fromCutTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<CourseData> getPreFilteredCourseIds() {
|
public Set<CourseDataShort> getCachedCourseData() {
|
||||||
return this.preFilteredCourseIds;
|
return new HashSet<>(this.cachedCourseData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLastRunTime() {
|
public long getLastRunTime() {
|
||||||
|
@ -100,7 +134,7 @@ public class MoodleCourseDataLazyLoader {
|
||||||
return this.lastLoadTime > 30 * Constants.SECOND_IN_MILLIS;
|
return this.lastLoadTime > 30 * Constants.SECOND_IN_MILLIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<CourseData> loadSync(final MoodleAPIRestTemplate restTemplate) {
|
public Set<CourseDataShort> loadSync(final MoodleAPIRestTemplate restTemplate) {
|
||||||
if (this.running) {
|
if (this.running) {
|
||||||
throw new IllegalStateException("Is already running asynchronously");
|
throw new IllegalStateException("Is already running asynchronously");
|
||||||
}
|
}
|
||||||
|
@ -109,9 +143,11 @@ public class MoodleCourseDataLazyLoader {
|
||||||
loadAndCache(restTemplate).run();
|
loadAndCache(restTemplate).run();
|
||||||
this.lastRunTime = Utils.getMillisecondsNow();
|
this.lastRunTime = Utils.getMillisecondsNow();
|
||||||
|
|
||||||
log.info("Loaded {} courses synchronously", this.preFilteredCourseIds.size());
|
log.info("LMS Setup: {} loaded {} courses synchronously",
|
||||||
|
this.lmsSetup,
|
||||||
|
this.cachedCourseData.size());
|
||||||
|
|
||||||
return this.preFilteredCourseIds;
|
return this.cachedCourseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadAsync(final MoodleAPIRestTemplate restTemplate) {
|
public void loadAsync(final MoodleAPIRestTemplate restTemplate) {
|
||||||
|
@ -126,6 +162,7 @@ public class MoodleCourseDataLazyLoader {
|
||||||
|
|
||||||
private Runnable loadAndCache(final MoodleAPIRestTemplate restTemplate) {
|
private Runnable loadAndCache(final MoodleAPIRestTemplate restTemplate) {
|
||||||
return () -> {
|
return () -> {
|
||||||
|
this.cachedCourseData.clear();
|
||||||
final long startTime = Utils.getMillisecondsNow();
|
final long startTime = Utils.getMillisecondsNow();
|
||||||
|
|
||||||
loadAllQuizzes(restTemplate);
|
loadAllQuizzes(restTemplate);
|
||||||
|
@ -133,7 +170,9 @@ public class MoodleCourseDataLazyLoader {
|
||||||
this.lastLoadTime = Utils.getMillisecondsNow() - startTime;
|
this.lastLoadTime = Utils.getMillisecondsNow() - startTime;
|
||||||
this.running = false;
|
this.running = false;
|
||||||
|
|
||||||
log.info("Loaded {} courses asynchronously", this.preFilteredCourseIds.size());
|
log.info("LMS Setup: {} loaded {} courses asynchronously",
|
||||||
|
this.lmsSetup,
|
||||||
|
this.cachedCourseData.size());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,8 +190,8 @@ public class MoodleCourseDataLazyLoader {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// first get courses from Moodle for page
|
// first get courses from Moodle for page
|
||||||
final Map<String, CourseData> courseData = new HashMap<>();
|
final Map<String, CourseDataShort> courseData = new HashMap<>();
|
||||||
final Collection<CourseData> coursesPage = getCoursesPage(restTemplate, page, 1000);
|
final Collection<CourseDataShort> coursesPage = getCoursesPage(restTemplate, page, this.pageSize);
|
||||||
|
|
||||||
if (coursesPage == null || coursesPage.isEmpty()) {
|
if (coursesPage == null || coursesPage.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -168,7 +207,8 @@ public class MoodleCourseDataLazyLoader {
|
||||||
MoodleCourseAccess.MOODLE_COURSE_API_COURSE_IDS,
|
MoodleCourseAccess.MOODLE_COURSE_API_COURSE_IDS,
|
||||||
new ArrayList<>(courseData.keySet()));
|
new ArrayList<>(courseData.keySet()));
|
||||||
|
|
||||||
final String quizzesJSON = restTemplate.callMoodleAPIFunction(
|
final String quizzesJSON = callMoodleRestAPI(
|
||||||
|
restTemplate,
|
||||||
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
MoodleCourseAccess.MOODLE_QUIZ_API_FUNCTION_NAME,
|
||||||
attributes);
|
attributes);
|
||||||
|
|
||||||
|
@ -185,28 +225,36 @@ public class MoodleCourseDataLazyLoader {
|
||||||
.stream()
|
.stream()
|
||||||
.filter(getQuizFilter())
|
.filter(getQuizFilter())
|
||||||
.forEach(quiz -> {
|
.forEach(quiz -> {
|
||||||
final CourseData data = courseData.get(quiz.course);
|
final CourseDataShort data = courseData.get(quiz.course);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
data.quizzes.add(quiz);
|
data.quizzes.add(quiz);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.preFilteredCourseIds.addAll(
|
courseData.values().stream()
|
||||||
courseData.values().stream()
|
.filter(c -> !c.quizzes.isEmpty())
|
||||||
.filter(c -> !c.quizzes.isEmpty())
|
.forEach(c -> {
|
||||||
.collect(Collectors.toList()));
|
if (this.cachedCourseData.size() >= this.maxSize) {
|
||||||
|
log.error(
|
||||||
|
"LMS Setup: {} Cache is full and has reached its maximal size. Skip data: -> {}",
|
||||||
|
this.lmsSetup,
|
||||||
|
c);
|
||||||
|
} else {
|
||||||
|
this.cachedCourseData.add(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Unexpected exception while trying to get course data: ", e);
|
log.error("LMS Setup: {} Unexpected exception while trying to get course data: ", this.lmsSetup, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<CourseData> getCoursesPage(
|
private Collection<CourseDataShort> getCoursesPage(
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
final int page,
|
final int page,
|
||||||
final int size) throws JsonParseException, JsonMappingException, IOException {
|
final int size) throws JsonParseException, JsonMappingException, IOException {
|
||||||
|
@ -219,7 +267,8 @@ public class MoodleCourseDataLazyLoader {
|
||||||
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_PAGE, String.valueOf(page));
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_PAGE, String.valueOf(page));
|
||||||
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_PAGE_SIZE, String.valueOf(size));
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_SEARCH_PAGE_SIZE, String.valueOf(size));
|
||||||
|
|
||||||
final String courseKeyPageJSON = restTemplate.callMoodleAPIFunction(
|
final String courseKeyPageJSON = callMoodleRestAPI(
|
||||||
|
restTemplate,
|
||||||
MoodleCourseAccess.MOODLE_COURSE_SEARCH_API_FUNCTION_NAME,
|
MoodleCourseAccess.MOODLE_COURSE_SEARCH_API_FUNCTION_NAME,
|
||||||
attributes);
|
attributes);
|
||||||
|
|
||||||
|
@ -228,7 +277,9 @@ public class MoodleCourseDataLazyLoader {
|
||||||
CoursePage.class);
|
CoursePage.class);
|
||||||
|
|
||||||
if (keysPage == null || keysPage.courseKeys == null || keysPage.courseKeys.isEmpty()) {
|
if (keysPage == null || keysPage.courseKeys == null || keysPage.courseKeys.isEmpty()) {
|
||||||
log.info("No courses found on page: {}", page);
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("LMS Setup: {} No courses found on page: {}", this.lmsSetup, page);
|
||||||
|
}
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +289,7 @@ public class MoodleCourseDataLazyLoader {
|
||||||
.map(key -> key.id)
|
.map(key -> key.id)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
final Collection<CourseData> result = getCoursesForIds(restTemplate, ids)
|
final Collection<CourseDataShort> result = getCoursesForIds(restTemplate, ids)
|
||||||
.stream()
|
.stream()
|
||||||
.filter(getCourseFilter())
|
.filter(getCourseFilter())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -247,19 +298,19 @@ public class MoodleCourseDataLazyLoader {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Unexpected error while trying to get courses page: ", e);
|
log.error("LMS Setup: {} Unexpected error while trying to get courses page: ", this.lmsSetup, e);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<CourseData> getCoursesForIds(
|
private Collection<CourseDataShort> getCoursesForIds(
|
||||||
final MoodleAPIRestTemplate restTemplate,
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
final Set<String> ids) {
|
final Set<String> ids) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Get courses for ids: {}", ids);
|
log.debug("LMS Setup: {} Get courses for ids: {}", this.lmsSetup, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String joinedIds = StringUtils.join(ids, Constants.COMMA);
|
final String joinedIds = StringUtils.join(ids, Constants.COMMA);
|
||||||
|
@ -267,32 +318,52 @@ public class MoodleCourseDataLazyLoader {
|
||||||
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
|
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_NAME, MoodleCourseAccess.MOODLE_COURSE_API_IDS);
|
||||||
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_FIELD_VALUE, joinedIds);
|
attributes.add(MoodleCourseAccess.MOODLE_COURSE_API_FIELD_VALUE, joinedIds);
|
||||||
final String coursePageJSON = restTemplate.callMoodleAPIFunction(
|
final String coursePageJSON = callMoodleRestAPI(
|
||||||
|
restTemplate,
|
||||||
MoodleCourseAccess.MOODLE_COURSE_BY_FIELD_API_FUNCTION_NAME,
|
MoodleCourseAccess.MOODLE_COURSE_BY_FIELD_API_FUNCTION_NAME,
|
||||||
attributes);
|
attributes);
|
||||||
|
|
||||||
return this.jsonMapper.<Courses> readValue(
|
return this.jsonMapper.readValue(
|
||||||
coursePageJSON,
|
coursePageJSON,
|
||||||
Courses.class).courses;
|
Courses.class).courses;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Unexpected error while trying to get courses for ids", e);
|
log.error("LMS Setup: {} Unexpected error while trying to get courses for ids", this.lmsSetup, e);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate<CourseQuiz> getQuizFilter() {
|
private String callMoodleRestAPI(
|
||||||
|
final MoodleAPIRestTemplate restTemplate,
|
||||||
|
final String function,
|
||||||
|
final MultiValueMap<String, String> queryAttributes) {
|
||||||
|
|
||||||
|
return this.moodleRestCall
|
||||||
|
.protectedRun(() -> restTemplate.callMoodleAPIFunction(
|
||||||
|
function,
|
||||||
|
queryAttributes))
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<CourseQuizShort> getQuizFilter() {
|
||||||
final long now = Utils.getSecondsNow();
|
final long now = Utils.getSecondsNow();
|
||||||
return quiz -> {
|
return quiz -> {
|
||||||
if (quiz.time_close == null || quiz.time_close == 0 || quiz.time_close > now) {
|
if (quiz.time_close == null || quiz.time_close == 0 || quiz.time_close > now) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("remove quiz {} end_time {} now {}", quiz.name, quiz.time_close, now);
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("LMS Setup: {} remove quiz {} end_time {} now {}",
|
||||||
|
this.lmsSetup,
|
||||||
|
quiz.name,
|
||||||
|
quiz.time_close,
|
||||||
|
now);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate<CourseData> getCourseFilter() {
|
private Predicate<CourseDataShort> getCourseFilter() {
|
||||||
final long now = Utils.getSecondsNow();
|
final long now = Utils.getSecondsNow();
|
||||||
return course -> {
|
return course -> {
|
||||||
if (course.start_date < this.fromCutTime) {
|
if (course.start_date < this.fromCutTime) {
|
||||||
|
@ -303,7 +374,13 @@ public class MoodleCourseDataLazyLoader {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("remove course {} end_time {} now {}", course.short_name, course.end_date, now);
|
if (log.isDebugEnabled()) {
|
||||||
|
log.info("LMS Setup: {} remove course {} end_time {} now {}",
|
||||||
|
this.lmsSetup,
|
||||||
|
course.short_name,
|
||||||
|
course.end_date,
|
||||||
|
now);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -356,77 +433,107 @@ public class MoodleCourseDataLazyLoader {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @JsonIgnoreProperties(ignoreUnknown = true)
|
/** Maps the Moodle course API course data */
|
||||||
// static final class CourseKeys {
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
// final Collection<CourseDataKey> courses;
|
static final class CourseDataShort {
|
||||||
//
|
final String id;
|
||||||
// @JsonCreator
|
final String short_name;
|
||||||
// protected CourseKeys(
|
final String idnumber;
|
||||||
// @JsonProperty(value = "courses") final Collection<CourseDataKey> courses) {
|
final Long start_date; // unix-time seconds UTC
|
||||||
// this.courses = courses;
|
final Long end_date; // unix-time seconds UTC
|
||||||
// }
|
final Long time_created; // unix-time seconds UTC
|
||||||
// }
|
final Collection<CourseQuizShort> quizzes = new ArrayList<>();
|
||||||
|
|
||||||
// /** Maps the Moodle course API course data */
|
@JsonCreator
|
||||||
// @JsonIgnoreProperties(ignoreUnknown = true)
|
protected CourseDataShort(
|
||||||
// static final class CourseDataKey {
|
@JsonProperty(value = "id") final String id,
|
||||||
// final String id;
|
@JsonProperty(value = "shortname") final String short_name,
|
||||||
// final String short_name;
|
@JsonProperty(value = "idnumber") final String idnumber,
|
||||||
// final Long start_date; // unix-time seconds UTC
|
@JsonProperty(value = "startdate") final Long start_date,
|
||||||
// final Long end_date; // unix-time seconds UTC
|
@JsonProperty(value = "enddate") final Long end_date,
|
||||||
// final Long time_created; // unix-time seconds UTC
|
@JsonProperty(value = "timecreated") final Long time_created) {
|
||||||
// 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)
|
this.id = id;
|
||||||
// static final class CourseQuizKeys {
|
this.short_name = short_name;
|
||||||
// final Collection<CourseQuizKey> quizzes;
|
this.idnumber = idnumber;
|
||||||
//
|
this.start_date = start_date;
|
||||||
// @JsonCreator
|
this.end_date = end_date;
|
||||||
// protected CourseQuizKeys(
|
this.time_created = time_created;
|
||||||
// @JsonProperty(value = "quizzes") final Collection<CourseQuizKey> quizzes) {
|
}
|
||||||
// this.quizzes = quizzes;
|
|
||||||
// }
|
@Override
|
||||||
// }
|
public int hashCode() {
|
||||||
//
|
final int prime = 31;
|
||||||
// @JsonIgnoreProperties(ignoreUnknown = true)
|
int result = 1;
|
||||||
// static final class CourseQuizKey {
|
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
|
||||||
// final String id;
|
return result;
|
||||||
// final String course;
|
}
|
||||||
// final String name;
|
|
||||||
// final Long time_open; // unix-time seconds UTC
|
@Override
|
||||||
// final Long time_close; // unix-time seconds UTC
|
public boolean equals(final Object obj) {
|
||||||
//
|
if (this == obj)
|
||||||
// @JsonCreator
|
return true;
|
||||||
// protected CourseQuizKey(
|
if (obj == null)
|
||||||
// @JsonProperty(value = "id") final String id,
|
return false;
|
||||||
// @JsonProperty(value = "course") final String course,
|
if (getClass() != obj.getClass())
|
||||||
// @JsonProperty(value = "name") final String name,
|
return false;
|
||||||
// @JsonProperty(value = "timeopen") final Long time_open,
|
final CourseDataShort other = (CourseDataShort) obj;
|
||||||
// @JsonProperty(value = "timeclose") final Long time_close) {
|
if (this.id == null) {
|
||||||
//
|
if (other.id != null)
|
||||||
// this.id = id;
|
return false;
|
||||||
// this.course = course;
|
} else if (!this.id.equals(other.id))
|
||||||
// this.name = name;
|
return false;
|
||||||
// this.time_open = time_open;
|
return true;
|
||||||
// this.time_close = time_close;
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
private static final class Courses {
|
||||||
|
final Collection<CourseDataShort> courses;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
protected Courses(
|
||||||
|
@JsonProperty(value = "courses") final Collection<CourseDataShort> courses) {
|
||||||
|
this.courses = courses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
static final class CourseQuizData {
|
||||||
|
final Collection<CourseQuizShort> quizzes;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
protected CourseQuizData(
|
||||||
|
@JsonProperty(value = "quizzes") final Collection<CourseQuizShort> quizzes) {
|
||||||
|
this.quizzes = quizzes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
static final class CourseQuizShort {
|
||||||
|
final String id;
|
||||||
|
final String course;
|
||||||
|
final String course_module;
|
||||||
|
final String name;
|
||||||
|
final Long time_open; // unix-time seconds UTC
|
||||||
|
final Long time_close; // unix-time seconds UTC
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
protected CourseQuizShort(
|
||||||
|
@JsonProperty(value = "id") final String id,
|
||||||
|
@JsonProperty(value = "course") final String course,
|
||||||
|
@JsonProperty(value = "coursemodule") final String course_module,
|
||||||
|
@JsonProperty(value = "name") final String name,
|
||||||
|
@JsonProperty(value = "timeopen") final Long time_open,
|
||||||
|
@JsonProperty(value = "timeclose") final Long time_close) {
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.course = course;
|
||||||
|
this.course_module = course_module;
|
||||||
|
this.name = name;
|
||||||
|
this.time_open = time_open;
|
||||||
|
this.time_close = time_close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,6 +12,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
|
||||||
|
@ -32,6 +33,7 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
|
|
||||||
private final JSONMapper jsonMapper;
|
private final JSONMapper jsonMapper;
|
||||||
private final AsyncService asyncService;
|
private final AsyncService asyncService;
|
||||||
|
private final Environment environment;
|
||||||
private final ClientCredentialService clientCredentialService;
|
private final ClientCredentialService clientCredentialService;
|
||||||
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
@ -40,6 +42,7 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
protected MoodleLmsAPITemplateFactory(
|
protected MoodleLmsAPITemplateFactory(
|
||||||
final JSONMapper jsonMapper,
|
final JSONMapper jsonMapper,
|
||||||
final AsyncService asyncService,
|
final AsyncService asyncService,
|
||||||
|
final Environment environment,
|
||||||
final ClientCredentialService clientCredentialService,
|
final ClientCredentialService clientCredentialService,
|
||||||
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
|
||||||
final ApplicationContext applicationContext,
|
final ApplicationContext applicationContext,
|
||||||
|
@ -47,6 +50,7 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.asyncService = asyncService;
|
this.asyncService = asyncService;
|
||||||
|
this.environment = environment;
|
||||||
this.clientCredentialService = clientCredentialService;
|
this.clientCredentialService = clientCredentialService;
|
||||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
|
@ -62,8 +66,9 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final MoodleCourseDataLazyLoader lazyLoaderPrototype =
|
final MoodleCourseDataAsyncLoader asyncLoaderPrototype =
|
||||||
this.applicationContext.getBean(MoodleCourseDataLazyLoader.class);
|
this.applicationContext.getBean(MoodleCourseDataAsyncLoader.class);
|
||||||
|
asyncLoaderPrototype.init(lmsSetup.name);
|
||||||
|
|
||||||
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
|
final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
|
@ -78,8 +83,9 @@ public class MoodleLmsAPITemplateFactory {
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
lmsSetup,
|
lmsSetup,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
lazyLoaderPrototype,
|
asyncLoaderPrototype,
|
||||||
this.asyncService);
|
this.asyncService,
|
||||||
|
this.environment);
|
||||||
|
|
||||||
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
|
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
|
||||||
this.jsonMapper,
|
this.jsonMapper,
|
||||||
|
|
|
@ -489,9 +489,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.filter(connection -> connection.pingIndicator != null &&
|
.filter(connection -> connection.pingIndicator != null &&
|
||||||
connection.clientConnection.status.establishedStatus)
|
connection.clientConnection.status.establishedStatus)
|
||||||
.map(connection -> connection.pingIndicator.updateLogEvent())
|
.forEach(connection -> connection.pingIndicator.updateLogEvent());
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.forEach(this.eventHandlingStrategy);
|
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to update ping events: ", e);
|
log.error("Failed to update ping events: ", e);
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
|
||||||
|
|
||||||
private final ClientEventDAO clientEventDAO;
|
private final ClientEventDAO clientEventDAO;
|
||||||
private final SEBClientInstructionService sebClientInstructionService;
|
private final SEBClientInstructionService sebClientInstructionService;
|
||||||
private final Set<Long> pendingNotifications;
|
private final Set<Long> pendingNotifications = new HashSet<>();
|
||||||
|
|
||||||
public SEBClientNotificationServiceImpl(
|
public SEBClientNotificationServiceImpl(
|
||||||
final ClientEventDAO clientEventDAO,
|
final ClientEventDAO clientEventDAO,
|
||||||
|
@ -50,19 +50,25 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
|
||||||
|
|
||||||
this.clientEventDAO = clientEventDAO;
|
this.clientEventDAO = clientEventDAO;
|
||||||
this.sebClientInstructionService = sebClientInstructionService;
|
this.sebClientInstructionService = sebClientInstructionService;
|
||||||
this.pendingNotifications = new HashSet<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean hasAnyPendingNotification(final Long clientConnectionId) {
|
public Boolean hasAnyPendingNotification(final Long clientConnectionId) {
|
||||||
if (this.pendingNotifications.contains(clientConnectionId)) {
|
|
||||||
|
if (this.pendingNotifications.add(clientConnectionId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean hasAnyPendingNotification = !getPendingNotifications(clientConnectionId)
|
final boolean hasAnyPendingNotification = !getPendingNotifications(clientConnectionId)
|
||||||
.getOr(Collections.emptyList())
|
.getOr(Collections.emptyList())
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
|
|
||||||
if (hasAnyPendingNotification) {
|
if (hasAnyPendingNotification) {
|
||||||
|
// NOTE this is a quick and dirty way to keep cache pendingNotifications cache size short.
|
||||||
|
// TODO find a better way to do this.
|
||||||
|
if (this.pendingNotifications.size() > 100) {
|
||||||
|
this.pendingNotifications.clear();
|
||||||
|
}
|
||||||
this.pendingNotifications.add(clientConnectionId);
|
this.pendingNotifications.add(clientConnectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
server.address=localhost
|
server.address=localhost
|
||||||
server.port=8090
|
server.port=8090
|
||||||
|
|
||||||
#logging.file=log/sebserver.log
|
|
||||||
|
|
||||||
# data source configuration
|
# data source configuration
|
||||||
spring.datasource.initialize=true
|
spring.datasource.initialize=true
|
||||||
spring.datasource.initialization-mode=always
|
spring.datasource.initialization-mode=always
|
||||||
|
|
|
@ -6,6 +6,8 @@ server.servlet.context-path=/
|
||||||
server.tomcat.uri-encoding=UTF-8
|
server.tomcat.uri-encoding=UTF-8
|
||||||
|
|
||||||
logging.level.ch=INFO
|
logging.level.ch=INFO
|
||||||
|
logging.level.org.springframework.cache=INFO
|
||||||
|
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl=DEBUG
|
||||||
|
|
||||||
sebserver.http.client.connect-timeout=150000
|
sebserver.http.client.connect-timeout=150000
|
||||||
sebserver.http.client.connection-request-timeout=100000
|
sebserver.http.client.connection-request-timeout=100000
|
||||||
|
|
|
@ -8,6 +8,10 @@ sebserver.init.adminaccount.gen-on-init=true
|
||||||
sebserver.init.organisation.name=SEB Server
|
sebserver.init.organisation.name=SEB Server
|
||||||
sebserver.init.adminaccount.username=sebserver-admin
|
sebserver.init.adminaccount.username=sebserver-admin
|
||||||
|
|
||||||
|
### webservice caching
|
||||||
|
spring.cache.jcache.provider=org.ehcache.jsr107.EhcacheCachingProvider
|
||||||
|
spring.cache.jcache.config=classpath:config/ehcache.xml
|
||||||
|
|
||||||
### webservice data source configuration
|
### webservice data source configuration
|
||||||
spring.datasource.username=root
|
spring.datasource.username=root
|
||||||
spring.datasource.initialize=true
|
spring.datasource.initialize=true
|
||||||
|
|
|
@ -13,7 +13,7 @@ server.port=8080
|
||||||
server.servlet.context-path=/
|
server.servlet.context-path=/
|
||||||
|
|
||||||
# Tomcat
|
# Tomcat
|
||||||
server.tomcat.max-threads=1000
|
server.tomcat.max-threads=2000
|
||||||
server.tomcat.accept-count=300
|
server.tomcat.accept-count=300
|
||||||
server.tomcat.uri-encoding=UTF-8
|
server.tomcat.uri-encoding=UTF-8
|
||||||
|
|
||||||
|
|
85
src/main/resources/config/ehcache.xml
Normal file
85
src/main/resources/config/ehcache.xml
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://www.ehcache.org/v3"
|
||||||
|
xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
|
||||||
|
xsi:schemaLocation="
|
||||||
|
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
|
||||||
|
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
|
||||||
|
|
||||||
|
<cache alias="RUNNING_EXAM">
|
||||||
|
<key-type>java.lang.Long</key-type>
|
||||||
|
<value-type>ch.ethz.seb.sebserver.gbl.model.exam.Exam</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">100</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
<cache alias="ACTIVE_CLIENT_CONNECTION">
|
||||||
|
<key-type>java.lang.String</key-type>
|
||||||
|
<value-type>ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">2000</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
<cache alias="SEB_CONFIG_EXAM">
|
||||||
|
<key-type>java.lang.Long</key-type>
|
||||||
|
<value-type>ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.InMemorySEBConfig</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">20</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
<cache alias="CACHE_NAME_PING_RECORD">
|
||||||
|
<key-type>java.lang.String</key-type>
|
||||||
|
<value-type>ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">2000</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
<cache alias="CONNECTION_TOKENS_CACHE">
|
||||||
|
<key-type>java.lang.Long</key-type>
|
||||||
|
<value-type>ch.ethz.seb.sebserver.gbl.util.Result</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">100</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
<cache alias="ACCESS_TOKEN_STORE_CACHE">
|
||||||
|
<key-type>org.springframework.security.oauth2.common.OAuth2AccessToken</key-type>
|
||||||
|
<value-type>org.springframework.security.oauth2.provider.OAuth2Authentication</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">100</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
<cache alias="EXAM_CLIENT_DETAILS_CACHE">
|
||||||
|
<key-type>java.lang.String</key-type>
|
||||||
|
<value-type>ch.ethz.seb.sebserver.gbl.util.Result</value-type>
|
||||||
|
<expiry>
|
||||||
|
<tti unit="hours">24</tti>
|
||||||
|
</expiry>
|
||||||
|
<resources>
|
||||||
|
<heap unit="entries">2000</heap>
|
||||||
|
</resources>
|
||||||
|
</cache>
|
||||||
|
|
||||||
|
</config>
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
<root level="DEBUG" additivity="true">
|
<root level="DEBUG" additivity="true">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
</root>
|
</root>
|
||||||
<Logger name="ch.ethz.seb.SEB_SERVER_INIT" level="INFO" additivity="false">
|
<Logger name="ch.ethz.seb.SEB_SERVER_INIT" level="INFO" additivity="false">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
|
|
@ -15,6 +15,9 @@ import static org.mockito.Mockito.*;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
@ -28,6 +31,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
|
||||||
|
|
||||||
public class MoodleCourseAccessTest {
|
public class MoodleCourseAccessTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
Environment env = new MockEnvironment();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetExamineeAccountDetails() {
|
public void testGetExamineeAccountDetails() {
|
||||||
|
|
||||||
|
@ -68,7 +74,8 @@ public class MoodleCourseAccessTest {
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class),
|
||||||
|
this.env);
|
||||||
|
|
||||||
final String examId = "123";
|
final String examId = "123";
|
||||||
final Result<ExamineeAccountDetails> examineeAccountDetails =
|
final Result<ExamineeAccountDetails> examineeAccountDetails =
|
||||||
|
@ -116,7 +123,8 @@ public class MoodleCourseAccessTest {
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class),
|
||||||
|
this.env);
|
||||||
|
|
||||||
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
||||||
assertNotNull(initAPIAccess);
|
assertNotNull(initAPIAccess);
|
||||||
|
@ -138,7 +146,8 @@ public class MoodleCourseAccessTest {
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class),
|
||||||
|
this.env);
|
||||||
|
|
||||||
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
||||||
assertNotNull(initAPIAccess);
|
assertNotNull(initAPIAccess);
|
||||||
|
@ -159,7 +168,8 @@ public class MoodleCourseAccessTest {
|
||||||
null,
|
null,
|
||||||
moodleRestTemplateFactory,
|
moodleRestTemplateFactory,
|
||||||
null,
|
null,
|
||||||
mock(AsyncService.class));
|
mock(AsyncService.class),
|
||||||
|
this.env);
|
||||||
|
|
||||||
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
|
||||||
assertNotNull(initAPIAccess);
|
assertNotNull(initAPIAccess);
|
||||||
|
|
|
@ -12,6 +12,9 @@ server.servlet.context-path=/
|
||||||
spring.main.allow-bean-definition-overriding=true
|
spring.main.allow-bean-definition-overriding=true
|
||||||
sebserver.password=test-password
|
sebserver.password=test-password
|
||||||
|
|
||||||
|
spring.cache.jcache.provider=
|
||||||
|
spring.cache.jcache.config=classpath:config/ehcache.xml
|
||||||
|
|
||||||
spring.h2.console.enabled=true
|
spring.h2.console.enabled=true
|
||||||
spring.datasource.platform=h2
|
spring.datasource.platform=h2
|
||||||
spring.datasource.url=jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
spring.datasource.url=jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||||
|
|
Loading…
Reference in a new issue