Commit f1ab0c3d authored by Daniel Gerhardt's avatar Daniel Gerhardt

Resolve cross-repository dependencies in persistance layer

Moved logic operating on multiple repositories to service layer.
parent 8a407bc0
......@@ -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);
}
......
......@@ -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);
}
......@@ -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);
......
......@@ -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);
......
......@@ -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;
}
}
......@@ -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()) {
......
......@@ -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
......
......@@ -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());
......
......@@ -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
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment