From bc1026a6b78b48cb58128cc410ebba5191f9b632 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 27 Apr 2022 11:40:54 +0200 Subject: [PATCH 1/2] SEBSERV-299 fixed for null end-time and added Mockup test quiz11 --- .../servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java | 7 +++++++ .../servicelayer/session/impl/ExamSessionControlTask.java | 2 +- .../webservice/integration/api/admin/QuizDataTest.java | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) 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/MockupLmsAPITemplate.java index ec71bb8e..0efaf594 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/MockupLmsAPITemplate.java @@ -130,6 +130,13 @@ public class MockupLmsAPITemplate implements LmsAPITemplate { DateTime.now(DateTimeZone.UTC).plus(6 * Constants.MINUTE_IN_MILLIS) .toString(Constants.DEFAULT_DATE_TIME_FORMAT), "http://lms.mockup.com/api/")); + this.mockups.add(new QuizData( + "quiz11", institutionId, lmsSetupId, lmsType, "Demo Quiz 11 (MOCKUP)", + "Starts in a minute and ends never", + DateTime.now(DateTimeZone.UTC).plus(Constants.MINUTE_IN_MILLIS) + .toString(Constants.DEFAULT_DATE_TIME_FORMAT), + null, + "http://lms.mockup.com/api/")); } @Override 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..5e4a4fb7 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 @@ -161,7 +161,7 @@ public class ExamSessionControlTask implements DisposableBean { .getOrThrow() .stream() .filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now)) - .filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isAfter(now)) + .filter(exam -> exam.endTime == null || exam.endTime.plus(this.examTimeSuffix).isAfter(now)) .flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setRunning(exam, updateId))) .collect(Collectors.toMap(Exam::getId, Exam::getName)); diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java index f0d9f4a6..b1f7d465 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java @@ -59,7 +59,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester { }); assertNotNull(quizzes); - assertTrue(quizzes.content.size() == 8); + assertTrue(quizzes.content.size() == 9); // for the inactive LmsSetup we should'nt get any quizzes quizzes = new RestAPITestHelper() @@ -109,7 +109,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester { }); assertNotNull(quizzes); - assertTrue(quizzes.content.size() == 8); + assertTrue(quizzes.content.size() == 9); // but for the now active lmsSetup2 we should get the quizzes quizzes = new RestAPITestHelper() @@ -120,7 +120,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester { }); assertNotNull(quizzes); - assertTrue(quizzes.content.size() == 8); + assertTrue(quizzes.content.size() == 9); } @Test From 0f8910bb3fe17855530605acc09711262be8b317 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 27 Apr 2022 13:40:25 +0200 Subject: [PATCH 2/2] SEBSERV-300 fixed by check the last follow-up id of the config from cache. If not the same, reolad the cache. --- .../servicelayer/dao/ConfigurationDAO.java | 6 ++++ .../dao/impl/ConfigurationDAOImpl.java | 18 +++++++++++- .../sebconfig/ExamConfigService.java | 7 +++++ .../sebconfig/impl/ExamConfigServiceImpl.java | 4 +++ .../session/impl/ExamSessionCacheService.java | 19 ++++++++++++- .../session/impl/ExamSessionServiceImpl.java | 19 ++++++++++++- .../session/impl/InMemorySEBConfig.java | 28 ++++++++++++++++++- 7 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java index 1228bb26..51c1449e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java @@ -105,4 +105,10 @@ public interface ConfigurationDAO extends EntityDAO getConfigurationLastStableVersion(Long configNodeId); + /** Use this to get the follow-up configuration identifer for a specified configuration node. + * + * @param configurationNode ConfigurationNode to get the current follow-up configuration from + * @return the current follow-up configuration identifier */ + Result getFollowupConfigurationId(Long configNodeId); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java index 5ce765ff..8f7b6cda 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java @@ -133,8 +133,24 @@ public class ConfigurationDAOImpl implements ConfigurationDAO { .build() .execute() .stream() - .collect(Utils.toSingleton())).flatMap(ConfigurationDAOImpl::toDomainModel); + .collect(Utils.toSingleton())) + .flatMap(ConfigurationDAOImpl::toDomainModel); + } + @Override + @Transactional(readOnly = true) + public Result getFollowupConfigurationId(final Long configNodeId) { + return Result.tryCatch(() -> this.configurationRecordMapper.selectIdsByExample() + .where( + ConfigurationRecordDynamicSqlSupport.configurationNodeId, + isEqualTo(configNodeId)) + .and( + ConfigurationRecordDynamicSqlSupport.followup, + isEqualTo(BooleanUtils.toInteger(true))) + .build() + .execute() + .stream() + .collect(Utils.toSingleton())); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java index 89c1dba7..41736016 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java @@ -35,6 +35,13 @@ public interface ExamConfigService { * @throws FieldValidationException on validation exception */ void validate(ConfigurationTableValues tableValue) throws FieldValidationException; + /** Get the follow-up configuration identifier for a given configuration node identifier. + * + * @param examConfigNodeId the exam configuration node identifier + * @return Result refer to the follow-up configuration identifier of the given config node or to an error when + * happened */ + Result getFollowupConfigurationId(final Long examConfigNodeId); + /** Used to export a specified SEB Exam Configuration as plain XML * This exports the values of the follow-up configuration defined by a given * ConfigurationNode (configurationNodeId) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java index 629e2c4b..b0ebf199 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java @@ -128,6 +128,10 @@ public class ExamConfigServiceImpl implements ExamConfigService { } } + public Result getFollowupConfigurationId(final Long examConfigNodeId) { + return this.configurationDAO.getFollowupConfigurationId(examConfigNodeId); + } + @Override public void exportPlainXML( final OutputStream out, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java index 112a6e92..3d673263 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java @@ -173,8 +173,12 @@ public class ExamSessionCacheService { byteOut, institutionId, examId); + final Long followupId = this.sebExamConfigService + .getFollowupConfigurationId(configId) + .onError(error -> log.error("Failed to get follow-up id for config node: {}", configId, error)) + .getOr(-1L); - return new InMemorySEBConfig(configId, examId, byteOut.toByteArray()); + return new InMemorySEBConfig(configId, followupId, examId, byteOut.toByteArray()); } catch (final Exception e) { log.error("Unexpected error while getting default exam configuration for running exam; {}", examId, e); @@ -182,6 +186,19 @@ public class ExamSessionCacheService { } } + public boolean isUpToDate(final InMemorySEBConfig inMemorySEBConfig) { + try { + final Long followupId = this.sebExamConfigService + .getFollowupConfigurationId(inMemorySEBConfig.configId) + .getOrThrow(); + + return followupId.equals(inMemorySEBConfig.follwupId); + } catch (final Exception e) { + log.error("Failed to check if InMemorySEBConfig is up to date for: {}", inMemorySEBConfig); + return true; + } + } + @CacheEvict( cacheNames = CACHE_NAME_SEB_CONFIG_EXAM, key = "#examId") 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 5ee1bb0c..2cb520c0 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 @@ -285,7 +285,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { log.trace("Trying to get exam from InMemorySEBConfig"); } - final InMemorySEBConfig sebConfigForExam = this.examSessionCacheService + InMemorySEBConfig sebConfigForExam = this.examSessionCacheService .getDefaultSEBConfigForExam(connection.examId, institutionId); if (sebConfigForExam == null) { @@ -293,6 +293,23 @@ public class ExamSessionServiceImpl implements ExamSessionService { return; } + // for distributed setups check if cached config is still up to date. Flush and reload if not. + if (this.distributedSetup && !this.examSessionCacheService.isUpToDate(sebConfigForExam)) { + + if (log.isDebugEnabled()) { + log.debug("Detected new version of exam configuration for exam {} ...flush cache", connection.examId); + } + + this.examSessionCacheService.evictDefaultSEBConfig(connection.examId); + sebConfigForExam = this.examSessionCacheService + .getDefaultSEBConfigForExam(connection.examId, institutionId); + } + + if (sebConfigForExam == null) { + log.error("Failed to get and cache InMemorySEBConfig for connection: {}", connection); + return; + } + try { if (log.isTraceEnabled()) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java index a648741c..35cea567 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java @@ -11,12 +11,19 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; public final class InMemorySEBConfig { public final Long configId; + public final Long follwupId; public final Long examId; private final byte[] data; - protected InMemorySEBConfig(final Long configId, final Long examId, final byte[] data) { + protected InMemorySEBConfig( + final Long configId, + final Long follwupId, + final Long examId, + final byte[] data) { + super(); this.configId = configId; + this.follwupId = follwupId; this.examId = examId; this.data = data; } @@ -39,6 +46,7 @@ public final class InMemorySEBConfig { int result = 1; result = prime * result + ((this.configId == null) ? 0 : this.configId.hashCode()); result = prime * result + ((this.examId == null) ? 0 : this.examId.hashCode()); + result = prime * result + ((this.follwupId == null) ? 0 : this.follwupId.hashCode()); return result; } @@ -61,7 +69,25 @@ public final class InMemorySEBConfig { return false; } else if (!this.examId.equals(other.examId)) return false; + if (this.follwupId == null) { + if (other.follwupId != null) + return false; + } else if (!this.follwupId.equals(other.follwupId)) + return false; return true; } + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("InMemorySEBConfig [configId="); + builder.append(this.configId); + builder.append(", follwupId="); + builder.append(this.follwupId); + builder.append(", examId="); + builder.append(this.examId); + builder.append("]"); + return builder.toString(); + } + }