diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreaker.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreaker.java index 2493bb23..31791d1d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreaker.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreaker.java @@ -50,13 +50,11 @@ public final class CircuitBreaker { private static final Logger log = LoggerFactory.getLogger(CircuitBreaker.class); + public static final String OPEN_CIRCUIT_BREAKER_EXCEPTION = "Open CircuitBreaker"; public static final int DEFAULT_MAX_FAILING_ATTEMPTS = 5; public static final long DEFAULT_MAX_BLOCKING_TIME = Constants.MINUTE_IN_MILLIS; public static final long DEFAULT_TIME_TO_RECOVER = Constants.MINUTE_IN_MILLIS * 10; - public static final RuntimeException OPEN_STATE_EXCEPTION = - new RuntimeException("Open CircuitBreaker"); - public enum State { CLOSED, HALF_OPEN, @@ -243,7 +241,7 @@ public final class CircuitBreaker { return protectedRun(supplier); } - return Result.ofError(OPEN_STATE_EXCEPTION); + return Result.ofError(new RuntimeException(OPEN_CIRCUIT_BREAKER_EXCEPTION)); } private Result attempt(final Supplier supplier) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java index 45c45a91..c453c9bc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/UpdateErrorHandler.java @@ -63,7 +63,7 @@ public final class UpdateErrorHandler implements Function { @Override public Boolean apply(final Exception error) { this.errors++; - log.error("Failed to update server push: {}", error.getMessage()); + log.error("Failed to update server push: {}", error.getMessage(), error); if (this.errors > 5) { checkUserSession(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java index 885dcc72..86686141 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java @@ -198,6 +198,9 @@ public class ClientConnectionDetails { this.connectionData.getIndicatorValues() .forEach(indValue -> { final IndicatorData indData = this.indicatorMapping.get(indValue.getIndicatorId()); + if (indData == null) { + return; + } final double value = indValue.getValue(); final String displayValue = IndicatorValue.getDisplayValue(indValue, indData.indicator.type); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index d8af46af..cf19890d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -496,8 +496,9 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate for (int i = 0; i < this.connectionData.indicatorValues.size(); i++) { final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i); - final IndicatorData indicatorData = - ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getIndicatorId()); + final IndicatorData indicatorData = ClientConnectionTable.this.indicatorMapping + .get(indicatorValue.getIndicatorId()); + if (indicatorData == null) { continue; } 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 fc6d3322..c7912042 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 @@ -109,7 +109,11 @@ public class ExamDAOImpl implements ExamDAO { final QuizData quizData = this.lmsAPIService .getLmsAPITemplate(record.getLmsSetupId()) .flatMap(template -> template.getQuiz(record.getExternalId())) - .getOrThrow(); + .onError(error -> log.error( + "Failed to load quiz data for exam: {} error: {}", + examId, + error.getMessage())) + .getOr(null); return toDomainModel(record, quizData, null, true); }); } @@ -791,26 +795,11 @@ public class ExamDAOImpl implements ExamDAO { log.info("Try to recover quiz data for Moodle quiz with internal identifier: {}", externalId); - // get additional quiz name attribute - final AdditionalAttributeRecord additionalAttribute = - this.additionalAttributeRecordMapper.selectByExample() - .where( - AdditionalAttributeRecordDynamicSqlSupport.entityType, - SqlBuilder.isEqualTo(EntityType.EXAM.name())) - .and( - AdditionalAttributeRecordDynamicSqlSupport.entityId, - SqlBuilder.isEqualTo(record.getId())) - .and( - AdditionalAttributeRecordDynamicSqlSupport.name, - SqlBuilder.isEqualTo(QuizData.QUIZ_ATTR_NAME)) - .build() - .execute() - .stream() - .findAny() - .orElse(null); - if (additionalAttribute != null) { + // get former quiz name attribute + final String formerName = getFormerName(record.getId()); + if (formerName != null) { - log.debug("Found additional quiz name attribute: {}", additionalAttribute); + log.debug("Found formerName quiz name: {}", formerName); // get the course name identifier final String shortname = MoodleCourseAccess.getShortname(externalId); @@ -827,7 +816,7 @@ public class ExamDAOImpl implements ExamDAO { final String qShortName = MoodleCourseAccess.getShortname(quiz.id); return qShortName != null && qShortName.equals(shortname); }) - .filter(quiz -> additionalAttribute.getValue().equals(quiz.name)) + .filter(quiz -> formerName.equals(quiz.name)) .findAny() .get()) .getOrThrow(); @@ -851,7 +840,7 @@ public class ExamDAOImpl implements ExamDAO { } } } catch (final Exception e) { - log.warn("Failed to try to recover from Moodle quiz restore: {}", e.getMessage()); + log.debug("Failed to try to recover from Moodle quiz restore: {}", e.getMessage()); } return null; } 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 7b12de7c..456ddf47 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 @@ -156,7 +156,7 @@ public class ExamSessionControlTask implements DisposableBean { final Map updated = this.examDAO.allForRunCheck() .getOrThrow() .stream() - .filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now)) + .filter(exam -> exam.startTime != null && exam.startTime.minus(this.examTimePrefix).isBefore(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/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 7fbe16c8..b657a0cc 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 @@ -245,12 +245,20 @@ public class ExamSessionServiceImpl implements ExamSessionService { .putIfAbsent(Exam.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) .putIfAbsent(Exam.FILTER_ATTR_STATUS, ExamStatus.RUNNING.name()); - // NOTE: we evict the exam from the cache (if present) to ensure user is seeing always the current state of the Exam return this.examDAO.allMatching(filterMap, predicate) .map(col -> col.stream() .map(exam -> { - this.examSessionCacheService.evict(exam); - return this.examSessionCacheService.getRunningExam(exam.id); + final Exam runningExam = this.examSessionCacheService.getRunningExam(exam.id); + if (runningExam == null) { + return null; + } + if (!isUpToDate(exam, runningExam)) { + // If the cached exam-quiz data differs from the one of the currently loaded exam, cache is updated + this.examSessionCacheService.evict(exam); + return this.examSessionCacheService.getRunningExam(exam.id); + } else { + return runningExam; + } }) .filter(Objects::nonNull) .collect(Collectors.toList())); @@ -514,4 +522,11 @@ public class ExamSessionServiceImpl implements ExamSessionService { } } + private boolean isUpToDate(final Exam exam, final Exam runningExam) { + return Objects.equals(exam.lastModified, runningExam.lastModified) + && Objects.equals(exam.startTime, runningExam.startTime) + && Objects.equals(exam.endTime, runningExam.endTime) + && Objects.equals(exam.name, runningExam.name); + } + } diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreakerTest.java b/src/test/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreakerTest.java index ee69e441..d57f65bb 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreakerTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/async/CircuitBreakerTest.java @@ -73,7 +73,7 @@ public class CircuitBreakerTest { Thread.sleep(100); result = circuitBreaker.protectedRun(tester); // 10. call... assertEquals(State.OPEN, circuitBreaker.getState()); - assertEquals(CircuitBreaker.OPEN_STATE_EXCEPTION, result.getError()); + assertEquals(CircuitBreaker.OPEN_CIRCUIT_BREAKER_EXCEPTION, result.getError().getMessage()); // wait time to recover Thread.sleep(1000);