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())
 | 
				
			||||||
                                .collect(Collectors.toList()));
 | 
					                        .forEach(c -> {
 | 
				
			||||||
 | 
					                            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…
	
	Add table
		
		Reference in a new issue