From cdd393aecdfc9111c0690a9bde68300473dff2fb Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Wed, 28 Jul 2021 15:21:22 +0200 Subject: [PATCH 1/7] Implement OlatLms integration --- .../lms/impl/olat/OlatLmsAPITemplate.java | 286 ++++++++++++------ .../lms/impl/olat/OlatLmsData.java | 82 +++++ .../lms/impl/olat/OlatLmsRestTemplate.java | 59 ++++ 3 files changed, 332 insertions(+), 95 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 7ecfa6a2..98d1da1e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -10,19 +10,33 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.cache.CacheManager; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.core.env.Environment; import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.web.client.RestTemplate; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.web.util.DefaultUriBuilderFactory; +import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; + import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import ch.ethz.seb.sebserver.gbl.api.APIMessage; @@ -47,19 +61,21 @@ 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; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.AssessmentData; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.UserData; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.RestrictionData; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.RestrictionDataPost; public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements LmsAPITemplate { - // TODO add needed dependencies here + private static final Logger log = LoggerFactory.getLogger(OlatLmsAPITemplate.class); + private final ClientHttpRequestFactoryService clientHttpRequestFactoryService; private final ClientCredentialService clientCredentialService; private final APITemplateDataSupplier apiTemplateDataSupplier; private final Long lmsSetupId; protected OlatLmsAPITemplate( - - // TODO if you need more dependencies inject them here and set the reference - final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final ClientCredentialService clientCredentialService, final APITemplateDataSupplier apiTemplateDataSupplier, @@ -95,32 +111,33 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings(); if (testLmsSetupSettings.hasAnyError()) { return testLmsSetupSettings; - } else { - } - - // TODO check if the course API of the remote LMS is available - // if not, create corresponding LmsSetupTestResult error - return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, "TODO: implement LMS access check"); - - //return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); + // TODO: improve error handling + try { + final RestTemplate restTemplate = this.getRestTemplate().get(); + } + catch (Exception e) { + return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, "Unspecific error connecting to OLAT API"); + } + return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); } @Override public LmsSetupTestResult testCourseRestrictionAPI() { - final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings(); + // TODO: Any reason to implement a separate check or is this good enough? + return testCourseAccessAPI(); + + /*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() { @@ -207,62 +224,96 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override protected Supplier> allQuizzesSupplier(final FilterMap filterMap) { - - @SuppressWarnings("unused") - final String quizName = filterMap.getString(QuizData.FILTER_ATTR_QUIZ_NAME); - @SuppressWarnings("unused") - final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null; - return () -> { - - // TODO Get all course / quiz data from remote LMS that matches the filter criteria. - // If the LMS API uses paging, go through all pages using the filter criteria - // and collect the course data. - // Transform the data from courses / quizzes from LMS into QuizData objects - // Put loaded QuizData objects to the cache: super.putToCache(quizDataCollection); - // before returning it. - - throw new RuntimeException("TODO"); + List res = getRestTemplate() + .map(t -> this.collectAllQuizzes(t, filterMap)) + .getOrThrow(); + super.putToCache(res); + return res; }; } + private String examUrl(long olatTestId) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + // TODO: at the moment, we don't know olatTestId because we get the assessment mode id (a.key), not the test id. + return lmsSetup.lmsApiUrl + "/auth/RepositoryEntry/" + olatTestId; + } + + private List collectAllQuizzes(final OlatLmsRestTemplate restTemplate, final FilterMap filterMap) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final String quizName = filterMap.getString(QuizData.FILTER_ATTR_QUIZ_NAME); + final DateTime quizFromTime = (filterMap != null) ? filterMap.getQuizFromTime() : null; + final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1; + + String url = "/restapi/assessment_modes/seb?"; + if (fromCutTime != -1) { url = String.format("%sdateFrom=%s&", url, fromCutTime); } + if (quizName != null) { url = String.format("%sname=%s&", url, quizName); } + + final List as = this.apiGetList(restTemplate, url, new ParameterizedTypeReference>(){}); + return as.stream() + .map(a -> new QuizData( + String.format("%d", a.key), + lmsSetup.getInstitutionId(), + lmsSetup.id, + lmsSetup.getLmsType(), + a.name, + a.description, + Utils.toDateTimeUTC(a.dateFrom), + Utils.toDateTimeUTC(a.dateTo), + examUrl(a.key), + new HashMap())) + .collect(Collectors.toList()); + } + @Override protected Supplier> quizzesSupplier(final Set ids) { - - return () -> { - - // TODO get all quiz / course data for specified identifiers from remote LMS - // Transform the data from courses / quizzes from LMS into QuizData objects - // and put it to the cache: super.putToCache(quizDataCollection); - // before returning it. - - throw new RuntimeException("TODO"); - }; + return () -> ids.stream().map(id -> quizSupplier(id).get()).collect(Collectors.toList()); } @Override protected Supplier quizSupplier(final String id) { - - return () -> { - - // 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. - - throw new RuntimeException("TODO"); - }; + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + return () -> getRestTemplate() + .map(t -> this.quizById(t, id)) + .getOrThrow(); } + private QuizData quizById(final OlatLmsRestTemplate restTemplate, final String id) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final String url = String.format("/restapi/assessment_modes/%s", id); + final AssessmentData a = this.apiGet(restTemplate, url, AssessmentData.class); + return new QuizData( + String.format("%d", a.key), + lmsSetup.getInstitutionId(), + lmsSetup.id, + lmsSetup.getLmsType(), + a.name, + a.description, + Utils.toDateTimeUTC(a.dateFrom), + Utils.toDateTimeUTC(a.dateTo), + examUrl(a.key), + new HashMap()); + } + + private ExamineeAccountDetails getExamineeById(final RestTemplate restTemplate, final String id) { + final String url = String.format("/restapi/users/%s/name_username", id); + final UserData u = this.apiGet(restTemplate, url, UserData.class); + final Map attrs = new HashMap<>(); + return new ExamineeAccountDetails( + String.valueOf(u.key), + u.lastName + ", " + u.firstName, + u.username, + // TODO: other placeholder value? null? + "OLAT does not provide email addresses", + attrs); + } + + @Override - protected Supplier accountDetailsSupplier(final String examineeSessionId) { - - return () -> { - - // 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. - - throw new RuntimeException("TODO"); - }; + protected Supplier accountDetailsSupplier(final String id) { + return () -> getRestTemplate() + .map(t -> this.getExamineeById(t, id)) + .getOrThrow(); } @Override @@ -272,33 +323,43 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm }; } + private SEBRestriction getRestrictionForAssignmentId(final RestTemplate restTemplate, String id) { + final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); + final RestrictionData r = this.apiGet(restTemplate, url, RestrictionData.class); + return new SEBRestriction(Long.valueOf(id), r.configKeys, r.browserExamKeys, new HashMap()); + } + + private SEBRestriction setRestrictionForAssignmentId(final RestTemplate restTemplate, String id, SEBRestriction restriction) { + final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); + final RestrictionDataPost post = new RestrictionDataPost(); + post.browserExamKeys = new ArrayList<>(restriction.browserExamKeys); + post.configKeys = new ArrayList<>(restriction.configKeys); + final RestrictionData r = this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class); + return new SEBRestriction(Long.valueOf(id), r.configKeys, r.browserExamKeys, new HashMap()); + } + + private SEBRestriction deleteRestrictionForAssignmentId(final RestTemplate restTemplate, String id) { + final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); + final RestrictionData r = this.apiDelete(restTemplate, url, RestrictionData.class); + // OLAT returns RestrictionData with null values upon deletion. + // We return it here for consistency, even though SEB server does not need it + return new SEBRestriction(Long.valueOf(id), r.configKeys, r.browserExamKeys, new HashMap()); + } + @Override public Result getSEBClientRestriction(final Exam exam) { - @SuppressWarnings("unused") - final String quizId = exam.externalId; - - return Result.tryCatch(() -> { - - // TODO get the SEB client restrictions that are currently set on the remote LMS for - // the given quiz / course derived from the given exam - - throw new RuntimeException("TODO"); - }); + return Result.of(getRestTemplate() + .map(t -> this.getRestrictionForAssignmentId(t, exam.externalId)) + .getOrThrow()); } @Override public Result applySEBClientRestriction( final String externalExamId, final SEBRestriction sebRestrictionData) { - - return Result.tryCatch(() -> { - - // 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 - - throw new RuntimeException("TODO"); - }); + return Result.of(getRestTemplate() + .map(t -> this.setRestrictionForAssignmentId(t, externalExamId, sebRestrictionData)) + .getOrThrow()); } @Override @@ -306,22 +367,57 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @SuppressWarnings("unused") final String quizId = exam.externalId; - return Result.tryCatch(() -> { - - // TODO Release respectively delete all SEB client restrictions for the given - // course / quize on the remote LMS. - - throw new RuntimeException("TODO"); - }); + return Result.of(getRestTemplate() + .map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId)) + .map(x -> exam) + .getOrThrow()); } - // TODO: This is an example of how to create a RestTemplate for the service to access the LMS API - // The example deals with a Http based API that is secured by an OAuth2 client-credential flow. - // You might need some different template, then you have to adapt this code - // To your needs. - @SuppressWarnings("unused") - private OAuth2RestTemplate createRestTemplate(final String accessTokenRequestPath) { + private T apiGet(final RestTemplate restTemplate, String url, Class type) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final ResponseEntity res = restTemplate.exchange( + lmsSetup.lmsApiUrl + url, + HttpMethod.GET, + HttpEntity.EMPTY, + type); + return res.getBody(); + } + private List apiGetList(final RestTemplate restTemplate, String url, ParameterizedTypeReference> type) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final ResponseEntity> res = restTemplate.exchange( + lmsSetup.lmsApiUrl + url, + HttpMethod.GET, + HttpEntity.EMPTY, + type); + return res.getBody(); + } + + private R apiPost(final RestTemplate restTemplate, String url, P post, Class

postType, Class responseType) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set("content-type", "application/json"); + HttpEntity

