From 8dae41f7546d27798db0a859cb805d57fb3139e2 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Tue, 15 Mar 2022 08:38:27 +0100
Subject: [PATCH 001/155] Merge remote-tracking branch 'origin/dev-1.3'
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 485f890d..3ab05932 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
jar
- 1.3.0
+ 1.4-SNAPSHOT
${sebserver-version}
${sebserver-version}
UTF-8
From eb08df6c0043a8077d5352241a9e309458399811 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Mon, 21 Mar 2022 13:37:33 +0100
Subject: [PATCH 002/155] SEBSERV-158 preparation - refactoring of
LmsAPITemplate
---
.../gbl/model/institution/LmsSetup.java | 2 +-
.../model/institution/LmsSetupTestResult.java | 8 +-
.../servicelayer/lms/CourseAccessAPI.java | 108 ++++
.../servicelayer/lms/LmsAPITemplate.java | 121 +---
.../servicelayer/lms/SEBRestrictionAPI.java | 53 ++
.../lms/impl/AbstractCachedCourseAccess.java | 13 +-
.../lms/impl/AbstractCourseAccess.java | 15 -
.../lms/impl/LmsAPIServiceImpl.java | 3 +-
.../lms/impl/LmsAPITemplateAdapter.java | 407 +++++++++++++
.../lms/impl/ans/AnsLmsAPITemplate.java | 56 +-
.../impl/ans/AnsLmsAPITemplateFactory.java | 9 +-
.../lms/impl/edx/OpenEdxCourseAccess.java | 159 ++---
.../impl/edx/OpenEdxCourseRestriction.java | 112 +---
.../lms/impl/edx/OpenEdxLmsAPITemplate.java | 159 -----
.../edx/OpenEdxLmsAPITemplateFactory.java | 8 +-
...Template.java => MockCourseAccessAPI.java} | 547 +++++++-----------
.../mockup/MockLmsAPITemplateFactory.java | 13 +-
.../impl/mockup/MockSEBRestrictionAPI.java | 52 ++
.../lms/impl/moodle/MoodleCourseAccess.java | 248 ++++----
.../impl/moodle/MoodleCourseRestriction.java | 37 +-
.../lms/impl/moodle/MoodleLmsAPITemplate.java | 165 ------
.../moodle/MoodleLmsAPITemplateFactory.java | 7 +-
.../lms/impl/olat/OlatLmsAPITemplate.java | 66 +--
.../impl/olat/OlatLmsAPITemplateFactory.java | 9 +-
.../config/application-dev.properties | 2 +-
.../impl/moodle/MoodleCourseAccessTest.java | 12 +-
26 files changed, 1204 insertions(+), 1187 deletions(-)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/CourseAccessAPI.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionAPI.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java
delete mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java
rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/{MockupLmsAPITemplate.java => MockCourseAccessAPI.java} (67%)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockSEBRestrictionAPI.java
delete mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java
index 7603eabc..c071df70 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetup.java
@@ -56,7 +56,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
public enum LmsType {
/** Mockup LMS type used to create test setups */
MOCKUP(Features.COURSE_API),
- /** The Open edX LMS binding features both APIs, course access as well as SEB restrcition */
+ /** The Open edX LMS binding features both APIs, course access as well as SEB restriction */
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
/** The Moodle binding features only the course access API so far */
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java
index 6cdc41e5..fbef5775 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/institution/LmsSetupTestResult.java
@@ -28,6 +28,7 @@ public final class LmsSetupTestResult {
public static final String ATTR_MISSING_ATTRIBUTE = "missingLMSSetupAttribute";
public enum ErrorType {
+ API_NOT_SUPPORTED,
MISSING_ATTRIBUTE,
TOKEN_REQUEST,
QUIZ_ACCESS_API_REQUEST,
@@ -109,7 +110,12 @@ public final class LmsSetupTestResult {
return new LmsSetupTestResult(lmsType);
}
- public static LmsSetupTestResult ofMissingAttributes(final LmsSetup.LmsType lmsType,
+ public static LmsSetupTestResult ofAPINotSupported(final LmsSetup.LmsType lmsType) {
+ return new LmsSetupTestResult(lmsType, new Error(ErrorType.TOKEN_REQUEST, "Not Supported"));
+ }
+
+ public static LmsSetupTestResult ofMissingAttributes(
+ final LmsSetup.LmsType lmsType,
final Collection attrs) {
return new LmsSetupTestResult(lmsType, new Error(ErrorType.MISSING_ATTRIBUTE, "missing attribute(s)"), attrs);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/CourseAccessAPI.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/CourseAccessAPI.java
new file mode 100644
index 00000000..455ffdd7
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/CourseAccessAPI.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
+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.webservice.servicelayer.dao.FilterMap;
+
+public interface CourseAccessAPI {
+
+ Logger log = LoggerFactory.getLogger(CourseAccessAPI.class);
+
+ /** Fetch status that indicates an asynchronous quiz data fetch status if the
+ * concrete implementation has such. */
+ public enum FetchStatus {
+ ALL_FETCHED,
+ ASYNC_FETCH_RUNNING,
+ FETCH_ERROR
+ }
+
+ /** Performs a test for the underling {@link LmsSetup } configuration and checks if the
+ * LMS and the course API of the LMS can be accessed or if there are some difficulties,
+ * missing configuration data or connection/authentication errors.
+ *
+ * @return {@link LmsSetupTestResult } instance with the test result report */
+ LmsSetupTestResult testCourseAccessAPI();
+
+ /** Get an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
+ *
+ * @param filterMap the {@link FilterMap } to get a filtered result. Possible filter attributes are:
+ *
+ *
+ * {@link QuizData.FILTER_ATTR_QUIZ_NAME } The quiz name filter text (exclude all names that do not contain the given text)
+ * {@link QuizData.FILTER_ATTR_START_TIME } The quiz start time (exclude all quizzes that starts before)
+ *
+ *
+ * @return Result of an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
+ * or refer to an error when happened */
+ Result> getQuizzes(FilterMap filterMap);
+
+ /** Get all {@link QuizData } for the set of {@link QuizData } identifiers from LMS API in a collection
+ * of Result. If particular quizzes cannot be loaded because of errors or deletion,
+ * the the referencing QuizData will not be in the resulting list and an error is logged.
+ *
+ * @param ids the Set of Quiz identifiers to get the {@link QuizData } for
+ * @return Collection of all {@link QuizData } from the given id set */
+ Result> getQuizzes(Set ids);
+
+ /** Get the quiz data with specified identifier.
+ *
+ * @param id the quiz data identifier
+ * @return Result refer to the quiz data or to an error when happened */
+ Result getQuiz(final String id);
+
+ /** Clears the underling caches if there are some for a particular implementation. */
+ void clearCourseCache();
+
+ /** Convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
+ * to LMS examinee account details by requesting them on the LMS API with the given examineeUserId
+ *
+ * @param examineeUserId the examinee user identifier derived from SEB Client
+ * @return a Result refer to the {@link ExamineeAccountDetails } instance or to an error when happened or not
+ * supported */
+ Result getExamineeAccountDetails(String examineeUserId);
+
+ /** Used to convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
+ * to a readable LMS examinee account name by requesting this on the LMS API with the given examineeUserId.
+ *
+ * If the underling concrete template implementation does not support this user name conversion,
+ * the given examineeSessionId shall be returned.
+ *
+ * @param examineeUserId the examinee user identifier derived from SEB Client
+ * @return a user account display name if supported or the given examineeSessionId if not. */
+ String getExamineeName(final String examineeUserId);
+
+ /** Used to get a list of chapters (display name and chapter-identifier) that can be used to
+ * apply chapter-based SEB restriction for a specified course.
+ *
+ * The availability of this depends on the type of LMS and on installed plugins that supports this feature.
+ * If this is not supported by the underling LMS a UnsupportedOperationException will be presented
+ * within the Result.
+ *
+ * @param courseId The course identifier
+ * @return Result referencing to the Chapters model for the given course or to an error when happened. */
+ Result getCourseChapters(String courseId);
+
+ default FetchStatus getFetchStatus() {
+ return FetchStatus.ALL_FETCHED;
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java
index 8b990a67..f6b7d4ee 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java
@@ -8,20 +8,9 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
-import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
-import ch.ethz.seb.sebserver.gbl.model.exam.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.LmsSetupTestResult;
-import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
-import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAccess;
@@ -75,7 +64,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCourseAcce
* or partial API Access and can flag missing or wrong {@link LmsSetup } attributes with the resulting
* {@link LmsSetupTestResult }.
* SEB Server than uses an instance of this template to communicate with the an LMS. */
-public interface LmsAPITemplate {
+public interface LmsAPITemplate extends CourseAccessAPI, SEBRestrictionAPI {
/** Get the LMS type of the concrete template implementation
*
@@ -87,110 +76,8 @@ public interface LmsAPITemplate {
* @return the underling {@link LmsSetup } configuration for this LmsAPITemplate */
LmsSetup lmsSetup();
- // *******************************************************************
- // **** Course API functions *****************************************
-
- /** Performs a test for the underling {@link LmsSetup } configuration and checks if the
- * LMS and the course API of the LMS can be accessed or if there are some difficulties,
- * missing configuration data or connection/authentication errors.
- *
- * @return {@link LmsSetupTestResult } instance with the test result report */
- LmsSetupTestResult testCourseAccessAPI();
-
- /** Get an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
- *
- * @param filterMap the {@link FilterMap } to get a filtered result. Possible filter attributes are:
- *
- *
- * {@link QuizData.FILTER_ATTR_QUIZ_NAME } The quiz name filter text (exclude all names that do not contain the given text)
- * {@link QuizData.FILTER_ATTR_START_TIME } The quiz start time (exclude all quizzes that starts before)
- *
- *
- * @return Result of an unsorted List of filtered {@link QuizData } from the LMS course/quiz API
- * or refer to an error when happened */
- Result> getQuizzes(FilterMap filterMap);
-
- /** Get all {@link QuizData } for the set of {@link QuizData } identifiers from LMS API in a collection
- * of Result. If particular quizzes cannot be loaded because of errors or deletion,
- * the the referencing QuizData will not be in the resulting list and an error is logged.
- *
- * @param ids the Set of Quiz identifiers to get the {@link QuizData } for
- * @return Collection of all {@link QuizData } from the given id set */
- Result> getQuizzes(Set ids);
-
- /** Get the quiz data with specified identifier.
- *
- * @param id the quiz data identifier
- * @return Result refer to the quiz data or to an error when happened */
- Result getQuiz(final String id);
-
- /** Clears the underling caches if there are some for a particular implementation. */
- void clearCache();
-
- /** Convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
- * to LMS examinee account details by requesting them on the LMS API with the given examineeUserId
- *
- * @param examineeUserId the examinee user identifier derived from SEB Client
- * @return a Result refer to the {@link ExamineeAccountDetails } instance or to an error when happened or not
- * supported */
- Result getExamineeAccountDetails(String examineeUserId);
-
- /** Used to convert an anonymous or temporary examineeUserId, sent by the SEB Client on LMS login,
- * to a readable LMS examinee account name by requesting this on the LMS API with the given examineeUserId.
- *
- * If the underling concrete template implementation does not support this user name conversion,
- * the given examineeSessionId shall be returned.
- *
- * @param examineeUserId the examinee user identifier derived from SEB Client
- * @return a user account display name if supported or the given examineeSessionId if not. */
- String getExamineeName(String examineeUserId);
-
- /** Used to get a list of chapters (display name and chapter-identifier) that can be used to
- * apply chapter-based SEB restriction for a specified course.
- *
- * The availability of this depends on the type of LMS and on installed plugins that supports this feature.
- * If this is not supported by the underling LMS a UnsupportedOperationException will be presented
- * within the Result.
- *
- * @param courseId The course identifier
- * @return Result referencing to the Chapters model for the given course or to an error when happened. */
- Result getCourseChapters(String courseId);
-
- // ****************************************************************************
- // **** SEB restriction API functions *****************************************
-
- /** Performs a test for the underling {@link LmsSetup } configuration and checks if the
- * LMS and the course restriction API of the LMS can be accessed or if there are some difficulties,
- * missing configuration data or connection/authentication errors.
- *
- * @return {@link LmsSetupTestResult } instance with the test result report */
- LmsSetupTestResult testCourseRestrictionAPI();
-
- /** Get SEB restriction data form LMS within a {@link SEBRestrictionData } instance. The available restriction
- * details
- * depends on the type of LMS but shall at least contains the config-key(s) and the browser-exam-key(s).
- *
- * @param exam the exam to get the SEB restriction data for
- * @return Result refer to the {@link SEBRestrictionData } instance or to an ResourceNotFoundException if the
- * restriction is
- * missing or to another exception on unexpected error case */
- Result getSEBClientRestriction(Exam exam);
-
- /** Applies SEB Client restrictions to the LMS with the given attributes.
- *
- * @param externalExamId The exam/course identifier from LMS side (Exam.externalId)
- * @param sebRestrictionData containing all data for SEB Client restriction to apply to the LMS
- * @return Result refer to the given {@link SEBRestrictionData } if restriction was successful or to an error if
- * not */
- Result applySEBClientRestriction(
- String externalExamId,
- SEBRestriction sebRestrictionData);
-
- /** Releases an already applied SEB Client restriction within the LMS for a given Exam.
- * This completely removes the SEB Client restriction on LMS side.
- *
- * @param exam the Exam to release the restriction for.
- * @return Result refer to the given Exam if successful or to an error if not */
- Result releaseSEBClientRestriction(Exam exam);
+ default void dispose() {
+ clearCourseCache();
+ }
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionAPI.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionAPI.java
new file mode 100644
index 00000000..681c248c
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/SEBRestrictionAPI.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 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;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+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.LmsSetupTestResult;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+
+public interface SEBRestrictionAPI {
+
+ /** Performs a test for the underling {@link LmsSetup } configuration and checks if the
+ * LMS and the course restriction API of the LMS can be accessed or if there are some difficulties,
+ * missing configuration data or connection/authentication errors.
+ *
+ * @return {@link LmsSetupTestResult } instance with the test result report */
+ LmsSetupTestResult testCourseRestrictionAPI();
+
+ /** Get SEB restriction data form LMS within a {@link SEBRestrictionData } instance. The available restriction
+ * details
+ * depends on the type of LMS but shall at least contains the config-key(s) and the browser-exam-key(s).
+ *
+ * @param exam the exam to get the SEB restriction data for
+ * @return Result refer to the {@link SEBRestrictionData } instance or to an ResourceNotFoundException if the
+ * restriction is
+ * missing or to another exception on unexpected error case */
+ Result getSEBClientRestriction(Exam exam);
+
+ /** Applies SEB Client restrictions to the LMS with the given attributes.
+ *
+ * @param externalExamId The exam/course identifier from LMS side (Exam.externalId)
+ * @param sebRestrictionData containing all data for SEB Client restriction to apply to the LMS
+ * @return Result refer to the given {@link SEBRestrictionData } if restriction was successful or to an error if
+ * not */
+ Result applySEBClientRestriction(
+ String externalExamId,
+ SEBRestriction sebRestrictionData);
+
+ /** Releases an already applied SEB Client restriction within the LMS for a given Exam.
+ * This completely removes the SEB Client restriction on LMS side.
+ *
+ * @param exam the Exam to release the restriction for.
+ * @return Result refer to the given Exam if successful or to an error if not */
+ Result releaseSEBClientRestriction(Exam exam);
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java
index 7e2a186a..1e78e578 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCachedCourseAccess.java
@@ -16,10 +16,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
-import org.springframework.core.env.Environment;
import ch.ethz.seb.sebserver.gbl.Constants;
-import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
/** This implements an overall short time cache for QuizData objects for all implementing
@@ -28,7 +26,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
* The QuizData are stored with a key composed from the id of the key
*
* The EH-Cache can be configured in file ehcache.xml **/
-public abstract class AbstractCachedCourseAccess extends AbstractCourseAccess {
+public abstract class AbstractCachedCourseAccess {
private static final Logger log = LoggerFactory.getLogger(AbstractCachedCourseAccess.class);
@@ -37,17 +35,12 @@ public abstract class AbstractCachedCourseAccess extends AbstractCourseAccess {
private final Cache cache;
- protected AbstractCachedCourseAccess(
- final AsyncService asyncService,
- final Environment environment,
- final CacheManager cacheManager) {
-
- super(asyncService, environment);
+ protected AbstractCachedCourseAccess(final CacheManager cacheManager) {
this.cache = cacheManager.getCache(CACHE_NAME_QUIZ_DATA);
}
/** Used to clear the entire cache */
- public void clearCache() {
+ public void clearCourseCache() {
final Object nativeCache = this.cache.getNativeCache();
if (nativeCache instanceof javax.cache.Cache) {
try {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java
index 5a5f3a3c..b374eb59 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/AbstractCourseAccess.java
@@ -20,7 +20,6 @@ import org.springframework.core.env.Environment;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
-import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker.State;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
@@ -35,14 +34,6 @@ public abstract class AbstractCourseAccess {
private static final Logger log = LoggerFactory.getLogger(AbstractCourseAccess.class);
- /** Fetch status that indicates an asynchronous quiz data fetch status if the
- * concrete implementation has such. */
- public enum FetchStatus {
- ALL_FETCHED,
- ASYNC_FETCH_RUNNING,
- FETCH_ERROR
- }
-
/** CircuitBreaker for protected quiz and course data requests */
protected final CircuitBreaker> allQuizzesRequest;
/** CircuitBreaker for protected quiz and course data requests */
@@ -194,10 +185,4 @@ public abstract class AbstractCourseAccess {
/** Provides a supplier for the course chapter data request to use within the circuit breaker */
protected abstract Supplier getCourseChaptersSupplier(final String courseId);
- protected FetchStatus getFetchStatus() {
- if (this.quizzesRequest.getState() != State.CLOSED) {
- return FetchStatus.FETCH_ERROR;
- }
- return FetchStatus.ALL_FETCHED;
- }
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java
index f873767b..cc20ce79 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java
@@ -96,12 +96,13 @@ public class LmsAPIServiceImpl implements LmsAPIService {
final LmsAPITemplate removedTemplate = this.cache
.remove(new CacheKey(lmsSetup.getModelId(), 0));
if (removedTemplate != null) {
- removedTemplate.clearCache();
+ removedTemplate.clearCourseCache();
}
}
@Override
public void cleanup() {
+ this.cache.values().forEach(LmsAPITemplate::dispose);
this.cache.clear();
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java
new file mode 100644
index 00000000..365c9452
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPITemplateAdapter.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2022 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.Environment;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.async.AsyncService;
+import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
+import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker.State;
+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.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.webservice.servicelayer.dao.FilterMap;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.CourseAccessAPI;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
+
+public class LmsAPITemplateAdapter implements LmsAPITemplate {
+
+ private static final Logger log = LoggerFactory.getLogger(LmsAPITemplateAdapter.class);
+
+ private final CourseAccessAPI courseAccessAPI;
+ private final SEBRestrictionAPI sebBestrictionAPI;
+ private final APITemplateDataSupplier apiTemplateDataSupplier;
+
+ /** CircuitBreaker for protected quiz and course data requests */
+ private final CircuitBreaker> allQuizzesRequest;
+ /** CircuitBreaker for protected quiz and course data requests */
+ private final CircuitBreaker> quizzesRequest;
+ /** CircuitBreaker for protected quiz and course data requests */
+ private final CircuitBreaker quizRequest;
+ /** CircuitBreaker for protected chapter data requests */
+ private final CircuitBreaker chaptersRequest;
+ /** CircuitBreaker for protected examinee account details requests */
+ private final CircuitBreaker accountDetailRequest;
+
+ private final CircuitBreaker restrictionRequest;
+ private final CircuitBreaker releaseRestrictionRequest;
+
+ public LmsAPITemplateAdapter(
+ final AsyncService asyncService,
+ final Environment environment,
+ final APITemplateDataSupplier apiTemplateDataSupplier,
+ final CourseAccessAPI courseAccessAPI,
+ final SEBRestrictionAPI sebBestrictionAPI) {
+
+ this.courseAccessAPI = courseAccessAPI;
+ this.sebBestrictionAPI = sebBestrictionAPI;
+ this.apiTemplateDataSupplier = apiTemplateDataSupplier;
+
+ this.allQuizzesRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
+ Integer.class,
+ 3),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
+ Long.class,
+ Constants.MINUTE_IN_MILLIS),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.timeToRecover",
+ Long.class,
+ Constants.MINUTE_IN_MILLIS));
+
+ this.quizzesRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
+ Integer.class,
+ 3),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 10),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.timeToRecover",
+ Long.class,
+ Constants.MINUTE_IN_MILLIS));
+
+ this.quizRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.attempts",
+ Integer.class,
+ 3),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.blockingTime",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 10),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.quizzesRequest.timeToRecover",
+ Long.class,
+ Constants.MINUTE_IN_MILLIS));
+
+ this.chaptersRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.chaptersRequest.attempts",
+ Integer.class,
+ 3),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.chaptersRequest.blockingTime",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 10),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.chaptersRequest.timeToRecover",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 30));
+
+ this.accountDetailRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.accountDetailRequest.attempts",
+ Integer.class,
+ 2),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.accountDetailRequest.blockingTime",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 10),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.accountDetailRequest.timeToRecover",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 30));
+
+ this.restrictionRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.sebrestriction.attempts",
+ Integer.class,
+ 2),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.sebrestriction.blockingTime",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 10),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.sebrestriction.timeToRecover",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 30));
+
+ this.releaseRestrictionRequest = asyncService.createCircuitBreaker(
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.sebrestriction.attempts",
+ Integer.class,
+ 2),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.sebrestriction.blockingTime",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 10),
+ environment.getProperty(
+ "sebserver.webservice.circuitbreaker.sebrestriction.timeToRecover",
+ Long.class,
+ Constants.SECOND_IN_MILLIS * 30));
+ }
+
+ @Override
+ public LmsType getType() {
+ return this.lmsSetup().getLmsType();
+ }
+
+ @Override
+ public LmsSetup lmsSetup() {
+ return this.apiTemplateDataSupplier.getLmsSetup();
+ }
+
+ @Override
+ public LmsSetupTestResult testCourseAccessAPI() {
+ if (this.courseAccessAPI != null) {
+ return this.courseAccessAPI.testCourseAccessAPI();
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Test Course Access API for LMSSetup: {}", lmsSetup());
+ }
+
+ return LmsSetupTestResult.ofAPINotSupported(getType());
+ }
+
+ @Override
+ public FetchStatus getFetchStatus() {
+ if (this.courseAccessAPI == null) {
+ return FetchStatus.FETCH_ERROR;
+ }
+
+ if (this.allQuizzesRequest.getState() != State.CLOSED) {
+ return FetchStatus.FETCH_ERROR;
+ }
+
+ return this.courseAccessAPI.getFetchStatus();
+ }
+
+ @Override
+ public Result> getQuizzes(final FilterMap filterMap) {
+
+ if (this.courseAccessAPI == null) {
+ return Result
+ .ofError(new UnsupportedOperationException("Course API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get quizzes for LMSSetup: {}", lmsSetup());
+ }
+
+ return this.allQuizzesRequest.protectedRun(() -> this.courseAccessAPI
+ .getQuizzes(filterMap)
+ .onError(error -> log.error(
+ "Failed to run protectedQuizzesRequest: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public Result> getQuizzes(final Set ids) {
+
+ if (this.courseAccessAPI == null) {
+ return Result
+ .ofError(new UnsupportedOperationException("Course API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get quizzes {} for LMSSetup: {}", ids, lmsSetup());
+ }
+
+ return this.quizzesRequest.protectedRun(() -> this.courseAccessAPI
+ .getQuizzes(ids)
+ .onError(error -> log.error(
+ "Failed to run protectedQuizzesRequest: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public Result getQuiz(final String id) {
+
+ if (this.courseAccessAPI == null) {
+ return Result
+ .ofError(new UnsupportedOperationException("Course API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get quiz {} for LMSSetup: {}", id, lmsSetup());
+ }
+
+ return this.quizRequest.protectedRun(() -> this.courseAccessAPI
+ .getQuiz(id)
+ .onError(error -> log.error(
+ "Failed to run protectedQuizRequest: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public void clearCourseCache() {
+ if (this.courseAccessAPI != null) {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Clear course cache for LMSSetup: {}", lmsSetup());
+ }
+
+ this.courseAccessAPI.clearCourseCache();
+ }
+ }
+
+ @Override
+ public Result getExamineeAccountDetails(final String examineeUserId) {
+
+ if (this.courseAccessAPI == null) {
+ return Result
+ .ofError(new UnsupportedOperationException("Course API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get examinee details {} for LMSSetup: {}", examineeUserId, lmsSetup());
+ }
+
+ return this.accountDetailRequest.protectedRun(() -> this.courseAccessAPI
+ .getExamineeAccountDetails(examineeUserId)
+ .onError(error -> log.error(
+ "Unexpected error while trying to get examinee account details: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public String getExamineeName(final String examineeUserId) {
+
+ if (this.courseAccessAPI == null) {
+ throw new UnsupportedOperationException("Course API Not Supported For: " + getType().name());
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get examinee name {} for LMSSetup: {}", examineeUserId, lmsSetup());
+ }
+
+ return this.courseAccessAPI.getExamineeName(examineeUserId);
+ }
+
+ @Override
+ public Result getCourseChapters(final String courseId) {
+
+ if (this.courseAccessAPI == null) {
+ return Result
+ .ofError(new UnsupportedOperationException("Course API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get course chapters {} for LMSSetup: {}", courseId, lmsSetup());
+ }
+
+ return this.chaptersRequest.protectedRun(() -> this.courseAccessAPI
+ .getCourseChapters(courseId)
+ .onError(error -> log.error(
+ "Failed to run getCourseChapters: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public LmsSetupTestResult testCourseRestrictionAPI() {
+ if (this.sebBestrictionAPI != null) {
+ return this.sebBestrictionAPI.testCourseRestrictionAPI();
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Test course restriction API for LMSSetup: {}", lmsSetup());
+ }
+
+ return LmsSetupTestResult.ofAPINotSupported(getType());
+ }
+
+ @Override
+ public Result getSEBClientRestriction(final Exam exam) {
+
+ if (this.sebBestrictionAPI == null) {
+ return Result.ofError(
+ new UnsupportedOperationException("SEB Restriction API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Get course restriction: {} for LMSSetup: {}", exam.externalId, lmsSetup());
+ }
+
+ return this.restrictionRequest.protectedRun(() -> this.sebBestrictionAPI
+ .getSEBClientRestriction(exam)
+ .onError(error -> log.error(
+ "Failed to get SEB restrictions: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public Result applySEBClientRestriction(
+ final String externalExamId,
+ final SEBRestriction sebRestrictionData) {
+
+ if (this.sebBestrictionAPI == null) {
+ return Result.ofError(
+ new UnsupportedOperationException("SEB Restriction API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Apply course restriction: {} for LMSSetup: {}", externalExamId, lmsSetup());
+ }
+
+ return this.restrictionRequest.protectedRun(() -> this.sebBestrictionAPI
+ .applySEBClientRestriction(externalExamId, sebRestrictionData)
+ .onError(error -> log.error(
+ "Failed to apply SEB restrictions: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+ @Override
+ public Result releaseSEBClientRestriction(final Exam exam) {
+
+ if (this.sebBestrictionAPI == null) {
+ return Result.ofError(
+ new UnsupportedOperationException("SEB Restriction API Not Supported For: " + getType().name()));
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Release course restriction: {} for LMSSetup: {}", exam.externalId, lmsSetup());
+ }
+
+ return this.releaseRestrictionRequest.protectedRun(() -> this.sebBestrictionAPI
+ .releaseSEBClientRestriction(exam)
+ .onError(error -> log.error(
+ "Failed to release SEB restrictions: {}",
+ error.getMessage()))
+ .getOrThrow());
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java
index 078227ac..6f160da9 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplate.java
@@ -18,7 +18,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -28,7 +27,6 @@ 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;
@@ -39,7 +37,6 @@ import org.springframework.web.client.RestTemplate;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
-import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
@@ -78,11 +75,9 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
final ClientCredentialService clientCredentialService,
final APITemplateDataSupplier apiTemplateDataSupplier,
- final AsyncService asyncService,
- final Environment environment,
final CacheManager cacheManager) {
- super(asyncService, environment, cacheManager);
+ super(cacheManager);
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.clientCredentialService = clientCredentialService;
@@ -170,7 +165,7 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
@Override
public Result> getQuizzes(final FilterMap filterMap) {
return this
- .protectedQuizzesRequest(filterMap)
+ .allQuizzesRequest(filterMap)
.map(quizzes -> quizzes.stream()
.filter(LmsAPIService.quizFilterPredicate(filterMap))
.collect(Collectors.toList()));
@@ -191,7 +186,7 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
});
if (!leftIds.isEmpty()) {
- result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow());
+ result.addAll(quizzesRequest(leftIds).getOrThrow());
}
return result;
@@ -205,7 +200,7 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
return Result.of(fromCache);
}
- return super.protectedQuizRequest(id);
+ return quizRequest(id);
}
private List collectAllQuizzes(final AnsPersonalRestTemplate restTemplate) {
@@ -279,33 +274,28 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
.collect(Collectors.toList());
}
- @Override
- protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
+ protected Result> allQuizzesRequest(final FilterMap filterMap) {
// We cannot filter by from-date or partial names using the Ans search API.
// Only exact matches are permitted. So we're not implementing filtering
// on the API level and always retrieve all assignments and let SEB server
// do the filtering.
- return () -> {
+ return Result.tryCatch(() -> {
final List res = getRestTemplate()
.map(this::collectAllQuizzes)
.getOrThrow();
super.putToCache(res);
return res;
- };
+ });
}
- @Override
- protected Supplier> quizzesSupplier(final Set ids) {
- return () -> getRestTemplate()
- .map(t -> this.getQuizzesByIds(t, ids))
- .getOrThrow();
+ protected Result> quizzesRequest(final Set ids) {
+ return getRestTemplate()
+ .map(t -> this.getQuizzesByIds(t, ids));
}
- @Override
- protected Supplier quizSupplier(final String id) {
- return () -> getRestTemplate()
- .map(t -> this.getQuizByAssignmentId(t, id))
- .getOrThrow();
+ protected Result quizRequest(final String id) {
+ return getRestTemplate()
+ .map(t -> this.getQuizByAssignmentId(t, id));
}
private ExamineeAccountDetails getExamineeById(final RestTemplate restTemplate, final String id) {
@@ -324,17 +314,21 @@ public class AnsLmsAPITemplate extends AbstractCachedCourseAccess implements Lms
}
@Override
- protected Supplier accountDetailsSupplier(final String id) {
- return () -> getRestTemplate()
- .map(t -> this.getExamineeById(t, id))
- .getOrThrow();
+ public Result getExamineeAccountDetails(final String examineeUserId) {
+ return getRestTemplate().map(t -> this.getExamineeById(t, examineeUserId));
}
@Override
- protected Supplier getCourseChaptersSupplier(final String courseId) {
- return () -> {
- throw new UnsupportedOperationException("not available yet");
- };
+ public String getExamineeName(final String examineeUserId) {
+ return getExamineeAccountDetails(examineeUserId)
+ .map(ExamineeAccountDetails::getDisplayName)
+ .onError(error -> log.warn("Failed to request user-name for ID: {}", error.getMessage(), error))
+ .getOr(examineeUserId);
+ }
+
+ @Override
+ public Result getCourseChapters(final String courseId) {
+ return Result.ofError(new UnsupportedOperationException("not available yet"));
}
@Override
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplateFactory.java
index 549bf5a5..d0fc5385 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/ans/AnsLmsAPITemplateFactory.java
@@ -22,6 +22,7 @@ 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;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
@Lazy
@Service
@@ -63,13 +64,17 @@ public class AnsLmsAPITemplateFactory implements LmsAPITemplateFactory {
@Override
public Result create(final APITemplateDataSupplier apiTemplateDataSupplier) {
return Result.tryCatch(() -> {
- return new AnsLmsAPITemplate(
+ final AnsLmsAPITemplate ansLmsAPITemplate = new AnsLmsAPITemplate(
this.clientHttpRequestFactoryService,
this.clientCredentialService,
apiTemplateDataSupplier,
+ this.cacheManager);
+ return new LmsAPITemplateAdapter(
this.asyncService,
this.environment,
- this.cacheManager);
+ apiTemplateDataSupplier,
+ ansLmsAPITemplate,
+ ansLmsAPITemplate);
});
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java
index fd4165b6..acce20b6 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseAccess.java
@@ -17,14 +17,12 @@ 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.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
-import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -45,7 +43,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
-import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@@ -57,12 +54,13 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
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.CourseAccessAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.AbstractCachedCourseAccess;
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
*
* See also: https://course-catalog-api-guide.readthedocs.io */
-final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
+final class OpenEdxCourseAccess extends AbstractCachedCourseAccess implements CourseAccessAPI {
private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseAccess.class);
@@ -84,11 +82,9 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
final JSONMapper jsonMapper,
final OpenEdxRestTemplateFactory openEdxRestTemplateFactory,
final WebserviceInfo webserviceInfo,
- final AsyncService asyncService,
- final Environment environment,
final CacheManager cacheManager) {
- super(asyncService, environment, cacheManager);
+ super(cacheManager);
this.jsonMapper = jsonMapper;
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
this.webserviceInfo = webserviceInfo;
@@ -104,7 +100,8 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
return this.lmsSetupId;
}
- LmsSetupTestResult initAPIAccess() {
+ @Override
+ public LmsSetupTestResult testCourseAccessAPI() {
final LmsSetupTestResult attributesCheck = this.openEdxRestTemplateFactory.test();
if (!attributesCheck.isOk()) {
@@ -141,68 +138,18 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
}
@Override
- protected Supplier accountDetailsSupplier(final String examineeSessionId) {
- return () -> {
- try {
- final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
- final HttpHeaders httpHeaders = new HttpHeaders();
- final OAuth2RestTemplate template = getRestTemplate()
- .getOrThrow();
+ public Result> getQuizzes(final FilterMap filterMap) {
+ return getRestTemplate().map(this::collectAllQuizzes);
+ }
- final String externalStartURI = this.webserviceInfo
- .getLmsExternalAddressAlias(lmsSetup.lmsApiUrl);
-
- final String uri = (externalStartURI != null)
- ? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId
- : lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeSessionId;
-
- final String responseJSON = template.exchange(
- uri,
- HttpMethod.GET,
- new HttpEntity<>(httpHeaders),
- String.class)
- .getBody();
-
- final EdxUserDetails[] userDetails = this.jsonMapper. readValue(
- responseJSON,
- new TypeReference() {
- });
-
- if (userDetails == null || userDetails.length <= 0) {
- throw new RuntimeException("No user details on Open edX API request");
- }
-
- final Map 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);
+ @Override
+ public Result getQuiz(final String id) {
+ return Result.tryCatch(() -> {
+ final QuizData fromCache = super.getFromCache(id);
+ if (fromCache != null) {
+ return fromCache;
}
- };
- }
- @Override
- protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
- return () -> getRestTemplate()
- .map(this::collectAllQuizzes)
- .getOrThrow();
- }
-
- @Override
- protected Supplier quizSupplier(final String id) {
- return () -> {
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
final String externalStartURI = getExternalLMSServerAddress(lmsSetup);
final QuizData quizData = quizDataOf(
@@ -214,13 +161,13 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
super.putToCache(quizData);
}
return quizData;
- };
+ });
}
@Override
- protected Supplier> quizzesSupplier(final Set ids) {
+ public Result> getQuizzes(final Set ids) {
if (ids.size() == 1) {
- return () -> {
+ return Result.tryCatch(() -> {
final String id = ids.iterator().next();
@@ -239,17 +186,73 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
getRestTemplate().getOrThrow(),
id),
externalStartURI));
- };
+ });
} else {
- return () -> getRestTemplate()
- .map(template -> this.collectQuizzes(template, ids))
- .getOrThrow();
+ return getRestTemplate().map(template -> this.collectQuizzes(template, ids));
}
}
@Override
- protected Supplier getCourseChaptersSupplier(final String courseId) {
- return () -> {
+ public Result getExamineeAccountDetails(final String examineeUserId) {
+ return Result.tryCatch(() -> {
+
+ final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
+ final HttpHeaders httpHeaders = new HttpHeaders();
+ final OAuth2RestTemplate template = getRestTemplate()
+ .getOrThrow();
+
+ final String externalStartURI = this.webserviceInfo
+ .getLmsExternalAddressAlias(lmsSetup.lmsApiUrl);
+
+ final String uri = (externalStartURI != null)
+ ? externalStartURI + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeUserId
+ : lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_USER_PROFILE_ENDPOINT + examineeUserId;
+
+ final String responseJSON = template.exchange(
+ uri,
+ HttpMethod.GET,
+ new HttpEntity<>(httpHeaders),
+ String.class)
+ .getBody();
+
+ final EdxUserDetails[] userDetails = this.jsonMapper. readValue(
+ responseJSON,
+ new TypeReference() {
+ });
+
+ if (userDetails == null || userDetails.length <= 0) {
+ throw new RuntimeException("No user details on Open edX API request");
+ }
+
+ final Map 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
+ public String getExamineeName(final String examineeUserId) {
+ return getExamineeAccountDetails(examineeUserId)
+ .map(ExamineeAccountDetails::getDisplayName)
+ .onError(error -> log.warn("Failed to request user-name for ID: {}", error.getMessage(), error))
+ .getOr(examineeUserId);
+ }
+
+ @Override
+ public Result getCourseChapters(final String courseId) {
+ return Result.tryCatch(() -> {
final LmsSetup lmsSetup = getApiTemplateDataSupplier().getLmsSetup();
final String uri =
@@ -262,7 +265,7 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
.filter(block -> OPEN_EDX_DEFAULT_BLOCKS_TYPE_CHAPTER.equals(block.type))
.map(block -> new Chapters.Chapter(block.display_name, block.block_id))
.collect(Collectors.toList()));
- };
+ });
}
public Result> getQuizzesFromCache(final Set ids) {
@@ -279,7 +282,7 @@ final class OpenEdxCourseAccess extends AbstractCachedCourseAccess {
});
if (!leftIds.isEmpty()) {
- result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow());
+ result.addAll(getQuizzes(leftIds).getOrThrow());
}
return result;
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java
index 8ba22c7d..4e32bdcb 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxCourseRestriction.java
@@ -22,17 +22,20 @@ import org.springframework.web.client.HttpClientErrorException;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
+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.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
/** The open edX SEB course restriction API implementation.
*
* See also : https://seb-openedx.readthedocs.io/en/latest/ */
-public class OpenEdxCourseRestriction {
+public class OpenEdxCourseRestriction implements SEBRestrictionAPI {
private static final Logger log = LoggerFactory.getLogger(OpenEdxCourseRestriction.class);
@@ -54,7 +57,8 @@ public class OpenEdxCourseRestriction {
this.openEdxRestTemplateFactory = openEdxRestTemplateFactory;
}
- LmsSetupTestResult initAPIAccess() {
+ @Override
+ public LmsSetupTestResult testCourseRestrictionAPI() {
final LmsSetupTestResult attributesCheck = this.openEdxRestTemplateFactory.test();
if (!attributesCheck.isOk()) {
@@ -95,21 +99,22 @@ public class OpenEdxCourseRestriction {
}
if (log.isDebugEnabled()) {
- log.debug("Sucessfully checked SEB Open edX integration Plugin");
+ log.debug("Successfully checked SEB Open edX integration Plugin");
}
}
return LmsSetupTestResult.ofOkay(LmsType.OPEN_EDX);
}
- Result getSEBRestriction(final String courseId) {
+ @Override
+ public Result getSEBClientRestriction(final Exam exam) {
if (log.isDebugEnabled()) {
- log.debug("GET SEB Client restriction on course: {}", courseId);
+ log.debug("GET SEB Client restriction on exam: {}", exam);
}
+ final String courseId = exam.externalId;
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
-
return Result.tryCatch(() -> {
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
final HttpHeaders httpHeaders = new HttpHeaders();
@@ -127,7 +132,7 @@ public class OpenEdxCourseRestriction {
if (log.isDebugEnabled()) {
log.debug("Successfully GET SEB Client restriction on course: {}", courseId);
}
- return data;
+ return SEBRestriction.from(exam.id, data);
} catch (final HttpClientErrorException ce) {
if (ce.getStatusCode() == HttpStatus.NOT_FOUND || ce.getStatusCode() == HttpStatus.UNAUTHORIZED) {
throw new NoSEBRestrictionException(ce);
@@ -137,17 +142,18 @@ public class OpenEdxCourseRestriction {
});
}
- Result putSEBRestriction(
- final String courseId,
- final OpenEdxSEBRestriction restriction) {
+ @Override
+ public Result applySEBClientRestriction(
+ final String externalExamId,
+ final SEBRestriction sebRestrictionData) {
if (log.isDebugEnabled()) {
- log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction);
+ log.debug("PUT SEB Client restriction on course: {} : {}", externalExamId, sebRestrictionData);
}
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
- final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
+ final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(externalExamId);
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
@@ -157,24 +163,26 @@ public class OpenEdxCourseRestriction {
.exchange(
url,
HttpMethod.PUT,
- new HttpEntity<>(toJson(restriction), httpHeaders),
+ new HttpEntity<>(toJson(OpenEdxSEBRestriction.from(sebRestrictionData)), httpHeaders),
OpenEdxSEBRestriction.class)
.getBody();
if (log.isDebugEnabled()) {
- log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
+ log.debug("Successfully PUT SEB Client restriction on course: {} : {}", externalExamId, body);
}
- return true;
+ return sebRestrictionData;
});
}
- Result deleteSEBRestriction(final String courseId) {
+ @Override
+ public Result releaseSEBClientRestriction(final Exam exam) {
if (log.isDebugEnabled()) {
- log.debug("DELETE SEB Client restriction on course: {}", courseId);
+ log.debug("DELETE SEB Client restriction on exam: {}", exam);
}
+ final String courseId = exam.externalId;
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
@@ -193,7 +201,7 @@ public class OpenEdxCourseRestriction {
if (log.isDebugEnabled()) {
log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
}
- return true;
+ return exam;
} else {
throw new RuntimeException("Unexpected response for deletion: " + exchange);
}
@@ -201,74 +209,6 @@ public class OpenEdxCourseRestriction {
}
-// private BooleanSupplier pushSEBRestrictionFunction(
-// final OpenEdxSEBRestriction restriction,
-// final String courseId) {
-//
-// final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
-// final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
-// final HttpHeaders httpHeaders = new HttpHeaders();
-// httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
-// httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
-// return () -> {
-// final OpenEdxSEBRestriction body = this.restTemplate.exchange(
-// url,
-// HttpMethod.PUT,
-// new HttpEntity<>(toJson(restriction), httpHeaders),
-// OpenEdxSEBRestriction.class)
-// .getBody();
-//
-// if (log.isDebugEnabled()) {
-// log.debug("Successfully PUT SEB Client restriction on course: {} : {}", courseId, body);
-// }
-//
-// return true;
-// };
-// }
-
-// private BooleanSupplier deleteSEBRestrictionFunction(final String courseId) {
-//
-// final LmsSetup lmsSetup = this.openEdxRestTemplateFactory.apiTemplateDataSupplier.getLmsSetup();
-// final String url = lmsSetup.lmsApiUrl + getSEBRestrictionUrl(courseId);
-// return () -> {
-// final HttpHeaders httpHeaders = new HttpHeaders();
-// httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
-// final ResponseEntity exchange = this.restTemplate.exchange(
-// url,
-// HttpMethod.DELETE,
-// new HttpEntity<>(httpHeaders),
-// Object.class);
-//
-// if (exchange.getStatusCode() == HttpStatus.NO_CONTENT) {
-// if (log.isDebugEnabled()) {
-// log.debug("Successfully PUT SEB Client restriction on course: {}", courseId);
-// }
-// } else {
-// log.error("Unexpected response for deletion: {}", exchange);
-// return false;
-// }
-//
-// return true;
-// };
-// }
-
-// private Result handleSEBRestriction(final BooleanSupplier task) {
-// return getRestTemplate()
-// .map(restTemplate -> {
-// try {
-// return task.getAsBoolean();
-// } catch (final HttpClientErrorException ce) {
-// if (ce.getStatusCode() == HttpStatus.UNAUTHORIZED) {
-// throw new APIMessageException(APIMessage.ErrorMessage.UNAUTHORIZED.of(ce.getMessage()
-// + " Unable to get access for API. Please check the corresponding LMS Setup "));
-// }
-// throw ce;
-// } catch (final Exception e) {
-// throw new RuntimeException("Unexpected: ", e);
-// }
-// });
-// }
-
private String getSEBRestrictionUrl(final String courseId) {
return String.format(OPEN_EDX_DEFAULT_COURSE_RESTRICTION_API_PATH, courseId);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java
deleted file mode 100644
index 8975f97a..00000000
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (c) 2019 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.edx;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-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.OpenEdxSEBRestriction;
-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.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.webservice.servicelayer.dao.FilterMap;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
-
-/** The OpenEdxLmsAPITemplate is separated into two parts:
- * - OpenEdxCourseAccess implements the course access API
- * - OpenEdxCourseRestriction implements the SEB restriction API
- * - Both uses the OpenEdxRestTemplateFactory to create a spring based RestTemplate to access the LMS API */
-final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
-
- private static final Logger log = LoggerFactory.getLogger(OpenEdxLmsAPITemplate.class);
-
- private final OpenEdxCourseAccess openEdxCourseAccess;
- private final OpenEdxCourseRestriction openEdxCourseRestriction;
-
- OpenEdxLmsAPITemplate(
- final OpenEdxCourseAccess openEdxCourseAccess,
- final OpenEdxCourseRestriction openEdxCourseRestriction) {
-
- this.openEdxCourseAccess = openEdxCourseAccess;
- this.openEdxCourseRestriction = openEdxCourseRestriction;
- }
-
- @Override
- public LmsType getType() {
- return LmsType.OPEN_EDX;
- }
-
- @Override
- public LmsSetup lmsSetup() {
- return this.openEdxCourseAccess
- .getApiTemplateDataSupplier()
- .getLmsSetup();
- }
-
- @Override
- public LmsSetupTestResult testCourseAccessAPI() {
- return this.openEdxCourseAccess.initAPIAccess();
- }
-
- @Override
- public LmsSetupTestResult testCourseRestrictionAPI() {
- return this.openEdxCourseRestriction.initAPIAccess();
- }
-
- @Override
- public Result> getQuizzes(final FilterMap filterMap) {
- return this.openEdxCourseAccess
- .protectedQuizzesRequest(filterMap)
- .map(quizzes -> quizzes.stream()
- .filter(LmsAPIService.quizFilterPredicate(filterMap))
- .collect(Collectors.toList()));
- }
-
- @Override
- public Result getQuiz(final String id) {
- final QuizData quizFromCache = this.openEdxCourseAccess.getQuizFromCache(id);
- if (quizFromCache != null) {
- return Result.of(quizFromCache);
- }
-
- return this.openEdxCourseAccess.protectedQuizRequest(id);
- }
-
- @Override
- public Result> getQuizzes(final Set ids) {
- return this.openEdxCourseAccess.getQuizzesFromCache(ids);
- }
-
- @Override
- public void clearCache() {
- this.openEdxCourseAccess.clearCache();
- }
-
- @Override
- public Result getCourseChapters(final String courseId) {
- return Result.tryCatch(() -> this.openEdxCourseAccess
- .getCourseChaptersSupplier(courseId)
- .get());
- }
-
- @Override
- public Result getExamineeAccountDetails(final String examineeSessionId) {
- return this.openEdxCourseAccess.getExamineeAccountDetails(examineeSessionId);
- }
-
- @Override
- public String getExamineeName(final String examineeSessionId) {
- return this.openEdxCourseAccess.getExamineeName(examineeSessionId);
- }
-
- @Override
- public Result getSEBClientRestriction(final Exam exam) {
- if (log.isDebugEnabled()) {
- log.debug("Get SEB Client restriction for Exam: {}", exam);
- }
-
- return this.openEdxCourseRestriction
- .getSEBRestriction(exam.externalId)
- .map(restriction -> SEBRestriction.from(exam.id, restriction));
- }
-
- @Override
- public Result applySEBClientRestriction(
- final String externalExamId,
- final SEBRestriction sebRestrictionData) {
-
- if (log.isDebugEnabled()) {
- log.debug("Apply SEB Client restriction: {}", sebRestrictionData);
- }
-
- return this.openEdxCourseRestriction
- .putSEBRestriction(
- externalExamId,
- OpenEdxSEBRestriction.from(sebRestrictionData))
- .map(result -> sebRestrictionData);
- }
-
- @Override
- public Result releaseSEBClientRestriction(final Exam exam) {
-
- if (log.isDebugEnabled()) {
- log.debug("Release SEB Client restriction for Exam: {}", exam);
- }
-
- return this.openEdxCourseRestriction
- .deleteSEBRestriction(exam.externalId)
- .map(result -> exam);
- }
-
-}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java
index a3609f16..20b3ea92 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplateFactory.java
@@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
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;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
@Lazy
@Service
@@ -87,8 +88,6 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
this.jsonMapper,
openEdxRestTemplateFactory,
this.webserviceInfo,
- this.asyncService,
- this.environment,
this.cacheManager);
final OpenEdxCourseRestriction openEdxCourseRestriction = new OpenEdxCourseRestriction(
@@ -96,7 +95,10 @@ public class OpenEdxLmsAPITemplateFactory implements LmsAPITemplateFactory {
openEdxRestTemplateFactory,
this.restrictionAPIPushCount);
- return new OpenEdxLmsAPITemplate(
+ return new LmsAPITemplateAdapter(
+ this.asyncService,
+ this.environment,
+ apiTemplateDataSupplier,
openEdxCourseAccess,
openEdxCourseRestriction);
});
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java
similarity index 67%
rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java
index ec71bb8e..90fbc680 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockCourseAccessAPI.java
@@ -1,323 +1,224 @@
-/*
- * Copyright (c) 2019 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.mockup;
-
-import java.util.ArrayList;
-import java.util.Collection;
-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.joda.time.DateTimeZone;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.core.env.Environment;
-
-import ch.ethz.seb.sebserver.gbl.Constants;
-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.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.webservice.WebserviceInfo;
-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.AbstractCourseAccess;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
-
-public class MockupLmsAPITemplate implements LmsAPITemplate {
-
- private static final Logger log = LoggerFactory.getLogger(MockupLmsAPITemplate.class);
-
- private final Collection mockups;
- private final WebserviceInfo webserviceInfo;
- private final APITemplateDataSupplier apiTemplateDataSupplier;
-
- private final AbstractCourseAccess abstractCourseAccess;
-
- MockupLmsAPITemplate(
- final AsyncService asyncService,
- final Environment environment,
- final APITemplateDataSupplier apiTemplateDataSupplier,
- final WebserviceInfo webserviceInfo) {
-
- this.apiTemplateDataSupplier = apiTemplateDataSupplier;
- this.webserviceInfo = webserviceInfo;
- this.mockups = new ArrayList<>();
-
- this.abstractCourseAccess = new AbstractCourseAccess(asyncService, environment) {
-
- @Override
- protected Supplier accountDetailsSupplier(final String examineeSessionId) {
- return () -> MockupLmsAPITemplate.this
- .getExamineeAccountDetails_protected(examineeSessionId)
- .getOrThrow();
- }
-
- @Override
- protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
- return () -> MockupLmsAPITemplate.this.getQuizzes_protected(filterMap).getOrThrow();
- }
-
- @Override
- protected Supplier> quizzesSupplier(final Set ids) {
- return () -> MockupLmsAPITemplate.this.getQuizzes_protected(ids).getOrThrow();
- }
-
- @Override
- protected Supplier quizSupplier(final String id) {
- return () -> MockupLmsAPITemplate.this.getQuiz_protected(id).getOrThrow();
- }
-
- @Override
- protected Supplier getCourseChaptersSupplier(final String courseId) {
- throw new UnsupportedOperationException("Course Chapter feature not supported");
- }
-
- };
-
- final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
- final Long lmsSetupId = lmsSetup.id;
- final Long institutionId = lmsSetup.getInstitutionId();
- final LmsType lmsType = lmsSetup.getLmsType();
-
- this.mockups.add(new QuizData(
- "quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "Demo Quiz Mockup
",
- "2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
- this.mockups.add(new QuizData(
- "quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP)", "Demo Quiz Mockup
",
- "2020-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
- this.mockups.add(new QuizData(
- "quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3 (MOCKUP)", "Demo Quiz Mockup
",
- "2018-07-30T09:00:00Z", "2018-08-01T00:00:00Z", "http://lms.mockup.com/api/"));
- this.mockups.add(new QuizData(
- "quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4 (MOCKUP)", "Demo Quiz Mockup
",
- "2018-01-01T00:00:00Z", "2025-01-01T00:00:00Z", "http://lms.mockup.com/api/"));
- this.mockups.add(new QuizData(
- "quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5 (MOCKUP)", "Demo Quiz Mockup
",
- "2018-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
- this.mockups.add(new QuizData(
- "quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6 (MOCKUP)", "Demo Quiz Mockup
",
- "2019-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
- this.mockups.add(new QuizData(
- "quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7 (MOCKUP)", "Demo Quiz Mockup
",
- "2018-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
-
- this.mockups.add(new QuizData(
- "quiz10", institutionId, lmsSetupId, lmsType, "Demo Quiz 10 (MOCKUP)",
- "Starts in a minute and ends after five minutes",
- DateTime.now(DateTimeZone.UTC).plus(Constants.MINUTE_IN_MILLIS)
- .toString(Constants.DEFAULT_DATE_TIME_FORMAT),
- DateTime.now(DateTimeZone.UTC).plus(6 * Constants.MINUTE_IN_MILLIS)
- .toString(Constants.DEFAULT_DATE_TIME_FORMAT),
- "http://lms.mockup.com/api/"));
- }
-
- @Override
- public LmsType getType() {
- return LmsType.MOCKUP;
- }
-
- @Override
- public LmsSetup lmsSetup() {
- return this.apiTemplateDataSupplier.getLmsSetup();
- }
-
- private List checkAttributes() {
- final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
- final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
- final List missingAttrs = new ArrayList<>();
- if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
- missingAttrs.add(APIMessage.fieldValidationError(
- LMS_SETUP.ATTR_LMS_URL,
- "lmsSetup:lmsUrl:notNull"));
- }
- if (!lmsClientCredentials.hasClientId()) {
- missingAttrs.add(APIMessage.fieldValidationError(
- LMS_SETUP.ATTR_LMS_CLIENTNAME,
- "lmsSetup:lmsClientname:notNull"));
- }
- if (!lmsClientCredentials.hasSecret()) {
- missingAttrs.add(APIMessage.fieldValidationError(
- LMS_SETUP.ATTR_LMS_CLIENTSECRET,
- "lmsSetup:lmsClientsecret:notNull"));
- }
- return missingAttrs;
- }
-
- @Override
- public LmsSetupTestResult testCourseAccessAPI() {
- log.info("Test Lms Binding for Mockup and LmsSetup: {}", this.apiTemplateDataSupplier.getLmsSetup());
-
- final List missingAttrs = checkAttributes();
-
- if (!missingAttrs.isEmpty()) {
- return LmsSetupTestResult.ofMissingAttributes(LmsType.MOCKUP, missingAttrs);
- }
-
- if (authenticate()) {
- return LmsSetupTestResult.ofOkay(LmsType.MOCKUP);
- } else {
- return LmsSetupTestResult.ofTokenRequestError(LmsType.MOCKUP, "Illegal access");
- }
- }
-
- @Override
- public LmsSetupTestResult testCourseRestrictionAPI() {
- return LmsSetupTestResult.ofQuizRestrictionAPIError(LmsType.MOCKUP, "unsupported");
- }
-
- @Override
- public Result> getQuizzes(final FilterMap filterMap) {
- return this.abstractCourseAccess.protectedQuizzesRequest(filterMap);
- }
-
- private Result> getQuizzes_protected(final FilterMap filterMap) {
- return Result.tryCatch(() -> {
- if (!authenticate()) {
- throw new IllegalArgumentException("Wrong clientId or secret");
- }
-
- return this.mockups
- .stream()
- .map(this::getExternalAddressAlias)
- .filter(LmsAPIService.quizFilterPredicate(filterMap))
- .collect(Collectors.toList());
- });
- }
-
- @Override
- public Result getQuiz(final String id) {
- return this.abstractCourseAccess.protectedQuizRequest(id);
- }
-
- private Result getQuiz_protected(final String id) {
- return Result.of(this.mockups
- .stream()
- .filter(q -> id.equals(q.id))
- .findFirst()
- .get());
- }
-
- @Override
- public Result> getQuizzes(final Set ids) {
- return this.abstractCourseAccess.protectedQuizzesRequest(ids);
- }
-
- private Result> getQuizzes_protected(final Set ids) {
-
- return Result.tryCatch(() -> {
- if (!authenticate()) {
- throw new IllegalArgumentException("Wrong clientId or secret");
- }
-
- return this.mockups
- .stream()
- .map(this::getExternalAddressAlias)
- .filter(mock -> ids.contains(mock.id))
- .collect(Collectors.toList());
- });
- }
-
- @Override
- public void clearCache() {
-
- }
-
- @Override
- public Result getCourseChapters(final String courseId) {
- return this.abstractCourseAccess.getCourseChapters(courseId);
- }
-
- @Override
- public Result getExamineeAccountDetails(final String examineeSessionId) {
- return this.abstractCourseAccess.getExamineeAccountDetails(examineeSessionId);
- }
-
- private Result getExamineeAccountDetails_protected(final String examineeSessionId) {
- return Result.ofError(new UnsupportedOperationException());
- }
-
- @Override
- public String getExamineeName(final String examineeSessionId) {
- return "--" + " (" + examineeSessionId + ")";
- }
-
- @Override
- public Result getSEBClientRestriction(final Exam exam) {
- log.info("Apply SEB Client restriction for Exam: {}", exam);
- return Result.ofError(new NoSEBRestrictionException());
- }
-
- @Override
- public Result applySEBClientRestriction(
- final String externalExamId,
- final SEBRestriction sebRestrictionData) {
-
- log.info("Apply SEB Client restriction: {}", sebRestrictionData);
- return Result.of(sebRestrictionData);
- }
-
- @Override
- public Result releaseSEBClientRestriction(final Exam exam) {
- log.info("Release SEB Client restriction for Exam: {}", exam);
- return Result.of(exam);
- }
-
- private QuizData getExternalAddressAlias(final QuizData quizData) {
- final String externalAddressAlias = this.webserviceInfo.getLmsExternalAddressAlias("lms.mockup.com");
- if (StringUtils.isNoneBlank(externalAddressAlias)) {
- try {
-
- final String _externalStartURI =
- this.webserviceInfo.getHttpScheme() +
- "://" + externalAddressAlias + "/api/";
-
- return new QuizData(
- quizData.id, quizData.institutionId, quizData.lmsSetupId, quizData.lmsType,
- quizData.name, quizData.description, quizData.startTime,
- quizData.endTime, _externalStartURI, quizData.additionalAttributes);
- } catch (final Exception e) {
- log.error("Failed to create external address from alias: ", e);
- return quizData;
- }
- } else {
- return quizData;
- }
- }
-
- private boolean authenticate() {
- try {
-
- final CharSequence plainClientId = this.apiTemplateDataSupplier.getLmsClientCredentials().clientId;
- if (plainClientId == null || plainClientId.length() <= 0) {
- throw new IllegalAccessException("Wrong client credential");
- }
-
- return true;
- } catch (final Exception e) {
- log.info("Authentication failed: ", e);
- return false;
- }
- }
-
-}
+/*
+ * Copyright (c) 2022 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.mockup;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.APIMessage;
+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.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
+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.webservice.WebserviceInfo;
+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.CourseAccessAPI;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
+
+public class MockCourseAccessAPI implements CourseAccessAPI {
+
+ private final Collection mockups;
+ private final WebserviceInfo webserviceInfo;
+ private final APITemplateDataSupplier apiTemplateDataSupplier;
+
+ public MockCourseAccessAPI(
+ final APITemplateDataSupplier apiTemplateDataSupplier,
+ final WebserviceInfo webserviceInfo) {
+
+ this.apiTemplateDataSupplier = apiTemplateDataSupplier;
+ this.webserviceInfo = webserviceInfo;
+ this.mockups = new ArrayList<>();
+
+ final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
+ final Long lmsSetupId = lmsSetup.id;
+ final Long institutionId = lmsSetup.getInstitutionId();
+ final LmsType lmsType = lmsSetup.getLmsType();
+
+ this.mockups.add(new QuizData(
+ "quiz1", institutionId, lmsSetupId, lmsType, "Demo Quiz 1 (MOCKUP)", "Demo Quiz Mockup
",
+ "2020-01-01T09:00:00Z", null, "http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz2", institutionId, lmsSetupId, lmsType, "Demo Quiz 2 (MOCKUP)", "Demo Quiz Mockup
",
+ "2020-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz3", institutionId, lmsSetupId, lmsType, "Demo Quiz 3 (MOCKUP)", "Demo Quiz Mockup
",
+ "2018-07-30T09:00:00Z", "2018-08-01T00:00:00Z", "http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz4", institutionId, lmsSetupId, lmsType, "Demo Quiz 4 (MOCKUP)", "Demo Quiz Mockup
",
+ "2018-01-01T00:00:00Z", "2025-01-01T00:00:00Z", "http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz5", institutionId, lmsSetupId, lmsType, "Demo Quiz 5 (MOCKUP)", "Demo Quiz Mockup
",
+ "2018-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz6", institutionId, lmsSetupId, lmsType, "Demo Quiz 6 (MOCKUP)", "Demo Quiz Mockup
",
+ "2019-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz7", institutionId, lmsSetupId, lmsType, "Demo Quiz 7 (MOCKUP)", "Demo Quiz Mockup
",
+ "2018-01-01T09:00:00Z", "2025-01-01T09:00:00Z", "http://lms.mockup.com/api/"));
+
+ this.mockups.add(new QuizData(
+ "quiz10", institutionId, lmsSetupId, lmsType, "Demo Quiz 10 (MOCKUP)",
+ "Starts in a minute and ends after five minutes",
+ DateTime.now(DateTimeZone.UTC).plus(Constants.MINUTE_IN_MILLIS)
+ .toString(Constants.DEFAULT_DATE_TIME_FORMAT),
+ DateTime.now(DateTimeZone.UTC).plus(6 * Constants.MINUTE_IN_MILLIS)
+ .toString(Constants.DEFAULT_DATE_TIME_FORMAT),
+ "http://lms.mockup.com/api/"));
+ }
+
+ @Override
+ public LmsSetupTestResult testCourseAccessAPI() {
+ log.info("Test Lms Binding for Mockup and LmsSetup: {}", this.apiTemplateDataSupplier.getLmsSetup());
+
+ final List missingAttrs = checkAttributes();
+
+ if (!missingAttrs.isEmpty()) {
+ return LmsSetupTestResult.ofMissingAttributes(LmsType.MOCKUP, missingAttrs);
+ }
+
+ if (authenticate()) {
+ return LmsSetupTestResult.ofOkay(LmsType.MOCKUP);
+ } else {
+ return LmsSetupTestResult.ofTokenRequestError(LmsType.MOCKUP, "Illegal access");
+ }
+ }
+
+ private List checkAttributes() {
+ final LmsSetup lmsSetup = this.apiTemplateDataSupplier.getLmsSetup();
+ final ClientCredentials lmsClientCredentials = this.apiTemplateDataSupplier.getLmsClientCredentials();
+ final List missingAttrs = new ArrayList<>();
+ if (StringUtils.isBlank(lmsSetup.lmsApiUrl)) {
+ missingAttrs.add(APIMessage.fieldValidationError(
+ LMS_SETUP.ATTR_LMS_URL,
+ "lmsSetup:lmsUrl:notNull"));
+ }
+ if (!lmsClientCredentials.hasClientId()) {
+ missingAttrs.add(APIMessage.fieldValidationError(
+ LMS_SETUP.ATTR_LMS_CLIENTNAME,
+ "lmsSetup:lmsClientname:notNull"));
+ }
+ if (!lmsClientCredentials.hasSecret()) {
+ missingAttrs.add(APIMessage.fieldValidationError(
+ LMS_SETUP.ATTR_LMS_CLIENTSECRET,
+ "lmsSetup:lmsClientsecret:notNull"));
+ }
+ return missingAttrs;
+ }
+
+ @Override
+ public Result> getQuizzes(final FilterMap filterMap) {
+ return Result.tryCatch(() -> {
+ if (!authenticate()) {
+ throw new IllegalArgumentException("Wrong clientId or secret");
+ }
+
+ return this.mockups
+ .stream()
+ .map(this::getExternalAddressAlias)
+ .filter(LmsAPIService.quizFilterPredicate(filterMap))
+ .collect(Collectors.toList());
+ });
+ }
+
+ @Override
+ public Result> getQuizzes(final Set ids) {
+ return Result.tryCatch(() -> {
+ if (!authenticate()) {
+ throw new IllegalArgumentException("Wrong clientId or secret");
+ }
+
+ return this.mockups
+ .stream()
+ .map(this::getExternalAddressAlias)
+ .filter(mock -> ids.contains(mock.id))
+ .collect(Collectors.toList());
+ });
+ }
+
+ @Override
+ public Result getQuiz(final String id) {
+ return Result.of(this.mockups
+ .stream()
+ .filter(q -> id.equals(q.id))
+ .findFirst()
+ .get());
+ }
+
+ @Override
+ public void clearCourseCache() {
+ // No cache here
+ }
+
+ @Override
+ public Result getExamineeAccountDetails(final String examineeUserId) {
+ return Result.ofError(new UnsupportedOperationException());
+ }
+
+ @Override
+ public String getExamineeName(final String examineeUserId) {
+ return "--" + " (" + examineeUserId + ")";
+ }
+
+ @Override
+ public Result getCourseChapters(final String courseId) {
+ return Result.ofError(new UnsupportedOperationException("Course Chapter feature not supported"));
+ }
+
+ private boolean authenticate() {
+ try {
+
+ final CharSequence plainClientId = this.apiTemplateDataSupplier.getLmsClientCredentials().clientId;
+ if (plainClientId == null || plainClientId.length() <= 0) {
+ throw new IllegalAccessException("Wrong client credential");
+ }
+
+ return true;
+ } catch (final Exception e) {
+ log.info("Authentication failed: ", e);
+ return false;
+ }
+ }
+
+ private QuizData getExternalAddressAlias(final QuizData quizData) {
+ final String externalAddressAlias = this.webserviceInfo.getLmsExternalAddressAlias("lms.mockup.com");
+ if (StringUtils.isNoneBlank(externalAddressAlias)) {
+ try {
+
+ final String _externalStartURI =
+ this.webserviceInfo.getHttpScheme() +
+ "://" + externalAddressAlias + "/api/";
+
+ return new QuizData(
+ quizData.id, quizData.institutionId, quizData.lmsSetupId, quizData.lmsType,
+ quizData.name, quizData.description, quizData.startTime,
+ quizData.endTime, _externalStartURI, quizData.additionalAttributes);
+ } catch (final Exception e) {
+ log.error("Failed to create external address from alias: ", e);
+ return quizData;
+ }
+ } else {
+ return quizData;
+ }
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockLmsAPITemplateFactory.java
index 032e7d6a..911b1ab2 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockLmsAPITemplateFactory.java
@@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
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;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
@Lazy
@Service
@@ -47,11 +48,19 @@ public class MockLmsAPITemplateFactory implements LmsAPITemplateFactory {
@Override
public Result create(final APITemplateDataSupplier apiTemplateDataSupplier) {
- return Result.tryCatch(() -> new MockupLmsAPITemplate(
+
+ final MockCourseAccessAPI mockCourseAccessAPI = new MockCourseAccessAPI(
+ apiTemplateDataSupplier,
+ this.webserviceInfo);
+
+ final MockSEBRestrictionAPI mockSEBRestrictionAPI = new MockSEBRestrictionAPI();
+
+ return Result.tryCatch(() -> new LmsAPITemplateAdapter(
this.asyncService,
this.environment,
apiTemplateDataSupplier,
- this.webserviceInfo));
+ mockCourseAccessAPI,
+ mockSEBRestrictionAPI));
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockSEBRestrictionAPI.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockSEBRestrictionAPI.java
new file mode 100644
index 00000000..5cfe2360
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockSEBRestrictionAPI.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022 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.mockup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
+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.util.Result;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
+
+public class MockSEBRestrictionAPI implements SEBRestrictionAPI {
+
+ private static final Logger log = LoggerFactory.getLogger(MockSEBRestrictionAPI.class);
+
+ @Override
+ public LmsSetupTestResult testCourseRestrictionAPI() {
+ return LmsSetupTestResult.ofQuizRestrictionAPIError(LmsType.MOCKUP, "unsupported");
+ }
+
+ @Override
+ public Result getSEBClientRestriction(final Exam exam) {
+ log.info("Apply SEB Client restriction for Exam: {}", exam);
+ return Result.ofError(new NoSEBRestrictionException());
+ }
+
+ @Override
+ public Result applySEBClientRestriction(
+ final String externalExamId,
+ final SEBRestriction sebRestrictionData) {
+
+ log.info("Apply SEB Client restriction: {}", sebRestrictionData);
+ return Result.of(sebRestrictionData);
+ }
+
+ @Override
+ public Result releaseSEBClientRestriction(final Exam exam) {
+ log.info("Release SEB Client restriction for Exam: {}", exam);
+ return Result.of(exam);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
index 54461dfe..8f034416 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
@@ -16,7 +16,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
-import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -37,9 +36,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
-import ch.ethz.seb.sebserver.gbl.async.AsyncService;
-import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
-import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker.State;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
@@ -50,7 +46,7 @@ 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.impl.AbstractCourseAccess;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.CourseAccessAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseDataShort;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseQuizShort;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
@@ -69,7 +65,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
* background task if needed and return immediately to do not block the request.
* The planed Moodle integration on moodle side also defines an improved course access API. This will
* possibly make this synchronous fetch strategy obsolete in the future. */
-public class MoodleCourseAccess extends AbstractCourseAccess {
+public class MoodleCourseAccess implements CourseAccessAPI {
private static final long INITIAL_WAIT_TIME = 3 * Constants.SECOND_IN_MILLIS;
@@ -94,7 +90,6 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
private final JSONMapper jsonMapper;
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
private final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader;
- private final CircuitBreaker> allQuizzesRequest;
private final boolean prependShortCourseName;
private MoodleAPIRestTemplate restTemplate;
@@ -103,28 +98,12 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
final JSONMapper jsonMapper,
final MoodleRestTemplateFactory moodleRestTemplateFactory,
final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader,
- final AsyncService asyncService,
final Environment environment) {
- super(asyncService, environment);
this.jsonMapper = jsonMapper;
this.moodleCourseDataAsyncLoader = moodleCourseDataAsyncLoader;
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
- this.allQuizzesRequest = asyncService.createCircuitBreaker(
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.attempts",
- Integer.class,
- 3),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.blockingTime",
- Long.class,
- Constants.MINUTE_IN_MILLIS),
- environment.getProperty(
- "sebserver.webservice.circuitbreaker.allQuizzesRequest.timeToRecover",
- Long.class,
- Constants.MINUTE_IN_MILLIS));
-
this.prependShortCourseName = BooleanUtils.toBoolean(environment.getProperty(
"sebserver.webservice.lms.moodle.prependShortCourseName",
Constants.TRUE_STRING));
@@ -135,67 +114,7 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
}
@Override
- protected Supplier accountDetailsSupplier(final String examineeSessionId) {
- return () -> {
- try {
- final MoodleAPIRestTemplate template = getRestTemplate()
- .getOrThrow();
-
- final MultiValueMap queryAttributes = new LinkedMultiValueMap<>();
- queryAttributes.add("field", "id");
- queryAttributes.add("values[0]", examineeSessionId);
-
- 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,
- Utils.truncateText(userDetailsJSON, 2000));
- throw new RuntimeException("No user details on Moodle API request (access-denied)");
- }
-
- final MoodleUserDetails[] userDetails = this.jsonMapper. readValue(
- userDetailsJSON,
- new TypeReference() {
- });
-
- if (userDetails == null || userDetails.length <= 0) {
- throw new RuntimeException("No user details on Moodle API request");
- }
-
- final Map 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);
- }
- };
- }
-
- LmsSetupTestResult initAPIAccess() {
-
+ public LmsSetupTestResult testCourseAccessAPI() {
final LmsSetupTestResult attributesCheck = this.moodleRestTemplateFactory.test();
if (!attributesCheck.isOk()) {
return attributesCheck;
@@ -223,7 +142,45 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
return LmsSetupTestResult.ofOkay(LmsType.MOODLE);
}
- public Result getQuizFromCache(final String id) {
+ @Override
+ public Result> getQuizzes(final FilterMap filterMap) {
+ return Result.tryCatch(() -> getRestTemplate()
+ .map(template -> collectAllQuizzes(template, filterMap))
+ .getOr(Collections.emptyList()));
+ }
+
+ @Override
+ public Result> getQuizzes(final Set ids) {
+ return Result.tryCatch(() -> {
+ final List cached = getCached();
+ final List available = (cached != null)
+ ? cached
+ : Collections.emptyList();
+
+ final Map quizMapping = available
+ .stream()
+ .collect(Collectors.toMap(q -> q.id, Function.identity()));
+
+ if (!quizMapping.keySet().containsAll(ids)) {
+
+ final Map collect = getRestTemplate()
+ .map(template -> getQuizzesForIds(template, ids))
+ .getOrElse(() -> Collections.emptyList())
+ .stream()
+ .collect(Collectors.toMap(qd -> qd.id, Function.identity()));
+ if (collect != null) {
+ quizMapping.clear();
+ quizMapping.putAll(collect);
+ }
+ }
+
+ return quizMapping.values();
+
+ });
+ }
+
+ @Override
+ public Result getQuiz(final String id) {
return Result.tryCatch(() -> {
final Map cachedCourseData = this.moodleCourseDataAsyncLoader
@@ -255,76 +212,93 @@ public class MoodleCourseAccess extends AbstractCourseAccess {
}
// get from LMS in protected request
- return super.protectedQuizRequest(id).getOrThrow();
- });
- }
-
- public Result> getQuizzesFromCache(final Set ids) {
- return Result.tryCatch(() -> {
- final List cached = getCached();
- final List available = (cached != null)
- ? cached
- : Collections.emptyList();
-
- final Map quizMapping = available
- .stream()
- .collect(Collectors.toMap(q -> q.id, Function.identity()));
-
- if (!quizMapping.keySet().containsAll(ids)) {
-
- final Map collect = super.quizzesRequest
- .protectedRun(quizzesSupplier(ids))
- .onError(error -> log.error("Failed to get quizzes by ids: ", error))
- .getOrElse(() -> Collections.emptyList())
- .stream()
- .collect(Collectors.toMap(qd -> qd.id, Function.identity()));
- if (collect != null) {
- quizMapping.clear();
- quizMapping.putAll(collect);
- }
- }
-
- return quizMapping.values();
-
- });
- }
-
- @Override
- protected Supplier quizSupplier(final String id) {
- return () -> {
final Set ids = Stream.of(id).collect(Collectors.toSet());
return getRestTemplate()
.map(template -> getQuizzesForIds(template, ids))
.getOr(Collections.emptyList())
.get(0);
- };
+ });
}
@Override
- protected Supplier> quizzesSupplier(final Set ids) {
- return () -> getRestTemplate()
- .map(template -> getQuizzesForIds(template, ids))
- .getOr(Collections.emptyList());
+ public void clearCourseCache() {
+ // TODO Auto-generated method stub
}
@Override
- protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
- return () -> getRestTemplate()
- .map(template -> collectAllQuizzes(template, filterMap))
- .getOr(Collections.emptyList());
+ public Result getExamineeAccountDetails(final String examineeSessionId) {
+ return Result.tryCatch(() -> {
+
+ final MoodleAPIRestTemplate template = getRestTemplate()
+ .getOrThrow();
+
+ final MultiValueMap queryAttributes = new LinkedMultiValueMap<>();
+ queryAttributes.add("field", "id");
+ queryAttributes.add("values[0]", examineeSessionId);
+
+ 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,
+ Utils.truncateText(userDetailsJSON, 2000));
+ throw new RuntimeException("No user details on Moodle API request (access-denied)");
+ }
+
+ final MoodleUserDetails[] userDetails = this.jsonMapper. readValue(
+ userDetailsJSON,
+ new TypeReference() {
+ });
+
+ if (userDetails == null || userDetails.length <= 0) {
+ throw new RuntimeException("No user details on Moodle API request");
+ }
+
+ final Map 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);
+ });
}
@Override
- protected Supplier getCourseChaptersSupplier(final String courseId) {
- throw new UnsupportedOperationException("not available yet");
+ public String getExamineeName(final String examineeUserId) {
+ return getExamineeAccountDetails(examineeUserId)
+ .map(ExamineeAccountDetails::getDisplayName)
+ .onError(error -> log.warn("Failed to request user-name for ID: {}", error.getMessage(), error))
+ .getOr(examineeUserId);
}
@Override
- protected FetchStatus getFetchStatus() {
- if (this.allQuizzesRequest.getState() != State.CLOSED) {
- return FetchStatus.FETCH_ERROR;
- }
+ public Result getCourseChapters(final String courseId) {
+ return Result.ofError(new UnsupportedOperationException("not available yet"));
+ }
+
+ @Override
+ public FetchStatus getFetchStatus() {
+
if (this.moodleCourseDataAsyncLoader.isRunning()) {
return FetchStatus.ASYNC_FETCH_RUNNING;
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java
index c36ef09e..44716a0f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java
@@ -22,10 +22,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.MoodleSEBRestriction;
+import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
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.util.Result;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
@@ -57,7 +60,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
* Delete all key (and remove restrictions):
* POST:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction_delete&courseId=123 */
-public class MoodleCourseRestriction {
+public class MoodleCourseRestriction implements SEBRestrictionAPI {
private static final Logger log = LoggerFactory.getLogger(MoodleCourseRestriction.class);
@@ -84,7 +87,8 @@ public class MoodleCourseRestriction {
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
}
- LmsSetupTestResult initAPIAccess() {
+ @Override
+ public LmsSetupTestResult testCourseRestrictionAPI() {
// try to call the SEB Restrictions API
try {
@@ -108,18 +112,35 @@ public class MoodleCourseRestriction {
return LmsSetupTestResult.ofOkay(LmsType.MOODLE);
}
- Result getSEBRestriction(
- final String internalId) {
-
+ @Override
+ public Result getSEBClientRestriction(final Exam exam) {
return Result.tryCatch(() -> {
return getSEBRestriction(
- MoodleCourseAccess.getQuizId(internalId),
- MoodleCourseAccess.getShortname(internalId),
- MoodleCourseAccess.getIdnumber(internalId))
+ MoodleCourseAccess.getQuizId(exam.externalId),
+ MoodleCourseAccess.getShortname(exam.externalId),
+ MoodleCourseAccess.getIdnumber(exam.externalId))
+ .map(restriction -> SEBRestriction.from(exam.id, restriction))
.getOrThrow();
});
}
+ @Override
+ public Result applySEBClientRestriction(
+ final String externalExamId,
+ final SEBRestriction sebRestrictionData) {
+
+ return this.updateSEBRestriction(
+ externalExamId,
+ MoodleSEBRestriction.from(sebRestrictionData))
+ .map(result -> sebRestrictionData);
+ }
+
+ @Override
+ public Result releaseSEBClientRestriction(final Exam exam) {
+ return this.deleteSEBRestriction(exam.externalId)
+ .map(result -> exam);
+ }
+
Result getSEBRestriction(
final String quizId,
final String shortname,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java
deleted file mode 100644
index 264d24ec..00000000
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (c) 2020 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.moodle;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-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.MoodleSEBRestriction;
-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.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.webservice.servicelayer.dao.FilterMap;
-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.NoSEBRestrictionException;
-
-/** The MoodleLmsAPITemplate is separated into two parts:
- * - MoodleCourseAccess implements the course access API
- * - MoodleCourseRestriction implements the SEB restriction API
- * - Both uses the MoodleRestTemplateFactore to create a spring based RestTemplate to access the LMS API
- *
- * NOTE: Because of the missing integration on Moodle side so far the MoodleCourseAccess
- * needs to deal with Moodle's standard API functions that don't allow to filter and page course/quiz data
- * in an easy and proper way. Therefore we have to fetch all course and quiz data from Moodle before
- * filtering and paging can be applied. Since there are possibly thousands of active courses and quizzes
- * this moodle course access implements an synchronous fetch as well as an asynchronous fetch strategy.
- * The asynchronous fetch strategy is started within a background task that batches the course and quiz
- * requests to Moodle and fill up a shared cache. A SEB Server LMS API request will start the
- * background task if needed and return immediately to do not block the request.
- * The planed Moodle integration on moodle side also defines an improved course access API. This will
- * possibly make this synchronous fetch strategy obsolete in the future. */
-public class MoodleLmsAPITemplate implements LmsAPITemplate {
-
- private static final Logger log = LoggerFactory.getLogger(MoodleLmsAPITemplate.class);
-
- private final MoodleCourseAccess moodleCourseAccess;
- private final MoodleCourseRestriction moodleCourseRestriction;
-
- protected MoodleLmsAPITemplate(
- final MoodleCourseAccess moodleCourseAccess,
- final MoodleCourseRestriction moodleCourseRestriction) {
-
- this.moodleCourseAccess = moodleCourseAccess;
- this.moodleCourseRestriction = moodleCourseRestriction;
- }
-
- @Override
- public LmsType getType() {
- return LmsType.MOODLE;
- }
-
- @Override
- public LmsSetup lmsSetup() {
- return this.moodleCourseAccess
- .getApiTemplateDataSupplier()
- .getLmsSetup();
- }
-
- @Override
- public LmsSetupTestResult testCourseAccessAPI() {
- return this.moodleCourseAccess.initAPIAccess();
- }
-
- @Override
- public LmsSetupTestResult testCourseRestrictionAPI() {
- throw new NoSEBRestrictionException();
- }
-
- @Override
- public Result> getQuizzes(final FilterMap filterMap) {
- return this.moodleCourseAccess
- .protectedQuizzesRequest(filterMap)
- .map(quizzes -> quizzes.stream()
- .filter(LmsAPIService.quizFilterPredicate(filterMap))
- .collect(Collectors.toList()));
- }
-
- @Override
- public Result getQuiz(final String id) {
- return this.moodleCourseAccess.getQuizFromCache(id);
- }
-
- @Override
- public Result> getQuizzes(final Set ids) {
- return this.moodleCourseAccess.getQuizzesFromCache(ids);
- }
-
- @Override
- public void clearCache() {
- this.moodleCourseAccess.clearCache();
- }
-
- @Override
- public Result getCourseChapters(final String courseId) {
- return Result.tryCatch(() -> this.moodleCourseAccess
- .getCourseChaptersSupplier(courseId)
- .get());
- }
-
- @Override
- public Result getExamineeAccountDetails(final String examineeSessionId) {
- return this.moodleCourseAccess.getExamineeAccountDetails(examineeSessionId);
- }
-
- @Override
- public String getExamineeName(final String examineeSessionId) {
- return this.moodleCourseAccess.getExamineeName(examineeSessionId);
- }
-
- @Override
- public Result getSEBClientRestriction(final Exam exam) {
- if (log.isDebugEnabled()) {
- log.debug("Get SEB Client restriction for Exam: {}", exam.externalId);
- }
-
- return this.moodleCourseRestriction
- .getSEBRestriction(exam.externalId)
- .map(restriction -> SEBRestriction.from(exam.id, restriction));
- }
-
- @Override
- public Result applySEBClientRestriction(
- final String externalExamId,
- final SEBRestriction sebRestrictionData) {
-
- if (log.isDebugEnabled()) {
- log.debug("Apply SEB Client restriction: {}", sebRestrictionData);
- }
-
- return this.moodleCourseRestriction
- .updateSEBRestriction(
- externalExamId,
- MoodleSEBRestriction.from(sebRestrictionData))
- .map(result -> sebRestrictionData);
- }
-
- @Override
- public Result releaseSEBClientRestriction(final Exam exam) {
- if (log.isDebugEnabled()) {
- log.debug("Release SEB Client restriction for Exam: {}", exam);
- }
-
- return this.moodleCourseRestriction
- .deleteSEBRestriction(exam.externalId)
- .map(result -> exam);
- }
-
-}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java
index 7659d3a7..3df814d7 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java
@@ -27,6 +27,7 @@ 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;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
@Lazy
@Service
@@ -88,14 +89,16 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
this.jsonMapper,
moodleRestTemplateFactory,
asyncLoaderPrototype,
- this.asyncService,
this.environment);
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
this.jsonMapper,
moodleRestTemplateFactory);
- return new MoodleLmsAPITemplate(
+ return new LmsAPITemplateAdapter(
+ this.asyncService,
+ this.environment,
+ apiTemplateDataSupplier,
moodleCourseAccess,
moodleCourseRestriction);
});
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 3a84b410..6b27ca98 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
@@ -14,8 +14,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
@@ -24,7 +24,6 @@ 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;
@@ -35,7 +34,6 @@ import org.springframework.web.client.RestTemplate;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
-import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
import ch.ethz.seb.sebserver.gbl.client.ProxyData;
@@ -75,11 +73,9 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
final ClientCredentialService clientCredentialService,
final APITemplateDataSupplier apiTemplateDataSupplier,
- final AsyncService asyncService,
- final Environment environment,
final CacheManager cacheManager) {
- super(asyncService, environment, cacheManager);
+ super(cacheManager);
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.clientCredentialService = clientCredentialService;
@@ -166,7 +162,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
@Override
public Result> getQuizzes(final FilterMap filterMap) {
return this
- .protectedQuizzesRequest(filterMap)
+ .allQuizzesRequest(filterMap)
.map(quizzes -> quizzes.stream()
.filter(LmsAPIService.quizFilterPredicate(filterMap))
.collect(Collectors.toList()));
@@ -187,7 +183,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
});
if (!leftIds.isEmpty()) {
- result.addAll(super.protectedQuizzesRequest(leftIds).getOrThrow());
+ result.addAll(quizzesRequest(leftIds).getOrThrow());
}
return result;
@@ -201,18 +197,35 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
return Result.of(fromCache);
}
- return super.protectedQuizRequest(id);
+ return quizRequest(id);
}
@Override
- protected Supplier> allQuizzesSupplier(final FilterMap filterMap) {
- return () -> {
+ public Result getExamineeAccountDetails(final String examineeUserId) {
+ return getRestTemplate().map(t -> this.getExamineeById(t, examineeUserId));
+ }
+
+ @Override
+ public String getExamineeName(final String examineeUserId) {
+ return getExamineeAccountDetails(examineeUserId)
+ .map(ExamineeAccountDetails::getDisplayName)
+ .onError(error -> log.warn("Failed to request user-name for ID: {}", error.getMessage(), error))
+ .getOr(examineeUserId);
+ }
+
+ @Override
+ public Result getCourseChapters(final String courseId) {
+ return Result.ofError(new UnsupportedOperationException("No Course Chapter available for OpenOLAT LMS"));
+ }
+
+ protected Result> allQuizzesRequest(final FilterMap filterMap) {
+ return Result.tryCatch(() -> {
final List res = getRestTemplate()
.map(t -> this.collectAllQuizzes(t, filterMap))
.getOrThrow();
super.putToCache(res);
return res;
- };
+ });
}
private String examUrl(final long olatRepositoryId) {
@@ -254,16 +267,15 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
.collect(Collectors.toList());
}
- @Override
- protected Supplier> quizzesSupplier(final Set ids) {
- return () -> ids.stream().map(id -> quizSupplier(id).get()).collect(Collectors.toList());
+ protected Result> quizzesRequest(final Set ids) {
+ return Result.tryCatch(() -> ids.stream()
+ .map(id -> quizRequest(id).getOr(null))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
}
- @Override
- protected Supplier quizSupplier(final String id) {
- return () -> getRestTemplate()
- .map(t -> this.quizById(t, id))
- .getOrThrow();
+ protected Result quizRequest(final String id) {
+ return getRestTemplate().map(t -> this.quizById(t, id));
}
private QuizData quizById(final OlatLmsRestTemplate restTemplate, final String id) {
@@ -295,20 +307,6 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
attrs);
}
- @Override
- protected Supplier accountDetailsSupplier(final String id) {
- return () -> getRestTemplate()
- .map(t -> this.getExamineeById(t, id))
- .getOrThrow();
- }
-
- @Override
- protected Supplier getCourseChaptersSupplier(final String courseId) {
- return () -> {
- throw new UnsupportedOperationException("No Course Chapter available for OpenOLAT LMS");
- };
- }
-
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);
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
index cb4b71a9..3330737b 100644
--- 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
@@ -22,6 +22,7 @@ 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;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
@Lazy
@Service
@@ -63,13 +64,17 @@ public class OlatLmsAPITemplateFactory implements LmsAPITemplateFactory {
@Override
public Result create(final APITemplateDataSupplier apiTemplateDataSupplier) {
return Result.tryCatch(() -> {
- return new OlatLmsAPITemplate(
+ final OlatLmsAPITemplate olatLmsAPITemplate = new OlatLmsAPITemplate(
this.clientHttpRequestFactoryService,
this.clientCredentialService,
apiTemplateDataSupplier,
+ this.cacheManager);
+ return new LmsAPITemplateAdapter(
this.asyncService,
this.environment,
- this.cacheManager);
+ apiTemplateDataSupplier,
+ olatLmsAPITemplate,
+ olatLmsAPITemplate);
});
}
diff --git a/src/main/resources/config/application-dev.properties b/src/main/resources/config/application-dev.properties
index e3848c27..88b4f69f 100644
--- a/src/main/resources/config/application-dev.properties
+++ b/src/main/resources/config/application-dev.properties
@@ -11,7 +11,7 @@ logging.level.ROOT=INFO
logging.level.ch=INFO
logging.level.ch.ethz.seb.sebserver.webservice.datalayer=INFO
logging.level.org.springframework.cache=INFO
-logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl=INFO
+logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl=DEBUG
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.session=DEBUG
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring=INFO
logging.level.ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator=DEBUG
diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java
index 8cd82b35..74a1b617 100644
--- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java
+++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java
@@ -22,8 +22,6 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
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.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
@@ -75,7 +73,6 @@ public class MoodleCourseAccessTest {
new JSONMapper(),
moodleRestTemplateFactory,
null,
- new AsyncService(new AsyncRunner()),
this.env);
final String examId = "123";
@@ -123,10 +120,9 @@ public class MoodleCourseAccessTest {
new JSONMapper(),
moodleRestTemplateFactory,
null,
- mock(AsyncService.class),
this.env);
- final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
+ final LmsSetupTestResult initAPIAccess = moodleCourseAccess.testCourseAccessAPI();
assertNotNull(initAPIAccess);
assertFalse(initAPIAccess.errors.isEmpty());
assertTrue(initAPIAccess.hasError(ErrorType.TOKEN_REQUEST));
@@ -145,10 +141,9 @@ public class MoodleCourseAccessTest {
new JSONMapper(),
moodleRestTemplateFactory,
null,
- mock(AsyncService.class),
this.env);
- final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
+ final LmsSetupTestResult initAPIAccess = moodleCourseAccess.testCourseAccessAPI();
assertNotNull(initAPIAccess);
assertFalse(initAPIAccess.errors.isEmpty());
assertTrue(initAPIAccess.hasError(ErrorType.QUIZ_ACCESS_API_REQUEST));
@@ -166,10 +161,9 @@ public class MoodleCourseAccessTest {
new JSONMapper(),
moodleRestTemplateFactory,
null,
- mock(AsyncService.class),
this.env);
- final LmsSetupTestResult initAPIAccess = moodleCourseAccess.initAPIAccess();
+ final LmsSetupTestResult initAPIAccess = moodleCourseAccess.testCourseAccessAPI();
assertNotNull(initAPIAccess);
assertTrue(initAPIAccess.errors.isEmpty());
From 091f7408a703a2b41f76292b87d40a38f8cf3acd Mon Sep 17 00:00:00 2001
From: anhefti
Date: Mon, 21 Mar 2022 13:54:48 +0100
Subject: [PATCH 003/155] SEBSERV-158 preparation
---
.../servicelayer/dao/impl/ExamDAOImpl.java | 2 +-
.../moodle/MoodleLmsAPITemplateFactory.java | 66 +++++++++++------
.../{ => legacy}/MoodleCourseAccess.java | 10 +--
.../MoodleCourseDataAsyncLoader.java | 6 +-
.../{ => legacy}/MoodleCourseRestriction.java | 6 +-
.../MoodleRestTemplateFactory.java | 4 +-
.../impl/moodle/plugin/MoodlePluginCheck.java | 31 ++++++++
.../plugin/MoodlePluginCourseAccess.java | 73 +++++++++++++++++++
.../plugin/MoodlePluginCourseRestriction.java | 44 +++++++++++
.../integration/UseCasesIntegrationTest.java | 5 +-
.../{ => legacy}/MoodleCourseAccessTest.java | 4 +-
11 files changed, 211 insertions(+), 40 deletions(-)
rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/{ => legacy}/MoodleCourseAccess.java (99%)
rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/{ => legacy}/MoodleCourseDataAsyncLoader.java (96%)
rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/{ => legacy}/MoodleCourseRestriction.java (96%)
rename src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/{ => legacy}/MoodleRestTemplateFactory.java (99%)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCheck.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseRestriction.java
rename src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/{ => legacy}/MoodleCourseAccessTest.java (96%)
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
index f9f1d6bb..666487df 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java
@@ -62,7 +62,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
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.moodle.MoodleCourseAccess;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseAccess;
@Lazy
@Component
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java
index 3df814d7..6db52389 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplateFactory.java
@@ -28,12 +28,20 @@ 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;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsAPITemplateAdapter;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseAccess;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseRestriction;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MoodlePluginCheck;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MoodlePluginCourseAccess;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.plugin.MoodlePluginCourseRestriction;
@Lazy
@Service
@WebServiceProfile
public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
+ private final MoodlePluginCheck moodlePluginCheck;
private final JSONMapper jsonMapper;
private final AsyncService asyncService;
private final Environment environment;
@@ -43,6 +51,7 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
private final String[] alternativeTokenRequestPaths;
protected MoodleLmsAPITemplateFactory(
+ final MoodlePluginCheck moodlePluginCheck,
final JSONMapper jsonMapper,
final AsyncService asyncService,
final Environment environment,
@@ -51,6 +60,7 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
final ApplicationContext applicationContext,
@Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths) {
+ this.moodlePluginCheck = moodlePluginCheck;
this.jsonMapper = jsonMapper;
this.asyncService = asyncService;
this.environment = environment;
@@ -73,34 +83,48 @@ public class MoodleLmsAPITemplateFactory implements LmsAPITemplateFactory {
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = apiTemplateDataSupplier.getLmsSetup();
-
final MoodleCourseDataAsyncLoader asyncLoaderPrototype = this.applicationContext
.getBean(MoodleCourseDataAsyncLoader.class);
asyncLoaderPrototype.init(lmsSetup.getModelId());
- final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
- this.jsonMapper,
- apiTemplateDataSupplier,
- this.clientCredentialService,
- this.clientHttpRequestFactoryService,
- this.alternativeTokenRequestPaths);
+ if (this.moodlePluginCheck.checkPluginAvailable(lmsSetup)) {
- final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
- this.jsonMapper,
- moodleRestTemplateFactory,
- asyncLoaderPrototype,
- this.environment);
+ final MoodlePluginCourseAccess moodlePluginCourseAccess = new MoodlePluginCourseAccess();
+ final MoodlePluginCourseRestriction moodlePluginCourseRestriction = new MoodlePluginCourseRestriction();
- final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
- this.jsonMapper,
- moodleRestTemplateFactory);
+ return new LmsAPITemplateAdapter(
+ this.asyncService,
+ this.environment,
+ apiTemplateDataSupplier,
+ moodlePluginCourseAccess,
+ moodlePluginCourseRestriction);
- return new LmsAPITemplateAdapter(
- this.asyncService,
- this.environment,
- apiTemplateDataSupplier,
- moodleCourseAccess,
- moodleCourseRestriction);
+ } else {
+
+ final MoodleRestTemplateFactory moodleRestTemplateFactory = new MoodleRestTemplateFactory(
+ this.jsonMapper,
+ apiTemplateDataSupplier,
+ this.clientCredentialService,
+ this.clientHttpRequestFactoryService,
+ this.alternativeTokenRequestPaths);
+
+ final MoodleCourseAccess moodleCourseAccess = new MoodleCourseAccess(
+ this.jsonMapper,
+ moodleRestTemplateFactory,
+ asyncLoaderPrototype,
+ this.environment);
+
+ final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
+ this.jsonMapper,
+ moodleRestTemplateFactory);
+
+ return new LmsAPITemplateAdapter(
+ this.asyncService,
+ this.environment,
+ apiTemplateDataSupplier,
+ moodleCourseAccess,
+ moodleCourseRestriction);
+ }
});
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseAccess.java
similarity index 99%
rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseAccess.java
index 8f034416..6e98b0b0 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccess.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseAccess.java
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
import java.util.ArrayList;
import java.util.Collection;
@@ -47,9 +47,9 @@ 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.CourseAccessAPI;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseDataShort;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseDataAsyncLoader.CourseQuizShort;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader.CourseDataShort;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseDataAsyncLoader.CourseQuizShort;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
/** Implements the LmsAPITemplate for Open edX LMS Course API access.
*
@@ -94,7 +94,7 @@ public class MoodleCourseAccess implements CourseAccessAPI {
private MoodleAPIRestTemplate restTemplate;
- protected MoodleCourseAccess(
+ public MoodleCourseAccess(
final JSONMapper jsonMapper,
final MoodleRestTemplateFactory moodleRestTemplateFactory,
final MoodleCourseDataAsyncLoader moodleCourseDataAsyncLoader,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseDataAsyncLoader.java
similarity index 96%
rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java
rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseDataAsyncLoader.java
index 290afbc2..35056b6f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseDataAsyncLoader.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseDataAsyncLoader.java
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
import java.io.IOException;
import java.util.ArrayList;
@@ -47,8 +47,8 @@ import ch.ethz.seb.sebserver.gbl.async.AsyncService;
import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleCourseAccess.Warning;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleCourseAccess.Warning;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
@Lazy
@Component
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseRestriction.java
similarity index 96%
rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java
rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseRestriction.java
index 44716a0f..ccff2e87 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseRestriction.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseRestriction.java
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
import java.util.ArrayList;
@@ -30,7 +30,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
/** GET:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123
@@ -79,7 +79,7 @@ public class MoodleCourseRestriction implements SEBRestrictionAPI {
private MoodleAPIRestTemplate restTemplate;
- protected MoodleCourseRestriction(
+ public MoodleCourseRestriction(
final JSONMapper jsonMapper,
final MoodleRestTemplateFactory moodleRestTemplateFactory) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleRestTemplateFactory.java
similarity index 99%
rename from src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java
rename to src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleRestTemplateFactory.java
index 39fc9654..5de31e82 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleRestTemplateFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleRestTemplateFactory.java
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
import java.util.ArrayList;
import java.util.Arrays;
@@ -53,7 +53,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.APITemplateDataSupplier;
-class MoodleRestTemplateFactory {
+public class MoodleRestTemplateFactory {
private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCheck.java
new file mode 100644
index 00000000..f0f9955b
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCheck.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 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.moodle.plugin;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
+import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
+
+@Lazy
+@Service
+@WebServiceProfile
+public class MoodlePluginCheck {
+
+ /** Used to check if the moodle SEB Server plugin is available for a given LMSSetup.
+ *
+ * @param lmsSetup The LMS Setup
+ * @return true if the SEB Server plugin is available */
+ public boolean checkPluginAvailable(final LmsSetup lmsSetup) {
+ // TODO check if the moodle plugin is installed for the specified LMS Setup
+ return false;
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java
new file mode 100644
index 00000000..83dc3ba2
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseAccess.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2022 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.moodle.plugin;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
+import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.CourseAccessAPI;
+
+public class MoodlePluginCourseAccess implements CourseAccessAPI {
+
+ @Override
+ public LmsSetupTestResult testCourseAccessAPI() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result> getQuizzes(final FilterMap filterMap) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result> getQuizzes(final Set ids) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result getQuiz(final String id) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void clearCourseCache() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Result getExamineeAccountDetails(final String examineeUserId) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getExamineeName(final String examineeUserId) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result getCourseChapters(final String courseId) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseRestriction.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseRestriction.java
new file mode 100644
index 00000000..d2eeb5e3
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/plugin/MoodlePluginCourseRestriction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 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.moodle.plugin;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
+import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
+import ch.ethz.seb.sebserver.gbl.util.Result;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
+
+public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
+
+ @Override
+ public LmsSetupTestResult testCourseRestrictionAPI() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result getSEBClientRestriction(final Exam exam) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result applySEBClientRestriction(final String externalExamId,
+ final SEBRestriction sebRestrictionData) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Result releaseSEBClientRestriction(final Exam exam) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java
index b837b219..3965aa8e 100644
--- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java
+++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java
@@ -27,7 +27,6 @@ import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
-import org.apache.commons.codec.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@@ -1555,7 +1554,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
.withURIVariable(API.PARAM_MODEL_ID, configurationNode.getModelId())
.withResponseExtractor(response -> {
final InputStream input = response.getBody();
- final String xmlString = StreamUtils.copyToString(input, Charsets.UTF_8);
+ final String xmlString = StreamUtils.copyToString(input, java.nio.charset.StandardCharsets.UTF_8);
assertNotNull(xmlString);
for (final ConfigurationAttribute attribute : attributes) {
if (attribute.name.contains(".") || attribute.name.equals("kioskMode")) {
@@ -2078,7 +2077,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
.withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(examConfigurationMap.examId))
.withResponseExtractor(response -> {
final InputStream input = response.getBody();
- final String xmlString = StreamUtils.copyToString(input, Charsets.UTF_8);
+ final String xmlString = StreamUtils.copyToString(input, java.nio.charset.StandardCharsets.UTF_8);
assertNotNull(xmlString);
// assertEquals(
// "allowAudioCapture allowBrowsingBackForward allowDictation allowDictionaryLookup allowDisplayMirroring allowDownUploads allowedDisplayBuiltin allowedDisplaysMaxNumber 1 allowFlashFullscreen allowiOSBetaVersionNumber 0 allowiOSVersionNumberMajor 9 allowiOSVersionNumberMinor 3 allowiOSVersionNumberPatch 5 allowPDFPlugIn allowPreferencesWindow allowQuit allowScreenSharing allowSiri allowSpellCheck allowSpellCheckDictionary da-DK en-AU en-GB en-US es-ES fr-FR pt-PT sv-SE sv-FI allowSwitchToApplications allowUserAppFolderInstall allowUserSwitching allowVideoCapture allowVirtualMachine allowWlan audioControlEnabled audioMute audioSetVolumeLevel audioVolumeLevel 25 blacklistURLFilter blockPopUpWindows browserMessagingPingTime 120000 browserMessagingSocket ws://localhost:8706 browserScreenKeyboard browserURLSalt browserUserAgent browserUserAgentiOS 0 browserUserAgentiOSCustom browserUserAgentMac 0 browserUserAgentMacCustom browserUserAgentWinDesktopMode 0 browserUserAgentWinDesktopModeCustom browserUserAgentWinTouchMode 0 browserUserAgentWinTouchModeCustom browserUserAgentWinTouchModeIPad Mozilla/5.0 (iPad; CPU OS 12_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1 browserViewMode 0 browserWindowAllowReload browserWindowShowURL 0 browserWindowTitleSuffix chooseFileToUploadPolicy 0 createNewDesktop detectStoppedProcess downloadAndOpenSebConfig downloadDirectoryOSX downloadDirectoryWin downloadPDFFiles enableAltEsc enableAltF4 enableAltMouseWheel enableAltTab enableAppSwitcherCheck enableBrowserWindowToolbar enableCtrlEsc enableDrawingEditor enableEsc enableF1 enableF10 enableF11 enableF12 enableF2 enableF3 enableF4 enableF5 enableF6 enableF7 enableF8 enableF9 enableJava enableJavaScript enableLogging enablePlugIns enablePrintScreen enablePrivateClipboard enableRightMouse enableSebBrowser enableStartMenu enableTouchExit enableZoomPage enableZoomText examSessionClearCookiesOnEnd examSessionClearCookiesOnStart exitKey1 2 exitKey2 10 exitKey3 5 forceAppFolderInstall hashedAdminPassword hashedQuitPassword hideBrowserWindowToolbar hookKeys ignoreExitKeys insideSebEnableChangeAPassword insideSebEnableEaseOfAccess insideSebEnableLockThisComputer insideSebEnableLogOff insideSebEnableNetworkConnectionSelector insideSebEnableShutDown insideSebEnableStartTaskManager insideSebEnableSwitchUser insideSebEnableVmWareClientShade killExplorerShell lockOnMessageSocketClose logDirectoryOSX logDirectoryWin logLevel 1 mainBrowserWindowHeight 100% mainBrowserWindowPositioning 1 mainBrowserWindowWidth 100% minMacOSVersion 0 mobileAllowPictureInPictureMediaPlayback mobileAllowQRCodeConfig mobileAllowSingleAppMode mobileEnableASAM mobileEnableGuidedAccessLinkTransform mobilePreventAutoLock mobileShowSettings mobileStatusBarAppearance 1 mobileStatusBarAppearanceExtended 1 monitorProcesses newBrowserWindowAllowReload newBrowserWindowByLinkBlockForeign newBrowserWindowByLinkHeight 100% newBrowserWindowByLinkPolicy 2 newBrowserWindowByLinkPositioning 2 newBrowserWindowByLinkWidth 100% newBrowserWindowByScriptBlockForeign newBrowserWindowByScriptPolicy 2 newBrowserWindowNavigation newBrowserWindowShowReloadWarning newBrowserWindowShowURL 1 openDownloads originatorVersion SEB_Server_0.3.0 permittedProcesses active allowUserToChooseApp arguments autostart description executable firefox.exe iconInTaskbar identifier Firefox originalName firefox.exe os 1 path ../xulrunner/ runInBackground strongKill title SEB pinEmbeddedCertificates prohibitedProcesses active currentUser description executable Riot identifier originalName Riot os 1 strongKill user active currentUser description executable seamonkey identifier originalName seamonkey os 1 strongKill user active currentUser description executable Discord identifier originalName Discord os 1 strongKill user active currentUser description executable Slack identifier originalName Slack os 1 strongKill user active currentUser description executable Teams identifier originalName Teams os 1 strongKill user active currentUser description executable CamRecorder identifier originalName CamRecorder os 1 strongKill user active currentUser description executable join.me identifier originalName join.me os 1 strongKill user active currentUser description executable RPCSuite identifier originalName RPCSuite os 1 strongKill user active currentUser description executable RPCService identifier originalName RPCService os 1 strongKill user active currentUser description executable RemotePCDesktop identifier originalName RemotePCDesktop os 1 strongKill user active currentUser description executable beamyourscreen-host identifier originalName beamyourscreen-host os 1 strongKill user active currentUser description executable AeroAdmin identifier originalName AeroAdmin os 1 strongKill user active currentUser description executable Mikogo-host identifier originalName Mikogo-host os 1 strongKill user active currentUser description executable chromoting identifier originalName chromoting os 1 strongKill user active currentUser description executable vncserverui identifier originalName vncserverui os 1 strongKill user active currentUser description executable vncviewer identifier originalName vncviewer os 1 strongKill user active currentUser description executable vncserver identifier originalName vncserver os 1 strongKill user active currentUser description executable TeamViewer identifier originalName TeamViewer os 1 strongKill user active currentUser description executable GotoMeetingWinStore identifier originalName GotoMeetingWinStore os 1 strongKill user active currentUser description executable g2mcomm.exe identifier originalName g2mcomm.exe os 1 strongKill user active currentUser description executable SkypeHost identifier originalName SkypeHost os 1 strongKill user active currentUser description executable Skype identifier originalName Skype os 1 strongKill user proxies AutoConfigurationEnabled AutoConfigurationJavaScript AutoConfigurationURL AutoDiscoveryEnabled ExceptionsList ExcludeSimpleHostnames FTPEnable FTPPassive FTPPassword FTPPort 21 FTPProxy FTPRequiresPassword FTPUsername HTTPEnable HTTPPassword HTTPPort 80 HTTPProxy HTTPRequiresPassword HTTPSEnable HTTPSPassword HTTPSPort 443 HTTPSProxy HTTPSRequiresPassword HTTPSUsername HTTPUsername RTSPEnable RTSPPassword RTSPPort 554 RTSPProxy RTSPRequiresPassword RTSPUsername SOCKSEnable SOCKSPassword SOCKSPort 1080 SOCKSProxy SOCKSRequiresPassword SOCKSUsername proxySettingsPolicy 0 quitURL quitURLConfirm removeBrowserProfile removeLocalStorage restartExamPasswordProtected restartExamText restartExamURL restartExamUseStartURL sebConfigPurpose 0 sebServicePolicy 2 sendBrowserExamKey showBackToStartButton showInputLanguage showMenuBar showNavigationButtons showReloadButton showReloadWarning showScanQRCodeButton showSettingsInApp showTaskBar showTime startResource taskBarHeight 40 touchOptimized URLFilterEnable URLFilterEnableContentFilter URLFilterMessage 0 URLFilterRules useAsymmetricOnlyEncryption whitelistURLFilter zoomMode 0 ",
diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseAccessTest.java
similarity index 96%
rename from src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java
rename to src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseAccessTest.java
index 74a1b617..5154e9b0 100644
--- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleCourseAccessTest.java
+++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/legacy/MoodleCourseAccessTest.java
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
+package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
@@ -27,7 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result;
-import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
+import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.legacy.MoodleRestTemplateFactory.MoodleAPIRestTemplate;
public class MoodleCourseAccessTest {
From e35b03808546a685d6fa7cffb3ea0b5c009e5b08 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Mon, 21 Mar 2022 15:29:08 +0100
Subject: [PATCH 004/155] try signing seb server docker image
---
.github/workflows/buildReporting.yml | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index 87151c1e..6063b0cc 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -111,6 +111,9 @@ jobs:
run: |
echo $TAG_NAME
echo ${{ env.TAG_NAME }}
+ -
+ name: Install Cosign
+ uses: sigstore/cosign-installer@main
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
@@ -140,4 +143,14 @@ jobs:
file: ./docker/Dockerfile
push: true
tags: |
- anhefti/seb-server:${{ env.TAG_NAME }}
\ No newline at end of file
+ anhefti/seb-server:${{ env.TAG_NAME }}
+ -
+ name: Sign image with a key
+ run: |
+ echo ${COSIGN_PRIVATE_KEY} > /tmp/my_cosign.key && \
+ cosign sign --key /tmp/my_cosign.key ${TAGS}
+ env:
+ TAGS: |
+ anhefti/seb-server:${{ env.TAG_NAME }}
+ COSIGN_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
+ COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
\ No newline at end of file
From 55958e7abaaa8654692fbd7e06bf1b625c495a80 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Mon, 21 Mar 2022 15:47:00 +0100
Subject: [PATCH 005/155] fixed docker build
---
.github/workflows/buildReporting.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index 6063b0cc..1fddb61b 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -147,8 +147,7 @@ jobs:
-
name: Sign image with a key
run: |
- echo ${COSIGN_PRIVATE_KEY} > /tmp/my_cosign.key && \
- cosign sign --key /tmp/my_cosign.key ${TAGS}
+ echo ${COSIGN_PRIVATE_KEY} > /tmp/my_cosign.key && cosign sign --key /tmp/my_cosign.key anhefti/seb-server:${{ env.TAG_NAME }}
env:
TAGS: |
anhefti/seb-server:${{ env.TAG_NAME }}
From 55718fb58d06a8fbdd1f77aa07fda80d59847dcb Mon Sep 17 00:00:00 2001
From: anhefti
Date: Mon, 21 Mar 2022 16:03:00 +0100
Subject: [PATCH 006/155] fix docker build
---
.github/workflows/buildReporting.yml | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index 1fddb61b..a701c6ac 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -147,9 +147,6 @@ jobs:
-
name: Sign image with a key
run: |
- echo ${COSIGN_PRIVATE_KEY} > /tmp/my_cosign.key && cosign sign --key /tmp/my_cosign.key anhefti/seb-server:${{ env.TAG_NAME }}
+ echo ${COSIGN_PRIVATE_KEY} > /tmp/cosign.key && cosign sign --key /tmp/cosign.key anhefti/seb-server:${{ env.TAG_NAME }}
env:
- TAGS: |
- anhefti/seb-server:${{ env.TAG_NAME }}
- COSIGN_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
\ No newline at end of file
From 314ca01d15f07fc6697181191036d5128e912814 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Mon, 21 Mar 2022 16:16:54 +0100
Subject: [PATCH 007/155] fix dockerbuild
---
.github/workflows/buildReporting.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index a701c6ac..e867fc92 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -147,6 +147,6 @@ jobs:
-
name: Sign image with a key
run: |
- echo ${COSIGN_PRIVATE_KEY} > /tmp/cosign.key && cosign sign --key /tmp/cosign.key anhefti/seb-server:${{ env.TAG_NAME }}
+ echo ${secrets.COSIGN_PRIVATE_KEY} > /tmp/cosign.key && cosign sign --key /tmp/cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
env:
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
\ No newline at end of file
From 305eda357f76184578b94642571ee110ecea7a5b Mon Sep 17 00:00:00 2001
From: Andreas Hefti
Date: Mon, 21 Mar 2022 16:25:09 +0100
Subject: [PATCH 008/155] Update buildReporting.yml
fixed signing
---
.github/workflows/buildReporting.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index e867fc92..71861f27 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -147,6 +147,7 @@ jobs:
-
name: Sign image with a key
run: |
- echo ${secrets.COSIGN_PRIVATE_KEY} > /tmp/cosign.key && cosign sign --key /tmp/cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
+ echo ${COSIGN_PRIVATE_KEY} > /tmp/cosign.key && cosign sign --key /tmp/cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
env:
- COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
\ No newline at end of file
+ COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
+ COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
From 242e8d1dd9abd1c91b9ec3a6e136aafd09186ec7 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Tue, 22 Mar 2022 08:46:06 +0100
Subject: [PATCH 009/155] fix docker build
---
.github/workflows/buildReporting.yml | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index 71861f27..a7958484 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -144,10 +144,16 @@ jobs:
push: true
tags: |
anhefti/seb-server:${{ env.TAG_NAME }}
+ -
+ name: Write signing key to disk
+ run: 'echo "$KEY" > cosign.key'
+ shell: bash
+ env:
+ KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
-
- name: Sign image with a key
+ name: Sign image with private key
run: |
- echo ${COSIGN_PRIVATE_KEY} > /tmp/cosign.key && cosign sign --key /tmp/cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
+ cosign sign --key cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
env:
COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
From b960b19056f168d0e404df8f4ab0d994235acbf4 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Tue, 22 Mar 2022 08:56:36 +0100
Subject: [PATCH 010/155] fix docker build
---
.github/workflows/buildReporting.yml | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index a7958484..ce04265e 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -151,9 +151,7 @@ jobs:
env:
KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
-
- name: Sign image with private key
- run: |
- cosign sign --key cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
- env:
- COSIGN_PRIVATE_KEY: ${{secrets.COSIGN_PRIVATE_KEY}}
- COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
+ name: Sign the published Docker image
+ env:
+ COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
+ run: cosign sign --key cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
From 51e27ff4e501d1e0993cf2540227373fc5e43241 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Tue, 22 Mar 2022 09:35:00 +0100
Subject: [PATCH 011/155] fix docker build
---
.github/workflows/buildReporting.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/buildReporting.yml b/.github/workflows/buildReporting.yml
index ce04265e..0f49487c 100644
--- a/.github/workflows/buildReporting.yml
+++ b/.github/workflows/buildReporting.yml
@@ -154,4 +154,4 @@ jobs:
name: Sign the published Docker image
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
- run: cosign sign --key cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
+ run: echo "$COSIGN_PASSWORD" && cosign sign --key cosign.key docker.io/anhefti/seb-server:${{ env.TAG_NAME }}
From 6d56e71dbef20785a38bbfe3da19c191309615aa Mon Sep 17 00:00:00 2001
From: anhefti
Date: Wed, 23 Mar 2022 08:06:25 +0100
Subject: [PATCH 012/155] SEBSERV-240 implementation
---
.../ch/ethz/seb/sebserver/gbl/api/API.java | 1 +
.../gui/content/action/ActionCategory.java | 4 +-
.../gui/content/action/ActionDefinition.java | 9 ++
.../gui/content/activity/ActivitiesPane.java | 15 +-
.../content/activity/ActivityDefinition.java | 1 +
.../activity/PageStateDefinitionImpl.java | 6 +
.../gui/content/monitoring/FinishedExam.java | 127 +++++++++++++++
.../content/monitoring/FinishedExamList.java | 149 ++++++++++++++++++
.../monitoring/MonitoringRunningExam.java | 7 +-
.../api/session/GetFinishedExamPage.java | 41 +++++
.../session/ExamSessionService.java | 9 ++
.../session/impl/ExamSessionServiceImpl.java | 9 ++
.../api/ExamMonitoringController.java | 61 +++++++
src/main/resources/messages.properties | 27 ++++
14 files changed, 459 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
index 2c5c715f..083cd248 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
@@ -193,6 +193,7 @@ public final class API {
public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification";
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
+ public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams";
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java
index ad3a207c..eed8b28a 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java
@@ -35,7 +35,9 @@ public enum ActionCategory {
LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0),
FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.filter"), 50),
- PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60);
+ PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60),
+
+ FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1);
public final LocTextKey title;
public final int slotPosition;
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
index 2922a097..851baf74 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
@@ -832,6 +832,15 @@ public enum ActionDefinition {
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.PROCTORING),
+ FINISHED_EXAM_VIEW_LIST(
+ new LocTextKey("sebserver.finished.action.list"),
+ PageStateDefinitionImpl.FINISHED_EXAM_LIST),
+ VIEW_EXAM_FROM_FINISHED_LIST(
+ new LocTextKey("sebserver.finished.exam.action.list.view"),
+ ImageIcon.SHOW,
+ PageStateDefinitionImpl.FINISHED_EXAM,
+ ActionCategory.FINISHED_EXAM_LIST),
+
LOGS_USER_ACTIVITY_LIST(
new LocTextKey("sebserver.logs.activity.userlogs"),
PageStateDefinitionImpl.USER_ACTIVITY_LOGS),
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java
index 35b35bd0..7a07a2f3 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java
@@ -339,14 +339,25 @@ public class ActivitiesPane implements TemplateComposer {
// Monitoring exams
if (isSupporter) {
- final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
+
+ final TreeItem monitoringExams = this.widgetFactory.treeItemLocalized(
monitoring,
ActivityDefinition.MONITORING_EXAMS.displayName);
injectActivitySelection(
- clientConfig,
+ monitoringExams,
actionBuilder
.newAction(ActionDefinition.RUNNING_EXAM_VIEW_LIST)
.create());
+
+ final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
+ monitoring,
+ ActivityDefinition.FINISHED_EXAMS.displayName);
+ injectActivitySelection(
+ clientConfig,
+ actionBuilder
+ .newAction(ActionDefinition.FINISHED_EXAM_VIEW_LIST)
+ .create());
+
}
// SEB Client Logs
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java
index a8d57e0d..190cc77f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java
@@ -28,6 +28,7 @@ public enum ActivityDefinition implements Activity {
SEB_CERTIFICATE_MANAGEMENT(new LocTextKey("sebserver.certificate.action.list")),
MONITORING(new LocTextKey("sebserver.overall.activity.title.monitoring")),
MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")),
+ FINISHED_EXAMS(new LocTextKey("sebserver.finished.action.list")),
SEB_CLIENT_LOGS(new LocTextKey("sebserver.logs.activity.seblogs"));
public final LocTextKey displayName;
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java
index 54b5d28e..8365bfa2 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java
@@ -33,6 +33,8 @@ import ch.ethz.seb.sebserver.gui.content.exam.IndicatorTemplateForm;
import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupForm;
import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupList;
import ch.ethz.seb.sebserver.gui.content.exam.QuizLookupList;
+import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExam;
+import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExamList;
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringClientConnection;
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExam;
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExamList;
@@ -96,6 +98,10 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS),
MONITORING_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.MONITORING_EXAMS),
+ FINISHED_EXAM_LIST(Type.LIST_VIEW, FinishedExamList.class, ActivityDefinition.FINISHED_EXAMS),
+ FINISHED_EXAM(Type.FORM_VIEW, FinishedExam.class, ActivityDefinition.FINISHED_EXAMS),
+ FINISHED_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.FINISHED_EXAMS),
+
USER_ACTIVITY_LOGS(Type.LIST_VIEW, UserActivityLogs.class, ActivityDefinition.USER_ACTIVITY_LOGS),
SEB_CLIENT_LOGS(Type.LIST_VIEW, SEBClientEvents.class, ActivityDefinition.SEB_CLIENT_LOGS)
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
new file mode 100644
index 00000000..f611d80c
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2022 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.gui.content.monitoring;
+
+import java.util.Collection;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionPage;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.TableBuilder;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+
+@Lazy
+@Component
+@GuiProfile
+public class FinishedExam implements TemplateComposer {
+
+ private static final LocTextKey TITLE_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.connections.title");
+ private static final LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.connections.empty");
+ private static final LocTextKey TABLE_COLUMN_NAME =
+ new LocTextKey("sebserver.finished.exam.connections.name");
+ private static final LocTextKey TABLE_COLUMN_INFO =
+ new LocTextKey("sebserver.finished.exam.connections.info");
+ private static final LocTextKey TABLE_COLUMN_STATUS =
+ new LocTextKey("sebserver.finished.exam.connections.status");
+
+ private final TableFilterAttribute nameFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
+ private final TableFilterAttribute infoFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.ATTR_INFO);
+ private final TableFilterAttribute statusFilter;
+
+ private final PageService pageService;
+ private final RestService restService;
+ private final ResourceService resourceService;
+ private final int pageSize;
+
+ public FinishedExam(
+ final ServerPushService serverPushService,
+ final PageService pageService,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.restService = pageService.getRestService();
+ this.resourceService = pageService.getResourceService();
+ this.pageSize = pageSize;
+
+ this.statusFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ ClientConnection.FILTER_ATTR_STATUS,
+ pageService.getResourceService()::localizedClientConnectionStatusResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final EntityKey examKey = pageContext.getEntityKey();
+
+ final RestService restService = this.pageService.getRestService();
+ final PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(pageContext.clearEntityKeys());
+
+ final Collection indicators = restService.getBuilder(GetIndicators.class)
+ .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, examKey.modelId)
+ .call()
+ .getOrThrow();
+
+ final TableBuilder tableBuilder =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetClientConnectionPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(10)
+ .withStaticFilter(ClientConnection.FILTER_ATTR_EXAM_ID, examKey.modelId)
+
+ .withColumn(new ColumnDefinition<>(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ TABLE_COLUMN_NAME,
+ ClientConnection::getUserSessionId)
+ .withFilter(this.nameFilter))
+
+ .withColumn(new ColumnDefinition<>(
+ ClientConnection.ATTR_INFO,
+ TABLE_COLUMN_INFO,
+ ClientConnection::getInfo)
+ .withFilter(this.infoFilter))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_CONNECTION.ATTR_STATUS,
+ TABLE_COLUMN_STATUS,
+ row -> this.pageService.getResourceService()
+ .localizedClientConnectionStatusName(row.getStatus()))
+ .withFilter(this.statusFilter))
+
+ .withDefaultAction(t -> actionBuilder
+ .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
+ .withParentEntityKey(examKey)
+ .create());
+
+ tableBuilder.compose(pageContext);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java
new file mode 100644
index 00000000..19fa548d
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2022 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.gui.content.monitoring;
+
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+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.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.content.exam.ExamList;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Component
+@GuiProfile
+public class FinishedExamList implements TemplateComposer {
+
+ private static final LocTextKey PAGE_TITLE_KEY =
+ new LocTextKey("sebserver.finished.exam.list.title");
+ private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.info.pleaseSelect");
+ private final static LocTextKey COLUMN_TITLE_NAME_KEY =
+ new LocTextKey("sebserver.finished.exam.list.column.name");
+ private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
+ new LocTextKey("sebserver.finished.exam.list.column.type");
+ private final static LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.list.empty");
+
+ private final TableFilterAttribute nameFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
+ private final TableFilterAttribute typeFilter;
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final int pageSize;
+
+ protected FinishedExamList(
+ final PageService pageService,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.resourceService = pageService.getResourceService();
+ this.pageSize = pageSize;
+
+ this.typeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Exam.FILTER_ATTR_TYPE,
+ this.resourceService::examTypeResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final RestService restService = this.resourceService.getRestService();
+ final I18nSupport i18nSupport = this.resourceService.getI18nSupport();
+
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ PAGE_TITLE_KEY);
+
+ final PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(pageContext.clearEntityKeys());
+
+ // table
+ final EntityTable table =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetFinishedExamPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+ .withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService))
+ .withDefaultSort(QuizData.QUIZ_ATTR_NAME)
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_NAME,
+ COLUMN_TITLE_NAME_KEY,
+ Exam::getName)
+ .withFilter(this.nameFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition(
+ Domain.EXAM.ATTR_TYPE,
+ COLUMN_TITLE_TYPE_KEY,
+ this.resourceService::localizedExamTypeName)
+ .withFilter(this.typeFilter)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_START_TIME,
+ new LocTextKey(
+ "sebserver.finished.exam.list.column.startTime",
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ Exam::getStartTime)
+ .sortable())
+
+ .withColumn(new ColumnDefinition<>(
+ QuizData.QUIZ_ATTR_END_TIME,
+ new LocTextKey(
+ "sebserver.finished.exam.list.column.endTime",
+ i18nSupport.getUsersTimeZoneTitleSuffix()),
+ Exam::getEndTime)
+ .sortable())
+
+ .withDefaultAction(actionBuilder
+ .newAction(ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST)
+ .create())
+
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST))
+
+ .compose(pageContext.copyOf(content));
+
+ actionBuilder
+
+ .newAction(ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST)
+ .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
+
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
index aa61abd4..21800970 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
@@ -98,7 +98,7 @@ public class MonitoringRunningExam implements TemplateComposer {
private final boolean distributedSetup;
private final long pollInterval;
- protected MonitoringRunningExam(
+ public MonitoringRunningExam(
final ServerPushService serverPushService,
final PageService pageService,
final AsyncRunner asyncRunner,
@@ -122,10 +122,9 @@ public class MonitoringRunningExam implements TemplateComposer {
@Override
public void compose(final PageContext pageContext) {
- final RestService restService = this.resourceService.getRestService();
final EntityKey entityKey = pageContext.getEntityKey();
final CurrentUser currentUser = this.resourceService.getCurrentUser();
- final Exam exam = restService.getBuilder(GetExam.class)
+ final Exam exam = this.restService.getBuilder(GetExam.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.getOrThrow();
@@ -134,7 +133,7 @@ public class MonitoringRunningExam implements TemplateComposer {
exam.supporter.contains(user.uuid);
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
- final Collection indicators = restService.getBuilder(GetIndicators.class)
+ final Collection indicators = this.restService.getBuilder(GetIndicators.class)
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
.call()
.getOrThrow();
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java
new file mode 100644
index 00000000..b70a7cf4
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamPage.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 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.gui.service.remote.webservice.api.session;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Page;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+
+@Lazy
+@Component
+@GuiProfile
+public class GetFinishedExamPage extends RestCall> {
+
+ public GetFinishedExamPage() {
+ super(new TypeKey<>(
+ CallType.GET_PAGE,
+ EntityType.EXAM,
+ new TypeReference>() {
+ }),
+ HttpMethod.GET,
+ MediaType.APPLICATION_FORM_URLENCODED,
+ API.EXAM_MONITORING_ENDPOINT + API.EXAM_MONITORING_FINISHED_ENDPOINT);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
index e66ebdaa..24b85227 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
@@ -134,6 +134,15 @@ public interface ExamSessionService {
FilterMap filterMap,
Predicate predicate);
+ /** Gets all finished Exams for a particular FilterMap.
+ *
+ * @param filterMap the FilterMap containing the filter attributes
+ * @param predicate additional filter predicate
+ * @return Result referencing the list of all currently finished Exams or to an error if happened. */
+ Result> getFilteredFinishedExams(
+ FilterMap filterMap,
+ Predicate predicate);
+
/** Streams the default SEB Exam Configuration to a ClientConnection with given connectionToken.
*
* @param institutionId the Institution identifier
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index c172a014..c32e8803 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -254,6 +254,15 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.collect(Collectors.toList()));
}
+ @Override
+ public Result> getFilteredFinishedExams(
+ final FilterMap filterMap,
+ final Predicate predicate) {
+
+ filterMap.putIfAbsent(Exam.FILTER_ATTR_STATUS, ExamStatus.FINISHED.name());
+ return this.examDAO.allMatching(filterMap, predicate);
+ }
+
@Override
public void streamDefaultExamConfig(
final Long institutionId,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java
index 0c1639b2..b0712afd 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java
@@ -174,6 +174,67 @@ public class ExamMonitoringController {
ExamAdministrationController.pageSort(sort));
}
+ /** Get a page of all currently finished exams
+ *
+ * GET /{api}/{entity-type-endpoint-name}
+ *
+ * GET /admin-api/v1/monitoring
+ * GET /admin-api/v1/monitoring?page_number=2&page_size=10&sort=-name
+ * GET /admin-api/v1/monitoring?name=seb&active=true
+ *
+ * @param institutionId The institution identifier of the request.
+ * Default is the institution identifier of the institution of the current user
+ * @param pageNumber the number of the page that is requested
+ * @param pageSize the size of the page that is requested
+ * @param sort the sort parameter to sort the list of entities before paging
+ * the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for
+ * descending sort order
+ * @param allRequestParams a MultiValueMap of all request parameter that is used for filtering
+ * @return Page of domain-model-entities of specified type */
+ @RequestMapping(
+ path = API.EXAM_MONITORING_FINISHED_ENDPOINT,
+ method = RequestMethod.GET,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public Page getFinishedExamsPage(
+ @RequestParam(
+ name = API.PARAM_INSTITUTION_ID,
+ required = true,
+ defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
+ @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
+ @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
+ @RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
+ @RequestParam final MultiValueMap allRequestParams,
+ final HttpServletRequest request) {
+
+ this.authorization.checkRole(
+ institutionId,
+ EntityType.EXAM,
+ UserRole.EXAM_SUPPORTER,
+ UserRole.EXAM_ADMIN);
+
+ final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
+
+ // if current user has no read access for specified entity type within other institution
+ // then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
+ if (!this.authorization.hasGrant(PrivilegeType.READ, EntityType.EXAM)) {
+ filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
+ }
+
+ final Collection exams = this.examSessionService
+ .getFilteredFinishedExams(
+ filterMap,
+ exam -> this.hasRunningExamPrivilege(exam, institutionId))
+ .getOrThrow();
+
+ return this.paginationService.buildPageFromList(
+ pageNumber,
+ pageSize,
+ sort,
+ exams,
+ ExamAdministrationController.pageSort(sort));
+ }
+
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index ef29c23f..c5d5dd54 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -1868,6 +1868,33 @@ sebserver.monitoring.exam.connection.status.CLOSED=Closed
sebserver.monitoring.exam.connection.status.ABORTED=Aborted
sebserver.monitoring.exam.connection.status.DISABLED=Canceled
+################################
+# Finished Exams
+################################
+sebserver.finished.action.list=Finished Exams
+sebserver.finished.exam.list.title=Finished Exams
+sebserver.finished.exam.list.actions=
+
+sebserver.finished.exam.info.pleaseSelect=At first please select an Exam from the list
+sebserver.finished.exam.list.empty=There are currently no finished exams
+
+sebserver.finished.exam.list.column.name=Name
+sebserver.finished.exam.list.column.name.tooltip=The name of the exam Use the filter above to narrow down to a specific exam name {0}
+sebserver.finished.exam.list.column.type=Type
+sebserver.finished.exam.list.column.type.tooltip=The type of the exam Use the filter above to set a specific exam type {0}
+sebserver.finished.exam.list.column.startTime=Start Time {0}
+sebserver.finished.exam.list.column.startTime.tooltip=The start date and time of the exam {0}
+sebserver.finished.exam.list.column.endTime=End Time {0}
+sebserver.finished.exam.list.column.endTime.tooltip=The end date and time of the exam {0}
+sebserver.finished.exam.action.list.view=View Finished Exam
+
+sebserver.finished.exam.connections.title=Search Connections
+sebserver.finished.exam.connections.action=Search
+sebserver.finished.exam.connections.empty=No Client Connections available
+sebserver.finished.exam.connections.name=Session or User Name
+sebserver.finished.exam.connections.info=Connection Info
+sebserver.finished.exam.connections.status=Status
+
################################
# Logs
################################
From a35ba4884496524fa6a13e03192980da1e895ceb Mon Sep 17 00:00:00 2001
From: anhefti
Date: Wed, 23 Mar 2022 11:34:54 +0100
Subject: [PATCH 013/155] SEBSERV-240 implementation
---
.../ch/ethz/seb/sebserver/gbl/api/API.java | 1 +
.../model/session/ClientConnectionData.java | 26 ++++++-
.../gui/content/monitoring/FinishedExam.java | 49 ++++++++++---
.../GetFinishedExamClientConnection.java | 41 +++++++++++
.../GetFinishedExamClientConnectionPage.java | 41 +++++++++++
.../session/SEBClientConnectionService.java | 8 +++
.../session/impl/ClientIndicatorFactory.java | 12 +++-
.../impl/SEBClientConnectionServiceImpl.java | 10 +++
.../indicator/AbstractClientIndicator.java | 23 +++---
.../AbstractLogLevelCountIndicator.java | 4 +-
.../indicator/AbstractLogNumberIndicator.java | 4 +-
.../impl/indicator/AbstractPingIndicator.java | 13 +---
.../PingIntervalClientIndicator.java | 8 +--
.../api/ClientConnectionController.java | 70 ++++++++++++++++++-
.../config/application-dev-ws.properties | 2 +-
15 files changed, 263 insertions(+), 49 deletions(-)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnection.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnectionPage.java
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
index 083cd248..aa529b4f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java
@@ -213,6 +213,7 @@ public final class API {
public static final String EXAM_PROCTORING_ATTR_ALLOW_CHAT = "allow_chat";
public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";
+ public static final String SEB_CLIENT_CONNECTION_DATA_ENDPOINT = "/data";
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";
public static final String SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT = "/search";
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java
index ddab83a6..9fa6a567 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java
@@ -17,10 +17,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true)
-public class ClientConnectionData {
+public class ClientConnectionData implements GrantEntity {
public static final String ATTR_CLIENT_CONNECTION = "cData";
public static final String ATTR_INDICATOR_VALUE = "iValues";
@@ -48,7 +50,7 @@ public class ClientConnectionData {
this.indicatorValues = Utils.immutableListOf(indicatorValues);
}
- protected ClientConnectionData(
+ public ClientConnectionData(
final ClientConnection clientConnection,
final List extends IndicatorValue> indicatorValues) {
@@ -58,6 +60,26 @@ public class ClientConnectionData {
this.indicatorValues = Utils.immutableListOf(indicatorValues);
}
+ @Override
+ public EntityType entityType() {
+ return this.clientConnection.entityType();
+ }
+
+ @Override
+ public String getName() {
+ return this.clientConnection.getName();
+ }
+
+ @Override
+ public String getModelId() {
+ return this.clientConnection.getModelId();
+ }
+
+ @Override
+ public Long getInstitutionId() {
+ return this.clientConnection.getInstitutionId();
+ }
+
@JsonProperty(ATTR_MISSING_PING)
public Boolean getMissingPing() {
return this.missingPing;
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
index f611d80c..e1b40515 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
@@ -9,15 +9,20 @@
package ch.ethz.seb.sebserver.gui.content.monitoring;
import java.util.Collection;
+import java.util.function.Function;
+import org.eclipse.swt.widgets.Composite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
+import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
@@ -29,7 +34,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
-import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamClientConnectionPage;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.TableBuilder;
@@ -91,29 +96,33 @@ public class FinishedExam implements TemplateComposer {
.call()
.getOrThrow();
- final TableBuilder tableBuilder =
- this.pageService.entityTableBuilder(restService.getRestCall(GetClientConnectionPage.class))
+ final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
+ pageContext.getParent(),
+ TITLE_TEXT_KEY);
+
+ final TableBuilder tableBuilder =
+ this.pageService.entityTableBuilder(restService.getRestCall(GetFinishedExamClientConnectionPage.class))
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(10)
+ .withPaging(this.pageSize)
.withStaticFilter(ClientConnection.FILTER_ATTR_EXAM_ID, examKey.modelId)
- .withColumn(new ColumnDefinition<>(
+ .withColumn(new ColumnDefinition(
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
TABLE_COLUMN_NAME,
- ClientConnection::getUserSessionId)
+ c -> c.clientConnection.getUserSessionId())
.withFilter(this.nameFilter))
- .withColumn(new ColumnDefinition<>(
+ .withColumn(new ColumnDefinition(
ClientConnection.ATTR_INFO,
TABLE_COLUMN_INFO,
- ClientConnection::getInfo)
+ c -> c.clientConnection.getInfo())
.withFilter(this.infoFilter))
- .withColumn(new ColumnDefinition(
+ .withColumn(new ColumnDefinition(
Domain.CLIENT_CONNECTION.ATTR_STATUS,
TABLE_COLUMN_STATUS,
row -> this.pageService.getResourceService()
- .localizedClientConnectionStatusName(row.getStatus()))
+ .localizedClientConnectionStatusName(row.clientConnection.getStatus()))
.withFilter(this.statusFilter))
.withDefaultAction(t -> actionBuilder
@@ -121,7 +130,25 @@ public class FinishedExam implements TemplateComposer {
.withParentEntityKey(examKey)
.create());
- tableBuilder.compose(pageContext);
+ indicators.stream().forEach(indicator -> {
+ tableBuilder.withColumn(new ColumnDefinition<>(
+ indicator.name,
+ new LocTextKey(indicator.name),
+ indicatorValueFunction(indicator)));
+ });
+
+ tableBuilder.compose(pageContext.copyOf(content));
+ }
+
+ private Function indicatorValueFunction(final Indicator indicator) {
+ return clientConnectionData -> {
+ return clientConnectionData.indicatorValues
+ .stream()
+ .filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicator.id))
+ .findFirst()
+ .map(iv -> IndicatorValue.getDisplayValue(iv, indicator.type))
+ .orElse(Constants.EMPTY_NOTE);
+ };
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnection.java
new file mode 100644
index 00000000..bcf12c43
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnection.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 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.gui.service.remote.webservice.api.session;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+
+@Lazy
+@Component
+@GuiProfile
+public class GetFinishedExamClientConnection extends RestCall {
+
+ public GetFinishedExamClientConnection() {
+ super(new TypeKey<>(
+ CallType.GET_SINGLE,
+ EntityType.CLIENT_CONNECTION,
+ new TypeReference() {
+ }),
+ HttpMethod.GET,
+ MediaType.APPLICATION_FORM_URLENCODED,
+ API.SEB_CLIENT_CONNECTION_ENDPOINT
+ + API.SEB_CLIENT_CONNECTION_DATA_ENDPOINT
+ + API.MODEL_ID_VAR_PATH_SEGMENT);
+ }
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnectionPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnectionPage.java
new file mode 100644
index 00000000..8d46b940
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetFinishedExamClientConnectionPage.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 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.gui.service.remote.webservice.api.session;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Page;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
+
+@Lazy
+@Component
+@GuiProfile
+public class GetFinishedExamClientConnectionPage extends RestCall> {
+
+ public GetFinishedExamClientConnectionPage() {
+ super(new TypeKey<>(
+ CallType.GET_PAGE,
+ EntityType.CLIENT_CONNECTION,
+ new TypeReference>() {
+ }),
+ HttpMethod.GET,
+ MediaType.APPLICATION_FORM_URLENCODED,
+ API.SEB_CLIENT_CONNECTION_ENDPOINT + API.SEB_CLIENT_CONNECTION_DATA_ENDPOINT);
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java
index f03105c0..c8e3d960 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java
@@ -13,6 +13,7 @@ import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.util.Result;
@@ -184,4 +185,11 @@ public interface SEBClientConnectionService {
* @param instructionConfirm the instruction confirm identifier */
void confirmInstructionDone(String connectionToken, String instructionConfirm);
+ /** Use this to get the get the specific indicator values for a given client connection.
+ *
+ * @param clientConnection The client connection values
+ * @return Result refer to ClientConnectionData instance containing the given clientConnection plus the indicator
+ * values or to an error when happened */
+ Result getIndicatorValues(final ClientConnection clientConnection);
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java
index 5be1a549..39102fc7 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientIndicatorFactory.java
@@ -54,8 +54,14 @@ public class ClientIndicatorFactory {
}
public List createFor(final ClientConnection clientConnection) {
- final List result = new ArrayList<>();
+ return createFor(clientConnection, false);
+ }
+ public List createFor(
+ final ClientConnection clientConnection,
+ final boolean enableCachingOverride) {
+
+ final List result = new ArrayList<>();
if (clientConnection.examId == null) {
return result;
}
@@ -82,7 +88,7 @@ public class ClientIndicatorFactory {
indicatorDef,
clientConnection.id,
clientConnection.status.clientActiveStatus,
- this.enableCaching);
+ this.enableCaching || enableCachingOverride);
result.add(indicator);
} catch (final Exception e) {
@@ -111,7 +117,7 @@ public class ClientIndicatorFactory {
indicator,
clientConnection.id,
clientConnection.status.clientActiveStatus,
- this.enableCaching);
+ this.enableCaching || enableCachingOverride);
result.add(pingIndicator);
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
index 29f68b4b..4af7ab47 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java
@@ -74,6 +74,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
private final SEBClientConfigDAO sebClientConfigDAO;
private final SEBClientInstructionService sebInstructionService;
private final ExamAdminService examAdminService;
+ private final ClientIndicatorFactory clientIndicatorFactory;
// TODO get rid of this dependency and use application events for signaling client connection state changes
private final DistributedIndicatorValueService distributedPingCache;
private final boolean isDistributedSetup;
@@ -84,6 +85,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final SEBClientConfigDAO sebClientConfigDAO,
final SEBClientInstructionService sebInstructionService,
final ExamAdminService examAdminService,
+ final ClientIndicatorFactory clientIndicatorFactory,
final DistributedIndicatorValueService distributedPingCache) {
this.examSessionService = examSessionService;
@@ -94,6 +96,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
this.sebClientConfigDAO = sebClientConfigDAO;
this.sebInstructionService = sebInstructionService;
this.examAdminService = examAdminService;
+ this.clientIndicatorFactory = clientIndicatorFactory;
this.distributedPingCache = distributedPingCache;
this.isDistributedSetup = sebInstructionService.getWebserviceInfo().isDistributed();
}
@@ -702,6 +705,13 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
this.sebInstructionService.confirmInstructionDone(connectionToken, instructionConfirm);
}
+ @Override
+ public Result getIndicatorValues(final ClientConnection clientConnection) {
+ return Result.tryCatch(() -> new ClientConnectionData(
+ clientConnection,
+ this.clientIndicatorFactory.createFor(clientConnection, true)));
+ }
+
private void checkExamRunning(final Long examId) {
if (examId != null && !this.examSessionService.isExamRunning(examId)) {
examNotRunningException(examId);
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java
index 06656d71..1ba12865 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractClientIndicator.java
@@ -21,7 +21,7 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
private static final Logger log = LoggerFactory.getLogger(AbstractClientIndicator.class);
- protected final DistributedIndicatorValueService distributedPingCache;
+ protected final DistributedIndicatorValueService distributedIndicatorValueService;
protected Long indicatorId = -1L;
protected Long examId = -1L;
@@ -38,9 +38,9 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
protected long lastUpdate = 0;
- public AbstractClientIndicator(final DistributedIndicatorValueService distributedPingCache) {
+ public AbstractClientIndicator(final DistributedIndicatorValueService distributedIndicatorValueService) {
super();
- this.distributedPingCache = distributedPingCache;
+ this.distributedIndicatorValueService = distributedIndicatorValueService;
}
@Override
@@ -70,10 +70,11 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
if (!this.cachingEnabled && this.active) {
try {
- this.ditributedIndicatorValueRecordId = this.distributedPingCache.initIndicatorForConnection(
- connectionId,
- getType(),
- initValue());
+ this.ditributedIndicatorValueRecordId =
+ this.distributedIndicatorValueService.initIndicatorForConnection(
+ connectionId,
+ getType(),
+ initValue());
} catch (final Exception e) {
tryRecoverIndicatorRecord();
}
@@ -94,7 +95,7 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
}
try {
- this.ditributedIndicatorValueRecordId = this.distributedPingCache.initIndicatorForConnection(
+ this.ditributedIndicatorValueRecordId = this.distributedIndicatorValueService.initIndicatorForConnection(
this.connectionId,
getType(),
initValue());
@@ -126,18 +127,18 @@ public abstract class AbstractClientIndicator implements ClientIndicator {
public double getValue() {
if (this.initialized && !this.cachingEnabled && this.active
- && this.lastUpdate != this.distributedPingCache.lastUpdate()) {
+ && this.lastUpdate != this.distributedIndicatorValueService.lastUpdate()) {
if (this.ditributedIndicatorValueRecordId == null) {
this.tryRecoverIndicatorRecord();
}
- final Long indicatorValue = this.distributedPingCache
+ final Long indicatorValue = this.distributedIndicatorValueService
.getIndicatorValue(this.ditributedIndicatorValueRecordId);
if (indicatorValue != null) {
this.currentValue = indicatorValue.doubleValue();
}
- this.lastUpdate = this.distributedPingCache.lastUpdate();
+ this.lastUpdate = this.distributedIndicatorValueService.lastUpdate();
}
return this.currentValue;
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java
index ce89c669..55461a1f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java
@@ -75,7 +75,7 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
// update active indicator value record on persistent when caching is not enabled
if (this.active && this.ditributedIndicatorValueRecordId != null) {
- this.distributedPingCache.updateIndicatorValue(
+ this.distributedIndicatorValueService.updateIndicatorValue(
this.ditributedIndicatorValueRecordId,
numberOfLogs.longValue());
}
@@ -115,7 +115,7 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
private void valueChanged(final String eventText) {
if (this.tags == null || this.tags.length == 0 || hasTag(eventText)) {
if (super.ditributedIndicatorValueRecordId != null) {
- this.distributedPingCache.incrementIndicatorValue(super.ditributedIndicatorValueRecordId);
+ this.distributedIndicatorValueService.incrementIndicatorValue(super.ditributedIndicatorValueRecordId);
}
this.currentValue = getValue() + 1d;
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java
index af05e7e5..994c307f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java
@@ -56,7 +56,7 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
private void valueChanged(final String text, final double value) {
if (this.tags == null || this.tags.length == 0 || hasTag(text)) {
if (super.ditributedIndicatorValueRecordId != null) {
- if (!this.distributedPingCache.updateIndicatorValueAsync(
+ if (!this.distributedIndicatorValueService.updateIndicatorValueAsync(
this.ditributedIndicatorValueRecordId,
Double.valueOf(value).longValue())) {
@@ -100,7 +100,7 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
// update active indicator value record on persistent when caching is not enabled
if (this.active && this.ditributedIndicatorValueRecordId != null) {
- this.distributedPingCache.updateIndicatorValue(
+ this.distributedIndicatorValueService.updateIndicatorValue(
this.ditributedIndicatorValueRecordId,
numericValue.longValue());
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java
index 4987c509..98127736 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractPingIndicator.java
@@ -12,7 +12,6 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
-import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
public abstract class AbstractPingIndicator extends AbstractClientIndicator {
@@ -23,16 +22,6 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
super(distributedPingCache);
}
- @Override
- public void init(
- final Indicator indicatorDefinition,
- final Long connectionId,
- final boolean active,
- final boolean cachingEnabled) {
-
- super.init(indicatorDefinition, connectionId, active, cachingEnabled);
- }
-
@Override
public Set observedEvents() {
return this.EMPTY_SET;
@@ -50,7 +39,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
}
}
- this.distributedPingCache.updatePingAsync(this.ditributedIndicatorValueRecordId);
+ this.distributedIndicatorValueService.updatePingAsync(this.ditributedIndicatorValueRecordId);
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java
index ba933dd9..a197e0ac 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/PingIntervalClientIndicator.java
@@ -35,8 +35,8 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
private boolean hidden = false;
- public PingIntervalClientIndicator(final DistributedIndicatorValueService distributedPingCache) {
- super(distributedPingCache);
+ public PingIntervalClientIndicator(final DistributedIndicatorValueService distributedIndicatorValueService) {
+ super(distributedIndicatorValueService);
this.cachingEnabled = true;
}
@@ -83,7 +83,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
}
if (this.initialized && !this.cachingEnabled && this.active
- && this.lastUpdate != this.distributedPingCache.lastUpdate()) {
+ && this.lastUpdate != this.distributedIndicatorValueService.lastUpdate()) {
final long currentTimeMillis = DateTimeUtils.currentTimeMillis();
this.currentValue = computeValueAt(currentTimeMillis);
@@ -110,7 +110,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
public final double computeValueAt(final long timestamp) {
if (super.ditributedIndicatorValueRecordId != null) {
- final Long lastPing = this.distributedPingCache
+ final Long lastPing = this.distributedIndicatorValueService
.getIndicatorValue(super.ditributedIndicatorValueRecordId);
return (lastPing != null)
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java
index 3362518c..3bd7ec26 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java
@@ -12,26 +12,37 @@ import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable;
+import org.springframework.http.MediaType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
+import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile
@@ -39,13 +50,16 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONNECTION_ENDPOINT)
public class ClientConnectionController extends ReadonlyEntityController {
+ private final SEBClientConnectionService sebClientConnectionService;
+
protected ClientConnectionController(
final AuthorizationService authorization,
final BulkActionService bulkActionService,
final ClientConnectionDAO clientConnectionDAO,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
- final BeanValidationService beanValidationService) {
+ final BeanValidationService beanValidationService,
+ final SEBClientConnectionService sebClientConnectionService) {
super(authorization,
bulkActionService,
@@ -53,6 +67,60 @@ public class ClientConnectionController extends ReadonlyEntityController getClientConnectionDataPage(
+ @RequestParam(
+ name = API.PARAM_INSTITUTION_ID,
+ required = true,
+ defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
+ @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
+ @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
+ @RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
+ @RequestParam final MultiValueMap allRequestParams,
+ final HttpServletRequest request) {
+
+ // at least current user must have read access for specified entity type within its own institution
+ checkReadPrivilege(institutionId);
+
+ final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
+ populateFilterMap(filterMap, institutionId, sort);
+
+ final Page page = this.paginationService.getPage(
+ pageNumber,
+ pageSize,
+ sort,
+ getSQLTableOfEntity().name(),
+ () -> getAllData(filterMap))
+ .getOrThrow();
+
+ return page;
+ }
+
+ @RequestMapping(
+ path = API.SEB_CLIENT_CONNECTION_DATA_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT,
+ method = RequestMethod.GET,
+ consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public ClientConnectionData getClientConnectionDataBy(@PathVariable final String modelId) {
+ return this.sebClientConnectionService
+ .getIndicatorValues(super.getBy(modelId))
+ .getOrThrow();
+ }
+
+ private Result> getAllData(final FilterMap filterMap) {
+ return getAll(filterMap)
+ .map(connection -> connection.stream()
+ .map(this.sebClientConnectionService::getIndicatorValues)
+ .flatMap(Result::onErrorLogAndSkip)
+ .collect(Collectors.toList()));
}
@Override
diff --git a/src/main/resources/config/application-dev-ws.properties b/src/main/resources/config/application-dev-ws.properties
index 1ba4288f..a5671ccc 100644
--- a/src/main/resources/config/application-dev-ws.properties
+++ b/src/main/resources/config/application-dev-ws.properties
@@ -25,7 +25,7 @@ sebserver.webservice.clean-db-on-startup=false
# webservice configuration
sebserver.init.adminaccount.gen-on-init=false
-sebserver.webservice.distributed=false
+sebserver.webservice.distributed=true
#sebserver.webservice.master.delay.threshold=10000
sebserver.webservice.http.external.scheme=http
sebserver.webservice.http.external.servername=localhost
From ebbbf5631460d6a1b9ffcf380ec7cb091426bdab Mon Sep 17 00:00:00 2001
From: anhefti
Date: Wed, 23 Mar 2022 13:42:18 +0100
Subject: [PATCH 014/155] implemented event handling for start and finish exams
---
.../lms/impl/SEBRestrictionServiceImpl.java | 27 ++++++++++
.../session/ExamFinishedEvent.java | 26 ++++++++++
.../session/ExamSessionService.java | 7 ---
.../session/ExamStartedEvent.java | 27 ++++++++++
.../session/impl/ExamSessionControlTask.java | 6 ---
.../session/impl/ExamSessionServiceImpl.java | 23 +++++----
.../session/impl/ExamUpdateHandler.java | 51 +++++++++++++------
.../ExamProctoringRoomServiceImpl.java | 10 ++++
8 files changed, 140 insertions(+), 37 deletions(-)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamFinishedEvent.java
create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamStartedEvent.java
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java
index bfc767d2..4a5b90fd 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/SEBRestrictionServiceImpl.java
@@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
+import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -38,6 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent;
@Lazy
@Service
@@ -188,6 +191,30 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
.flatMap(this.examDAO::byPK);
}
+ @EventListener
+ public void notifyExamStarted(final ExamStartedEvent event) {
+
+ log.info("ExamStartedEvent received, process applySEBClientRestriction...");
+
+ applySEBClientRestriction(event.exam)
+ .onError(error -> log.error(
+ "Failed to apply SEB restrictions for started exam: {}",
+ event.exam,
+ error));
+ }
+
+ @EventListener
+ public void notifyExamFinished(final ExamFinishedEvent event) {
+
+ log.info("ExamFinishedEvent received, process releaseSEBClientRestriction...");
+
+ releaseSEBClientRestriction(event.exam)
+ .onError(error -> log.error(
+ "Failed to release SEB restrictions for finished exam: {}",
+ event.exam,
+ error));
+ }
+
@Override
public Result applySEBClientRestriction(final Exam exam) {
return Result.tryCatch(() -> {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamFinishedEvent.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamFinishedEvent.java
new file mode 100644
index 00000000..72ddaf49
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamFinishedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 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.session;
+
+import org.springframework.context.ApplicationEvent;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+
+/** This event is fired just after an exam has been finished */
+public class ExamFinishedEvent extends ApplicationEvent {
+
+ private static final long serialVersionUID = -1528880878532843063L;
+
+ public final Exam exam;
+
+ public ExamFinishedEvent(final Exam exam) {
+ super(exam);
+ this.exam = exam;
+ }
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
index 24d0296b..24b85227 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java
@@ -191,13 +191,6 @@ public interface ExamSessionService {
* @return Result refer to the collection of connection tokens or to an error when happened. */
Result> getActiveConnectionTokens(Long examId);
- /** Called to notify that the given exam has just been finished.
- * This cleanup all exam session caches for the given exam and also cleanup session based stores on the persistent.
- *
- * @param exam the Exam that has just been finished
- * @return Result refer to the finished exam or to an error when happened. */
- Result notifyExamFinished(final Exam exam);
-
/** Use this to check if the current cached running exam is up to date
* and if not to flush the cache.
*
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamStartedEvent.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamStartedEvent.java
new file mode 100644
index 00000000..ee6ac689
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamStartedEvent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022 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.session;
+
+import org.springframework.context.ApplicationEvent;
+
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+
+/** This event is fired just after an exam has been started */
+public class ExamStartedEvent extends ApplicationEvent {
+
+ private static final long serialVersionUID = -6564345490588661010L;
+
+ public final Exam exam;
+
+ public ExamStartedEvent(final Exam exam) {
+ super(exam);
+ this.exam = exam;
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
index c5d335c2..642f0839 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
@@ -29,7 +29,6 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
@Service
@@ -43,7 +42,6 @@ public class ExamSessionControlTask implements DisposableBean {
private final ExamUpdateHandler examUpdateHandler;
private final ExamProctoringRoomService examProcotringRoomService;
private final WebserviceInfo webserviceInfo;
- private final ExamSessionService examSessionService;
private final Long examTimePrefix;
private final Long examTimeSuffix;
@@ -56,7 +54,6 @@ public class ExamSessionControlTask implements DisposableBean {
final ExamUpdateHandler examUpdateHandler,
final ExamProctoringRoomService examProcotringRoomService,
final WebserviceInfo webserviceInfo,
- final ExamSessionService examSessionService,
@Value("${sebserver.webservice.api.exam.time-prefix:3600000}") final Long examTimePrefix,
@Value("${sebserver.webservice.api.exam.time-suffix:3600000}") final Long examTimeSuffix,
@Value("${sebserver.webservice.api.exam.update-interval:1 * * * * *}") final String examTaskCron,
@@ -66,7 +63,6 @@ public class ExamSessionControlTask implements DisposableBean {
this.sebClientConnectionService = sebClientConnectionService;
this.examUpdateHandler = examUpdateHandler;
this.webserviceInfo = webserviceInfo;
- this.examSessionService = examSessionService;
this.examTimePrefix = examTimePrefix;
this.examTimeSuffix = examTimeSuffix;
this.examTaskCron = examTaskCron;
@@ -188,8 +184,6 @@ public class ExamSessionControlTask implements DisposableBean {
.stream()
.filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isBefore(now))
.flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setFinished(exam, updateId)))
- .flatMap(exam -> Result.skipOnError(this.examProcotringRoomService.disposeRoomsForExam(exam)))
- .flatMap(exam -> Result.skipOnError(this.examSessionService.notifyExamFinished(exam)))
.collect(Collectors.toMap(Exam::getId, Exam::getName));
if (!updated.isEmpty()) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index d761f068..e2f341a8 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Lazy;
+import org.springframework.context.event.EventListener;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
@@ -47,6 +48,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
@Lazy
@@ -402,20 +404,23 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.getActiveConnctionTokens(examId);
}
- @Override
- public Result notifyExamFinished(final Exam exam) {
- return Result.tryCatch(() -> {
- if (!isExamRunning(exam.id)) {
- this.flushCache(exam);
+ @EventListener
+ public void notifyExamFinished(final ExamFinishedEvent event) {
+
+ log.info("ExamFinishedEvent received, process exam session cleanup...");
+
+ try {
+ if (!isExamRunning(event.exam.id)) {
+ this.flushCache(event.exam);
if (this.distributedSetup) {
this.clientConnectionDAO
- .deleteClientIndicatorValues(exam)
+ .deleteClientIndicatorValues(event.exam)
.getOrThrow();
}
}
-
- return exam;
- });
+ } catch (final Exception e) {
+ log.error("Failed to cleanup on finished exam: {}", event.exam, e);
+ }
}
@Override
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java
index 3defad8d..c77244dc 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamUpdateHandler.java
@@ -13,6 +13,7 @@ import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -24,6 +25,8 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamStartedEvent;
@Lazy
@Service
@@ -33,17 +36,20 @@ class ExamUpdateHandler {
private static final Logger log = LoggerFactory.getLogger(ExamUpdateHandler.class);
private final ExamDAO examDAO;
+ private final ApplicationEventPublisher applicationEventPublisher;
private final SEBRestrictionService sebRestrictionService;
private final String updatePrefix;
private final Long examTimeSuffix;
public ExamUpdateHandler(
final ExamDAO examDAO,
+ final ApplicationEventPublisher applicationEventPublisher,
final SEBRestrictionService sebRestrictionService,
final WebserviceInfo webserviceInfo,
@Value("${sebserver.webservice.api.exam.time-suffix:3600000}") final Long examTimeSuffix) {
this.examDAO = examDAO;
+ this.applicationEventPublisher = applicationEventPublisher;
this.sebRestrictionService = sebRestrictionService;
this.updatePrefix = webserviceInfo.getLocalHostAddress()
+ "_" + webserviceInfo.getServerPort() + "_";
@@ -79,14 +85,21 @@ class ExamUpdateHandler {
return this.examDAO
.placeLock(exam.id, updateId)
- .flatMap(e -> this.examDAO.updateState(
- exam.id,
- ExamStatus.RUNNING,
- updateId))
- .flatMap(this.sebRestrictionService::applySEBClientRestriction)
- .flatMap(e -> this.examDAO.releaseLock(e, updateId))
- .onError(error -> this.examDAO.forceUnlock(exam.id)
- .onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)));
+ .flatMap(e -> this.examDAO.updateState(exam.id, ExamStatus.RUNNING, updateId))
+ .map(e -> {
+ this.examDAO
+ .releaseLock(e, updateId)
+ .onError(error -> this.examDAO
+ .forceUnlock(exam.id)
+ .onError(unlockError -> log.error(
+ "Failed to force unlock update look for exam: {}",
+ exam.id)));
+ return e;
+ })
+ .map(e -> {
+ this.applicationEventPublisher.publishEvent(new ExamStartedEvent(exam));
+ return exam;
+ });
}
Result setFinished(final Exam exam, final String updateId) {
@@ -96,13 +109,21 @@ class ExamUpdateHandler {
return this.examDAO
.placeLock(exam.id, updateId)
- .flatMap(e -> this.examDAO.updateState(
- exam.id,
- ExamStatus.FINISHED,
- updateId))
- .flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
- .flatMap(e -> this.examDAO.releaseLock(e, updateId))
- .onError(error -> this.examDAO.forceUnlock(exam.id));
+ .flatMap(e -> this.examDAO.updateState(exam.id, ExamStatus.FINISHED, updateId))
+ .map(e -> {
+ this.examDAO
+ .releaseLock(e, updateId)
+ .onError(error -> this.examDAO
+ .forceUnlock(exam.id)
+ .onError(unlockError -> log.error(
+ "Failed to force unlock update look for exam: {}",
+ exam.id)));
+ return e;
+ })
+ .map(e -> {
+ this.applicationEventPublisher.publishEvent(new ExamFinishedEvent(exam));
+ return exam;
+ });
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java
index a14265d2..2cb299f9 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java
@@ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
+import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
@@ -155,6 +156,15 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
});
}
+ @EventListener
+ public void notifyExamFinished(final ExamFinishedEvent event) {
+
+ log.info("ExamFinishedEvent received, process disposeRoomsForExam...");
+
+ disposeRoomsForExam(event.exam)
+ .onError(error -> log.error("Failed to dispose rooms for finished exam: {}", event.exam, error));
+ }
+
@Override
public Result disposeRoomsForExam(final Exam exam) {
From 5b3648bcee435bd61e620524b1fbc05e11e50987 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Thu, 24 Mar 2022 08:24:03 +0100
Subject: [PATCH 015/155] SEBSERV-240 implementation
---
.../gbl/model/session/IndicatorValue.java | 2 +-
.../gui/content/action/ActionDefinition.java | 12 +-
.../activity/PageStateDefinitionImpl.java | 3 +-
.../sebserver/gui/content/exam/ExamForm.java | 23 +-
.../gui/content/monitoring/FinishedExam.java | 56 +++-
.../FinishedExamClientConnection.java | 283 ++++++++++++++++++
.../content/monitoring/FinishedExamList.java | 6 +-
.../MonitoringClientConnection.java | 9 +-
.../monitoring/MonitoringRunningExam.java | 2 -
.../AbstractLogLevelCountIndicator.java | 4 +-
.../indicator/AbstractLogNumberIndicator.java | 4 +-
src/main/resources/messages.properties | 29 +-
12 files changed, 389 insertions(+), 44 deletions(-)
create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamClientConnection.java
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java
index ef817f5e..3569e254 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/IndicatorValue.java
@@ -32,7 +32,7 @@ public interface IndicatorValue extends IndicatorValueHolder {
return Constants.EMPTY_NOTE;
}
if (type.integerValue) {
- return String.valueOf((int) indicatorValue.getValue());
+ return String.valueOf((long) indicatorValue.getValue());
} else {
return String.valueOf(indicatorValue.getValue());
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
index 851baf74..36186c37 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java
@@ -835,11 +835,21 @@ public enum ActionDefinition {
FINISHED_EXAM_VIEW_LIST(
new LocTextKey("sebserver.finished.action.list"),
PageStateDefinitionImpl.FINISHED_EXAM_LIST),
- VIEW_EXAM_FROM_FINISHED_LIST(
+ VIEW_FINISHED_EXAM_FROM_LIST(
new LocTextKey("sebserver.finished.exam.action.list.view"),
ImageIcon.SHOW,
PageStateDefinitionImpl.FINISHED_EXAM,
ActionCategory.FINISHED_EXAM_LIST),
+ VIEW_FINISHED_EXAM_CLIENT_CONNECTION(
+ new LocTextKey("sebserver.finished.exam.connection.action.view"),
+ ImageIcon.SHOW,
+ PageStateDefinitionImpl.FINISHED_CLIENT_CONNECTION,
+ ActionCategory.CLIENT_EVENT_LIST),
+ FINISHED_EXAM_BACK_TO_OVERVIEW(
+ new LocTextKey("sebserver.finished.exam.action.detail.view"),
+ ImageIcon.SHOW,
+ PageStateDefinitionImpl.FINISHED_EXAM,
+ ActionCategory.FORM),
LOGS_USER_ACTIVITY_LIST(
new LocTextKey("sebserver.logs.activity.userlogs"),
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java
index 8365bfa2..2a485fd2 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java
@@ -34,6 +34,7 @@ import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupForm;
import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupList;
import ch.ethz.seb.sebserver.gui.content.exam.QuizLookupList;
import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExam;
+import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExamClientConnection;
import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExamList;
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringClientConnection;
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExam;
@@ -100,7 +101,7 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
FINISHED_EXAM_LIST(Type.LIST_VIEW, FinishedExamList.class, ActivityDefinition.FINISHED_EXAMS),
FINISHED_EXAM(Type.FORM_VIEW, FinishedExam.class, ActivityDefinition.FINISHED_EXAMS),
- FINISHED_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.FINISHED_EXAMS),
+ FINISHED_CLIENT_CONNECTION(Type.FORM_VIEW, FinishedExamClientConnection.class, ActivityDefinition.FINISHED_EXAMS),
USER_ACTIVITY_LOGS(Type.LIST_VIEW, UserActivityLogs.class, ActivityDefinition.USER_ACTIVITY_LOGS),
SEB_CLIENT_LOGS(Type.LIST_VIEW, SEBClientEvents.class, ActivityDefinition.SEB_CLIENT_LOGS)
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java
index ae492339..78928680 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ExamForm.java
@@ -244,10 +244,9 @@ public class ExamForm implements TemplateComposer {
final boolean modifyGrant = userGrantCheck.m();
final boolean writeGrant = userGrantCheck.w();
final ExamStatus examStatus = exam.getStatus();
- final boolean editable = modifyGrant && (examStatus == ExamStatus.UP_COMING ||
- examStatus == ExamStatus.RUNNING);
+ final boolean editable = modifyGrant &&
+ (examStatus == ExamStatus.UP_COMING || examStatus == ExamStatus.RUNNING);
-// TODO this is not performat try to improve by doing one check with the CheckExamConsistency above
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
.getBuilder(CheckSEBRestriction.class)
@@ -408,6 +407,11 @@ public class ExamForm implements TemplateComposer {
.withEntityKey(entityKey)
.publishIf(() -> modifyGrant && readonly && editable)
+ .newAction(ActionDefinition.EXAM_DELETE)
+ .withEntityKey(entityKey)
+ .withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
+ .publishIf(() -> writeGrant && readonly)
+
.newAction(ActionDefinition.EXAM_SAVE)
.withExec(action -> (importFromQuizData)
? importExam(action, formHandle, sebRestrictionAvailable && exam.status == ExamStatus.RUNNING)
@@ -451,20 +455,15 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_PROCTORING_ON)
.withEntityKey(entityKey)
- .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
+ .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant && editable))
.noEventPropagation()
- .publishIf(() -> editable && proctoringEnabled && readonly)
+ .publishIf(() -> proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_PROCTORING_OFF)
.withEntityKey(entityKey)
- .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
+ .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant && editable))
.noEventPropagation()
- .publishIf(() -> editable && !proctoringEnabled && readonly)
-
- .newAction(ActionDefinition.EXAM_DELETE)
- .withEntityKey(entityKey)
- .withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
- .publishIf(() -> writeGrant && readonly);
+ .publishIf(() -> !proctoringEnabled && readonly);
// additional data in read-only view
if (readonly && !importFromQuizData) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
index e1b40515..4dae0d35 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
@@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.gui.content.monitoring;
import java.util.Collection;
+import java.util.function.BooleanSupplier;
import java.util.function.Function;
import org.eclipse.swt.widgets.Composite;
@@ -17,26 +18,34 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
+import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
+import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
-import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamClientConnectionPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.table.TableBuilder;
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
@@ -45,6 +54,8 @@ import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
@GuiProfile
public class FinishedExam implements TemplateComposer {
+ private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.emptySelection");
private static final LocTextKey TITLE_TEXT_KEY =
new LocTextKey("sebserver.finished.exam.connections.title");
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
@@ -64,7 +75,6 @@ public class FinishedExam implements TemplateComposer {
private final PageService pageService;
private final RestService restService;
- private final ResourceService resourceService;
private final int pageSize;
public FinishedExam(
@@ -74,7 +84,6 @@ public class FinishedExam implements TemplateComposer {
this.pageService = pageService;
this.restService = pageService.getRestService();
- this.resourceService = pageService.getResourceService();
this.pageSize = pageSize;
this.statusFilter = new TableFilterAttribute(
@@ -86,19 +95,29 @@ public class FinishedExam implements TemplateComposer {
@Override
public void compose(final PageContext pageContext) {
final EntityKey examKey = pageContext.getEntityKey();
+ final CurrentUser currentUser = this.pageService.getResourceService().getCurrentUser();
+ final UserInfo user = currentUser.get();
- final RestService restService = this.pageService.getRestService();
+ final RestService restService = this.pageService
+ .getRestService();
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
-
- final Collection indicators = restService.getBuilder(GetIndicators.class)
+ final Collection indicators = restService
+ .getBuilder(GetIndicators.class)
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, examKey.modelId)
.call()
.getOrThrow();
+ final Exam exam = this.restService.getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, examKey.modelId)
+ .call()
+ .getOrThrow();
+ final boolean supporting = user.hasRole(UserRole.EXAM_SUPPORTER) &&
+ exam.supporter.contains(user.uuid);
+ final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
pageContext.getParent(),
- TITLE_TEXT_KEY);
+ new LocTextKey(TITLE_TEXT_KEY.name, exam.getName()));
final TableBuilder tableBuilder =
this.pageService.entityTableBuilder(restService.getRestCall(GetFinishedExamClientConnectionPage.class))
@@ -126,21 +145,30 @@ public class FinishedExam implements TemplateComposer {
.withFilter(this.statusFilter))
.withDefaultAction(t -> actionBuilder
- .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
+ .newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(examKey)
.create());
indicators.stream().forEach(indicator -> {
- tableBuilder.withColumn(new ColumnDefinition<>(
- indicator.name,
- new LocTextKey(indicator.name),
- indicatorValueFunction(indicator)));
+ if (indicator.type != IndicatorType.LAST_PING) {
+ tableBuilder.withColumn(new ColumnDefinition<>(
+ indicator.name,
+ new LocTextKey(indicator.name),
+ indicatorValueFunction(indicator)));
+ }
});
- tableBuilder.compose(pageContext.copyOf(content));
+ final EntityTable table = tableBuilder.compose(pageContext.copyOf(content));
+
+ actionBuilder
+
+ .newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
+ .withParentEntityKey(examKey)
+ .withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
+ .publishIf(isExamSupporter, false);
}
- private Function indicatorValueFunction(final Indicator indicator) {
+ public Function indicatorValueFunction(final Indicator indicator) {
return clientConnectionData -> {
return clientConnectionData.indicatorValues
.stream()
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamClientConnection.java
new file mode 100644
index 00000000..e5a1936e
--- /dev/null
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamClientConnection.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2022 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.gui.content.monitoring;
+
+import java.util.Collection;
+import java.util.function.BooleanSupplier;
+
+import org.eclipse.swt.widgets.Composite;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.seb.sebserver.gbl.Constants;
+import ch.ethz.seb.sebserver.gbl.api.API;
+import ch.ethz.seb.sebserver.gbl.api.EntityType;
+import ch.ethz.seb.sebserver.gbl.model.Domain;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
+import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
+import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
+import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
+import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
+import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
+import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
+import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
+import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
+import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
+import ch.ethz.seb.sebserver.gbl.util.Utils;
+import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
+import ch.ethz.seb.sebserver.gui.form.FormBuilder;
+import ch.ethz.seb.sebserver.gui.service.ResourceService;
+import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
+import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
+import ch.ethz.seb.sebserver.gui.service.page.PageContext;
+import ch.ethz.seb.sebserver.gui.service.page.PageService;
+import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamClientConnection;
+import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
+import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
+import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
+import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
+
+@Lazy
+@Component
+@GuiProfile
+public class FinishedExamClientConnection implements TemplateComposer {
+
+ private static final LocTextKey PAGE_TITLE_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.title");
+
+ private final static LocTextKey EXAM_NAME_TEXT_KEY =
+ new LocTextKey("sebserver.finished.connection.form.exam");
+ private final static LocTextKey CONNECTION_ID_TEXT_KEY =
+ new LocTextKey("sebserver.finished.connection.form.id");
+ private final static LocTextKey CONNECTION_INFO_TEXT_KEY =
+ new LocTextKey("sebserver.finished.connection.form.info");
+ private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
+ new LocTextKey("sebserver.finished.connection.form.status");
+
+ private static final LocTextKey EVENT_LIST_TITLE_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.title");
+ private static final LocTextKey EVENT_LIST_TITLE_TOOLTIP_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.title.tooltip");
+ private static final LocTextKey EMPTY_LIST_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.empty");
+ private static final LocTextKey LIST_COLUMN_TYPE_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.type");
+
+ private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.clienttime");
+ private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.servertime");
+ private static final LocTextKey LIST_COLUMN_VALUE_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.value");
+ private static final LocTextKey LIST_COLUMN_TEXT_KEY =
+ new LocTextKey("sebserver.finished.exam.connection.eventlist.text");
+
+ private final PageService pageService;
+ private final ResourceService resourceService;
+ private final I18nSupport i18nSupport;
+ private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
+ private final int pageSize;
+
+ private final TableFilterAttribute typeFilter;
+ private final TableFilterAttribute textFilter =
+ new TableFilterAttribute(CriteriaType.TEXT, ClientEvent.FILTER_ATTR_TEXT);
+
+ protected FinishedExamClientConnection(
+ final PageService pageService,
+ final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
+ @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
+
+ this.pageService = pageService;
+ this.resourceService = pageService.getResourceService();
+ this.i18nSupport = this.resourceService.getI18nSupport();
+ this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
+ this.pageSize = pageSize;
+
+ this.typeFilter = new TableFilterAttribute(
+ CriteriaType.SINGLE_SELECTION,
+ Domain.CLIENT_EVENT.ATTR_TYPE,
+ this.resourceService::clientEventTypeResources);
+ }
+
+ @Override
+ public void compose(final PageContext pageContext) {
+ final RestService restService = this.resourceService.getRestService();
+ final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
+ final CurrentUser currentUser = this.resourceService.getCurrentUser();
+ final EntityKey parentEntityKey = pageContext.getParentEntityKey();
+ final EntityKey entityKey = pageContext.getEntityKey();
+
+ // content page layout with title
+ final Composite content = widgetFactory.defaultPageLayout(
+ pageContext.getParent(),
+ PAGE_TITLE_KEY);
+ final Exam exam = restService
+ .getBuilder(GetExam.class)
+ .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
+ .call()
+ .onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
+ .getOrThrow();
+ final UserInfo user = currentUser.get();
+ final boolean supporting = user.hasRole(UserRole.EXAM_SUPPORTER) &&
+ exam.supporter.contains(user.uuid);
+ final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
+ final Collection indicators = restService
+ .getBuilder(GetIndicators.class)
+ .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId)
+ .call()
+ .getOrThrow();
+ final ClientConnectionData connectionData = restService
+ .getBuilder(GetFinishedExamClientConnection.class)
+ .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
+ .call()
+ .getOrThrow();
+
+ final FormBuilder formBuilder = this.pageService.formBuilder(pageContext.copyOf(content))
+ .readonly(true)
+ .addField(FormBuilder.text(
+ QuizData.QUIZ_ATTR_NAME,
+ EXAM_NAME_TEXT_KEY,
+ exam.getName()))
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ CONNECTION_ID_TEXT_KEY,
+ connectionData.clientConnection.userSessionId))
+ .addField(FormBuilder.text(
+ ClientConnection.ATTR_INFO,
+ CONNECTION_INFO_TEXT_KEY,
+ connectionData.clientConnection.info))
+ .withDefaultSpanInput(3)
+ .addField(FormBuilder.text(
+ Domain.CLIENT_CONNECTION.ATTR_STATUS,
+ CONNECTION_STATUS_TEXT_KEY,
+ this.resourceService.localizedClientConnectionStatusName(
+ connectionData.clientConnection.status))
+ .asColorBox())
+ .addEmptyCell();
+
+ indicators.forEach(indicator -> formBuilder.addField(FormBuilder.text(
+ indicator.name,
+ new LocTextKey(indicator.name),
+ connectionData.indicatorValues
+ .stream()
+ .filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicator.id))
+ .findFirst()
+ .map(iv -> IndicatorValue.getDisplayValue(iv, indicator.type))
+ .orElse(Constants.EMPTY_NOTE))
+ .asColorBox()
+ .withDefaultLabel(indicator.name))
+ .addEmptyCell());
+
+ formBuilder.build();
+
+ // CLIENT EVENTS
+ final PageService.PageActionBuilder actionBuilder = this.pageService
+ .pageActionBuilder(
+ pageContext
+ .clearAttributes()
+ .clearEntityKeys());
+
+ widgetFactory.addFormSubContextHeader(
+ content,
+ EVENT_LIST_TITLE_KEY,
+ EVENT_LIST_TITLE_TOOLTIP_KEY);
+
+ // client event table for this connection
+ this.pageService
+ .entityTableBuilder(
+ "seb-client-" + connectionData.getModelId(),
+ restService.getRestCall(GetExtendedClientEventPage.class))
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(this.pageSize)
+ .withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
+ ClientEvent.FILTER_ATTR_CONNECTION_ID,
+ entityKey.modelId))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_TYPE,
+ LIST_COLUMN_TYPE_KEY,
+ this.resourceService::getEventTypeName)
+ .withFilter(this.typeFilter)
+ .sortable()
+ .widthProportion(2))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_TEXT,
+ LIST_COLUMN_TEXT_KEY,
+ ClientEvent::getText)
+ .withFilter(this.textFilter)
+ .sortable()
+ .withCellTooltip()
+ .widthProportion(4))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
+ LIST_COLUMN_VALUE_KEY,
+ ClientEvent::getValue)
+ .widthProportion(1))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
+ new LocTextKey(LIST_COLUMN_CLIENT_TIME_KEY.name,
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()),
+ this::getClientTime)
+ .sortable()
+ .widthProportion(1))
+
+ .withColumn(new ColumnDefinition(
+ Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
+ new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
+ this.i18nSupport.getUsersTimeZoneTitleSuffix()),
+ this::getServerTime)
+ .sortable()
+ .widthProportion(1))
+
+ .withDefaultAction(t -> actionBuilder
+ .newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
+ .withExec(action -> this.sebClientLogDetailsPopup.showDetails(action,
+ t.getSingleSelectedROWData()))
+ .noEventPropagation()
+ .create())
+
+ .compose(pageContext.copyOf(content));
+
+ actionBuilder
+ .newAction(ActionDefinition.FINISHED_EXAM_BACK_TO_OVERVIEW)
+ .withEntityKey(parentEntityKey)
+ .publishIf(isExamSupporter);
+ }
+
+ private String getClientTime(final ClientEvent event) {
+ if (event == null || event.getClientTime() == null) {
+ return Constants.EMPTY_NOTE;
+ }
+
+ return this.i18nSupport
+ .formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
+ }
+
+ private String getServerTime(final ClientEvent event) {
+ if (event == null || event.getServerTime() == null) {
+ return Constants.EMPTY_NOTE;
+ }
+
+ return this.i18nSupport
+ .formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
+ }
+
+}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java
index 19fa548d..ff21ef96 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExamList.java
@@ -129,18 +129,18 @@ public class FinishedExamList implements TemplateComposer {
.sortable())
.withDefaultAction(actionBuilder
- .newAction(ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST)
+ .newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST)
.create())
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
- ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST))
+ ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST))
.compose(pageContext.copyOf(content));
actionBuilder
- .newAction(ActionDefinition.VIEW_EXAM_FROM_FINISHED_LIST)
+ .newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST)
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java
index 5a18104a..aceef164 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java
@@ -90,6 +90,11 @@ public class MonitoringClientConnection implements TemplateComposer {
private static final LocTextKey NOTIFICATION_LIST_COLUMN_TYPE_KEY =
new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.type");
+ private static final LocTextKey CONFIRM_QUIT =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
+ private static final LocTextKey CONFIRM_OPEN_SINGLE_ROOM =
+ new LocTextKey("sebserver.monitoring.exam.connection.action.singleroom.confirm");
+
private static final LocTextKey EVENT_LIST_TITLE_KEY =
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
private static final LocTextKey EVENT_LIST_TITLE_TOOLTIP_KEY =
@@ -107,10 +112,6 @@ public class MonitoringClientConnection implements TemplateComposer {
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.value");
private static final LocTextKey LIST_COLUMN_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
- private static final LocTextKey CONFIRM_QUIT =
- new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
- private static final LocTextKey CONFIRM_OPEN_SINGLE_ROOM =
- new LocTextKey("sebserver.monitoring.exam.connection.action.singleroom.confirm");
private final ServerPushService serverPushService;
private final PageService pageService;
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
index 21800970..0a953fd5 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java
@@ -70,8 +70,6 @@ import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService
@GuiProfile
public class MonitoringRunningExam implements TemplateComposer {
- //private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
-
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java
index 55461a1f..769cacf1 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogLevelCountIndicator.java
@@ -55,8 +55,8 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
@Override
public double computeValueAt(final long timestamp) {
- if (log.isDebugEnabled()) {
- log.debug("computeValueAt: {}", timestamp);
+ if (log.isTraceEnabled()) {
+ log.trace("computeValueAt: {}", timestamp);
}
try {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java
index 994c307f..2ad92b56 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/indicator/AbstractLogNumberIndicator.java
@@ -73,8 +73,8 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
@Override
public double computeValueAt(final long timestamp) {
- if (log.isDebugEnabled()) {
- log.debug("computeValueAt: {}", timestamp);
+ if (log.isTraceEnabled()) {
+ log.trace("computeValueAt: {}", timestamp);
}
try {
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index c5d5dd54..a7225916 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -1831,7 +1831,6 @@ sebserver.monitoring.exam.connection.notificationlist.pleaseSelect=At first plea
sebserver.monitoring.exam.connection.notificationlist.title=Pending Notification
sebserver.monitoring.exam.connection.notificationlist.title.tooltip=All pending notifications sent by the SEB Client
-
sebserver.monitoring.exam.connection.eventlist.title=Events
sebserver.monitoring.exam.connection.eventlist.title.tooltip=All events and logs sent by the SEB Client
sebserver.monitoring.exam.connection.eventlist.empty=No event found
@@ -1888,12 +1887,38 @@ sebserver.finished.exam.list.column.endTime=End Time {0}
sebserver.finished.exam.list.column.endTime.tooltip=The end date and time of the exam {0}
sebserver.finished.exam.action.list.view=View Finished Exam
-sebserver.finished.exam.connections.title=Search Connections
+sebserver.finished.exam.connections.title=Finished Exam ({0})
sebserver.finished.exam.connections.action=Search
sebserver.finished.exam.connections.empty=No Client Connections available
sebserver.finished.exam.connections.name=Session or User Name
sebserver.finished.exam.connections.info=Connection Info
sebserver.finished.exam.connections.status=Status
+sebserver.finished.exam.connection.emptySelection=At first please select a Connection from the list
+
+sebserver.finished.exam.connection.title=SEB Client Connection
+sebserver.finished.connection.form.id=User Name or Session
+sebserver.finished.connection.form.id.tooltip=The user session identifier or username sent by the SEB client after LMS login
+sebserver.finished.connection.form.info=Connection Info
+sebserver.finished.connection.form.info.tooltip=Format: IP Address,SEB Version, OSName
+sebserver.finished.connection.form.status=Status
+sebserver.finished.connection.form.status.tooltip=The current connection status
+sebserver.finished.connection.form.exam=Exam
+sebserver.finished.connection.form.exam.tooltip=The exam name
+
+sebserver.finished.exam.connection.eventlist.title=Events
+sebserver.finished.exam.connection.eventlist.title.tooltip=All events and logs sent by the SEB Client
+sebserver.finished.exam.connection.eventlist.empty=No event found
+sebserver.finished.exam.connection.eventlist.type=Event Type
+sebserver.finished.exam.connection.eventlist.type.tooltip=The type of the log event Use the filter above to set a specific event type {0}
+sebserver.finished.exam.connection.eventlist.clienttime=Client Time {0}
+sebserver.finished.exam.connection.eventlist.clienttime.tooltip=The time the SEB client has sent within the log event {0}
+sebserver.finished.exam.connection.eventlist.servertime=Server Time {0}
+sebserver.finished.exam.connection.eventlist.servertime.tooltip=The exact time (UTC) the SEB Server has received the log event {0}
+sebserver.finished.exam.connection.eventlist.value=Value
+sebserver.finished.exam.connection.eventlist.value.tooltip=The value of the log event {0}
+sebserver.finished.exam.connection.eventlist.text=Text
+sebserver.finished.exam.connection.eventlist.text.tooltip=The text of the log event {0}
+sebserver.finished.exam.action.detail.view=Back To Exam
################################
# Logs
From 1cd6fb1888988957006a969ff28c50f0e97fff09 Mon Sep 17 00:00:00 2001
From: anhefti
Date: Thu, 24 Mar 2022 15:33:11 +0100
Subject: [PATCH 016/155] SEBSERV-240 implementation and fixes
---
.../gui/content/monitoring/FinishedExam.java | 16 ++++++----
.../FinishedExamClientConnection.java | 30 +++++++++++--------
src/main/resources/messages.properties | 1 +
3 files changed, 29 insertions(+), 18 deletions(-)
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
index 4dae0d35..19f173d9 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
@@ -147,15 +147,19 @@ public class FinishedExam implements TemplateComposer {
.withDefaultAction(t -> actionBuilder
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(examKey)
- .create());
+ .create())
+ .withSelectionListener(this.pageService.getSelectionPublisher(
+ pageContext,
+ ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION));
indicators.stream().forEach(indicator -> {
- if (indicator.type != IndicatorType.LAST_PING) {
- tableBuilder.withColumn(new ColumnDefinition<>(
- indicator.name,
- new LocTextKey(indicator.name),
- indicatorValueFunction(indicator)));
+ if (indicator.type == IndicatorType.LAST_PING || indicator.type == IndicatorType.NONE) {
+ return;
}
+ tableBuilder.withColumn(new ColumnDefinition<>(
+ indicator.name,
+ new LocTextKey(indicator.name),
+ indicatorValueFunction(indicator)));
});
final EntityTable