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(); + } + }