requestEntity = new HttpEntity<>(post, httpHeaders); + final ResponseEntity res = restTemplate.exchange( + lmsSetup.lmsApiUrl + url, + HttpMethod.POST, + requestEntity, + responseType); + return res.getBody(); + } + + private T apiDelete(final RestTemplate restTemplate, String url, Class type) { + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final ResponseEntity res = restTemplate.exchange( + lmsSetup.lmsApiUrl + url, + HttpMethod.DELETE, + HttpEntity.EMPTY, + type); + return res.getBody(); + } + + private Result getRestTemplate() { + // TODO: cache/reuse authenticated template for more than 1 request? final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); @@ -332,7 +428,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm .getOrThrow(); final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); - details.setAccessTokenUri(lmsSetup.lmsApiUrl + accessTokenRequestPath); + details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/"); details.setClientId(plainClientId.toString()); details.setClientSecret(plainClientSecret.toString()); @@ -340,10 +436,10 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm .getClientHttpRequestFactory(proxyData) .getOrThrow(); - final OAuth2RestTemplate template = new OAuth2RestTemplate(details); + final OlatLmsRestTemplate template = new OlatLmsRestTemplate(details); template.setRequestFactory(clientHttpRequestFactory); - return template; + return Result.of(template); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java new file mode 100644 index 00000000..9fe7b961 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +public final class OlatLmsData { + + @JsonIgnoreProperties(ignoreUnknown = true) + static public final class AssessmentData { + /* OLAT API example: + { + "key":7405568, + "name":"Test1", + "description":"", + "courseName":"test", + "dateFrom":1626515100000, + "dateTo":1626523260000 + } + */ + public long key; + public String name; + public String description; + public String courseName; + public long dateFrom; + public long dateTo; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class UserData { + /* OLAT API example: + { + "firstName": "OpenOLAT", + "key": 360448, + "lastName": "Administrator", + "username": "administrator" + } + */ + public long key; + public String firstName; + public String lastName; + public String username; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class RestrictionData { + /* OLAT API example: + { + "browserExamKeys": [ "1" ], + "configKeys": null, + "key": 8028160 + } + */ + public long key; + public List browserExamKeys; + public List configKeys; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class RestrictionDataPost { + /* OLAT API example: + { + "configKeys": ["a", "b"], + "browserExamKeys": ["1", "2"] + } + */ + public List browserExamKeys; + public List configKeys; + } + +} + + diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java new file mode 100644 index 00000000..65112dd6 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -0,0 +1,59 @@ +/* + * 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.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.web.client.RestTemplate; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +public class OlatLmsRestTemplate extends RestTemplate { + + private static final Logger log = LoggerFactory.getLogger(OlatLmsRestTemplate.class); + + public String token; + + public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) { + super(); + + // Authenticate with OLAT and store the received X-OLAT-TOKEN + final String authUrl = String.format("%s%s?password=%s", + details.getAccessTokenUri(), + details.getClientId(), + details.getClientSecret()); + final HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set("accept", "application/json"); + ResponseEntity response = this.getForEntity(authUrl, String.class); + HttpHeaders responseHeaders = response.getHeaders(); + log.debug("OLAT Auth Response Headers: {}", responseHeaders); + token = responseHeaders.getFirst("X-OLAT-TOKEN"); + + // Add X-OLAT-TOKEN request header to every request done using this RestTemplate + this.getInterceptors().add(new ClientHttpRequestInterceptor(){ + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().set("X-OLAT-TOKEN", token); + request.getHeaders().set("accept", "application/json"); + HttpHeaders responseHeaders = response.getHeaders(); + return execution.execute(request, body); + } + }); + } +} + From ae5149226a5fddebef4cf908963f430eb83fbea9 Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Wed, 28 Jul 2021 22:47:49 +0200 Subject: [PATCH 2/7] generate proper exam url using updated OLAT api --- .../lms/impl/olat/OlatLmsAPITemplate.java | 8 ++++---- .../servicelayer/lms/impl/olat/OlatLmsData.java | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 98d1da1e..70f38b23 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -252,7 +252,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final List as = this.apiGetList(restTemplate, url, new ParameterizedTypeReference>(){}); return as.stream() .map(a -> new QuizData( - String.format("%d", a.key), + String.format("%d", a.assessmentModeKey), lmsSetup.getInstitutionId(), lmsSetup.id, lmsSetup.getLmsType(), @@ -260,7 +260,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm a.description, Utils.toDateTimeUTC(a.dateFrom), Utils.toDateTimeUTC(a.dateTo), - examUrl(a.key), + examUrl(a.repositoryEntryKey), new HashMap())) .collect(Collectors.toList()); } @@ -283,7 +283,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final String url = String.format("/restapi/assessment_modes/%s", id); final AssessmentData a = this.apiGet(restTemplate, url, AssessmentData.class); return new QuizData( - String.format("%d", a.key), + String.format("%d", a.assessmentModeKey), lmsSetup.getInstitutionId(), lmsSetup.id, lmsSetup.getLmsType(), @@ -291,7 +291,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm a.description, Utils.toDateTimeUTC(a.dateFrom), Utils.toDateTimeUTC(a.dateTo), - examUrl(a.key), + examUrl(a.repositoryEntryKey), new HashMap()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java index 9fe7b961..5698080b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java @@ -19,15 +19,17 @@ public final class OlatLmsData { static public final class AssessmentData { /* OLAT API example: { - "key":7405568, - "name":"Test1", - "description":"", - "courseName":"test", - "dateFrom":1626515100000, - "dateTo":1626523260000 + "courseName": "course 1", + "dateFrom": 1624420800000, + "dateTo": 1624658400000, + "description": "", + "assessmentModeKey": 6356992, + “repositoryEntryKey”: 462324, + "name": "SEB test" } */ - public long key; + public long assessmentModeKey; + public long repositoryEntryKey; public String name; public String description; public String courseName; From 59da4bcf4e24ca8a72f6e00efc6029bbb5ce86eb Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Fri, 30 Jul 2021 13:23:25 +0200 Subject: [PATCH 3/7] minor improvements, re-use OlatLmsRestTemplate with existing token --- .../lms/impl/olat/OlatLmsAPITemplate.java | 90 ++++++++----------- .../lms/impl/olat/OlatLmsData.java | 4 +- .../lms/impl/olat/OlatLmsRestTemplate.java | 59 +++++++----- 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 70f38b23..68cb949d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -75,6 +75,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm private final APITemplateDataSupplier apiTemplateDataSupplier; private final Long lmsSetupId; + private OlatLmsRestTemplate cachedRestTemplate; + protected OlatLmsAPITemplate( final ClientHttpRequestFactoryService clientHttpRequestFactoryService, final ClientCredentialService clientCredentialService, @@ -112,9 +114,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm if (testLmsSetupSettings.hasAnyError()) { return testLmsSetupSettings; } - // TODO: improve error handling try { - final RestTemplate restTemplate = this.getRestTemplate().get(); + this.getRestTemplate().get(); } catch (Exception e) { return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, "Unspecific error connecting to OLAT API"); @@ -124,20 +125,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override public LmsSetupTestResult testCourseRestrictionAPI() { - // TODO: Any reason to implement a separate check or is this good enough? return testCourseAccessAPI(); - - /*final LmsSetupTestResult testLmsSetupSettings = testLmsSetupSettings(); - if (testLmsSetupSettings.hasAnyError()) { - return testLmsSetupSettings; - } - - if (LmsType.OPEN_OLAT.features.contains(Features.SEB_RESTRICTION)) { - - } - - return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); - */ } private LmsSetupTestResult testLmsSetupSettings() { @@ -233,10 +221,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm }; } - private String examUrl(long olatTestId) { + private String examUrl(long olatRepositoryId) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); - // TODO: at the moment, we don't know olatTestId because we get the assessment mode id (a.key), not the test id. - return lmsSetup.lmsApiUrl + "/auth/RepositoryEntry/" + olatTestId; + return lmsSetup.lmsApiUrl + "/auth/RepositoryEntry/" + olatRepositoryId; } private List collectAllQuizzes(final OlatLmsRestTemplate restTemplate, final FilterMap filterMap) { @@ -251,8 +238,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final List as = this.apiGetList(restTemplate, url, new ParameterizedTypeReference>(){}); return as.stream() - .map(a -> new QuizData( - String.format("%d", a.assessmentModeKey), + .map(a -> { + return new QuizData( + String.format("%d", a.key), lmsSetup.getInstitutionId(), lmsSetup.id, lmsSetup.getLmsType(), @@ -261,7 +249,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm Utils.toDateTimeUTC(a.dateFrom), Utils.toDateTimeUTC(a.dateTo), examUrl(a.repositoryEntryKey), - new HashMap())) + new HashMap());}) .collect(Collectors.toList()); } @@ -283,7 +271,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final String url = String.format("/restapi/assessment_modes/%s", id); final AssessmentData a = this.apiGet(restTemplate, url, AssessmentData.class); return new QuizData( - String.format("%d", a.assessmentModeKey), + String.format("%d", a.key), lmsSetup.getInstitutionId(), lmsSetup.id, lmsSetup.getLmsType(), @@ -303,8 +291,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm String.valueOf(u.key), u.lastName + ", " + u.firstName, u.username, - // TODO: other placeholder value? null? - "OLAT does not provide email addresses", + "OLAT API does not provide email addresses", attrs); } @@ -348,18 +335,16 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override public Result getSEBClientRestriction(final Exam exam) { - return Result.of(getRestTemplate() - .map(t -> this.getRestrictionForAssignmentId(t, exam.externalId)) - .getOrThrow()); + return getRestTemplate() + .map(t -> this.getRestrictionForAssignmentId(t, exam.externalId)); } @Override public Result applySEBClientRestriction( final String externalExamId, final SEBRestriction sebRestrictionData) { - return Result.of(getRestTemplate() - .map(t -> this.setRestrictionForAssignmentId(t, externalExamId, sebRestrictionData)) - .getOrThrow()); + return getRestTemplate() + .map(t -> this.setRestrictionForAssignmentId(t, externalExamId, sebRestrictionData)); } @Override @@ -367,10 +352,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @SuppressWarnings("unused") final String quizId = exam.externalId; - return Result.of(getRestTemplate() + return getRestTemplate() .map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId)) - .map(x -> exam) - .getOrThrow()); + .map(x -> exam); } private T apiGet(final RestTemplate restTemplate, String url, Class type) { @@ -417,29 +401,33 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm } private Result getRestTemplate() { - // TODO: cache/reuse authenticated template for more than 1 request? - final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); - final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); - final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); + return Result.tryCatch(() -> { + if (this.cachedRestTemplate != null) { return this.cachedRestTemplate; } + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); + final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); + final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); - final CharSequence plainClientId = credentials.clientId; - final CharSequence plainClientSecret = this.clientCredentialService - .getPlainClientSecret(credentials) - .getOrThrow(); + final CharSequence plainClientId = credentials.clientId; + final CharSequence plainClientSecret = this.clientCredentialService + .getPlainClientSecret(credentials) + .getOrThrow(); - final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); - details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/"); - details.setClientId(plainClientId.toString()); - details.setClientSecret(plainClientSecret.toString()); + final ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); + details.setAccessTokenUri(lmsSetup.lmsApiUrl + "/restapi/auth/"); + details.setClientId(plainClientId.toString()); + details.setClientSecret(plainClientSecret.toString()); - final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService - .getClientHttpRequestFactory(proxyData) - .getOrThrow(); + final ClientHttpRequestFactory clientHttpRequestFactory = this.clientHttpRequestFactoryService + .getClientHttpRequestFactory(proxyData) + .getOrThrow(); - final OlatLmsRestTemplate template = new OlatLmsRestTemplate(details); - template.setRequestFactory(clientHttpRequestFactory); + final OlatLmsRestTemplate template = new OlatLmsRestTemplate(details); + template.setRequestFactory(clientHttpRequestFactory); - return Result.of(template); + this.cachedRestTemplate = template; + + return this.cachedRestTemplate; + }); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java index 5698080b..9f7dff08 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsData.java @@ -23,12 +23,12 @@ public final class OlatLmsData { "dateFrom": 1624420800000, "dateTo": 1624658400000, "description": "", - "assessmentModeKey": 6356992, + "key": 6356992, “repositoryEntryKey”: 462324, "name": "SEB test" } */ - public long assessmentModeKey; + public long key; public long repositoryEntryKey; public String name; public String description; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java index 65112dd6..18e2a7a0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory; import org.springframework.web.client.RestTemplate; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; import org.springframework.http.HttpRequest; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; @@ -25,35 +26,53 @@ import org.springframework.http.client.ClientHttpResponse; public class OlatLmsRestTemplate extends RestTemplate { - private static final Logger log = LoggerFactory.getLogger(OlatLmsRestTemplate.class); + private static final Logger log = LoggerFactory.getLogger(OlatLmsRestTemplate.class); - public String token; + public String token; + private ClientCredentialsResourceDetails details; - public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) { + public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) { super(); - - // Authenticate with OLAT and store the received X-OLAT-TOKEN - final String authUrl = String.format("%s%s?password=%s", - details.getAccessTokenUri(), - details.getClientId(), - details.getClientSecret()); - final HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.set("accept", "application/json"); - ResponseEntity response = this.getForEntity(authUrl, String.class); - HttpHeaders responseHeaders = response.getHeaders(); - log.debug("OLAT Auth Response Headers: {}", responseHeaders); - token = responseHeaders.getFirst("X-OLAT-TOKEN"); + this.details = details; + authenticate(); // Add X-OLAT-TOKEN request header to every request done using this RestTemplate this.getInterceptors().add(new ClientHttpRequestInterceptor(){ @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().set("X-OLAT-TOKEN", token); - request.getHeaders().set("accept", "application/json"); - HttpHeaders responseHeaders = response.getHeaders(); - return execution.execute(request, body); + request.getHeaders().set("accept", "application/json"); + // if we don't have a token (this is normal during authentication), just do the call + if (token == null) { return execution.execute(request, body); } + // otherwise, add the X-OLAT-TOKEN + request.getHeaders().set("X-OLAT-TOKEN", token); + ClientHttpResponse response = execution.execute(request, body); + log.debug("OLAT [regular API call] Response Headers: {}", response.getHeaders()); + // If we get a 401, re-authenticate and try once more + if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { + authenticate(); + request.getHeaders().set("X-OLAT-TOKEN", token); + response = execution.execute(request, body); + log.debug("OLAT [retry API call] Response Headers: {}", response.getHeaders()); + } + return response; } }); - } + } + + private void authenticate() { + // Authenticate with OLAT and store the received X-OLAT-TOKEN + token = null; + final String authUrl = String.format("%s%s?password=%s", + details.getAccessTokenUri(), + details.getClientId(), + details.getClientSecret()); + final HttpHeaders httpHeaders = new HttpHeaders(); + ResponseEntity response = this.getForEntity(authUrl, String.class); + HttpHeaders responseHeaders = response.getHeaders(); + log.debug("OLAT [authenticate] Response Headers: {}", responseHeaders); + token = responseHeaders.getFirst("X-OLAT-TOKEN"); + } + + } From 0029cd4ec32511ad805ad12a11ba7f49ab762b5f Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Fri, 30 Jul 2021 14:19:53 +0200 Subject: [PATCH 4/7] get OLAT token lazily when first needed, not upon instantiation --- .../lms/impl/olat/OlatLmsRestTemplate.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java index 18e2a7a0..fd2982e5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -34,25 +34,26 @@ public class OlatLmsRestTemplate extends RestTemplate { public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) { super(); this.details = details; - authenticate(); // Add X-OLAT-TOKEN request header to every request done using this RestTemplate this.getInterceptors().add(new ClientHttpRequestInterceptor(){ @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().set("accept", "application/json"); - // if we don't have a token (this is normal during authentication), just do the call - if (token == null) { return execution.execute(request, body); } + // if there's no token, authenticate first + if (token == null) { authenticate(); } + // when authenticating, just do a normal call + else if (token == "authenticating") { return execution.execute(request, body); } // otherwise, add the X-OLAT-TOKEN + request.getHeaders().set("accept", "application/json"); request.getHeaders().set("X-OLAT-TOKEN", token); ClientHttpResponse response = execution.execute(request, body); - log.debug("OLAT [regular API call] Response Headers: {}", response.getHeaders()); + log.debug("OLAT [regular API call] {} Headers: {}", response.getStatusCode(), response.getHeaders()); // If we get a 401, re-authenticate and try once more if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { authenticate(); request.getHeaders().set("X-OLAT-TOKEN", token); response = execution.execute(request, body); - log.debug("OLAT [retry API call] Response Headers: {}", response.getHeaders()); + log.debug("OLAT [retry API call] {} Headers: {}", response.getStatusCode(), response.getHeaders()); } return response; } @@ -61,16 +62,21 @@ public class OlatLmsRestTemplate extends RestTemplate { private void authenticate() { // Authenticate with OLAT and store the received X-OLAT-TOKEN - token = null; + token = "authenticating"; final String authUrl = String.format("%s%s?password=%s", details.getAccessTokenUri(), details.getClientId(), details.getClientSecret()); - final HttpHeaders httpHeaders = new HttpHeaders(); - ResponseEntity response = this.getForEntity(authUrl, String.class); - HttpHeaders responseHeaders = response.getHeaders(); - log.debug("OLAT [authenticate] Response Headers: {}", responseHeaders); - token = responseHeaders.getFirst("X-OLAT-TOKEN"); + try { + ResponseEntity response = this.getForEntity(authUrl, String.class); + HttpHeaders responseHeaders = response.getHeaders(); + log.debug("OLAT [authenticate] {} Headers: {}", response.getStatusCode(), responseHeaders); + token = responseHeaders.getFirst("X-OLAT-TOKEN"); + } + catch (Exception e) { + token = null; + throw e; + } } From 8376a1c3fd2eb9a9d515776275017fdc5e3729e8 Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Tue, 3 Aug 2021 07:27:57 +0200 Subject: [PATCH 5/7] minor cleanups --- .../servicelayer/lms/impl/olat/OlatLmsAPITemplate.java | 7 +++---- .../servicelayer/lms/impl/olat/OlatLmsRestTemplate.java | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 68cb949d..92dd4b55 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -118,7 +118,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm this.getRestTemplate().get(); } catch (Exception e) { - return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, "Unspecific error connecting to OLAT API"); + log.error("Failed to access OLAT course API: ", e); + return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, e.getMessage()); } return LmsSetupTestResult.ofOkay(LmsType.OPEN_OLAT); } @@ -349,9 +350,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override public Result releaseSEBClientRestriction(final Exam exam) { - @SuppressWarnings("unused") final String quizId = exam.externalId; - return getRestTemplate() .map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId)) .map(x -> exam); @@ -403,6 +402,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm private Result getRestTemplate() { return Result.tryCatch(() -> { if (this.cachedRestTemplate != null) { return this.cachedRestTemplate; } + final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials(); final ProxyData proxyData = this.apiTemplateDataSupplier.getProxyData(); @@ -425,7 +425,6 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm template.setRequestFactory(clientHttpRequestFactory); this.cachedRestTemplate = template; - return this.cachedRestTemplate; }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java index fd2982e5..1f8e11d5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -28,7 +28,7 @@ public class OlatLmsRestTemplate extends RestTemplate { private static final Logger log = LoggerFactory.getLogger(OlatLmsRestTemplate.class); - public String token; + private String token; private ClientCredentialsResourceDetails details; public OlatLmsRestTemplate(ClientCredentialsResourceDetails details) { @@ -68,8 +68,8 @@ public class OlatLmsRestTemplate extends RestTemplate { details.getClientId(), details.getClientSecret()); try { - ResponseEntity response = this.getForEntity(authUrl, String.class); - HttpHeaders responseHeaders = response.getHeaders(); + final ResponseEntity response = this.getForEntity(authUrl, String.class); + final HttpHeaders responseHeaders = response.getHeaders(); log.debug("OLAT [authenticate] {} Headers: {}", response.getStatusCode(), responseHeaders); token = responseHeaders.getFirst("X-OLAT-TOKEN"); } From d627bb7edbed93c96c7997818257c560dede2069 Mon Sep 17 00:00:00 2001 From: Carol Alexandru Date: Tue, 3 Aug 2021 09:52:02 +0200 Subject: [PATCH 6/7] fix silly beginner mistake --- .../servicelayer/lms/impl/olat/OlatLmsRestTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java index 1f8e11d5..f270a648 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsRestTemplate.java @@ -42,7 +42,7 @@ public class OlatLmsRestTemplate extends RestTemplate { // if there's no token, authenticate first if (token == null) { authenticate(); } // when authenticating, just do a normal call - else if (token == "authenticating") { return execution.execute(request, body); } + else if (token.equals("authenticating")) { return execution.execute(request, body); } // otherwise, add the X-OLAT-TOKEN request.getHeaders().set("accept", "application/json"); request.getHeaders().set("X-OLAT-TOKEN", token); From d1685f4675ce770c66675bc196a3ac2fc9280e0f Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 12 Aug 2021 10:55:28 +0200 Subject: [PATCH 7/7] some code cleanup --- .../lms/impl/olat/OlatLmsAPITemplate.java | 122 +++++++++--------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java index 92dd4b55..27408b08 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java @@ -10,33 +10,28 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.cache.CacheManager; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.env.Environment; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.core.env.Environment; import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.web.util.DefaultUriBuilderFactory; -import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; - +import org.springframework.web.client.RestTemplate; import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService; import ch.ethz.seb.sebserver.gbl.api.APIMessage; @@ -50,7 +45,6 @@ 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; @@ -62,9 +56,9 @@ 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; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.AssessmentData; -import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.UserData; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.RestrictionData; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.RestrictionDataPost; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat.OlatLmsData.UserData; public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements LmsAPITemplate { @@ -116,8 +110,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm } try { this.getRestTemplate().get(); - } - catch (Exception e) { + } catch (final Exception e) { log.error("Failed to access OLAT course API: ", e); return LmsSetupTestResult.ofQuizAccessAPIError(LmsType.OPEN_OLAT, e.getMessage()); } @@ -214,15 +207,15 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override protected Supplier> allQuizzesSupplier(final FilterMap filterMap) { return () -> { - List res = getRestTemplate() - .map(t -> this.collectAllQuizzes(t, filterMap)) - .getOrThrow(); - super.putToCache(res); - return res; + final List res = getRestTemplate() + .map(t -> this.collectAllQuizzes(t, filterMap)) + .getOrThrow(); + super.putToCache(res); + return res; }; } - private String examUrl(long olatRepositoryId) { + private String examUrl(final long olatRepositoryId) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); return lmsSetup.lmsApiUrl + "/auth/RepositoryEntry/" + olatRepositoryId; } @@ -234,24 +227,31 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final long fromCutTime = (quizFromTime != null) ? Utils.toUnixTimeInSeconds(quizFromTime) : -1; String url = "/restapi/assessment_modes/seb?"; - if (fromCutTime != -1) { url = String.format("%sdateFrom=%s&", url, fromCutTime); } - if (quizName != null) { url = String.format("%sname=%s&", url, quizName); } + if (fromCutTime != -1) { + url = String.format("%sdateFrom=%s&", url, fromCutTime); + } + if (quizName != null) { + url = String.format("%sname=%s&", url, quizName); + } - final List as = this.apiGetList(restTemplate, url, new ParameterizedTypeReference>(){}); + final List as = + this.apiGetList(restTemplate, url, new ParameterizedTypeReference>() { + }); return as.stream() - .map(a -> { - return new QuizData( - String.format("%d", a.key), - lmsSetup.getInstitutionId(), - lmsSetup.id, - lmsSetup.getLmsType(), - a.name, - a.description, - Utils.toDateTimeUTC(a.dateFrom), - Utils.toDateTimeUTC(a.dateTo), - examUrl(a.repositoryEntryKey), - new HashMap());}) - .collect(Collectors.toList()); + .map(a -> { + return new QuizData( + String.format("%d", a.key), + lmsSetup.getInstitutionId(), + lmsSetup.id, + lmsSetup.getLmsType(), + a.name, + a.description, + Utils.toDateTimeUTC(a.dateFrom), + Utils.toDateTimeUTC(a.dateTo), + examUrl(a.repositoryEntryKey), + new HashMap()); + }) + .collect(Collectors.toList()); } @Override @@ -261,10 +261,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override protected Supplier quizSupplier(final String id) { - final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); return () -> getRestTemplate() - .map(t -> this.quizById(t, id)) - .getOrThrow(); + .map(t -> this.quizById(t, id)) + .getOrThrow(); } private QuizData quizById(final OlatLmsRestTemplate restTemplate, final String id) { @@ -289,14 +288,13 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm final UserData u = this.apiGet(restTemplate, url, UserData.class); final Map attrs = new HashMap<>(); return new ExamineeAccountDetails( - String.valueOf(u.key), - u.lastName + ", " + u.firstName, - u.username, - "OLAT API does not provide email addresses", - attrs); + String.valueOf(u.key), + u.lastName + ", " + u.firstName, + u.username, + "OLAT API does not provide email addresses", + attrs); } - @Override protected Supplier accountDetailsSupplier(final String id) { return () -> getRestTemplate() @@ -311,22 +309,27 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm }; } - private SEBRestriction getRestrictionForAssignmentId(final RestTemplate restTemplate, String id) { + private SEBRestriction getRestrictionForAssignmentId(final RestTemplate restTemplate, final String id) { final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); final RestrictionData r = this.apiGet(restTemplate, url, RestrictionData.class); return new SEBRestriction(Long.valueOf(id), r.configKeys, r.browserExamKeys, new HashMap()); } - private SEBRestriction setRestrictionForAssignmentId(final RestTemplate restTemplate, String id, SEBRestriction restriction) { + private SEBRestriction setRestrictionForAssignmentId( + final RestTemplate restTemplate, + final String id, + final SEBRestriction restriction) { + final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); final RestrictionDataPost post = new RestrictionDataPost(); post.browserExamKeys = new ArrayList<>(restriction.browserExamKeys); post.configKeys = new ArrayList<>(restriction.configKeys); - final RestrictionData r = this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class); + final RestrictionData r = + this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class); return new SEBRestriction(Long.valueOf(id), r.configKeys, r.browserExamKeys, new HashMap()); } - private SEBRestriction deleteRestrictionForAssignmentId(final RestTemplate restTemplate, String id) { + private SEBRestriction deleteRestrictionForAssignmentId(final RestTemplate restTemplate, final String id) { final String url = String.format("/restapi/assessment_modes/%s/seb_restriction", id); final RestrictionData r = this.apiDelete(restTemplate, url, RestrictionData.class); // OLAT returns RestrictionData with null values upon deletion. @@ -350,13 +353,12 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm @Override public Result releaseSEBClientRestriction(final Exam exam) { - final String quizId = exam.externalId; return getRestTemplate() .map(t -> this.deleteRestrictionForAssignmentId(t, exam.externalId)) .map(x -> exam); } - private T apiGet(final RestTemplate restTemplate, String url, Class type) { + private T apiGet(final RestTemplate restTemplate, final String url, final Class type) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final ResponseEntity res = restTemplate.exchange( lmsSetup.lmsApiUrl + url, @@ -366,7 +368,8 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm return res.getBody(); } - private List apiGetList(final RestTemplate restTemplate, String url, ParameterizedTypeReference> type) { + private List apiGetList(final RestTemplate restTemplate, final String url, + final ParameterizedTypeReference> type) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final ResponseEntity> res = restTemplate.exchange( lmsSetup.lmsApiUrl + url, @@ -376,11 +379,12 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm return res.getBody(); } - private R apiPost(final RestTemplate restTemplate, String url, P post, Class

postType, Class responseType) { + private R apiPost(final RestTemplate restTemplate, final String url, final P post, final Class

postType, + final Class responseType) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("content-type", "application/json"); - HttpEntity

requestEntity = new HttpEntity<>(post, httpHeaders); + final HttpEntity

requestEntity = new HttpEntity<>(post, httpHeaders); final ResponseEntity res = restTemplate.exchange( lmsSetup.lmsApiUrl + url, HttpMethod.POST, @@ -389,7 +393,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm return res.getBody(); } - private T apiDelete(final RestTemplate restTemplate, String url, Class type) { + private T apiDelete(final RestTemplate restTemplate, final String url, final Class type) { final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final ResponseEntity res = restTemplate.exchange( lmsSetup.lmsApiUrl + url, @@ -401,7 +405,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm private Result getRestTemplate() { return Result.tryCatch(() -> { - if (this.cachedRestTemplate != null) { return this.cachedRestTemplate; } + if (this.cachedRestTemplate != null) { + return this.cachedRestTemplate; + } final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup(); final ClientCredentials credentials = this.apiTemplateDataSupplier.getLmsClientCredentials();