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
new file mode 100644
index 00000000..b25519f8
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplate.java
@@ -0,0 +1,284 @@
+/*
+ * 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 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> getQuizzes(final FilterMap filterMap) {
+ return this
+ .protectedQuizzesRequest(filterMap)
+ .map(quizzes -> quizzes.stream()
+ .filter(LmsAPIService.quizFilterPredicate(filterMap))
+ .collect(Collectors.toList()));
+ }
+
+ @Override
+ public Result> getQuizzes(final Set ids) {
+ return Result.tryCatch(() -> {
+ final HashSet leftIds = new HashSet<>(ids);
+ final Collection result = new ArrayList<>();
+ ids.stream()
+ .map(id -> super.getFromCache(id))
+ .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 getQuiz(final String id) {
+ final QuizData fromCache = super.getFromCache(id);
+ if (fromCache != null) {
+ return Result.of(fromCache);
+ }
+
+ return super.protectedQuizRequest(id);
+ }
+
+ @Override
+ protected Supplier> 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> quizzesSupplier(final Set 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 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 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 getCourseChaptersSupplier(final String courseId) {
+ return () -> {
+ throw new UnsupportedOperationException("not available yet");
+ };
+ }
+
+ @Override
+ public Result 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 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 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");
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplateFactory.java
new file mode 100644
index 00000000..05cce45b
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/olat/OlatLmsAPITemplateFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.olat;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Service;
+
+import ch.ethz.seb.sebserver.gbl.async.AsyncService;
+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
+/** Factory for OlatLmsAPITemplate. Since a LmsAPITemplate of a specific LMS type
+ * is whether a singleton component nor a simple prototype but one (singleton) instance
+ * can exist per defined LMSSetup, we need a specialized factory to build such
+ * a LmsAPITemplate for a specific LMSSetup.
+ *
+ * Add needed dependencies as final fields and let them inject within the constructor
+ * as usual. Just add the additionally needed dependencies used to build a OlatLmsAPITemplate. */
+public class OlatLmsAPITemplateFactory implements LmsAPITemplateFactory {
+
+ private final AsyncService asyncService;
+ private final Environment environment;
+ private final CacheManager cacheManager;
+
+ public OlatLmsAPITemplateFactory(
+ final AsyncService asyncService,
+ final Environment environment,
+ final CacheManager cacheManager) {
+
+ this.asyncService = asyncService;
+ this.environment = environment;
+ this.cacheManager = cacheManager;
+ }
+
+ @Override
+ public LmsType lmsType() {
+ return LmsType.OPEN_OLAT;
+ }
+
+ @Override
+ public Result create(final APITemplateDataSupplier apiTemplateDataSupplier) {
+ return Result.tryCatch(() -> {
+ return new OlatLmsAPITemplate(
+ apiTemplateDataSupplier,
+ this.asyncService,
+ this.environment,
+ this.cacheManager);
+ });
+ }
+
+}