diff --git a/src/main/java/de/thm/arsnova/controller/ContentController.java b/src/main/java/de/thm/arsnova/controller/ContentController.java index 691a1e261882f771ec61252e2a4729506f56391e..166c579b4bea12e52974a929f47b344ea45a2d4a 100644 --- a/src/main/java/de/thm/arsnova/controller/ContentController.java +++ b/src/main/java/de/thm/arsnova/controller/ContentController.java @@ -315,12 +315,12 @@ public class ContentController extends PaginationController { ) { if (lectureQuestionsOnly) { contentService.deleteLectureQuestions(sessionkey); - } else if (flashcardsOnly) { - contentService.deleteFlashcards(sessionkey); } else if (preparationQuestionsOnly) { contentService.deletePreparationQuestions(sessionkey); + } else if (flashcardsOnly) { + contentService.deleteFlashcards(sessionkey); } else { - contentService.deleteBySessionKey(sessionkey); + contentService.deleteAllContent(sessionkey); } } @@ -337,10 +337,10 @@ public class ContentController extends PaginationController { ) { if (lectureQuestionsOnly) { return contentService.countLectureQuestions(sessionkey); - } else if (flashcardsOnly) { - return contentService.countFlashcards(sessionkey); } else if (preparationQuestionsOnly) { return contentService.countPreparationQuestions(sessionkey); + } else if (flashcardsOnly) { + return contentService.countFlashcards(sessionkey); } else { return contentService.countBySessionKey(sessionkey); } diff --git a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java index be68c003820f67157437be2ae3d282c5c434a968..3f25db49d692f1e32e4eb74ef84f374989e82195 100644 --- a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java @@ -18,8 +18,6 @@ package de.thm.arsnova.persistance; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Content; -import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import org.springframework.data.repository.CrudRepository; @@ -35,13 +33,10 @@ public interface AnswerRepository extends CrudRepository<Answer, String> { List<Answer> findByUserSessionId(User user, String sessionId); int countBySessionKey(String sessionKey); int deleteByContentId(String contentId); - Answer create(Answer answer, User user, Content content, Session session); void update(Answer answer); void delete(String answerId); int countBySessionIdLectureVariant(String sessionId); int countBySessionIdPreparationVariant(String sessionId); - int deleteAllQuestionsAnswers(String sessionId); - int deleteAllPreparationAnswers(String sessionId); - int deleteAllLectureAnswers(String sessionId); - int[] deleteAllAnswersWithQuestions(List<Content> contents); + int deleteAllAnswersForQuestions(List<String> contentIds); + int deleteByContentIds(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 a9e076d8b2cc67c644223ec40435f1505f246efb..df5ddc8a5e3a71fecd7b81ded9199eee65ac0de9 100644 --- a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java @@ -8,7 +8,7 @@ import org.springframework.data.repository.CrudRepository; import java.util.List; public interface CommentRepository extends CrudRepository<Comment, String> { - int countBySessionKey(String sessionKey); + int countBySessionId(String sessionKey); CommentReadingCount countReadingBySessionId(String sessionId); CommentReadingCount countReadingBySessionIdAndUser(String sessionId, User user); List<Comment> findBySessionId(String sessionId, int start, int limit); diff --git a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java index a40526c6b8077d7c1bcab499892672ad43568e61..7bea1b52329ef520d084a07c5d899447d9d534ab 100644 --- a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java @@ -14,8 +14,8 @@ public interface ContentRepository extends CrudRepository<Content, String> { List<Content> findBySessionIdForSpeaker(String sessionId); int countBySessionId(String sessionId); List<String> findIdsBySessionId(String sessionId); - int deleteQuestionWithAnswers(String contentId); - int[] deleteAllQuestionsWithAnswers(String sessionId); + 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); @@ -34,9 +34,6 @@ public interface ContentRepository extends CrudRepository<Content, String> { void resetQuestionsRoundState(String sessionId, List<Content> contents); void setVotingAdmissions(String sessionId, boolean disableVoting, List<Content> contents); List<Content> setVotingAdmissionForAllQuestions(String sessionId, boolean disableVoting); - int[] deleteAllLectureQuestionsWithAnswers(String sessionId); - int[] deleteAllFlashcardsWithAnswers(String sessionId); - int[] deleteAllPreparationQuestionsWithAnswers(String sessionId); 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/couchdb/CouchDbAnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java index 281a230b17f9cbc74101483b45142c84b67f4c2b..3ecdfc18fc4d5e81f42eac5b31f7e5f39ed2db7f 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java @@ -3,16 +3,9 @@ package de.thm.arsnova.persistance.couchdb; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Content; -import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; -import de.thm.arsnova.entities.transport.AnswerQueueElement; -import de.thm.arsnova.events.NewAnswerEvent; -import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.persistance.AnswerRepository; -import de.thm.arsnova.persistance.ContentRepository; import de.thm.arsnova.persistance.LogEntryRepository; -import de.thm.arsnova.persistance.SessionRepository; import org.ektorp.BulkDeleteDocument; import org.ektorp.ComplexKey; import org.ektorp.CouchDbConnector; @@ -26,62 +19,23 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.scheduling.annotation.Scheduled; import java.util.ArrayList; import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.stream.Collectors; public class CouchDbAnswerRepository extends CouchDbCrudRepository<Answer> implements AnswerRepository, ApplicationEventPublisherAware { private static final int BULK_PARTITION_SIZE = 500; private static final Logger logger = LoggerFactory.getLogger(CouchDbAnswerRepository.class); - private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>(); - @Autowired private LogEntryRepository dbLogger; - @Autowired - private SessionRepository sessionRepository; - - @Autowired - private ContentRepository contentRepository; - private ApplicationEventPublisher publisher; public CouchDbAnswerRepository(final CouchDbConnector db, final boolean createIfNotExists) { super(Answer.class, db, "by_sessionid", createIfNotExists); } - @Scheduled(fixedDelay = 5000) - public void flushAnswerQueue() { - if (answerQueue.isEmpty()) { - // no need to send an empty bulk request. - return; - } - - final List<Answer> answerList = new ArrayList<>(); - final List<AnswerQueueElement> elements = new ArrayList<>(); - AnswerQueueElement entry; - while ((entry = this.answerQueue.poll()) != null) { - final Answer answer = entry.getAnswer(); - answerList.add(answer); - elements.add(entry); - } - try { - db.executeBulk(answerList); - - // Send NewAnswerEvents ... - for (AnswerQueueElement e : elements) { - this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion())); - } - } catch (final DbAccessException e) { - logger.error("Could not bulk save answers from queue.", e); - } - } - @Override public void setApplicationEventPublisher(final ApplicationEventPublisher publisher) { this.publisher = publisher; @@ -219,24 +173,11 @@ public class CouchDbAnswerRepository extends CouchDbCrudRepository<Answer> imple @Override public int countBySessionKey(final String sessionKey) { - final Session s = sessionRepository.findByKeyword(sessionKey); - if (s == null) { - throw new NotFoundException(); - } - final ViewResult result = db.queryView(createQuery("by_sessionid_variant").key(s.getId())); + final ViewResult result = db.queryView(createQuery("by_sessionid_variant").key(sessionKey)); return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); } - @CacheEvict(value = "answers", key = "#content") - @Override - public Answer create(final Answer answer, final User user, final Content content, final Session session) { - db.create(answer); - this.answerQueue.offer(new AnswerQueueElement(session, content, answer, user)); - - return answer; - } - /* 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) { @@ -279,39 +220,7 @@ 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) @Override - public int deleteAllQuestionsAnswers(final String sessionId) { - final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(sessionId); - contentRepository.resetQuestionsRoundState(sessionId, contents); - final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); - - return deleteAllAnswersForQuestions(contentIds); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public int deleteAllPreparationAnswers(final String sessionId) { - final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(sessionId, "preparation"); - contentRepository.resetQuestionsRoundState(sessionId, contents); - final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); - - return deleteAllAnswersForQuestions(contentIds); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public int deleteAllLectureAnswers(final String sessionId) { - final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(sessionId, "lecture"); - contentRepository.resetQuestionsRoundState(sessionId, contents); - final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); - - return deleteAllAnswersForQuestions(contentIds); - } - public int deleteAllAnswersForQuestions(final List<String> contentIds) { final ViewResult result = db.queryView(createQuery("by_questionid") .keys(contentIds)); @@ -331,35 +240,24 @@ public class CouchDbAnswerRepository extends CouchDbCrudRepository<Answer> imple return 0; } - /* TODO: Split up - the combined action should be handled on the service level. */ - public int[] deleteAllAnswersWithQuestions(final List<Content> contents) { - List<String> questionIds = new ArrayList<>(); - final List<BulkDeleteDocument> allQuestions = new ArrayList<>(); - for (final Content q : contents) { - final BulkDeleteDocument d = new BulkDeleteDocument(q.getId(), q.getRevision()); - questionIds.add(q.getId()); - allQuestions.add(d); - } - + @Override + public int deleteByContentIds(final List<String> contentIds) { final ViewResult result = db.queryView(createQuery("by_questionid") - .key(questionIds)); - final List<BulkDeleteDocument> allAnswers = new ArrayList<>(); + .keys(contentIds)); + final List<BulkDeleteDocument> deleteDocs = new ArrayList<>(); for (final ViewResult.Row a : result.getRows()) { final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText()); - allAnswers.add(d); + deleteDocs.add(d); } try { - final List<BulkDeleteDocument> deleteList = new ArrayList<>(allAnswers); - deleteList.addAll(allQuestions); - final List<DocumentOperationResult> errors = db.executeBulk(deleteList); + final List<DocumentOperationResult> errors = db.executeBulk(deleteDocs); - /* TODO: subtract errors from count */ - return new int[] {allQuestions.size(), allAnswers.size()}; + return deleteDocs.size() - errors.size(); } catch (final DbAccessException e) { - logger.error("Could not bulk delete contents and answers.", e); + logger.error("Could not bulk delete answers.", e); } - return new int[] {0, 0}; + return 0; } } 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 f117e4679d576232b4bb278225be0574fd18c487..a71faeea7a7e433bb3446134541cf5636970a9bd 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java @@ -3,12 +3,9 @@ package de.thm.arsnova.persistance.couchdb; import com.fasterxml.jackson.databind.JsonNode; import de.thm.arsnova.entities.Comment; import de.thm.arsnova.entities.CommentReadingCount; -import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; -import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.persistance.CommentRepository; import de.thm.arsnova.persistance.LogEntryRepository; -import de.thm.arsnova.persistance.SessionRepository; import org.ektorp.ComplexKey; import org.ektorp.CouchDbConnector; import org.ektorp.UpdateConflictException; @@ -25,22 +22,14 @@ public class CouchDbCommentRepository extends CouchDbCrudRepository<Comment> imp @Autowired private LogEntryRepository dbLogger; - @Autowired - private SessionRepository sessionRepository; - public CouchDbCommentRepository(final CouchDbConnector db, final boolean createIfNotExists) { super(Comment.class, db, "by_sessionid", createIfNotExists); } @Override - public int countBySessionKey(final String sessionKey) { - final Session s = sessionRepository.findByKeyword(sessionKey); - if (s == null) { - throw new NotFoundException(); - } - + public int countBySessionId(final String sessionId) { final ViewResult result = db.queryView(createQuery("by_sessionid") - .key(s.getId()) + .key(sessionId) .reduce(true) .group(true)); if (result.isEmpty()) { 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 214100a36c23f5c88cfde4aa9046b0d8c4f3bf60..c09f433c7fb5bdd3b75320a3c073e68789ca39ac 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java @@ -2,13 +2,14 @@ package de.thm.arsnova.persistance.couchdb; import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.User; -import de.thm.arsnova.persistance.AnswerRepository; import de.thm.arsnova.persistance.ContentRepository; 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; @@ -33,9 +34,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp @Autowired private LogEntryRepository dbLogger; - @Autowired - private AnswerRepository answerRepository; - public CouchDbContentRepository(final CouchDbConnector db, final boolean createIfNotExists) { super(Content.class, db, "by_sessionid", createIfNotExists); } @@ -131,62 +129,33 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp @Override public List<String> findIdsBySessionId(final String sessionId) { - return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active").key(sessionId))); + return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(sessionId)) + .endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject())))); } - /* TODO: Move to service layer. */ - /* TODO: Only evict cache entry for the content's session. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", key = "#content.id"), - @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')") }) @Override - public int deleteQuestionWithAnswers(final String contentId) { - try { - final int count = answerRepository.deleteByContentId(contentId); - db.delete(contentId); - dbLogger.log("delete", "type", "content", "answerCount", count); - - return count; - } catch (final IllegalArgumentException e) { - logger.error("Could not delete content {}.", contentId, e); - } - - return 0; + public List<String> findIdsBySessionIdAndVariant(final String sessionId, final String variant) { + return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(sessionId, variant)) + .endKey(ComplexKey.of(sessionId, variant, ComplexKey.emptyObject())))); } - /* TODO: Move to service layer. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#sessionId"), - @CacheEvict(value = "lecturequestions", key = "#sessionId"), - @CacheEvict(value = "preparationquestions", key = "#sessionId"), - @CacheEvict(value = "flashcardquestions", key = "#sessionId") }) @Override - public int[] deleteAllQuestionsWithAnswers(final String sessionId) { + public int deleteBySessionId(final String sessionId) { final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") .startKey(ComplexKey.of(sessionId)) .endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject())) .reduce(false)); - return deleteAllQuestionDocumentsWithAnswers(result); - } - - /* TODO: Move to service layer. */ - private int[] deleteAllQuestionDocumentsWithAnswers(final ViewResult viewResult) { - List<Content> contents = new ArrayList<>(); - for (final ViewResult.Row row : viewResult.getRows()) { - final Content q = new Content(); - q.setId(row.getId()); - q.setRevision(row.getValueAsNode().get("_rev").asText()); - contents.add(q); + final List<BulkDeleteDocument> deleteDocs = new ArrayList<>(); + for (final ViewResult.Row a : result.getRows()) { + final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText()); + deleteDocs.add(d); } + List<DocumentOperationResult> errors = db.executeBulk(deleteDocs); - final int[] count = answerRepository.deleteAllAnswersWithQuestions(contents); - dbLogger.log("delete", "type", "question", "questionCount", count[0]); - dbLogger.log("delete", "type", "answer", "answerCount", count[1]); - - return count; + return deleteDocs.size() - errors.size(); } @Override @@ -314,54 +283,6 @@ public class CouchDbContentRepository extends CouchDbCrudRepository<Content> imp return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); } - /* TODO: Move to service layer. */ - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict("skillquestions"), - @CacheEvict("lecturequestions"), - @CacheEvict(value = "answers", allEntries = true)}) - @Override - public int[] deleteAllLectureQuestionsWithAnswers(final String sessionId) { - final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") - .startKey(ComplexKey.of(sessionId, "lecture")) - .endKey(ComplexKey.of(sessionId, "lecture", ComplexKey.emptyObject())) - .reduce(false)); - - return deleteAllQuestionDocumentsWithAnswers(result); - } - - /* TODO: Move to service layer. */ - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict("skillquestions"), - @CacheEvict("flashcardquestions"), - @CacheEvict(value = "answers", allEntries = true)}) - @Override - public int[] deleteAllFlashcardsWithAnswers(final String sessionId) { - final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") - .startKey(ComplexKey.of(sessionId, "flashcard")) - .endKey(ComplexKey.of(sessionId, "flashcard", ComplexKey.emptyObject())) - .reduce(false)); - - return deleteAllQuestionDocumentsWithAnswers(result); - } - - /* TODO: Move to service layer. */ - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict("skillquestions"), - @CacheEvict("preparationquestions"), - @CacheEvict(value = "answers", allEntries = true)}) - @Override - public int[] deleteAllPreparationQuestionsWithAnswers(final String sessionId) { - final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") - .startKey(ComplexKey.of(sessionId, "preparation")) - .endKey(ComplexKey.of(sessionId, "preparation", ComplexKey.emptyObject())) - .reduce(false)); - - return deleteAllQuestionDocumentsWithAnswers(result); - } - private List<String> collectUnansweredQuestionIds( final List<String> contentIds, final List<String> answeredContentIds 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 7e0542270bd64d2d0edd7bf08b2da7cd974ceda3..a21ecbab96f3ce40dcabd8090acf829e6c0bba86 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java @@ -249,7 +249,7 @@ public class CouchDbSessionRepository extends CouchDbCrudRepository<Session> imp throw new UnsupportedOperationException(); // final int[] count = new int[] {0, 0}; // try { -// count = deleteAllQuestionsWithAnswers(session); +// count = deleteBySessionId(session); // remove(session); // logger.debug("Deleted session document {} and related data.", session.getId()); // dbLogger.log("delete", "type", "session", "id", session.getId()); diff --git a/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java b/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java index 0c1f15288ec9223be0a94d693011085d49a865fc..01d71afe6db2edd4f96d2689de4b5cde22c6a490 100644 --- a/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java @@ -95,7 +95,7 @@ public class CommentServiceImpl extends EntityService<Comment> implements Commen @Override @PreAuthorize("isAuthenticated()") public int count(final String sessionKey) { - return commentRepository.countBySessionKey(sessionKey); + return commentRepository.countBySessionId(getSession(sessionKey).getId()); } @Override diff --git a/src/main/java/de/thm/arsnova/services/ContentService.java b/src/main/java/de/thm/arsnova/services/ContentService.java index fc9e6c5a16f0a26233b2dd0e6ac32eb1f25f0746..c2735488fc2b4002a260f9e0e89facb1d5dbaf24 100644 --- a/src/main/java/de/thm/arsnova/services/ContentService.java +++ b/src/main/java/de/thm/arsnova/services/ContentService.java @@ -18,8 +18,6 @@ package de.thm.arsnova.services; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Comment; -import de.thm.arsnova.entities.CommentReadingCount; import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.User; @@ -40,8 +38,6 @@ public interface ContentService { void delete(String questionId); - void deleteBySessionKey(String sessionKeyword); - void startNewPiRound(String questionId, User user); void startNewPiRoundDelayed(String questionId, int time); @@ -112,12 +108,14 @@ public interface ContentService { int countFlashcardsForUserInternal(String sessionkey); - void deleteLectureQuestions(String sessionkey); + void deleteAllContent(String sessionkey); - void deleteFlashcards(String sessionkey); + void deleteLectureQuestions(String sessionkey); void deletePreparationQuestions(String sessionkey); + void deleteFlashcards(String sessionkey); + List<String> getUnAnsweredLectureQuestionIds(String sessionkey); List<String> getUnAnsweredLectureQuestionIds(String sessionKey, User user); diff --git a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java index 7ab3d3f23d9c68185315729321f05a5f5bb5f1aa..1318d12f91b7cda99788ef0484cfa0c98fc44887 100644 --- a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.services; +import de.thm.arsnova.entities.transport.AnswerQueueElement; +import de.thm.arsnova.persistance.LogEntryRepository; import de.thm.arsnova.util.ImageUtils; import de.thm.arsnova.entities.Answer; import de.thm.arsnova.entities.Content; @@ -29,13 +31,17 @@ import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.persistance.AnswerRepository; import de.thm.arsnova.persistance.ContentRepository; import de.thm.arsnova.persistance.SessionRepository; +import org.ektorp.DbAccessException; 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.Caching; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -44,8 +50,11 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; /** * Performs all content and answer related operations. @@ -54,6 +63,8 @@ import java.util.TimerTask; public class ContentServiceImpl extends EntityService<Content> implements ContentService, ApplicationEventPublisherAware { private UserService userService; + private LogEntryRepository dbLogger; + private SessionRepository sessionRepository; private ContentRepository contentRepository; @@ -71,10 +82,13 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten private HashMap<String, Timer> timerList = new HashMap<>(); + private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>(); + public ContentServiceImpl( ContentRepository repository, AnswerRepository answerRepository, SessionRepository sessionRepository, + LogEntryRepository dbLogger, UserService userService, ImageUtils imageUtils, @Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { @@ -82,10 +96,38 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten this.contentRepository = repository; this.answerRepository = answerRepository; this.sessionRepository = sessionRepository; + this.dbLogger = dbLogger; this.userService = userService; this.imageUtils = imageUtils; } + @Scheduled(fixedDelay = 5000) + public void flushAnswerQueue() { + if (answerQueue.isEmpty()) { + // no need to send an empty bulk request. + return; + } + + final List<Answer> answerList = new ArrayList<>(); + final List<AnswerQueueElement> elements = new ArrayList<>(); + AnswerQueueElement entry; + while ((entry = this.answerQueue.poll()) != null) { + final Answer answer = entry.getAnswer(); + answerList.add(answer); + elements.add(entry); + } + try { + answerRepository.save(answerList); + + // Send NewAnswerEvents ... + for (AnswerQueueElement e : elements) { + this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion())); + } + } catch (final DbAccessException e) { + logger.error("Could not bulk save answers from queue.", e); + } + } + @Override @PreAuthorize("isAuthenticated()") public List<Content> getBySessionKey(final String sessionkey) { @@ -152,10 +194,17 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten return result; } + /* TODO: Only evict cache entry for the content's session. This requires some refactoring. */ @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") - public void delete(final String questionId) { - final Content content = contentRepository.findOne(questionId); + @PreAuthorize("isAuthenticated() and hasPermission(#contentId, 'content', 'owner')") + @Caching(evict = { + @CacheEvict(value = "questions", key = "#contentId"), + @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')"*/) }) + public void delete(final String contentId) { + final Content content = contentRepository.findOne(contentId); if (content == null) { throw new NotFoundException(); } @@ -164,22 +213,71 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten if (session == null) { throw new UnauthorizedException(); } - contentRepository.deleteQuestionWithAnswers(content.getId()); + + try { + final int count = answerRepository.deleteByContentId(contentId); + contentRepository.delete(contentId); + dbLogger.log("delete", "type", "content", "answerCount", count); + } catch (final IllegalArgumentException e) { + logger.error("Could not delete content {}.", contentId, e); + } final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, content); this.publisher.publishEvent(event); } - @Override - @PreAuthorize("isAuthenticated() and hasPermission(#sessionKeyword, 'session', 'owner')") - public void deleteBySessionKey(final String sessionKeyword) { - final Session session = getSessionWithAuthCheck(sessionKeyword); - contentRepository.deleteAllQuestionsWithAnswers(session.getId()); + @PreAuthorize("isAuthenticated() and hasPermission(#session, 'owner')") + @Caching(evict = { + @CacheEvict(value = "questions", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#session.getId()"), + @CacheEvict(value = "lecturequestions", key = "#session.getId()", condition = "'lecture'.equals(#variant)"), + @CacheEvict(value = "preparationquestions", key = "#session.getId()", condition = "'preparation'.equals(#variant)"), + @CacheEvict(value = "flashcardquestions", key = "#session.getId()", condition = "'flashcard'.equals(#variant)") }) + private void deleteBySessionAndVariant(final Session session, final String variant) { + final List<String> contentIds; + if ("all".equals(variant)) { + contentIds = contentRepository.findIdsBySessionId(session.getId()); + } else { + contentIds = contentRepository.findIdsBySessionIdAndVariant(session.getId(), variant); + } + + final int answerCount = answerRepository.deleteByContentIds(contentIds); + final int contentCount = contentRepository.deleteBySessionId(session.getId()); + dbLogger.log("delete", "type", "question", "questionCount", contentCount); + dbLogger.log("delete", "type", "answer", "answerCount", answerCount); final DeleteAllQuestionsEvent event = new DeleteAllQuestionsEvent(this, session); this.publisher.publishEvent(event); } + @Override + @PreAuthorize("isAuthenticated()") + public void deleteAllContent(final String sessionkey) { + final Session session = getSessionWithAuthCheck(sessionkey); + deleteBySessionAndVariant(session, "all"); + } + + @Override + @PreAuthorize("isAuthenticated()") + public void deleteLectureQuestions(final String sessionkey) { + final Session session = getSessionWithAuthCheck(sessionkey); + deleteBySessionAndVariant(session, "lecture"); + } + + @Override + @PreAuthorize("isAuthenticated()") + public void deletePreparationQuestions(final String sessionkey) { + final Session session = getSessionWithAuthCheck(sessionkey); + deleteBySessionAndVariant(session, "preparation"); + } + + @Override + @PreAuthorize("isAuthenticated()") + public void deleteFlashcards(final String sessionkey) { + final Session session = getSessionWithAuthCheck(sessionkey); + deleteBySessionAndVariant(session, "flashcard"); + } + @Override @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void startNewPiRound(final String questionId, User user) { @@ -576,6 +674,7 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answers", key = "#content") public Answer saveAnswer(final String questionId, final de.thm.arsnova.entities.transport.Answer answer) { final User user = getCurrentUser(); final Content content = get(questionId); @@ -601,7 +700,9 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten } } - return answerRepository.create(theAnswer, user, content, session); + this.answerQueue.offer(new AnswerQueueElement(session, content, theAnswer, user)); + + return theAnswer; } @Override @@ -774,27 +875,6 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten return contentRepository.findBySessionIdOnlyFlashcardVariantAndActive(getSession(sessionkey).getId()).size(); } - @Override - @PreAuthorize("isAuthenticated()") - public void deleteLectureQuestions(final String sessionkey) { - final Session session = getSessionWithAuthCheck(sessionkey); - contentRepository.deleteAllLectureQuestionsWithAnswers(session.getId()); - } - - @Override - @PreAuthorize("isAuthenticated()") - public void deleteFlashcards(final String sessionkey) { - final Session session = getSessionWithAuthCheck(sessionkey); - contentRepository.deleteAllFlashcardsWithAnswers(session.getId()); - } - - @Override - @PreAuthorize("isAuthenticated()") - public void deletePreparationQuestions(final String sessionkey) { - final Session session = getSessionWithAuthCheck(sessionkey); - contentRepository.deleteAllPreparationQuestionsWithAnswers(session.getId()); - } - @Override @PreAuthorize("isAuthenticated()") public List<String> getUnAnsweredLectureQuestionIds(final String sessionkey) { @@ -859,31 +939,48 @@ public class ContentServiceImpl extends EntityService<Content> implements Conten @Override @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answers", allEntries = true) public void deleteAllQuestionsAnswers(final String sessionkey) { final User user = getCurrentUser(); final Session session = getSession(sessionkey); if (!session.isCreator(user)) { throw new UnauthorizedException(); } - answerRepository.deleteAllQuestionsAnswers(session.getId()); + + final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId()); + contentRepository.resetQuestionsRoundState(session.getId(), contents); + final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); + answerRepository.deleteAllAnswersForQuestions(contentIds); this.publisher.publishEvent(new DeleteAllQuestionsAnswersEvent(this, session)); } + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") + @CacheEvict(value = "answers", allEntries = true) public void deleteAllPreparationAnswers(String sessionkey) { final Session session = getSession(sessionkey); - answerRepository.deleteAllPreparationAnswers(session.getId()); + + final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId(), "preparation"); + contentRepository.resetQuestionsRoundState(session.getId(), contents); + final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList()); + answerRepository.deleteAllAnswersForQuestions(contentIds); this.publisher.publishEvent(new DeleteAllPreparationAnswersEvent(this, session)); } + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") + @CacheEvict(value = "answers", allEntries = true) public void deleteAllLectureAnswers(String sessionkey) { final Session session = getSession(sessionkey); - answerRepository.deleteAllLectureAnswers(session.getId()); + + final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId(), "lecture"); + contentRepository.resetQuestionsRoundState(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)); }