From 951457c99e89937ac85cd4dd71f593377fb917ee Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Thu, 10 Aug 2017 18:27:02 +0200 Subject: [PATCH] Move domain logic and caching to service layer --- .../de/thm/arsnova/cache/CacheBusterImpl.java | 2 +- .../arsnova/controller/MotdController.java | 16 +- .../arsnova/controller/SessionController.java | 3 +- .../arsnova/persistance/AnswerRepository.java | 2 - .../persistance/CommentRepository.java | 3 - .../persistance/ContentRepository.java | 8 - .../arsnova/persistance/MotdRepository.java | 2 - .../persistance/SessionRepository.java | 16 +- .../couchdb/CouchDbAnswerRepository.java | 27 -- .../couchdb/CouchDbCommentRepository.java | 42 ---- .../couchdb/CouchDbContentRepository.java | 156 ------------ .../couchdb/CouchDbMotdListRepository.java | 4 - .../couchdb/CouchDbMotdRepository.java | 37 --- .../couchdb/CouchDbSessionRepository.java | 118 +-------- .../CouchDbSessionStatisticsRepository.java | 2 - .../couchdb/CouchDbStatisticsRepository.java | 2 - .../arsnova/services/CommentServiceImpl.java | 12 +- .../thm/arsnova/services/ContentService.java | 4 +- .../arsnova/services/ContentServiceImpl.java | 231 +++++++++++------- .../de/thm/arsnova/services/MotdService.java | 4 +- .../thm/arsnova/services/MotdServiceImpl.java | 30 ++- .../thm/arsnova/services/SessionService.java | 2 +- .../arsnova/services/SessionServiceImpl.java | 116 +++++++-- .../services/StatisticsServiceImpl.java | 8 +- .../score/VariantScoreCalculator.java | 12 +- src/site/markdown/development/caching.md | 2 +- 26 files changed, 319 insertions(+), 542 deletions(-) diff --git a/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java b/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java index dad103c84..ac29ca5f8 100644 --- a/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java +++ b/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java @@ -51,7 +51,7 @@ public class CacheBusterImpl implements CacheBuster, ArsnovaEventVisitor { @Override public void visit(LockQuestionsEvent lockQuestionsEvent) { } - @CacheEvict(value = "answers", key = "#event.content") + @CacheEvict(value = "answers", key = "#event.content.id") @Override public void visit(NewAnswerEvent event) { } diff --git a/src/main/java/de/thm/arsnova/controller/MotdController.java b/src/main/java/de/thm/arsnova/controller/MotdController.java index acc933533..e9a19f58e 100644 --- a/src/main/java/de/thm/arsnova/controller/MotdController.java +++ b/src/main/java/de/thm/arsnova/controller/MotdController.java @@ -63,18 +63,18 @@ public class MotdController extends AbstractController { @ApiParam(value = "sessionkey", required = false) @RequestParam(value = "sessionkey", defaultValue = "null") final String sessionkey ) { List<Motd> motds; - Date client = new Date(System.currentTimeMillis()); + Date date = new Date(System.currentTimeMillis()); if (!clientdate.isEmpty()) { - client.setTime(Long.parseLong(clientdate)); + date.setTime(Long.parseLong(clientdate)); } if (adminview) { - if ("null".equals(sessionkey)) { - motds = motdService.getAdminMotds(); - } else { - motds = motdService.getAllSessionMotds(sessionkey); - } + motds = "session".equals(audience) ? + motdService.getAllSessionMotds(sessionkey) : + motdService.getAdminMotds(); } else { - motds = motdService.getCurrentMotds(client, audience, sessionkey); + motds = "session".equals(audience) ? + motdService.getCurrentSessionMotds(date, sessionkey) : + motdService.getCurrentMotds(date, audience); } return motds; } diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java index 3f11525fa..325818ccc 100644 --- a/src/main/java/de/thm/arsnova/controller/SessionController.java +++ b/src/main/java/de/thm/arsnova/controller/SessionController.java @@ -86,7 +86,8 @@ public class SessionController extends PaginationController { nickname = "deleteSession") @RequestMapping(value = "/{sessionkey}", method = RequestMethod.DELETE) public void deleteSession(@ApiParam(value = "Session-Key from current session", required = true) @PathVariable final String sessionkey) { - sessionService.delete(sessionkey); + Session session = sessionService.getByKey(sessionkey); + sessionService.deleteCascading(session); } @ApiOperation(value = "count active users", diff --git a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java index 3f25db49d..dc2628a16 100644 --- a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java @@ -33,8 +33,6 @@ public interface AnswerRepository extends CrudRepository<Answer, String> { List<Answer> findByUserSessionId(User user, String sessionId); int countBySessionKey(String sessionKey); int deleteByContentId(String contentId); - void update(Answer answer); - void delete(String answerId); int countBySessionIdLectureVariant(String sessionId); int countBySessionIdPreparationVariant(String sessionId); int deleteAllAnswersForQuestions(List<String> contentIds); diff --git a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java index df5ddc8a5..6e97fb98a 100644 --- a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java @@ -14,9 +14,6 @@ public interface CommentRepository extends CrudRepository<Comment, String> { List<Comment> findBySessionId(String sessionId, int start, int limit); List<Comment> findBySessionIdAndUser(String sessionId, User user, int start, int limit); Comment findOne(String commentId); - Comment save(String sessionId, Comment comment, User user); - void markInterposedQuestionAsRead(Comment comment); - void delete(Comment comment); int deleteBySessionId(String sessionId); int deleteBySessionIdAndUser(String sessionId, User user); } diff --git a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java index 7bea1b523..9dbef6ce1 100644 --- a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java @@ -8,8 +8,6 @@ import java.util.List; public interface ContentRepository extends CrudRepository<Content, String> { List<Content> findBySessionIdAndVariantAndActive(Object... keys); - Content findOne(String id); - Content save(String sessionId, Content content); List<Content> findBySessionIdForUsers(String sessionId); List<Content> findBySessionIdForSpeaker(String sessionId); int countBySessionId(String sessionId); @@ -17,7 +15,6 @@ public interface ContentRepository extends CrudRepository<Content, String> { List<String> findIdsBySessionIdAndVariant(String sessionId, String variant); int deleteBySessionId(String sessionId); List<String> findUnansweredIdsBySessionIdAndUser(String sessionId, User user); - void update(Content content); List<Content> findBySessionIdOnlyLectureVariantAndActive(String sessionId); List<Content> findBySessionIdOnlyLectureVariant(String sessionId); List<Content> findBySessionIdOnlyFlashcardVariantAndActive(String sessionId); @@ -28,12 +25,7 @@ public interface ContentRepository extends CrudRepository<Content, String> { int countLectureVariantBySessionId(String sessionId); int countFlashcardVariantBySessionId(String sessionId); int countPreparationVariantBySessionId(String sessionId); - void publishQuestions(String sessionId, boolean publish, List<Content> contents); - List<Content> publishAllQuestions(String sessionId, boolean publish); List<String> findIdsBySessionIdAndVariantAndSubject(String sessionId, String questionVariant, String subject); - void resetQuestionsRoundState(String sessionId, List<Content> contents); - void setVotingAdmissions(String sessionId, boolean disableVoting, List<Content> contents); - List<Content> setVotingAdmissionForAllQuestions(String sessionId, boolean disableVoting); List<String> findSubjectsBySessionIdAndVariant(String sessionId, String questionVariant); List<String> findUnansweredIdsBySessionIdAndUserOnlyLectureVariant(String sessionId, User user); List<String> findUnansweredIdsBySessionIdAndUserOnlyPreparationVariant(String sessionId, User user); diff --git a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java index 44cdea7a3..deb9e9334 100644 --- a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java @@ -30,6 +30,4 @@ public interface MotdRepository extends CrudRepository<Motd, String> { List<Motd> findForStudents(); List<Motd> findBySessionKey(String sessionkey); Motd findByKey(String key); - Motd save(Motd motd); - void delete(Motd motd); } diff --git a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java index 55597d5c9..43d88c292 100644 --- a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java @@ -28,26 +28,12 @@ import org.springframework.data.repository.CrudRepository; import java.util.List; public interface SessionRepository extends CrudRepository<Session, String> { - Session findOne(String sessionId); Session findByKeyword(String keyword); - Session save(User user, Session session); - void update(Session session); - - /** - * Deletes a session and related data. - * - * @param session the session for deletion - */ - int[] deleteSession(Session session); - - Session changeSessionCreator(Session session, String newCreator); - int[] deleteInactiveGuestSessions(long lastActivityBefore); + List<Session> findInactiveGuestSessionsMetadata(long lastActivityBefore); List<Session> findByUser(User user, int start, int limit); List<Session> findByUsername(String username, int start, int limit); List<Session> findAllForPublicPool(); List<Session> findForPublicPoolByUser(User user); - boolean sessionKeyAvailable(String keyword); - Session updateSessionOwnerActivity(Session session); List<Session> findVisitedByUsername(String username, int start, int limit); List<SessionInfo> getMySessionsInfo(User user, int start, int limit); List<SessionInfo> findInfosForPublicPool(); diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java index 3ecdfc18f..96f8e43ed 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java @@ -16,7 +16,6 @@ import org.ektorp.ViewResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -41,7 +40,6 @@ public class CouchDbAnswerRepository extends CouchDbCrudRepository<Answer> imple this.publisher = publisher; } - @CacheEvict("answers") @Override public int deleteByContentId(final String contentId) { try { @@ -178,31 +176,6 @@ public class CouchDbAnswerRepository extends CouchDbCrudRepository<Answer> imple return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); } - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - public void update(final Answer answer) { - try { - super.update(answer); - } catch (final UpdateConflictException e) { - logger.error("Could not update answer {}.", answer, e); - throw e; - } - } - - /* TODO: Only evict cache entry for the answer's session. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public void delete(final String answerId) { - try { - /* TODO: use id and rev instead of loading the answer */ - db.delete(get(answerId)); - dbLogger.log("delete", "type", "answer"); - } catch (final DbAccessException e) { - logger.error("Could not delete answer {}.", answerId, e); - throw e; - } - } - @Override public int countBySessionIdLectureVariant(final String sessionId) { return countBySessionIdVariant(sessionId, "lecture"); diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java index a71faeea7..6276c44fb 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java @@ -142,48 +142,6 @@ public class CouchDbCommentRepository extends CouchDbCrudRepository<Comment> imp return comments; } - /* TODO: Move to service layer. */ - @Override - public Comment save(final String sessionId, final Comment comment, final User user) { - /* TODO: This should be done on the service level. */ - comment.setSessionId(sessionId); - comment.setCreator(user.getUsername()); - comment.setRead(false); - if (comment.getTimestamp() == 0) { - comment.setTimestamp(System.currentTimeMillis()); - } - try { - db.create(comment); - - return comment; - } catch (final IllegalArgumentException e) { - logger.error("Could not save comment {}.", comment, e); - } - - return null; - } - - /* TODO: Move to service layer. */ - @Override - public void markInterposedQuestionAsRead(final Comment comment) { - try { - comment.setRead(true); - db.update(comment); - } catch (final UpdateConflictException e) { - logger.error("Could not mark comment as read {}.", comment.getId(), e); - } - } - - @Override - public void delete(final Comment comment) { - try { - db.delete(comment.getId(), comment.getRevision()); - dbLogger.log("delete", "type", "comment"); - } catch (final UpdateConflictException e) { - logger.error("Could not delete comment {}.", comment.getId(), e); - } - } - @Override public int deleteBySessionId(final String sessionId) { final ViewResult result = db.queryView(createQuery("by_sessionid").key(sessionId)); diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java index c09f433c7..ab9934fd1 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java @@ -7,18 +7,11 @@ import de.thm.arsnova.persistance.LogEntryRepository; import org.ektorp.BulkDeleteDocument; import org.ektorp.ComplexKey; import org.ektorp.CouchDbConnector; -import org.ektorp.DbAccessException; -import org.ektorp.DocumentNotFoundException; import org.ektorp.DocumentOperationResult; -import org.ektorp.UpdateConflictException; import org.ektorp.ViewResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; import java.util.ArrayList; import java.util.Arrays; @@ -38,7 +31,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp super(Content.class, db, "by_sessionid", createIfNotExists); } - @Cacheable("skillquestions") @Override public List<Content> findBySessionIdForUsers(final String sessionId) { final List<Content> contents = new ArrayList<>(); @@ -52,7 +44,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return contents; } - @Cacheable("skillquestions") @Override public List<Content> findBySessionIdForSpeaker(final String sessionId) { return findBySessionIdAndVariantAndActive(new Object[] {sessionId}, sessionId); @@ -67,66 +58,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return result.getSize(); } - /* TODO: Move to service layer. */ - @Caching(evict = {@CacheEvict(value = "skillquestions", key = "#sessionId"), - @CacheEvict(value = "lecturequestions", key = "#sessionId", condition = "#content.getQuestionVariant().equals('lecture')"), - @CacheEvict(value = "preparationquestions", key = "#sessionId", condition = "#content.getQuestionVariant().equals('preparation')"), - @CacheEvict(value = "flashcardquestions", key = "#sessionId", condition = "#content.getQuestionVariant().equals('flashcard')") }, - put = {@CachePut(value = "questions", key = "#content.id")}) - @Override - public Content save(final String sessionId, final Content content) { - /* TODO: This should be done on the service level. */ - content.setSessionId(sessionId); - try { - db.create(content); - - return content; - } catch (final IllegalArgumentException e) { - logger.error("Could not save content {}.", content, e); - } - - return null; - } - - /* TODO: Move to service layer. */ - /* TODO: Only evict cache entry for the content's session. This requires some refactoring. */ - @Caching(evict = {@CacheEvict(value = "skillquestions", allEntries = true), - @CacheEvict(value = "lecturequestions", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"), - @CacheEvict(value = "preparationquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"), - @CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") }, - put = {@CachePut(value = "questions", key = "#content.id")}) - @Override - public void update(final Content content) { - try { - /* TODO: This should be done on the service level. Make sure that - * sessionId is valid before so the content does not need to be retrieved. */ - final Content oldContent = get(content.getId()); - content.setId(oldContent.getId()); - content.setRevision(oldContent.getRevision()); - content.updateRoundManagementState(); - super.update(content); - } catch (final UpdateConflictException e) { - logger.error("Could not update content {}.", content, e); - } - } - - /* TODO: Move to service layer. */ - @Cacheable("questions") - @Override - public Content findOne(final String id) { - try { - final Content content = get(id); - content.updateRoundManagementState(); - //content.setSessionKeyword(sessionRepository.getSessionFromId(content.getSessionId()).getKeyword()); - - return content; - } catch (final DocumentNotFoundException e) { - logger.error("Could not get question {}.", id, e); - } - - return null; - } - @Override public List<String> findIdsBySessionId(final String sessionId) { return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active") @@ -197,7 +128,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return collectUnansweredQuestionIdsByPiRound(findBySessionIdOnlyPreparationVariantAndActive(sessionId), answeredQuestions); } - @Cacheable("lecturequestions") @Override public List<Content> findBySessionIdOnlyLectureVariantAndActive(final String sessionId) { return findBySessionIdAndVariantAndActive(sessionId, "lecture", true); @@ -208,7 +138,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return findBySessionIdAndVariantAndActive(sessionId, "lecture"); } - @Cacheable("flashcardquestions") @Override public List<Content> findBySessionIdOnlyFlashcardVariantAndActive(final String sessionId) { return findBySessionIdAndVariantAndActive(sessionId, "flashcard", true); @@ -219,7 +148,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return findBySessionIdAndVariantAndActive(sessionId, "flashcard"); } - @Cacheable("preparationquestions") @Override public List<Content> findBySessionIdOnlyPreparationVariantAndActive(final String sessionId) { return findBySessionIdAndVariantAndActive(sessionId, "preparation", true); @@ -320,71 +248,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return ids; } - /* TODO: Move to service layer. */ - @Override - public List<Content> publishAllQuestions(final String sessionId, final boolean publish) { - final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active") - .startKey(ComplexKey.of(sessionId)) - .endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject())), - Content.class); - /* FIXME: caching */ - publishQuestions(sessionId, publish, contents); - - return contents; - } - - /* TODO: Move to service layer. */ - @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#sessionId"), - @CacheEvict(value = "lecturequestions", key = "#sessionId"), - @CacheEvict(value = "preparationquestions", key = "#sessionId"), - @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) - @Override - public void publishQuestions(final String sessionId, final boolean publish, final List<Content> contents) { - for (final Content content : contents) { - content.setActive(publish); - } - try { - db.executeBulk(contents); - } catch (final DbAccessException e) { - logger.error("Could not bulk publish all contents.", e); - } - } - - /* TODO: Move to service layer. */ - @Override - public List<Content> setVotingAdmissionForAllQuestions(final String sessionId, final boolean disableVoting) { - final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active") - .startKey(ComplexKey.of(sessionId)) - .endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject())) - .includeDocs(true), - Content.class); - /* FIXME: caching */ - setVotingAdmissions(sessionId, disableVoting, contents); - - return contents; - } - - @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#sessionId"), - @CacheEvict(value = "lecturequestions", key = "#sessionId"), - @CacheEvict(value = "preparationquestions", key = "#sessionId"), - @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) - @Override - public void setVotingAdmissions(final String sessionId, final boolean disableVoting, final List<Content> contents) { - for (final Content q : contents) { - if (!"flashcard".equals(q.getQuestionType())) { - q.setVotingDisabled(disableVoting); - } - } - - try { - db.executeBulk(contents); - } catch (final DbAccessException e) { - logger.error("Could not bulk set voting admission for all contents.", e); - } - } - /* TODO: remove if this method is no longer used */ @Override public List<String> findIdsBySessionIdAndVariantAndSubject(final String sessionId, final String questionVariant, final String subject) { @@ -416,23 +279,4 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return new ArrayList<>(uniqueSubjects); } - - /* TODO: Move to service layer. */ - @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#sessionId"), - @CacheEvict(value = "lecturequestions", key = "#sessionId"), - @CacheEvict(value = "preparationquestions", key = "#sessionId"), - @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) - @Override - public void resetQuestionsRoundState(final String sessionId, final List<Content> contents) { - for (final Content q : contents) { - q.setSessionId(sessionId); - q.resetQuestionState(); - } - try { - db.executeBulk(contents); - } catch (final DbAccessException e) { - logger.error("Could not bulk reset all contents round state.", e); - } - } } diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java index 1b7e36990..499013a87 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java @@ -7,8 +7,6 @@ import org.ektorp.DbAccessException; import org.ektorp.support.CouchDbRepositorySupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; import java.util.List; @@ -20,7 +18,6 @@ public class CouchDbMotdListRepository extends CouchDbRepositorySupport<MotdList } @Override - @Cacheable(cacheNames = "motdlist", key = "#p0") public MotdList findByUsername(final String username) { final List<MotdList> motdListList = queryView("by_username", username); return motdListList.isEmpty() ? new MotdList() : motdListList.get(0); @@ -28,7 +25,6 @@ public class CouchDbMotdListRepository extends CouchDbRepositorySupport<MotdList /* TODO: Move to service layer. */ @Override - @CachePut(cacheNames = "motdlist", key = "#p0.username") public MotdList save(final MotdList motdlist) { try { if (motdlist.getId() != null) { diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java index 8c2de65f8..7a00e2886 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java @@ -19,13 +19,9 @@ package de.thm.arsnova.persistance.couchdb; import de.thm.arsnova.entities.Motd; import de.thm.arsnova.persistance.MotdRepository; -import de.thm.arsnova.services.SessionService; import org.ektorp.CouchDbConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import java.util.ArrayList; import java.util.List; @@ -33,9 +29,6 @@ import java.util.List; public class CouchDbMotdRepository extends CouchDbCrudRepository<Motd> implements MotdRepository { private static final Logger logger = LoggerFactory.getLogger(CouchDbMotdRepository.class); - @Autowired - private SessionService sessionService; - public CouchDbMotdRepository(final CouchDbConnector db, final boolean createIfNotExists) { super(Motd.class, db, "by_sessionkey", createIfNotExists); } @@ -46,19 +39,16 @@ public class CouchDbMotdRepository extends CouchDbCrudRepository<Motd> implement } @Override - @Cacheable(cacheNames = "motds", key = "'all'") public List<Motd> findGlobalForAll() { return find("by_audience_for_global", "all"); } @Override - @Cacheable(cacheNames = "motds", key = "'loggedIn'") public List<Motd> findGlobalForLoggedIn() { return find("by_audience_for_global", "loggedIn"); } @Override - @Cacheable(cacheNames = "motds", key = "'tutors'") public List<Motd> findGlobalForTutors() { final List<Motd> union = new ArrayList<>(); union.addAll(find("by_audience_for_global", "loggedIn")); @@ -68,7 +58,6 @@ public class CouchDbMotdRepository extends CouchDbCrudRepository<Motd> implement } @Override - @Cacheable(cacheNames = "motds", key = "'students'") public List<Motd> findForStudents() { final List<Motd> union = new ArrayList<>(); union.addAll(find("by_audience_for_global", "loggedIn")); @@ -78,7 +67,6 @@ public class CouchDbMotdRepository extends CouchDbCrudRepository<Motd> implement } @Override - @Cacheable(cacheNames = "motds", key = "('session').concat(#p0)") public List<Motd> findBySessionKey(final String sessionkey) { return find("by_sessionkey", sessionkey); } @@ -93,29 +81,4 @@ public class CouchDbMotdRepository extends CouchDbCrudRepository<Motd> implement return motd.get(0); } - - @Override - @CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)") - public Motd save(final Motd motd) { - final String id = motd.getId(); - final String rev = motd.getRevision(); - - if (null != id) { - Motd oldMotd = get(id); - motd.setMotdkey(oldMotd.getMotdkey()); - update(motd); - } else { - motd.setMotdkey(sessionService.generateKey()); - add(motd); - } - - return motd; - } - - /* TODO: Redundant -> remove. Move cache handling to service layer. */ - @Override - @CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)") - public void delete(final Motd motd) { - db.delete(motd); - } } diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java index a21ecbab9..91be570e1 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java @@ -29,7 +29,6 @@ import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.persistance.LogEntryRepository; import de.thm.arsnova.persistance.MotdRepository; import de.thm.arsnova.persistance.SessionRepository; -import de.thm.arsnova.services.SessionService; import org.ektorp.ComplexKey; import org.ektorp.CouchDbConnector; import org.ektorp.DocumentNotFoundException; @@ -39,10 +38,7 @@ import org.ektorp.ViewResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; import java.io.IOException; import java.util.AbstractMap; @@ -57,9 +53,6 @@ import java.util.stream.Collectors; public class CouchDbSessionRepository extends CouchDbCrudRepository<Session> implements SessionRepository { private static final Logger logger = LoggerFactory.getLogger(CouchDbSessionRepository.class); - @Autowired - private SessionService sessionService; - @Autowired private LogEntryRepository dbLogger; @@ -78,37 +71,6 @@ public class CouchDbSessionRepository extends CouchDbCrudRepository<Session> imp return !session.isEmpty() ? session.get(0) : null; } - /* TODO: Redundant -> remove. Move cache handling to service layer. */ - @Override - @Cacheable("sessions") - public Session findOne(final String sessionId) { - return get(sessionId); - } - - /* TODO: Move to service layer. */ - @Override - @Caching(evict = @CacheEvict(cacheNames = "sessions", key = "#result.keyword")) - public Session save(final User user, final Session session) { - session.setKeyword(sessionService.generateKey()); - session.setCreator(user.getUsername()); - session.setActive(true); - session.setFeedbackLock(false); - - try { - db.create(session); - } catch (final IllegalArgumentException e) { - logger.error("Could not save session to database.", e); - } - - return session.getId() != null ? session : null; - } - - /* TODO: Move to service layer. */ - @Override - public boolean sessionKeyAvailable(final String keyword) { - return findByKeyword(keyword) == null; - } - /* TODO: Move to service layer. */ private String getSessionKeyword(final String internalSessionId) throws IOException { final Session session = get(internalSessionId); @@ -121,26 +83,6 @@ public class CouchDbSessionRepository extends CouchDbCrudRepository<Session> imp return session.getKeyword(); } - /* TODO: Move to service layer. */ - @Override - @CachePut(value = "sessions") - public Session updateSessionOwnerActivity(final Session session) { - try { - /* Do not clutter CouchDB. Only update once every 3 hours. */ - if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) { - return session; - } - - session.setLastOwnerActivity(System.currentTimeMillis()); - update(session); - - return session; - } catch (final UpdateConflictException e) { - logger.error("Failed to update lastOwnerActivity for session {}.", session, e); - return session; - } - } - @Override public List<Session> findVisitedByUsername(final String username, final int start, final int limit) { final int qSkip = start > 0 ? start : -1; @@ -216,73 +158,21 @@ public class CouchDbSessionRepository extends CouchDbCrudRepository<Session> imp ComplexKey.of(courses.stream().map(Course::getId).collect(Collectors.toList()))); } - /* TODO: Redundant -> remove. Move cache handling to service layer. */ - @Override - @CachePut(value = "sessions") - public void update(final Session session) { - try { - super.update(session); - } catch (final UpdateConflictException e) { - logger.error("Could not update session {}.", session, e); - } - } - - /* TODO: Move to service layer. */ - @Override - @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") }) - public Session changeSessionCreator(final Session session, final String newCreator) { - final Session s = get(session.getId()); - s.setCreator(newCreator); - try { - update(s); - } catch (final UpdateConflictException e) { - logger.error("Could not update creator for session {}.", session, e); - } - - return s; - } - @Override - @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") }) - public int[] deleteSession(final Session session) { - /* FIXME: not yet migrated - move to service layer */ - throw new UnsupportedOperationException(); -// final int[] count = new int[] {0, 0}; -// try { -// count = deleteBySessionId(session); -// remove(session); -// logger.debug("Deleted session document {} and related data.", session.getId()); -// dbLogger.log("delete", "type", "session", "id", session.getId()); -// } catch (final Exception e) { -// /* TODO: improve error handling */ -// logger.error("Could not delete session {}.", session, e); -// } -// -// return count; - } - - @Override - public int[] deleteInactiveGuestSessions(final long lastActivityBefore) { + public List<Session> findInactiveGuestSessionsMetadata(final long lastActivityBefore) { final ViewResult result = db.queryView( createQuery("by_lastactivity_for_guests").endKey(lastActivityBefore)); final int[] count = new int[3]; + List<Session> sessions = new ArrayList<>(); for (final ViewResult.Row row : result.getRows()) { final Session s = new Session(); s.setId(row.getId()); s.setRevision(row.getValueAsNode().get("_rev").asText()); - final int[] qaCount = deleteSession(s); - count[1] += qaCount[0]; - count[2] += qaCount[1]; - } - - if (!result.isEmpty()) { - logger.info("Deleted {} inactive guest sessions.", result.getSize()); - dbLogger.log("cleanup", "type", "session", "sessionCount", result.getSize(), "questionCount", count[1], "answerCount", count[2]); + sessions.add(s); } - count[0] = result.getSize(); - return count; + return sessions; } /* TODO: Move to service layer. */ diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java index 8796f056e..e0c5f4927 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java @@ -8,14 +8,12 @@ import org.ektorp.ComplexKey; import org.ektorp.CouchDbConnector; import org.ektorp.ViewResult; import org.ektorp.support.CouchDbRepositorySupport; -import org.springframework.cache.annotation.Cacheable; public class CouchDbSessionStatisticsRepository extends CouchDbRepositorySupport implements SessionStatisticsRepository { public CouchDbSessionStatisticsRepository(final CouchDbConnector db, final boolean createIfNotExists) { super(Object.class, db, "learning_progress", createIfNotExists); } - @Cacheable("learningprogress") @Override public Score getLearningProgress(final Session session) { final ViewResult maximumValueResult = db.queryView(createQuery("maximum_value_of_question") diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java index f3b27feff..3506f7da4 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java @@ -8,7 +8,6 @@ import org.ektorp.ViewResult; import org.ektorp.support.CouchDbRepositorySupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cache.annotation.Cacheable; import java.util.HashSet; import java.util.Set; @@ -20,7 +19,6 @@ public class CouchDbStatisticsRepository extends CouchDbRepositorySupport implem super(Object.class, db, "statistics", createIfNotExists); } - @Cacheable("statistics") @Override public Statistics getStatistics() { final Statistics stats = new Statistics(); diff --git a/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java b/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java index 01d71afe6..1a1428994 100644 --- a/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java @@ -53,7 +53,14 @@ public class CommentServiceImpl extends EntityService<Comment> implements Commen @PreAuthorize("isAuthenticated()") public boolean save(final Comment comment) { final Session session = sessionRepository.findByKeyword(comment.getSessionId()); - final Comment result = commentRepository.save(session.getId(), comment, userService.getCurrentUser()); + final User user = userService.getCurrentUser(); + comment.setSessionId(session.getId()); + comment.setCreator(user.getUsername()); + comment.setRead(false); + if (comment.getTimestamp() == 0) { + comment.setTimestamp(System.currentTimeMillis()); + } + final Comment result = super.create(comment); if (null != result) { final NewCommentEvent event = new NewCommentEvent(this, session, result); @@ -151,7 +158,8 @@ public class CommentServiceImpl extends EntityService<Comment> implements Commen throw new UnauthorizedException(); } if (session.isCreator(user)) { - commentRepository.markInterposedQuestionAsRead(comment); + comment.setRead(true); + save(comment); } return comment; } diff --git a/src/main/java/de/thm/arsnova/services/ContentService.java b/src/main/java/de/thm/arsnova/services/ContentService.java index c2735488f..0c3f13037 100644 --- a/src/main/java/de/thm/arsnova/services/ContentService.java +++ b/src/main/java/de/thm/arsnova/services/ContentService.java @@ -72,9 +72,9 @@ public interface ContentService { int countTotalAnswersByQuestionId(String questionId); - Content update(Content content); + Content save(final String sessionId, final Content content); - Content update(Content content, User user); + Content update(Content content); void deleteAnswers(String questionId); diff --git a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java index 1318d12f9..d7f9ef655 100644 --- a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java @@ -32,11 +32,14 @@ import de.thm.arsnova.persistance.AnswerRepository; import de.thm.arsnova.persistance.ContentRepository; import de.thm.arsnova.persistance.SessionRepository; import org.ektorp.DbAccessException; +import org.ektorp.DocumentNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -128,8 +131,90 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten } } + @Cacheable("questions") + @Override + public Content get(final String id) { + try { + final Content content = super.get(id); + if (!"freetext".equals(content.getQuestionType()) && 0 == content.getPiRound()) { + /* needed for legacy questions whose piRound property has not been set */ + content.setPiRound(1); + } + content.updateRoundManagementState(); + //content.setSessionKeyword(sessionRepository.getSessionFromId(content.getSessionId()).getKeyword()); + + return content; + } catch (final DocumentNotFoundException e) { + logger.error("Could not get question {}.", id, e); + } + + return null; + } + + @Override + @Caching(evict = {@CacheEvict(value = "skillquestions", key = "#sessionId"), + @CacheEvict(value = "lecturequestions", key = "#sessionId", condition = "#content.getQuestionVariant().equals('lecture')"), + @CacheEvict(value = "preparationquestions", key = "#sessionId", condition = "#content.getQuestionVariant().equals('preparation')"), + @CacheEvict(value = "flashcardquestions", key = "#sessionId", condition = "#content.getQuestionVariant().equals('flashcard')") }, + put = {@CachePut(value = "questions", key = "#content.id")}) + public Content save(final String sessionId, final Content content) { + content.setSessionId(sessionId); + try { + contentRepository.save(content); + + return content; + } catch (final IllegalArgumentException e) { + logger.error("Could not save content {}.", content, e); + } + + return null; + } + @Override @PreAuthorize("isAuthenticated()") + @Caching(evict = { + @CacheEvict(value = "skillquestions", allEntries = true), + @CacheEvict(value = "lecturequestions", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"), + @CacheEvict(value = "preparationquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"), + @CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") }, + put = {@CachePut(value = "questions", key = "#content.id")}) + public Content update(final Content content) { + final User user = userService.getCurrentUser(); + final Content oldContent = contentRepository.findOne(content.getId()); + if (null == oldContent) { + throw new NotFoundException(); + } + + final Session session = sessionRepository.findOne(content.getSessionId()); + if (user == null || session == null || !session.isCreator(user)) { + throw new UnauthorizedException(); + } + + if ("freetext".equals(content.getQuestionType())) { + content.setPiRound(0); + } else if (content.getPiRound() < 1 || content.getPiRound() > 2) { + content.setPiRound(oldContent.getPiRound() > 0 ? oldContent.getPiRound() : 1); + } + + content.setId(oldContent.getId()); + content.setRevision(oldContent.getRevision()); + content.updateRoundManagementState(); + contentRepository.save(content); + + if (!oldContent.isActive() && content.isActive()) { + final UnlockQuestionEvent event = new UnlockQuestionEvent(this, session, content); + this.publisher.publishEvent(event); + } else if (oldContent.isActive() && !content.isActive()) { + final LockQuestionEvent event = new LockQuestionEvent(this, session, content); + this.publisher.publishEvent(event); + } + return content; + } + + /* FIXME: caching */ + @Override + @PreAuthorize("isAuthenticated()") + //@Cacheable("skillquestions") public List<Content> getBySessionKey(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); @@ -171,7 +256,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten } } - final Content result = contentRepository.save(session.getId(), content); + final Content result = save(session.getId(), content); final NewQuestionEvent event = new NewQuestionEvent(this, session, result); this.publisher.publishEvent(event); @@ -179,25 +264,11 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten return result; } - @Override - @PreAuthorize("isAuthenticated()") - public Content get(final String id) { - final Content result = contentRepository.findOne(id); - if (result == null) { - return null; - } - if (!"freetext".equals(result.getQuestionType()) && 0 == result.getPiRound()) { - /* needed for legacy questions whose piRound property has not been set */ - result.setPiRound(1); - } - - return result; - } - /* TODO: Only evict cache entry for the content's session. This requires some refactoring. */ @Override @PreAuthorize("isAuthenticated() and hasPermission(#contentId, 'content', 'owner')") @Caching(evict = { + @CacheEvict("answers"), @CacheEvict(value = "questions", key = "#contentId"), @CacheEvict(value = "skillquestions", allEntries = true), @CacheEvict(value = "lecturequestions", allEntries = true /*, condition = "#content.getQuestionVariant().equals('lecture')"*/), @@ -293,7 +364,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten content.setPiRoundEndTime(0); content.setVotingDisabled(true); content.updateRoundManagementState(); - update(content, user); + update(content); this.publisher.publishEvent(new PiRoundEndEvent(this, session, content)); } @@ -356,6 +427,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") + @CacheEvict("answers") public void resetPiRoundState(final String questionId) { final Content content = contentRepository.findOne(questionId); final Session session = sessionRepository.findOne(content.getSessionId()); @@ -384,7 +456,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten content.setActive(true); update(content); } else { - contentRepository.update(content); + update(content); } ArsnovaEvent event; if (disableVoting) { @@ -397,13 +469,22 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") + @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#sessionId"), + @CacheEvict(value = "lecturequestions", key = "#sessionId"), + @CacheEvict(value = "preparationquestions", key = "#sessionId"), + @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) public void setVotingAdmissions(final String sessionkey, final boolean disableVoting, List<Content> contents) { final User user = getCurrentUser(); final Session session = getSession(sessionkey); if (!session.isCreator(user)) { throw new UnauthorizedException(); } - contentRepository.setVotingAdmissions(session.getId(), disableVoting, contents); + for (final Content q : contents) { + if (!"flashcard".equals(q.getQuestionType())) { + q.setVotingDisabled(disableVoting); + } + } ArsnovaEvent event; if (disableVoting) { event = new LockVotesEvent(this, session, contents); @@ -421,14 +502,8 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten if (!session.isCreator(user)) { throw new UnauthorizedException(); } - final List<Content> contents = contentRepository.setVotingAdmissionForAllQuestions(session.getId(), disableVoting); - ArsnovaEvent event; - if (disableVoting) { - event = new LockVotesEvent(this, session, contents); - } else { - event = new UnlockVotesEvent(this, session, contents); - } - this.publisher.publishEvent(event); + final List<Content> contents = contentRepository.findBySessionId(session.getId()); + setVotingAdmissionForAllQuestions(session.getId(), disableVoting); } private Session getSessionWithAuthCheck(final String sessionKeyword) { @@ -445,7 +520,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten public void deleteAnswers(final String questionId) { final Content content = contentRepository.findOne(questionId); content.resetQuestionState(); - contentRepository.update(content); + update(content); answerRepository.deleteByContentId(content.getId()); } @@ -487,7 +562,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten final Session session = sessionRepository.findOne(answer.getSessionId()); if (session.isCreator(user)) { answer.setRead(true); - answerRepository.update(answer); + answerRepository.save(answer); } } @@ -599,7 +674,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten public List<Answer> getMyAnswersBySessionKey(final String sessionKey) { final Session session = getSession(sessionKey); // Load contents first because we are only interested in answers of the latest piRound. - final List<Content> contents = contentRepository.findBySessionIdForUsers(session.getId()); + final List<Content> contents = getBySessionKey(sessionKey); final Map<String, Content> questionIdToQuestion = new HashMap<>(); for (final Content content : contents) { questionIdToQuestion.put(content.getId(), content); @@ -636,48 +711,10 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") - public Content update(final Content content) { - final User user = userService.getCurrentUser(); - return update(content, user); - } - - @Override - @PreAuthorize("isAuthenticated()") - public Content update(final Content content, User user) { - final Content oldContent = contentRepository.findOne(content.getId()); - if (null == oldContent) { - throw new NotFoundException(); - } - - final Session session = sessionRepository.findOne(content.getSessionId()); - if (user == null || session == null || !session.isCreator(user)) { - throw new UnauthorizedException(); - } - - if ("freetext".equals(content.getQuestionType())) { - content.setPiRound(0); - } else if (content.getPiRound() < 1 || content.getPiRound() > 2) { - content.setPiRound(oldContent.getPiRound() > 0 ? oldContent.getPiRound() : 1); - } - - contentRepository.update(content); - - if (!oldContent.isActive() && content.isActive()) { - final UnlockQuestionEvent event = new UnlockQuestionEvent(this, session, content); - this.publisher.publishEvent(event); - } else if (oldContent.isActive() && !content.isActive()) { - final LockQuestionEvent event = new LockQuestionEvent(this, session, content); - this.publisher.publishEvent(event); - } - return content; - } - - @Override - @PreAuthorize("isAuthenticated()") - @CacheEvict(value = "answers", key = "#content") - public Answer saveAnswer(final String questionId, final de.thm.arsnova.entities.transport.Answer answer) { + @CacheEvict(value = "answers", key = "#contentId") + public Answer saveAnswer(final String contentId, final de.thm.arsnova.entities.transport.Answer answer) { final User user = getCurrentUser(); - final Content content = get(questionId); + final Content content = get(contentId); if (content == null) { throw new NotFoundException(); } @@ -707,6 +744,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answers", allEntries = true) public Answer updateAnswer(final Answer answer) { final User user = userService.getCurrentUser(); final Answer realAnswer = this.getMyAnswer(answer.getQuestionId()); @@ -723,7 +761,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten answer.setUser(user.getUsername()); answer.setQuestionId(content.getId()); answer.setSessionId(session.getId()); - answerRepository.update(realAnswer); + answerRepository.save(realAnswer); this.publisher.publishEvent(new NewAnswerEvent(this, session, answer, user, content)); return answer; @@ -731,6 +769,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answers", allEntries = true) public void deleteAnswer(final String questionId, final String answerId) { final Content content = contentRepository.findOne(questionId); if (content == null) { @@ -746,8 +785,10 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten this.publisher.publishEvent(new DeleteAnswerEvent(this, session, content)); } + /* FIXME: caching */ @Override @PreAuthorize("isAuthenticated()") + //@Cacheable("lecturequestions") public List<Content> getLectureQuestions(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); @@ -758,8 +799,10 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten } } + /* FIXME: caching */ @Override @PreAuthorize("isAuthenticated()") + //@Cacheable("flashcardquestions") public List<Content> getFlashcards(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); @@ -770,8 +813,10 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten } } + /* FIXME: caching */ @Override @PreAuthorize("isAuthenticated()") + //@Cacheable("preparationquestions") public List<Content> getPreparationQuestions(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); @@ -904,30 +949,33 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") public void publishAll(final String sessionkey, final boolean publish) { + /* TODO: resolve redundancies */ final User user = getCurrentUser(); final Session session = getSession(sessionkey); if (!session.isCreator(user)) { throw new UnauthorizedException(); } - final List<Content> contents = contentRepository.publishAllQuestions(session.getId(), publish); - ArsnovaEvent event; - if (publish) { - event = new UnlockQuestionsEvent(this, session, contents); - } else { - event = new LockQuestionsEvent(this, session, contents); - } - this.publisher.publishEvent(event); + final List<Content> contents = contentRepository.findBySessionId(session.getId()); + publishQuestions(sessionkey, publish, contents); } @Override @PreAuthorize("isAuthenticated()") + @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#sessionId"), + @CacheEvict(value = "lecturequestions", key = "#sessionId"), + @CacheEvict(value = "preparationquestions", key = "#sessionId"), + @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) public void publishQuestions(final String sessionkey, final boolean publish, List<Content> contents) { final User user = getCurrentUser(); final Session session = getSession(sessionkey); if (!session.isCreator(user)) { throw new UnauthorizedException(); } - contentRepository.publishQuestions(session.getId(), publish, contents); + for (final Content content : contents) { + content.setActive(publish); + } + contentRepository.save(contents); ArsnovaEvent event; if (publish) { event = new UnlockQuestionsEvent(this, session, contents); @@ -948,7 +996,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten } final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId()); - contentRepository.resetQuestionsRoundState(session.getId(), contents); + resetContentsRoundState(session.getId(), contents); final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); answerRepository.deleteAllAnswersForQuestions(contentIds); @@ -963,7 +1011,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten final Session session = getSession(sessionkey); final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId(), "preparation"); - contentRepository.resetQuestionsRoundState(session.getId(), contents); + resetContentsRoundState(session.getId(), contents); final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); answerRepository.deleteAllAnswersForQuestions(contentIds); @@ -978,13 +1026,28 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten final Session session = getSession(sessionkey); final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId(), "lecture"); - contentRepository.resetQuestionsRoundState(session.getId(), contents); + resetContentsRoundState(session.getId(), contents); final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); answerRepository.deleteAllAnswersForQuestions(contentIds); this.publisher.publishEvent(new DeleteAllLectureAnswersEvent(this, session)); } + @Caching(evict = { + @CacheEvict(value = "contents", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#sessionId"), + @CacheEvict(value = "lecturequestions", key = "#sessionId"), + @CacheEvict(value = "preparationquestions", key = "#sessionId"), + @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) + private void resetContentsRoundState(final String sessionId, final List<Content> contents) { + for (final Content q : contents) { + /* TODO: Check if setting the sessionId is necessary. */ + q.setSessionId(sessionId); + q.resetQuestionState(); + } + contentRepository.save(contents); + } + @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; diff --git a/src/main/java/de/thm/arsnova/services/MotdService.java b/src/main/java/de/thm/arsnova/services/MotdService.java index c010663a9..177e721d5 100644 --- a/src/main/java/de/thm/arsnova/services/MotdService.java +++ b/src/main/java/de/thm/arsnova/services/MotdService.java @@ -33,7 +33,9 @@ public interface MotdService { List<Motd> getAllSessionMotds(final String sessionkey); - List<Motd> getCurrentMotds(final Date clientdate, final String audience, final String sessionkey); + List<Motd> getCurrentMotds(final Date clientdate, final String audience); + + List<Motd> getCurrentSessionMotds(final Date clientdate, final String sessionkey); List<Motd> filterMotdsByDate(List<Motd> list, Date clientdate); diff --git a/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java b/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java index 1400daf56..86c0382fb 100644 --- a/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java @@ -25,6 +25,9 @@ import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.persistance.MotdListRepository; import de.thm.arsnova.persistance.MotdRepository; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -79,16 +82,24 @@ public class MotdServiceImpl extends EntityService<Motd> implements MotdService } @Override - public List<Motd> getCurrentMotds(final Date clientdate, final String audience, final String sessionkey) { + @Cacheable(cacheNames = "motds", key = "('session').concat(#sessionkey)") + public List<Motd> getCurrentSessionMotds(final Date clientdate, final String sessionkey) { + final List<Motd> motds = motdRepository.findBySessionKey(sessionkey); + return filterMotdsByDate(motds, clientdate); + } + + @Override + @Cacheable(cacheNames = "motds", key = "#audience") + public List<Motd> getCurrentMotds(final Date clientdate, final String audience) { final List<Motd> motds; switch (audience) { case "all": motds = motdRepository.findGlobalForAll(); break; case "loggedIn": motds = motdRepository.findGlobalForLoggedIn(); break; case "students": motds = motdRepository.findForStudents(); break; case "tutors": motds = motdRepository.findGlobalForTutors(); break; - case "session": motds = motdRepository.findBySessionKey(sessionkey); break; - default: motds = motdRepository.findGlobalForAll(); break; + default: throw new IllegalArgumentException("Invalid audience."); } + return filterMotdsByDate(motds, clientdate); } @@ -135,7 +146,6 @@ public class MotdServiceImpl extends EntityService<Motd> implements MotdService Session session = sessionService.getByKey(sessionkey); motd.setSessionId(session.getId()); - return createOrUpdateMotd(motd); } @@ -151,6 +161,7 @@ public class MotdServiceImpl extends EntityService<Motd> implements MotdService return createOrUpdateMotd(motd); } + @CacheEvict(cacheNames = "motds", key = "#motd.audience.concat(#motd.sessionkey)") private Motd createOrUpdateMotd(final Motd motd) { if (motd.getMotdkey() != null) { Motd oldMotd = motdRepository.findByKey(motd.getMotdkey()); @@ -160,11 +171,20 @@ public class MotdServiceImpl extends EntityService<Motd> implements MotdService } } + if (null != motd.getId()) { + Motd oldMotd = get(motd.getId()); + motd.setMotdkey(oldMotd.getMotdkey()); + } else { + motd.setMotdkey(sessionService.generateKey()); + } + save(motd); + return motdRepository.save(motd); } @Override @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')") + @CacheEvict(cacheNames = "motds", key = "#motd.audience.concat(#motd.sessionkey)") public void delete(Motd motd) { motdRepository.delete(motd); } @@ -177,6 +197,7 @@ public class MotdServiceImpl extends EntityService<Motd> implements MotdService @Override @PreAuthorize("isAuthenticated()") + @Cacheable(cacheNames = "motdlist", key = "#username") public MotdList getMotdListByUsername(final String username) { final User user = userService.getCurrentUser(); if (username.equals(user.getUsername()) && !"guest".equals(user.getType())) { @@ -187,6 +208,7 @@ public class MotdServiceImpl extends EntityService<Motd> implements MotdService @Override @PreAuthorize("isAuthenticated()") + @CachePut(cacheNames = "motdlist", key = "#motdList.username") public MotdList saveMotdList(MotdList motdList) { final User user = userService.getCurrentUser(); if (user.getUsername().equals(motdList.getUsername())) { diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index 1aaba3134..58f687791 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -66,7 +66,7 @@ public interface SessionService { Session updateInternal(Session session, User user); - void delete(String sessionkey); + int[] deleteCascading(Session session); ScoreStatistics getLearningProgress(String sessionkey, String type, String questionVariant); diff --git a/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java b/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java index bfc3532d8..dd0194392 100644 --- a/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java @@ -17,6 +17,10 @@ */ package de.thm.arsnova.services; +import de.thm.arsnova.persistance.AnswerRepository; +import de.thm.arsnova.persistance.CommentRepository; +import de.thm.arsnova.persistance.ContentRepository; +import de.thm.arsnova.persistance.LogEntryRepository; import de.thm.arsnova.util.ImageUtils; import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.model.Course; @@ -42,11 +46,15 @@ import de.thm.arsnova.exceptions.PayloadTooLargeException; import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.persistance.SessionRepository; import de.thm.arsnova.persistance.VisitedSessionRepository; +import org.ektorp.UpdateConflictException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Caching; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; @@ -68,8 +76,16 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio private static final Logger logger = LoggerFactory.getLogger(SessionServiceImpl.class); + private LogEntryRepository dbLogger; + private SessionRepository sessionRepository; + private ContentRepository contentRepository; + + private AnswerRepository answerRepository; + + private CommentRepository commentRepository; + private VisitedSessionRepository visitedSessionRepository; private UserService userService; @@ -92,7 +108,11 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio public SessionServiceImpl( SessionRepository repository, + ContentRepository contentRepository, + AnswerRepository answerRepository, + CommentRepository commentRepository, VisitedSessionRepository visitedSessionRepository, + LogEntryRepository dbLogger, UserService userService, FeedbackService feedbackService, ScoreCalculatorFactory scoreCalculatorFactory, @@ -100,7 +120,11 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio @Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { super(Session.class, repository, jackson2HttpMessageConverter.getObjectMapper()); this.sessionRepository = repository; + this.contentRepository = contentRepository; + this.answerRepository = answerRepository; + this.commentRepository = commentRepository; this.visitedSessionRepository = visitedSessionRepository; + this.dbLogger = dbLogger; this.userService = userService; this.feedbackService = feedbackService; this.scoreCalculatorFactory = scoreCalculatorFactory; @@ -154,7 +178,23 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio logger.info("Delete inactive sessions."); long unixTime = System.currentTimeMillis(); long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L; - sessionRepository.deleteInactiveGuestSessions(lastActivityBefore); + int totalCount[] = new int[] {0, 0, 0}; + List<Session> inactiveSessions = sessionRepository.findInactiveGuestSessionsMetadata(lastActivityBefore); + for (Session session : inactiveSessions) { + int[] count = deleteCascading(session); + totalCount[0] += count[0]; + totalCount[1] += count[1]; + totalCount[2] += count[2]; + } + + if (!inactiveSessions.isEmpty()) { + logger.info("Deleted {} inactive guest sessions.", inactiveSessions.size()); + dbLogger.log("cleanup", "type", "session", + "sessionCount", inactiveSessions.size(), + "questionCount", totalCount[1], + "answerCount", totalCount[2], + "commentCount", totalCount[3]); + } } } @@ -183,7 +223,7 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio userService.addUserToSessionBySocketId(socketId, keyword); if (session.getCreator().equals(user.getUsername())) { - sessionRepository.updateSessionOwnerActivity(session); + updateSessionOwnerActivity(session); } sessionRepository.registerAsOnlineUser(user, session); @@ -197,6 +237,24 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio return session; } + @CachePut(value = "sessions") + private Session updateSessionOwnerActivity(final Session session) { + try { + /* Do not clutter CouchDB. Only update once every 3 hours. */ + if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) { + return session; + } + + session.setLastOwnerActivity(System.currentTimeMillis()); + save(session); + + return session; + } catch (final UpdateConflictException e) { + logger.error("Failed to update lastOwnerActivity for session {}.", session, e); + return session; + } + } + @Override @PreAuthorize("isAuthenticated()") public Session getByKey(final String keyword) { @@ -286,6 +344,7 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio @Override @PreAuthorize("isAuthenticated()") + @Caching(evict = @CacheEvict(cacheNames = "sessions", key = "#result.keyword")) public Session save(final Session session) { if (connectorClient != null && session.getCourseId() != null) { if (!connectorClient.getMembership( @@ -310,14 +369,19 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio sf.setPi(true); session.setFeatures(sf); - final Session result = sessionRepository.save(userService.getCurrentUser(), session); + session.setKeyword(generateKey()); + session.setCreator(userService.getCurrentUser().getUsername()); + session.setActive(true); + session.setFeedbackLock(false); + + final Session result = save(session); this.publisher.publishEvent(new NewSessionEvent(this, result)); return result; } @Override public boolean isKeyAvailable(final String keyword) { - return sessionRepository.sessionKeyAvailable(keyword); + return getByKey(keyword) == null; } @Override @@ -356,13 +420,14 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio } session.setActive(lock); this.publisher.publishEvent(new StatusSessionEvent(this, session)); - sessionRepository.update(session); + sessionRepository.save(session); return session; } @Override @PreAuthorize("isAuthenticated() and hasPermission(#session, 'owner')") + @CachePut(value = "sessions", key = "#session") public Session update(final String sessionkey, final Session session) { final Session existingSession = sessionRepository.findByKeyword(sessionkey); @@ -384,19 +449,24 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio handleLogo(session); existingSession.setPpLogo(session.getPpLogo()); - sessionRepository.update(existingSession); + sessionRepository.save(existingSession); return session; } @Override - @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')") + @PreAuthorize("isAuthenticated() and hasPermission(1, 'motd', 'admin')") + @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#sessionkey.keyword") }) public Session updateCreator(String sessionkey, String newCreator) { - final Session existingSession = sessionRepository.findByKeyword(sessionkey); - if (existingSession == null) { + final Session session = sessionRepository.findByKeyword(sessionkey); + if (session == null) { throw new NullPointerException("Could not load session " + sessionkey + "."); } - return sessionRepository.changeSessionCreator(existingSession, newCreator); + + session.setCreator(newCreator); + save(session); + + return save(session); } /* @@ -406,20 +476,28 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio @Override public Session updateInternal(final Session session, final User user) { if (session.isCreator(user)) { - sessionRepository.update(session); + sessionRepository.save(session); return session; } return null; } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") - public void delete(final String sessionkey) { - final Session session = sessionRepository.findByKeyword(sessionkey); - - sessionRepository.deleteSession(session); + @PreAuthorize("isAuthenticated() and hasPermission(#session, 'owner')") + @CacheEvict("sessions") + public int[] deleteCascading(final Session session) { + int[] count = new int[] {0, 0, 0}; + List<String> contentIds = contentRepository.findIdsBySessionId(session.getId()); + count[2] = commentRepository.deleteBySessionId(session.getId()); + count[1] = answerRepository.deleteByContentIds(contentIds); + count[0] = contentRepository.deleteBySessionId(session.getId()); + sessionRepository.delete(session); + logger.debug("Deleted session document {} and related data.", session.getId()); + dbLogger.log("delete", "type", "session", "id", session.getId()); this.publisher.publishEvent(new DeleteSessionEvent(this, session)); + + return count; } @Override @@ -485,7 +563,7 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio } session.setFeatures(features); this.publisher.publishEvent(new FeatureChangeEvent(this, session)); - sessionRepository.update(session); + sessionRepository.save(session); return session.getFeatures(); } @@ -503,7 +581,7 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio session.setFeedbackLock(lock); this.publisher.publishEvent(new LockFeedbackEvent(this, session)); - sessionRepository.update(session); + sessionRepository.save(session); return session.getFeedbackLock(); } @@ -517,7 +595,7 @@ public class SessionServiceImpl extends EntityService<Session> implements Sessio } session.setFlipFlashcards(flip); this.publisher.publishEvent(new FlipFlashcardsEvent(this, session)); - sessionRepository.update(session); + sessionRepository.save(session); return session.getFlipFlashcards(); } diff --git a/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java b/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java index 7345ec3b6..0d57a8443 100644 --- a/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java @@ -19,6 +19,7 @@ package de.thm.arsnova.services; import de.thm.arsnova.entities.Statistics; import de.thm.arsnova.persistance.StatisticsRepository; +import org.springframework.cache.annotation.Cacheable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -43,7 +44,12 @@ public class StatisticsServiceImpl implements StatisticsService { @Scheduled(initialDelay = 0, fixedRate = 10000) private void refreshStatistics() { - statistics = statisticsRepository.getStatistics(); + statistics = loadStatistics(); + } + + @Cacheable("statistics") + private Statistics loadStatistics() { + return statisticsRepository.getStatistics(); } @Override diff --git a/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java b/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java index 3fb7266b1..269b173c2 100644 --- a/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java +++ b/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java @@ -21,6 +21,7 @@ import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.ScoreStatistics; import de.thm.arsnova.persistance.SessionStatisticsRepository; +import org.springframework.cache.annotation.Cacheable; /** * Base class for the score feature that allows filtering on the question variant. @@ -37,7 +38,12 @@ abstract class VariantScoreCalculator implements ScoreCalculator { this.sessionStatisticsRepository = sessionStatisticsRepository; } - private void loadProgress(final Session session) { + @Cacheable("learningprogress") + private Score loadProgress(final Session session) { + return sessionStatisticsRepository.getLearningProgress(session); + } + + private void refreshProgress(final Session session) { this.courseScore = sessionStatisticsRepository.getLearningProgress(session); } @@ -47,7 +53,7 @@ abstract class VariantScoreCalculator implements ScoreCalculator { @Override public ScoreStatistics getCourseProgress(Session session) { - this.loadProgress(session); + this.refreshProgress(session); this.filterVariant(); return this.createCourseProgress(); } @@ -56,7 +62,7 @@ abstract class VariantScoreCalculator implements ScoreCalculator { @Override public ScoreStatistics getMyProgress(Session session, User user) { - this.loadProgress(session); + this.refreshProgress(session); this.filterVariant(); return this.createMyProgress(user); } diff --git a/src/site/markdown/development/caching.md b/src/site/markdown/development/caching.md index 108591d71..76ed0623a 100644 --- a/src/site/markdown/development/caching.md +++ b/src/site/markdown/development/caching.md @@ -52,7 +52,7 @@ Cache name | Key | Description `flashcardquestions` | database id of session | Contains all "flashcard" variant questions for the specified session. `questions` | `Question` entity | Contains single question objects. `questions` | database id of question | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Question` object might be associated with two different keys. -`answers`| `Question` entity | Contains single answer objects. +`answers`| database id of question | Contains single answer objects. `learningprogress` | `Session` entity | Contains `CourseScore` objects to calculate the learning progress values for the specified session. `sessions` | keyword of session | Contains sessions identified by their keywords. `sessions` | database id of session | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Session` object might be associated with two different keys. -- GitLab