added OlatLmsAPITemplate skeleton ready to be implemented
This commit is contained in:
		
							parent
							
								
									213cf443e1
								
							
						
					
					
						commit
						89c306e35a
					
				
					 7 changed files with 430 additions and 116 deletions
				
			
		|  | @ -15,19 +15,19 @@ import org.springframework.stereotype.Service; | ||||||
| 
 | 
 | ||||||
| @Lazy | @Lazy | ||||||
| @Service | @Service | ||||||
| /** Implements a asynchronous service to manly support  CircuitBreaker and MemoizingCircuitBreaker functionality. */ | /** Implements a asynchronous service to manly support CircuitBreaker and MemoizingCircuitBreaker functionality. */ | ||||||
| public class AsyncService { | public class AsyncService { | ||||||
| 
 | 
 | ||||||
|     private final AsyncRunner asyncRunner; |     private final AsyncRunner asyncRunner; | ||||||
| 
 | 
 | ||||||
|     protected AsyncService(final AsyncRunner asyncRunner) { |     public AsyncService(final AsyncRunner asyncRunner) { | ||||||
|         this.asyncRunner = asyncRunner; |         this.asyncRunner = asyncRunner; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** Create a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class |     /** Create a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class | ||||||
|      * |      * | ||||||
|      * @param <T> the type of the CircuitBreaker |      * @param <T> the type of the CircuitBreaker | ||||||
|      * @return  a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class */ |      * @return a CircuitBreaker of specified type with the default parameter defined in the CircuitBreaker class */ | ||||||
|     public <T> CircuitBreaker<T> createCircuitBreaker() { |     public <T> CircuitBreaker<T> createCircuitBreaker() { | ||||||
|         return new CircuitBreaker<>(this.asyncRunner); |         return new CircuitBreaker<>(this.asyncRunner); | ||||||
|     } |     } | ||||||
|  | @ -38,7 +38,7 @@ public class AsyncService { | ||||||
|      * @param maxBlockingTime maximal time since call CircuitBreaker waits for a response before going onto open state. |      * @param maxBlockingTime maximal time since call CircuitBreaker waits for a response before going onto open state. | ||||||
|      * @param timeToRecover the time the CircuitBreaker takes to recover form open state. |      * @param timeToRecover the time the CircuitBreaker takes to recover form open state. | ||||||
|      * @param <T> the type of the CircuitBreaker |      * @param <T> the type of the CircuitBreaker | ||||||
|      * @return  a CircuitBreaker of specified type */ |      * @return a CircuitBreaker of specified type */ | ||||||
|     public <T> CircuitBreaker<T> createCircuitBreaker( |     public <T> CircuitBreaker<T> createCircuitBreaker( | ||||||
|             final int maxFailingAttempts, |             final int maxFailingAttempts, | ||||||
|             final long maxBlockingTime, |             final long maxBlockingTime, | ||||||
|  | @ -52,7 +52,7 @@ public class AsyncService { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** Create a MemoizingCircuitBreaker of specified type that memoize a successful result and return the last |     /** Create a MemoizingCircuitBreaker of specified type that memoize a successful result and return the last | ||||||
|      *  successful result on fail as long as maxMemoizingTime is not exceeded. |      * successful result on fail as long as maxMemoizingTime is not exceeded. | ||||||
|      * |      * | ||||||
|      * @param blockingSupplier the blocking result supplier that the MemoizingCircuitBreaker must call |      * @param blockingSupplier the blocking result supplier that the MemoizingCircuitBreaker must call | ||||||
|      * @param maxFailingAttempts maximal number of attempts the CircuitBreaker allows before going onto open state. |      * @param maxFailingAttempts maximal number of attempts the CircuitBreaker allows before going onto open state. | ||||||
|  | @ -61,7 +61,7 @@ public class AsyncService { | ||||||
|      * @param momoized whether the memoizing functionality is on or off |      * @param momoized whether the memoizing functionality is on or off | ||||||
|      * @param maxMemoizingTime the maximal time memorized data is valid |      * @param maxMemoizingTime the maximal time memorized data is valid | ||||||
|      * @param <T> the type of the CircuitBreaker |      * @param <T> the type of the CircuitBreaker | ||||||
|      * @return  a CircuitBreaker of specified type */ |      * @return a CircuitBreaker of specified type */ | ||||||
|     public <T> MemoizingCircuitBreaker<T> createMemoizingCircuitBreaker( |     public <T> MemoizingCircuitBreaker<T> createMemoizingCircuitBreaker( | ||||||
|             final Supplier<T> blockingSupplier, |             final Supplier<T> blockingSupplier, | ||||||
|             final int maxFailingAttempts, |             final int maxFailingAttempts, | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ | ||||||
| package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; | package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; | ||||||
| 
 | 
 | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.function.Supplier; | import java.util.function.Supplier; | ||||||
|  | @ -134,10 +133,8 @@ public abstract class AbstractCourseAccess { | ||||||
|         return this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap)); |         return this.allQuizzesRequest.protectedRun(allQuizzesSupplier(filterMap)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Collection<QuizData> protectedQuizzesRequest(final Set<String> ids) { |     public Result<Collection<QuizData>> protectedQuizzesRequest(final Set<String> ids) { | ||||||
|         return this.quizzesRequest.protectedRun(quizzesSupplier(ids)) |         return this.quizzesRequest.protectedRun(quizzesSupplier(ids)); | ||||||
|                 .onError(error -> log.error("Failed to get QuizData for ids: ", error)) |  | ||||||
|                 .getOrElse(() -> Collections.emptyList()); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Result<QuizData> protectedQuizRequest(final String id) { |     public Result<QuizData> protectedQuizRequest(final String id) { | ||||||
|  | @ -145,7 +142,8 @@ public abstract class AbstractCourseAccess { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) { |     public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) { | ||||||
|         return this.accountDetailRequest.protectedRun(accountDetailsSupplier(examineeSessionId)); |         final Supplier<ExamineeAccountDetails> accountDetailsSupplier = accountDetailsSupplier(examineeSessionId); | ||||||
|  |         return this.accountDetailRequest.protectedRun(accountDetailsSupplier); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** Default implementation that uses getExamineeAccountDetails to geht the examinee name |     /** Default implementation that uses getExamineeAccountDetails to geht the examinee name | ||||||
|  | @ -163,19 +161,7 @@ public abstract class AbstractCourseAccess { | ||||||
|         return this.chaptersRequest.protectedRun(getCourseChaptersSupplier(courseId)); |         return this.chaptersRequest.protectedRun(getCourseChaptersSupplier(courseId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** NOTE: this returns a ExamineeAccountDetails with given examineeSessionId for default. |     protected abstract Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId); | ||||||
|      * Override this if requesting account details is supported for specified LMS access. |  | ||||||
|      * |  | ||||||
|      * @param examineeSessionId |  | ||||||
|      * @return this returns a ExamineeAccountDetails with given examineeSessionId for default */ |  | ||||||
|     protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) { |  | ||||||
|         return () -> new ExamineeAccountDetails( |  | ||||||
|                 examineeSessionId, |  | ||||||
|                 examineeSessionId, |  | ||||||
|                 examineeSessionId, |  | ||||||
|                 examineeSessionId, |  | ||||||
|                 Collections.emptyMap()); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /** Provides a supplier to supply request to use within the circuit breaker */ |     /** Provides a supplier to supply request to use within the circuit breaker */ | ||||||
|     protected abstract Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap); |     protected abstract Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap); | ||||||
|  |  | ||||||
|  | @ -141,52 +141,56 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) { |     protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) { | ||||||
|         return Result.tryCatch(() -> { |         return () -> { | ||||||
|             final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup(); |             try { | ||||||
|             final HttpHeaders httpHeaders = new HttpHeaders(); |                 final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup(); | ||||||
|             final OAuth2RestTemplate template = getRestTemplate() |                 final HttpHeaders httpHeaders = new HttpHeaders(); | ||||||
|                     .getOrThrow(); |                 final OAuth2RestTemplate template = getRestTemplate() | ||||||
|  |                         .getOrThrow(); | ||||||
| 
 | 
 | ||||||
|             final String externalStartURI = this.webserviceInfo |                 final String externalStartURI = this.webserviceInfo | ||||||
|                     .getLmsExternalAddressAlias(lmsSetup.lmsApiUrl); |                         .getLmsExternalAddressAlias(lmsSetup.lmsApiUrl); | ||||||
| 
 | 
 | ||||||
|             final String uri = (externalStartURI != null) |                 final String uri = (externalStartURI != null) | ||||||
|                     ? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId |                         ? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId | ||||||
|                     : lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId; |                         : lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId; | ||||||
| 
 | 
 | ||||||
|             final String responseJSON = template.exchange( |                 final String responseJSON = template.exchange( | ||||||
|                     uri, |                         uri, | ||||||
|                     HttpMethod.GET, |                         HttpMethod.GET, | ||||||
|                     new HttpEntity<>(httpHeaders), |                         new HttpEntity<>(httpHeaders), | ||||||
|                     String.class) |                         String.class) | ||||||
|                     .getBody(); |                         .getBody(); | ||||||
| 
 | 
 | ||||||
|             final EdxUserDetails[] userDetails = this.jsonMapper.<EdxUserDetails[]> readValue( |                 final EdxUserDetails[] userDetails = this.jsonMapper.<EdxUserDetails[]> readValue( | ||||||
|                     responseJSON, |                         responseJSON, | ||||||
|                     new TypeReference<EdxUserDetails[]>() { |                         new TypeReference<EdxUserDetails[]>() { | ||||||
|                     }); |                         }); | ||||||
| 
 | 
 | ||||||
|             if (userDetails == null || userDetails.length <= 0) { |                 if (userDetails == null || userDetails.length <= 0) { | ||||||
|                 throw new RuntimeException("No user details on Open edX API request"); |                     throw new RuntimeException("No user details on Open edX API request"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 final Map<String, String> additionalAttributes = new HashMap<>(); | ||||||
|  |                 additionalAttributes.put("bio", userDetails[0].bio); | ||||||
|  |                 additionalAttributes.put("country", userDetails[0].country); | ||||||
|  |                 additionalAttributes.put("date_joined", userDetails[0].date_joined); | ||||||
|  |                 additionalAttributes.put("gender", userDetails[0].gender); | ||||||
|  |                 additionalAttributes.put("is_active", String.valueOf(userDetails[0].is_active)); | ||||||
|  |                 additionalAttributes.put("mailing_address", userDetails[0].mailing_address); | ||||||
|  |                 additionalAttributes.put("secondary_email", userDetails[0].secondary_email); | ||||||
|  | 
 | ||||||
|  |                 return new ExamineeAccountDetails( | ||||||
|  |                         userDetails[0].username, | ||||||
|  |                         userDetails[0].name, | ||||||
|  |                         userDetails[0].username, | ||||||
|  |                         userDetails[0].email, | ||||||
|  |                         additionalAttributes); | ||||||
|  |             } catch (final Exception e) { | ||||||
|  |                 throw new RuntimeException(e); | ||||||
|             } |             } | ||||||
| 
 |         }; | ||||||
|             final Map<String, String> additionalAttributes = new HashMap<>(); |  | ||||||
|             additionalAttributes.put("bio", userDetails[0].bio); |  | ||||||
|             additionalAttributes.put("country", userDetails[0].country); |  | ||||||
|             additionalAttributes.put("date_joined", userDetails[0].date_joined); |  | ||||||
|             additionalAttributes.put("gender", userDetails[0].gender); |  | ||||||
|             additionalAttributes.put("is_active", String.valueOf(userDetails[0].is_active)); |  | ||||||
|             additionalAttributes.put("mailing_address", userDetails[0].mailing_address); |  | ||||||
|             additionalAttributes.put("secondary_email", userDetails[0].secondary_email); |  | ||||||
| 
 |  | ||||||
|             return new ExamineeAccountDetails( |  | ||||||
|                     userDetails[0].username, |  | ||||||
|                     userDetails[0].name, |  | ||||||
|                     userDetails[0].username, |  | ||||||
|                     userDetails[0].email, |  | ||||||
|                     additionalAttributes); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -275,7 +279,7 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess { | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|             if (!leftIds.isEmpty()) { |             if (!leftIds.isEmpty()) { | ||||||
|                 result.addAll(super.protectedQuizzesRequest(leftIds)); |                 result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow()); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return result; |             return result; | ||||||
|  |  | ||||||
|  | @ -129,59 +129,63 @@ public class MoodleCourseAccess extends AbstractCourseAccess { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeSessionId) { |     protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) { | ||||||
|         return Result.tryCatch(() -> { |         return () -> { | ||||||
|             final MoodleAPIRestTemplate template = getRestTemplate() |             try { | ||||||
|                     .getOrThrow(); |                 final MoodleAPIRestTemplate template = getRestTemplate() | ||||||
|  |                         .getOrThrow(); | ||||||
| 
 | 
 | ||||||
|             final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>(); |                 final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>(); | ||||||
|             queryAttributes.add("field", "id"); |                 queryAttributes.add("field", "id"); | ||||||
|             queryAttributes.add("values[0]", examineeSessionId); |                 queryAttributes.add("values[0]", examineeSessionId); | ||||||
| 
 | 
 | ||||||
|             final String userDetailsJSON = template.callMoodleAPIFunction( |                 final String userDetailsJSON = template.callMoodleAPIFunction( | ||||||
|                     MOODLE_USER_PROFILE_API_FUNCTION_NAME, |  | ||||||
|                     queryAttributes); |  | ||||||
| 
 |  | ||||||
|             if (checkAccessDeniedError(userDetailsJSON)) { |  | ||||||
|                 final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup(); |  | ||||||
|                 log.error("Get access denied error from Moodle: {} for API call: {}, response: {}", |  | ||||||
|                         lmsSetup, |  | ||||||
|                         MOODLE_USER_PROFILE_API_FUNCTION_NAME, |                         MOODLE_USER_PROFILE_API_FUNCTION_NAME, | ||||||
|                         Utils.truncateText(userDetailsJSON, 2000)); |                         queryAttributes); | ||||||
|                 throw new RuntimeException("No user details on Moodle API request (access-denied)"); | 
 | ||||||
|  |                 if (checkAccessDeniedError(userDetailsJSON)) { | ||||||
|  |                     final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup(); | ||||||
|  |                     log.error("Get access denied error from Moodle: {} for API call: {}, response: {}", | ||||||
|  |                             lmsSetup, | ||||||
|  |                             MOODLE_USER_PROFILE_API_FUNCTION_NAME, | ||||||
|  |                             Utils.truncateText(userDetailsJSON, 2000)); | ||||||
|  |                     throw new RuntimeException("No user details on Moodle API request (access-denied)"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 final MoodleUserDetails[] userDetails = this.jsonMapper.<MoodleUserDetails[]> readValue( | ||||||
|  |                         userDetailsJSON, | ||||||
|  |                         new TypeReference<MoodleUserDetails[]>() { | ||||||
|  |                         }); | ||||||
|  | 
 | ||||||
|  |                 if (userDetails == null || userDetails.length <= 0) { | ||||||
|  |                     throw new RuntimeException("No user details on Moodle API request"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 final Map<String, String> additionalAttributes = new HashMap<>(); | ||||||
|  |                 additionalAttributes.put("firstname", userDetails[0].firstname); | ||||||
|  |                 additionalAttributes.put("lastname", userDetails[0].lastname); | ||||||
|  |                 additionalAttributes.put("department", userDetails[0].department); | ||||||
|  |                 additionalAttributes.put("firstaccess", String.valueOf(userDetails[0].firstaccess)); | ||||||
|  |                 additionalAttributes.put("lastaccess", String.valueOf(userDetails[0].lastaccess)); | ||||||
|  |                 additionalAttributes.put("auth", userDetails[0].auth); | ||||||
|  |                 additionalAttributes.put("suspended", String.valueOf(userDetails[0].suspended)); | ||||||
|  |                 additionalAttributes.put("confirmed", String.valueOf(userDetails[0].confirmed)); | ||||||
|  |                 additionalAttributes.put("lang", userDetails[0].lang); | ||||||
|  |                 additionalAttributes.put("theme", userDetails[0].theme); | ||||||
|  |                 additionalAttributes.put("timezone", userDetails[0].timezone); | ||||||
|  |                 additionalAttributes.put("description", userDetails[0].description); | ||||||
|  |                 additionalAttributes.put("mailformat", String.valueOf(userDetails[0].mailformat)); | ||||||
|  |                 additionalAttributes.put("descriptionformat", String.valueOf(userDetails[0].descriptionformat)); | ||||||
|  |                 return new ExamineeAccountDetails( | ||||||
|  |                         userDetails[0].id, | ||||||
|  |                         userDetails[0].fullname, | ||||||
|  |                         userDetails[0].username, | ||||||
|  |                         userDetails[0].email, | ||||||
|  |                         additionalAttributes); | ||||||
|  |             } catch (final Exception e) { | ||||||
|  |                 throw new RuntimeException(e); | ||||||
|             } |             } | ||||||
| 
 |         }; | ||||||
|             final MoodleUserDetails[] userDetails = this.jsonMapper.<MoodleUserDetails[]> readValue( |  | ||||||
|                     userDetailsJSON, |  | ||||||
|                     new TypeReference<MoodleUserDetails[]>() { |  | ||||||
|                     }); |  | ||||||
| 
 |  | ||||||
|             if (userDetails == null || userDetails.length <= 0) { |  | ||||||
|                 throw new RuntimeException("No user details on Moodle API request"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             final Map<String, String> additionalAttributes = new HashMap<>(); |  | ||||||
|             additionalAttributes.put("firstname", userDetails[0].firstname); |  | ||||||
|             additionalAttributes.put("lastname", userDetails[0].lastname); |  | ||||||
|             additionalAttributes.put("department", userDetails[0].department); |  | ||||||
|             additionalAttributes.put("firstaccess", String.valueOf(userDetails[0].firstaccess)); |  | ||||||
|             additionalAttributes.put("lastaccess", String.valueOf(userDetails[0].lastaccess)); |  | ||||||
|             additionalAttributes.put("auth", userDetails[0].auth); |  | ||||||
|             additionalAttributes.put("suspended", String.valueOf(userDetails[0].suspended)); |  | ||||||
|             additionalAttributes.put("confirmed", String.valueOf(userDetails[0].confirmed)); |  | ||||||
|             additionalAttributes.put("lang", userDetails[0].lang); |  | ||||||
|             additionalAttributes.put("theme", userDetails[0].theme); |  | ||||||
|             additionalAttributes.put("timezone", userDetails[0].timezone); |  | ||||||
|             additionalAttributes.put("description", userDetails[0].description); |  | ||||||
|             additionalAttributes.put("mailformat", String.valueOf(userDetails[0].mailformat)); |  | ||||||
|             additionalAttributes.put("descriptionformat", String.valueOf(userDetails[0].descriptionformat)); |  | ||||||
|             return new ExamineeAccountDetails( |  | ||||||
|                     userDetails[0].id, |  | ||||||
|                     userDetails[0].fullname, |  | ||||||
|                     userDetails[0].username, |  | ||||||
|                     userDetails[0].email, |  | ||||||
|                     additionalAttributes); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     LmsSetupTestResult initAPIAccess() { |     LmsSetupTestResult initAPIAccess() { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,282 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |  * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.function.Supplier; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | 
 | ||||||
|  | import org.apache.commons.lang3.StringUtils; | ||||||
|  | import org.joda.time.DateTime; | ||||||
|  | import org.springframework.cache.CacheManager; | ||||||
|  | import org.springframework.core.env.Environment; | ||||||
|  | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.async.AsyncService; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess; | ||||||
|  | 
 | ||||||
|  | public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements LmsAPITemplate { | ||||||
|  | 
 | ||||||
|  |     // TODO add needed dependencies here | ||||||
|  |     private final APITemplateDataSupplier apiTemplateDataSupplier; | ||||||
|  |     private final Long lmsSetupId; | ||||||
|  | 
 | ||||||
|  |     protected OlatLmsAPITemplate( | ||||||
|  |             // TODO if you need more dependencies inject them here and set the reference | ||||||
|  |             final APITemplateDataSupplier apiTemplateDataSupplier, | ||||||
|  |             final AsyncService asyncService, | ||||||
|  |             final Environment environment, | ||||||
|  |             final CacheManager cacheManager) { | ||||||
|  | 
 | ||||||
|  |         super(asyncService, environment, cacheManager); | ||||||
|  | 
 | ||||||
|  |         this.apiTemplateDataSupplier = apiTemplateDataSupplier; | ||||||
|  |         this.lmsSetupId = apiTemplateDataSupplier.getLmsSetup().id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public LmsType getType() { | ||||||
|  |         return LmsType.OPEN_OLAT; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public LmsSetup lmsSetup() { | ||||||
|  |         return this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Long getLmsSetupId() { | ||||||
|  |         return this.lmsSetupId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public LmsSetupTestResult testCourseAccessAPI() { | ||||||
|  |         final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings(); | ||||||
|  |         if (testLmsSetupSettings.hasAnyError()) { | ||||||
|  |             return testLmsSetupSettings; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // TODO check if the course API of the remote LMS is available | ||||||
|  |         // if not, create corresponding LmsSetupTestResult error | ||||||
|  | 
 | ||||||
|  |         return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public LmsSetupTestResult testCourseRestrictionAPI() { | ||||||
|  |         final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings(); | ||||||
|  |         if (testLmsSetupSettings.hasAnyError()) { | ||||||
|  |             return testLmsSetupSettings; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (LmsType.OPEN_OLAT.features.contains(Features.SEB_RESTRICTION)) { | ||||||
|  | 
 | ||||||
|  |             // TODO check if the course API of the remote LMS is available | ||||||
|  |             // if not, create corresponding LmsSetupTestResult error | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private LmsSetupTestResult testLmsSetupSettings() { | ||||||
|  | 
 | ||||||
|  |         final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); | ||||||
|  |         final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); | ||||||
|  |         final List<APIMessage> missingAttrs = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|  |         // Check given LMS URL | ||||||
|  |         if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) { | ||||||
|  |             missingAttrs.add(APIMessage.fieldValidationError( | ||||||
|  |                     LMS_SETUP.ATTR_LMS_URL, | ||||||
|  |                     "lmsSetup:lmsUrl:notNull")); | ||||||
|  |         } else { | ||||||
|  |             // Try to connect to the URL | ||||||
|  |             if (!Utils.pingHost(lmsSetup.lmsApiUrl)) { | ||||||
|  |                 missingAttrs.add(APIMessage.fieldValidationError( | ||||||
|  |                         LMS_SETUP.ATTR_LMS_URL, | ||||||
|  |                         "lmsSetup:lmsUrl:url.invalid")); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Client id is mandatory | ||||||
|  |         if (!lmsClientCredentials.hasClientId()) { | ||||||
|  |             missingAttrs.add(APIMessage.fieldValidationError( | ||||||
|  |                     LMS_SETUP.ATTR_LMS_CLIENTNAME, | ||||||
|  |                     "lmsSetup:lmsClientname:notNull")); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Client secret is mandatory | ||||||
|  |         if (!lmsClientCredentials.hasSecret()) { | ||||||
|  |             missingAttrs.add(APIMessage.fieldValidationError( | ||||||
|  |                     LMS_SETUP.ATTR_LMS_CLIENTSECRET, | ||||||
|  |                     "lmsSetup:lmsClientsecret:notNull")); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!missingAttrs.isEmpty()) { | ||||||
|  |             return LmsSetupTestResult.ofMissingAttributes(LmsType.OPEN_OLAT, missingAttrs); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<List<QuizData>> getQuizzes(final FilterMap filterMap) { | ||||||
|  |         return this | ||||||
|  |                 .protectedQuizzesRequest(filterMap) | ||||||
|  |                 .map(quizzes -> quizzes.stream() | ||||||
|  |                         .filter(LmsAPIService.quizFilterPredicate(filterMap)) | ||||||
|  |                         .collect(Collectors.toList())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<Collection<QuizData>> getQuizzes(final Set<String> ids) { | ||||||
|  |         return Result.tryCatch(() -> { | ||||||
|  |             final HashSet<String> leftIds = new HashSet<>(ids); | ||||||
|  |             final Collection<QuizData> result = new ArrayList<>(); | ||||||
|  |             ids.stream() | ||||||
|  |                     .map(super::getFromCache) | ||||||
|  |                     .forEach(q -> { | ||||||
|  |                         if (q != null) { | ||||||
|  |                             leftIds.remove(q.id); | ||||||
|  |                             result.add(q); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  | 
 | ||||||
|  |             if (!leftIds.isEmpty()) { | ||||||
|  |                 result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return result; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<QuizData> getQuiz(final String id) { | ||||||
|  |         final QuizData fromCache = super.getFromCache(id); | ||||||
|  |         if (fromCache != null) { | ||||||
|  |             return Result.of(fromCache); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return super.protectedQuizRequest(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Supplier<List<QuizData>> allQuizzesSupplier(final FilterMap filterMap) { | ||||||
|  | 
 | ||||||
|  |         final String quizName = filterMap.getString(QuizData.FILTER_ATTR_QUIZ_NAME); | ||||||
|  |         final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null; | ||||||
|  | 
 | ||||||
|  |         // TODO get all course / quiz data from remote LMS that matches the filter criteria. | ||||||
|  |         //      put loaded QuizData to the cache: super.putToCache(quizDataCollection); | ||||||
|  |         //      before returning it. | ||||||
|  | 
 | ||||||
|  |         return () -> { | ||||||
|  |             throw new RuntimeException("TODO"); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Supplier<Collection<QuizData>> quizzesSupplier(final Set<String> ids) { | ||||||
|  | 
 | ||||||
|  |         // TODO get all quiz / course data for specified identifiers from remote LMS | ||||||
|  |         //      and put it to the cache: super.putToCache(quizDataCollection); | ||||||
|  |         //      before returning it. | ||||||
|  | 
 | ||||||
|  |         return () -> { | ||||||
|  |             throw new RuntimeException("TODO"); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Supplier<QuizData> quizSupplier(final String id) { | ||||||
|  | 
 | ||||||
|  |         // TODO get the specified quiz / course data for specified identifier from remote LMS | ||||||
|  |         //      and put it to the cache: super.putToCache(quizDataCollection); | ||||||
|  |         //      before returning it. | ||||||
|  | 
 | ||||||
|  |         return () -> { | ||||||
|  |             throw new RuntimeException("TODO"); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Supplier<ExamineeAccountDetails> accountDetailsSupplier(final String examineeSessionId) { | ||||||
|  | 
 | ||||||
|  |         // TODO get the examinee's account details by the given examineeSessionId from remote LMS. | ||||||
|  |         //      Currently only the name is needed to display on monitoring view. | ||||||
|  | 
 | ||||||
|  |         return () -> { | ||||||
|  |             throw new RuntimeException("TODO"); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Supplier<Chapters> getCourseChaptersSupplier(final String courseId) { | ||||||
|  |         return () -> { | ||||||
|  |             throw new UnsupportedOperationException("not available yet"); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<SEBRestriction> getSEBClientRestriction(final Exam exam) { | ||||||
|  | 
 | ||||||
|  |         final String quizId = exam.externalId; | ||||||
|  | 
 | ||||||
|  |         // TODO get the SEB client restrictions that are currently set on the remote LMS for | ||||||
|  |         //      the given quiz / course derived from the given exam | ||||||
|  | 
 | ||||||
|  |         return Result.ofRuntimeError("TODO"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<SEBRestriction> applySEBClientRestriction( | ||||||
|  |             final String externalExamId, | ||||||
|  |             final SEBRestriction sebRestrictionData) { | ||||||
|  | 
 | ||||||
|  |         // TODO apply the given sebRestrictionData settings as current SEB client restriction setting | ||||||
|  |         //      to the remote LMS for the given quiz / course. | ||||||
|  |         //      Mainly SEBRestriction.configKeys and SEBRestriction.browserExamKeys | ||||||
|  | 
 | ||||||
|  |         return Result.ofRuntimeError("TODO"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<Exam> releaseSEBClientRestriction(final Exam exam) { | ||||||
|  | 
 | ||||||
|  |         final String quizId = exam.externalId; | ||||||
|  | 
 | ||||||
|  |         // TODO Release respectively delete all SEB client restrictions for the given | ||||||
|  |         //      course / quize on the remote LMS. | ||||||
|  | 
 | ||||||
|  |         return Result.ofRuntimeError("TODO"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |  * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat; | ||||||
|  | 
 | ||||||
|  | import org.springframework.context.annotation.Lazy; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplateFactory; | ||||||
|  | 
 | ||||||
|  | @Lazy | ||||||
|  | @Service | ||||||
|  | @WebServiceProfile | ||||||
|  | public class OlatLmsAPITemplateFactory implements LmsAPITemplateFactory { | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public LmsType lmsType() { | ||||||
|  |         return LmsType.OPEN_OLAT; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Result<LmsAPITemplate> create(final APITemplateDataSupplier apiTemplateDataSupplier) { | ||||||
|  |         // TODO Auto-generated method stub | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -22,6 +22,7 @@ import org.springframework.util.LinkedMultiValueMap; | ||||||
| import org.springframework.util.MultiValueMap; | import org.springframework.util.MultiValueMap; | ||||||
| 
 | 
 | ||||||
| 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.AsyncService; | import ch.ethz.seb.sebserver.gbl.async.AsyncService; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; | ||||||
|  | @ -74,7 +75,7 @@ public class MoodleCourseAccessTest { | ||||||
|                 new JSONMapper(), |                 new JSONMapper(), | ||||||
|                 moodleRestTemplateFactory, |                 moodleRestTemplateFactory, | ||||||
|                 null, |                 null, | ||||||
|                 mock(AsyncService.class), |                 new AsyncService(new AsyncRunner()), | ||||||
|                 this.env); |                 this.env); | ||||||
| 
 | 
 | ||||||
|         final String examId = "123"; |         final String examId = "123"; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti