From 22482b6cf3c83655837503c074392427f44686d3 Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Tue, 6 Mar 2018 14:24:37 +0100 Subject: [PATCH] Move Answer related methods to a new AnswerService Additionally, added AnswerController for API v3. --- .../arsnova/controller/AnswerController.java | 34 ++ .../controller/v2/ContentController.java | 48 +- .../thm/arsnova/services/AnswerService.java | 59 +++ .../arsnova/services/AnswerServiceImpl.java | 455 ++++++++++++++++++ .../thm/arsnova/services/ContentService.java | 69 +-- .../arsnova/services/ContentServiceImpl.java | 381 +-------------- .../websocket/ArsnovaSocketioServerImpl.java | 20 +- 7 files changed, 607 insertions(+), 459 deletions(-) create mode 100644 src/main/java/de/thm/arsnova/controller/AnswerController.java create mode 100644 src/main/java/de/thm/arsnova/services/AnswerService.java create mode 100644 src/main/java/de/thm/arsnova/services/AnswerServiceImpl.java diff --git a/src/main/java/de/thm/arsnova/controller/AnswerController.java b/src/main/java/de/thm/arsnova/controller/AnswerController.java new file mode 100644 index 000000000..8eeb8e736 --- /dev/null +++ b/src/main/java/de/thm/arsnova/controller/AnswerController.java @@ -0,0 +1,34 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2018 The ARSnova Team + * + * ARSnova Backend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova Backend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.thm.arsnova.controller; + +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.services.AnswerService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/answer") +public class AnswerController extends AbstractEntityController<Answer> { + private AnswerService answerService; + + public AnswerController(final AnswerService answerService) { + super(answerService); + this.answerService = answerService; + } +} diff --git a/src/main/java/de/thm/arsnova/controller/v2/ContentController.java b/src/main/java/de/thm/arsnova/controller/v2/ContentController.java index be4e5c7b5..1467f0755 100644 --- a/src/main/java/de/thm/arsnova/controller/v2/ContentController.java +++ b/src/main/java/de/thm/arsnova/controller/v2/ContentController.java @@ -30,6 +30,7 @@ import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.NotImplementedException; +import de.thm.arsnova.services.AnswerService; import de.thm.arsnova.services.ContentService; import de.thm.arsnova.services.RoomService; import de.thm.arsnova.services.TimerService; @@ -69,6 +70,9 @@ public class ContentController extends PaginationController { @Autowired private ContentService contentService; + @Autowired + private AnswerService answerService; + @Autowired private RoomService roomService; @@ -444,7 +448,7 @@ public class ContentController extends PaginationController { final HttpServletResponse response ) { final de.thm.arsnova.entities.Content content = contentService.get(contentId); - final de.thm.arsnova.entities.Answer answer = contentService.getMyAnswer(contentId); + final de.thm.arsnova.entities.Answer answer = answerService.getMyAnswer(contentId); if (answer == null) { response.setStatus(HttpStatus.NO_CONTENT.value()); return null; @@ -481,21 +485,21 @@ public class ContentController extends PaginationController { final HttpServletResponse response) { final de.thm.arsnova.entities.Content content = contentService.get(contentId); if (content instanceof ChoiceQuestionContent) { - return toV2Migrator.migrate(contentService.getAllStatistics(contentId), + return toV2Migrator.migrate(answerService.getAllStatistics(contentId), (ChoiceQuestionContent) content, content.getState().getRound()); } else { List<de.thm.arsnova.entities.TextAnswer> answers; if (allAnswers) { - answers = contentService.getAllTextAnswers(contentId, -1, -1); + answers = answerService.getAllTextAnswers(contentId, -1, -1); } else if (null == piRound) { - answers = contentService.getTextAnswers(contentId, offset, limit); + answers = answerService.getTextAnswers(contentId, offset, limit); } else { if (piRound < 1 || piRound > 2) { response.setStatus(HttpStatus.BAD_REQUEST.value()); return null; } - answers = contentService.getTextAnswers(contentId, piRound, offset, limit); + answers = answerService.getTextAnswers(contentId, piRound, offset, limit); } if (answers == null) { return new ArrayList<>(); @@ -517,9 +521,9 @@ public class ContentController extends PaginationController { final de.thm.arsnova.entities.Answer answerV3 = fromV2Migrator.migrate(answer, contentV2); if (answerV3 instanceof TextAnswer) { - return toV2Migrator.migrate((TextAnswer) contentService.saveAnswer(contentId, answerV3)); + return toV2Migrator.migrate((TextAnswer) answerService.saveAnswer(contentId, answerV3)); } else { - return toV2Migrator.migrate((ChoiceAnswer) contentService.saveAnswer(contentId, answerV3), (ChoiceQuestionContent) content); + return toV2Migrator.migrate((ChoiceAnswer) answerService.saveAnswer(contentId, answerV3), (ChoiceQuestionContent) content); } } @@ -537,9 +541,9 @@ public class ContentController extends PaginationController { final de.thm.arsnova.entities.Answer answerV3 = fromV2Migrator.migrate(answer, contentV2); if (answerV3 instanceof TextAnswer) { - return toV2Migrator.migrate((TextAnswer) contentService.updateAnswer(answerV3)); + return toV2Migrator.migrate((TextAnswer) answerService.updateAnswer(answerV3)); } else { - return toV2Migrator.migrate((ChoiceAnswer) contentService.updateAnswer(answerV3), (ChoiceQuestionContent) content); + return toV2Migrator.migrate((ChoiceAnswer) answerService.updateAnswer(answerV3), (ChoiceQuestionContent) content); } } @@ -563,7 +567,7 @@ public class ContentController extends PaginationController { @PathVariable final String answerId, final HttpServletResponse response ) { - contentService.deleteAnswer(contentId, answerId); + answerService.deleteAnswer(contentId, answerId); } @ApiOperation(value = "Delete answers from a content, identified by content ID", @@ -573,7 +577,7 @@ public class ContentController extends PaginationController { @PathVariable final String contentId, final HttpServletResponse response ) { - contentService.deleteAnswers(contentId); + answerService.deleteAnswers(contentId); } @ApiOperation(value = "Delete all answers and contents from a room, identified by room short ID", @@ -614,7 +618,7 @@ public class ContentController extends PaginationController { @Deprecated @RequestMapping(value = "/{contentId}/answercount", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) public String getAnswerCount(@PathVariable final String contentId) { - return String.valueOf(contentService.countAnswersByContentIdAndRound(contentId)); + return String.valueOf(answerService.countAnswersByContentIdAndRound(contentId)); } @ApiOperation(value = "Get the amount of answers for a content, identified by the content ID", @@ -622,8 +626,8 @@ public class ContentController extends PaginationController { @RequestMapping(value = "/{contentId}/allroundanswercount", method = RequestMethod.GET) public List<Integer> getAllAnswerCount(@PathVariable final String contentId) { return Arrays.asList( - contentService.countAnswersByContentIdAndRound(contentId, 1), - contentService.countAnswersByContentIdAndRound(contentId, 2) + answerService.countAnswersByContentIdAndRound(contentId, 1), + answerService.countAnswersByContentIdAndRound(contentId, 2) ); } @@ -631,7 +635,7 @@ public class ContentController extends PaginationController { nickname = "getTotalAnswerCountByContent") @RequestMapping(value = "/{contentId}/totalanswercount", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) public String getTotalAnswerCountByContent(@PathVariable final String contentId) { - return String.valueOf(contentService.countTotalAnswersByContentId(contentId)); + return String.valueOf(answerService.countTotalAnswersByContentId(contentId)); } @ApiOperation(value = "Get the amount of answers and abstention answers by a content, identified by the content ID", @@ -639,8 +643,8 @@ public class ContentController extends PaginationController { @RequestMapping(value = "/{contentId}/answerandabstentioncount", method = RequestMethod.GET) public List<Integer> getAnswerAndAbstentionCount(@PathVariable final String contentId) { return Arrays.asList( - contentService.countAnswersByContentIdAndRound(contentId), - contentService.countTotalAbstentionsByContentId(contentId) + answerService.countAnswersByContentIdAndRound(contentId), + answerService.countTotalAbstentionsByContentId(contentId) ); } @@ -649,7 +653,7 @@ public class ContentController extends PaginationController { @RequestMapping(value = "/{contentId}/freetextanswer/", method = RequestMethod.GET) @Pagination public List<Answer> getFreetextAnswers(@PathVariable final String contentId) { - return contentService.getTextAnswersByContentId(contentId, offset, limit).stream() + return answerService.getTextAnswersByContentId(contentId, offset, limit).stream() .map(toV2Migrator::migrate).collect(Collectors.toList()); } @@ -659,7 +663,7 @@ public class ContentController extends PaginationController { @Deprecated @RequestMapping(value = "/myanswers", method = RequestMethod.GET) public List<Answer> getMyAnswers(@RequestParam(value = "sessionkey") final String roomShortId) throws OperationNotSupportedException { - return contentService.getMyAnswersByRoomId(roomService.getIdByShortId(roomShortId)).stream() + return answerService.getMyAnswersByRoomId(roomService.getIdByShortId(roomShortId)).stream() .map(a -> { if (a instanceof ChoiceAnswer) { return toV2Migrator.migrate( @@ -685,11 +689,11 @@ public class ContentController extends PaginationController { /* FIXME: Content variant is ignored for now */ lectureContentsOnly = preparationContentsOnly = false; if (lectureContentsOnly) { - count = contentService.countLectureContentAnswers(roomId); + count = answerService.countLectureContentAnswers(roomId); } else if (preparationContentsOnly) { - count = contentService.countPreparationContentAnswers(roomId); + count = answerService.countPreparationContentAnswers(roomId); } else { - count = contentService.countTotalAnswersByRoomId(roomId); + count = answerService.countTotalAnswersByRoomId(roomId); } return String.valueOf(count); diff --git a/src/main/java/de/thm/arsnova/services/AnswerService.java b/src/main/java/de/thm/arsnova/services/AnswerService.java new file mode 100644 index 000000000..ea1769e10 --- /dev/null +++ b/src/main/java/de/thm/arsnova/services/AnswerService.java @@ -0,0 +1,59 @@ +package de.thm.arsnova.services; + +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.AnswerStatistics; +import de.thm.arsnova.entities.TextAnswer; +import de.thm.arsnova.entities.migration.v2.ClientAuthentication; + +import java.util.List; +import java.util.Map; + +public interface AnswerService extends EntityService<Answer> { + Answer getMyAnswer(String contentId); + + void getFreetextAnswerAndMarkRead(String answerId, ClientAuthentication user); + + AnswerStatistics getStatistics(String contentId, int piRound); + + AnswerStatistics getStatistics(String contentId); + + AnswerStatistics getAllStatistics(String contentId); + + List<TextAnswer> getTextAnswers(String contentId, int piRound, int offset, int limit); + + List<TextAnswer> getTextAnswers(String contentId, int offset, int limit); + + List<TextAnswer> getAllTextAnswers(String contentId, int offset, int limit); + + int countAnswersByContentIdAndRound(String contentId); + + int countAnswersByContentIdAndRound(String contentId, int piRound); + + List<TextAnswer> getTextAnswersByContentId(String contentId, int offset, int limit); + + List<Answer> getMyAnswersByRoomId(String roomId); + + int countTotalAnswersByRoomId(String roomId); + + int countTotalAnswersByContentId(String contentId); + + void deleteAnswers(String contentId); + + Answer saveAnswer(String contentId, Answer answer); + + Answer updateAnswer(Answer answer); + + void deleteAnswer(String contentId, String answerId); + + Map<String, Object> countAnswersAndAbstentionsInternal(String contentId); + + int countLectureContentAnswers(String roomId); + + int countLectureQuestionAnswersInternal(String roomId); + + int countPreparationContentAnswers(String roomId); + + int countPreparationQuestionAnswersInternal(String roomId); + + int countTotalAbstentionsByContentId(String contentId); +} diff --git a/src/main/java/de/thm/arsnova/services/AnswerServiceImpl.java b/src/main/java/de/thm/arsnova/services/AnswerServiceImpl.java new file mode 100644 index 000000000..00eae4abc --- /dev/null +++ b/src/main/java/de/thm/arsnova/services/AnswerServiceImpl.java @@ -0,0 +1,455 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2018 The ARSnova Team and Contributors + * + * ARSnova Backend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova Backend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package de.thm.arsnova.services; + +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.AnswerStatistics; +import de.thm.arsnova.entities.ChoiceQuestionContent; +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.Room; +import de.thm.arsnova.entities.TextAnswer; +import de.thm.arsnova.entities.migration.v2.ClientAuthentication; +import de.thm.arsnova.entities.transport.AnswerQueueElement; +import de.thm.arsnova.events.DeleteAnswerEvent; +import de.thm.arsnova.events.NewAnswerEvent; +import de.thm.arsnova.exceptions.NotFoundException; +import de.thm.arsnova.exceptions.UnauthorizedException; +import de.thm.arsnova.persistance.AnswerRepository; +import de.thm.arsnova.persistance.ContentRepository; +import de.thm.arsnova.persistance.RoomRepository; +import org.ektorp.DbAccessException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.cache.annotation.CacheEvict; +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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Performs all answer related operations. + */ +@Service +public class AnswerServiceImpl extends DefaultEntityServiceImpl<Answer> + implements AnswerService, ApplicationEventPublisherAware { + private static final Logger logger = LoggerFactory.getLogger(ContentServiceImpl.class); + + private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>(); + + private ApplicationEventPublisher publisher; + + private RoomRepository roomRepository; + private ContentRepository contentRepository; + private AnswerRepository answerRepository; + private ContentService contentService; + private UserService userService; + + public AnswerServiceImpl( + AnswerRepository repository, + ContentRepository contentRepository, + RoomRepository roomRepository, + ContentService contentService, + UserService userService, + @Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { + super(Answer.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + this.answerRepository = repository; + this.contentRepository = contentRepository; + this.roomRepository = roomRepository; + this.contentService = contentService; + this.userService = userService; + } + + @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.getRoom(), e.getAnswer(), e.getUser(), e.getQuestion())); + } + } catch (final DbAccessException e) { + logger.error("Could not bulk save answers from queue.", e); + } + } + + @Override + @PreAuthorize("hasPermission(#contentId, 'content', 'owner')") + public void deleteAnswers(final String contentId) { + final Content content = contentRepository.findOne(contentId); + content.resetState(); + /* FIXME: cancel timer */ + contentService.update(content); + answerRepository.deleteByContentId(content.getId()); + } + + @Override + @PreAuthorize("isAuthenticated()") + public Answer getMyAnswer(final String contentId) { + final Content content = contentService.get(contentId); + if (content == null) { + throw new NotFoundException(); + } + return answerRepository.findByContentIdUserPiRound(contentId, Answer.class, userService.getCurrentUser(), content.getState().getRound()); + } + + @Override + public void getFreetextAnswerAndMarkRead(final String answerId, final ClientAuthentication user) { + final Answer answer = answerRepository.findOne(answerId); + if (!(answer instanceof TextAnswer)) { + throw new NotFoundException(); + } + final TextAnswer textAnswer = (TextAnswer) answer; + if (textAnswer.isRead()) { + return; + } + final Room room = roomRepository.findOne(textAnswer.getRoomId()); + if (room.getOwnerId().equals(user.getId())) { + textAnswer.setRead(true); + answerRepository.save(textAnswer); + } + } + + @Override + @PreAuthorize("isAuthenticated()") + public AnswerStatistics getStatistics(final String contentId, final int round) { + final ChoiceQuestionContent content = (ChoiceQuestionContent) contentRepository.findOne(contentId); + if (content == null) { + throw new NotFoundException(); + } + AnswerStatistics stats = answerRepository.findByContentIdRound( + content.getId(), round, content.getOptions().size()); + /* Fill list with zeros to prevent IndexOutOfBoundsExceptions */ + List<Integer> independentCounts = stats.getRoundStatistics().get(round - 1).getIndependentCounts(); + while (independentCounts.size() < content.getOptions().size()) { + independentCounts.add(0); + } + + return stats; + } + + @Override + @PreAuthorize("isAuthenticated()") + public AnswerStatistics getStatistics(final String contentId) { + final Content content = contentService.get(contentId); + if (content == null) { + throw new NotFoundException(); + } + + return getStatistics(content.getId(), content.getState().getRound()); + } + + @Override + @PreAuthorize("isAuthenticated()") + public AnswerStatistics getAllStatistics(final String contentId) { + final Content content = contentService.get(contentId); + if (content == null) { + throw new NotFoundException(); + } + AnswerStatistics stats = getStatistics(content.getId(), 1); + AnswerStatistics stats2 = getStatistics(content.getId(), 2); + stats.getRoundStatistics().add(stats2.getRoundStatistics().get(1)); + + return stats; + } + + @Override + @PreAuthorize("isAuthenticated()") + public List<TextAnswer> getTextAnswers(final String contentId, final int piRound, final int offset, final int limit) { + /* FIXME: round support not implemented */ + final Content content = contentRepository.findOne(contentId); + if (content == null) { + throw new NotFoundException(); + } + + return getTextAnswersByContentId(contentId, offset, limit); + } + + @Override + @PreAuthorize("isAuthenticated()") + public List<TextAnswer> getTextAnswers(final String contentId, final int offset, final int limit) { + return getTextAnswers(contentId, 0, offset, limit); + } + + @Override + @PreAuthorize("isAuthenticated()") + public List<TextAnswer> getAllTextAnswers(final String contentId, final int offset, final int limit) { + final Content content = contentService.get(contentId); + if (content == null) { + throw new NotFoundException(); + } + + return getTextAnswersByContentId(contentId, offset, limit); + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countAnswersByContentIdAndRound(final String contentId) { + final Content content = contentService.get(contentId); + if (content == null) { + return 0; + } + + if (content.getFormat() == Content.Format.TEXT) { + return answerRepository.countByContentId(content.getId()); + } else { + return answerRepository.countByContentIdRound(content.getId(), content.getState().getRound()); + } + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countAnswersByContentIdAndRound(final String contentId, final int piRound) { + final Content content = contentService.get(contentId); + if (content == null) { + return 0; + } + + return answerRepository.countByContentIdRound(content.getId(), piRound); + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countTotalAbstentionsByContentId(final String contentId) { + final Content content = contentService.get(contentId); + if (content == null) { + return 0; + } + + return answerRepository.countByContentId(contentId); + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countTotalAnswersByContentId(final String contentId) { + final Content content = contentService.get(contentId); + if (content == null) { + return 0; + } + + return answerRepository.countByContentId(content.getId()); + } + + @Override + @PreAuthorize("isAuthenticated()") + public List<TextAnswer> getTextAnswersByContentId(final String contentId, final int offset, final int limit) { + final List<TextAnswer> answers = answerRepository.findByContentId(contentId, TextAnswer.class, offset, limit); + if (answers == null) { + throw new NotFoundException(); + } + + return answers; + } + + @Override + @PreAuthorize("isAuthenticated()") + public List<Answer> getMyAnswersByRoomId(final String roomId) { + // Load contents first because we are only interested in answers of the latest piRound. + final List<Content> contents = contentService.getByRoomId(roomId); + final Map<String, Content> contentIdToContent = new HashMap<>(); + for (final Content content : contents) { + contentIdToContent.put(content.getId(), content); + } + + /* filter answers by active piRound per content */ + final List<Answer> answers = answerRepository.findByUserRoomId(userService.getCurrentUser(), roomId); + final List<Answer> filteredAnswers = new ArrayList<>(); + for (final Answer answer : answers) { + final Content content = contentIdToContent.get(answer.getContentId()); + if (content == null) { + // Content is not present. Most likely it has been locked by the + // Room's creator. Locked Questions do not appear in this list. + continue; + } + + // discard all answers that aren't in the same piRound as the content + if (answer.getRound() == content.getState().getRound()) { + filteredAnswers.add(answer); + } + } + + return filteredAnswers; + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countTotalAnswersByRoomId(final String roomId) { + return answerRepository.countByRoomId(roomId); + } + + @Override + @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answerlists", key = "#contentId") + public Answer saveAnswer(final String contentId, final Answer answer) { + final ClientAuthentication user = userService.getCurrentUser(); + final Content content = contentService.get(contentId); + if (content == null) { + throw new NotFoundException(); + } + final Room room = roomRepository.findOne(content.getRoomId()); + + answer.setCreatorId(user.getId()); + answer.setContentId(content.getId()); + answer.setRoomId(room.getId()); + + /* FIXME: migrate + answer.setQuestionValue(content.calculateValue(answer)); + */ + + if (content.getFormat() == Content.Format.TEXT) { + answer.setRound(0); + /* FIXME: migrate + imageUtils.generateThumbnailImage(answer); + if (content.isFixedAnswer() && content.getBody() != null) { + answer.setAnswerTextRaw(answer.getAnswerText()); + + if (content.isStrictMode()) { + content.checkTextStrictOptions(answer); + } + answer.setQuestionValue(content.evaluateCorrectAnswerFixedText(answer.getAnswerTextRaw())); + answer.setSuccessfulFreeTextAnswer(content.isSuccessfulFreeTextAnswer(answer.getAnswerTextRaw())); + } + */ + } else { + answer.setRound(content.getState().getRound()); + } + + this.answerQueue.offer(new AnswerQueueElement(room, content, answer, user)); + + return answer; + } + + @Override + @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answerlists", allEntries = true) + public Answer updateAnswer(final Answer answer) { + final ClientAuthentication user = userService.getCurrentUser(); + final Answer realAnswer = this.getMyAnswer(answer.getContentId()); + if (user == null || realAnswer == null || !user.getId().equals(realAnswer.getCreatorId())) { + throw new UnauthorizedException(); + } + + final Content content = contentService.get(answer.getContentId()); + /* FIXME: migrate + if (content.getFormat() == Content.Format.TEXT) { + imageUtils.generateThumbnailImage(realAnswer); + content.checkTextStrictOptions(realAnswer); + } + */ + final Room room = roomRepository.findOne(content.getRoomId()); + answer.setCreatorId(user.getId()); + answer.setContentId(content.getId()); + answer.setRoomId(room.getId()); + answerRepository.save(realAnswer); + this.publisher.publishEvent(new NewAnswerEvent(this, room, answer, user, content)); + + return answer; + } + + @Override + @PreAuthorize("isAuthenticated()") + @CacheEvict(value = "answerlists", allEntries = true) + public void deleteAnswer(final String contentId, final String answerId) { + final Content content = contentRepository.findOne(contentId); + if (content == null) { + throw new NotFoundException(); + } + final ClientAuthentication user = userService.getCurrentUser(); + final Room room = roomRepository.findOne(content.getRoomId()); + if (user == null || room == null || !room.getOwnerId().equals(user.getId())) { + throw new UnauthorizedException(); + } + answerRepository.delete(answerId); + + this.publisher.publishEvent(new DeleteAnswerEvent(this, room, content)); + } + + /* + * The "internal" suffix means it is called by internal services that have no authentication! + * TODO: Find a better way of doing this... + */ + @Override + public int countLectureQuestionAnswersInternal(final String roomId) { + return answerRepository.countByRoomIdOnlyLectureVariant(roomRepository.findOne(roomId).getId()); + } + + @Override + public Map<String, Object> countAnswersAndAbstentionsInternal(final String contentId) { + final Content content = contentService.get(contentId); + HashMap<String, Object> map = new HashMap<>(); + + if (content == null) { + return null; + } + + map.put("_id", contentId); + map.put("answers", answerRepository.countByContentIdRound(content.getId(), content.getState().getRound())); + map.put("abstentions", answerRepository.countByContentId(contentId)); + + return map; + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countLectureContentAnswers(final String roomId) { + return this.countLectureQuestionAnswersInternal(roomId); + } + + @Override + @PreAuthorize("isAuthenticated()") + public int countPreparationContentAnswers(final String roomId) { + return this.countPreparationQuestionAnswersInternal(roomId); + } + + /* + * The "internal" suffix means it is called by internal services that have no authentication! + * TODO: Find a better way of doing this... + */ + @Override + public int countPreparationQuestionAnswersInternal(final String roomId) { + return answerRepository.countByRoomIdOnlyPreparationVariant(roomRepository.findOne(roomId).getId()); + } + + @Override + public void setApplicationEventPublisher(final ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } +} diff --git a/src/main/java/de/thm/arsnova/services/ContentService.java b/src/main/java/de/thm/arsnova/services/ContentService.java index 0d7ea8679..aad4dfab8 100644 --- a/src/main/java/de/thm/arsnova/services/ContentService.java +++ b/src/main/java/de/thm/arsnova/services/ContentService.java @@ -1,3 +1,20 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2018 The ARSnova Team and Contributors + * + * ARSnova Backend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova Backend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ /* * This file is part of ARSnova Backend. * Copyright (C) 2012-2018 The ARSnova Team and Contributors @@ -17,14 +34,10 @@ */ package de.thm.arsnova.services; -import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.AnswerStatistics; import de.thm.arsnova.entities.Content; -import de.thm.arsnova.entities.TextAnswer; import de.thm.arsnova.entities.migration.v2.ClientAuthentication; import java.util.List; -import java.util.Map; /** * The functionality the question service should provide. @@ -42,46 +55,10 @@ public interface ContentService extends EntityService<Content> { List<String> getUnAnsweredContentIds(String roomId); - Answer getMyAnswer(String contentId); - - void getFreetextAnswerAndMarkRead(String answerId, ClientAuthentication user); - - AnswerStatistics getStatistics(String contentId, int piRound); - - AnswerStatistics getStatistics(String contentId); - - AnswerStatistics getAllStatistics(String contentId); - - List<TextAnswer> getTextAnswers(String contentId, int piRound, int offset, int limit); - - List<TextAnswer> getTextAnswers(String contentId, int offset, int limit); - - List<TextAnswer> getAllTextAnswers(String contentId, int offset, int limit); - - int countAnswersByContentIdAndRound(String contentId); - - int countAnswersByContentIdAndRound(String contentId, int piRound); - - List<TextAnswer> getTextAnswersByContentId(String contentId, int offset, int limit); - - List<Answer> getMyAnswersByRoomId(String roomId); - - int countTotalAnswersByRoomId(String roomId); - - int countTotalAnswersByContentId(String contentId); - Content save(final String roomId, final Content content); Content update(Content content); - void deleteAnswers(String contentId); - - Answer saveAnswer(String contentId, Answer answer); - - Answer updateAnswer(Answer answer); - - void deleteAnswer(String contentId, String answerId); - List<Content> getLectureContents(String roomId); List<Content> getFlashcards(String roomId); @@ -94,16 +71,6 @@ public interface ContentService extends EntityService<Content> { int countPreparationContents(String roomId); - Map<String, Object> countAnswersAndAbstentionsInternal(String contentId); - - int countLectureContentAnswers(String roomId); - - int countLectureQuestionAnswersInternal(String roomId); - - int countPreparationContentAnswers(String roomId); - - int countPreparationQuestionAnswersInternal(String roomId); - int countFlashcardsForUserInternal(String roomId); void deleteAllContents(String roomId); @@ -132,8 +99,6 @@ public interface ContentService extends EntityService<Content> { void deleteAllLectureAnswers(String roomId); - int countTotalAbstentionsByContentId(String contentId); - void setVotingAdmission(String contentId, boolean disableVoting); void setVotingAdmissions(String roomId, boolean disableVoting, List<Content> contents); diff --git a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java index d1673162e..046d2e8bd 100644 --- a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java @@ -17,14 +17,9 @@ */ package de.thm.arsnova.services; -import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.AnswerStatistics; -import de.thm.arsnova.entities.ChoiceQuestionContent; import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Room; -import de.thm.arsnova.entities.TextAnswer; import de.thm.arsnova.entities.migration.v2.ClientAuthentication; -import de.thm.arsnova.entities.transport.AnswerQueueElement; import de.thm.arsnova.events.*; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; @@ -32,7 +27,6 @@ import de.thm.arsnova.persistance.AnswerRepository; import de.thm.arsnova.persistance.ContentRepository; import de.thm.arsnova.persistance.LogEntryRepository; import de.thm.arsnova.persistance.RoomRepository; -import org.ektorp.DbAccessException; import org.ektorp.DocumentNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,22 +38,18 @@ 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; import java.io.IOException; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; /** - * Performs all content and answer related operations. + * Performs all content related operations. */ @Service public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implements ContentService, ApplicationEventPublisherAware { @@ -77,8 +67,6 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem private static final Logger logger = LoggerFactory.getLogger(ContentServiceImpl.class); - private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>(); - public ContentServiceImpl( ContentRepository repository, AnswerRepository answerRepository, @@ -94,33 +82,6 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem this.userService = userService; } - @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.getRoom(), e.getAnswer(), e.getUser(), e.getQuestion())); - } - } catch (final DbAccessException e) { - logger.error("Could not bulk save answers from queue.", e); - } - } - @Cacheable("contents") @Override public Content get(final String id) { @@ -412,16 +373,6 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem return room; } - @Override - @PreAuthorize("hasPermission(#contentId, 'content', 'owner')") - public void deleteAnswers(final String contentId) { - final Content content = contentRepository.findOne(contentId); - content.resetState(); - /* FIXME: cancel timer */ - update(content); - answerRepository.deleteByContentId(content.getId()); - } - @Override @PreAuthorize("isAuthenticated()") public List<String> getUnAnsweredContentIds(final String roomId) { @@ -437,287 +388,6 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem return user; } - @Override - @PreAuthorize("isAuthenticated()") - public Answer getMyAnswer(final String contentId) { - final Content content = get(contentId); - if (content == null) { - throw new NotFoundException(); - } - return answerRepository.findByContentIdUserPiRound(contentId, Answer.class, userService.getCurrentUser(), content.getState().getRound()); - } - - @Override - public void getFreetextAnswerAndMarkRead(final String answerId, final ClientAuthentication user) { - final Answer answer = answerRepository.findOne(answerId); - if (!(answer instanceof TextAnswer)) { - throw new NotFoundException(); - } - final TextAnswer textAnswer = (TextAnswer) answer; - if (textAnswer.isRead()) { - return; - } - final Room room = roomRepository.findOne(textAnswer.getRoomId()); - if (room.getOwnerId().equals(user.getId())) { - textAnswer.setRead(true); - answerRepository.save(textAnswer); - } - } - - @Override - @PreAuthorize("isAuthenticated()") - public AnswerStatistics getStatistics(final String contentId, final int round) { - final ChoiceQuestionContent content = (ChoiceQuestionContent) contentRepository.findOne(contentId); - if (content == null) { - throw new NotFoundException(); - } - AnswerStatistics stats = answerRepository.findByContentIdRound( - content.getId(), round, content.getOptions().size()); - /* Fill list with zeros to prevent IndexOutOfBoundsExceptions */ - List<Integer> independentCounts = stats.getRoundStatistics().get(round - 1).getIndependentCounts(); - while (independentCounts.size() < content.getOptions().size()) { - independentCounts.add(0); - } - - return stats; - } - - @Override - @PreAuthorize("isAuthenticated()") - public AnswerStatistics getStatistics(final String contentId) { - final Content content = get(contentId); - if (content == null) { - throw new NotFoundException(); - } - - return getStatistics(content.getId(), content.getState().getRound()); - } - - @Override - @PreAuthorize("isAuthenticated()") - public AnswerStatistics getAllStatistics(final String contentId) { - final Content content = get(contentId); - if (content == null) { - throw new NotFoundException(); - } - AnswerStatistics stats = getStatistics(content.getId(), 1); - AnswerStatistics stats2 = getStatistics(content.getId(), 2); - stats.getRoundStatistics().add(stats2.getRoundStatistics().get(1)); - - return stats; - } - - @Override - @PreAuthorize("isAuthenticated()") - public List<TextAnswer> getTextAnswers(final String contentId, final int piRound, final int offset, final int limit) { - /* FIXME: round support not implemented */ - final Content content = contentRepository.findOne(contentId); - if (content == null) { - throw new NotFoundException(); - } - - return getTextAnswersByContentId(contentId, offset, limit); - } - - @Override - @PreAuthorize("isAuthenticated()") - public List<TextAnswer> getTextAnswers(final String contentId, final int offset, final int limit) { - return getTextAnswers(contentId, 0, offset, limit); - } - - @Override - @PreAuthorize("isAuthenticated()") - public List<TextAnswer> getAllTextAnswers(final String contentId, final int offset, final int limit) { - final Content content = get(contentId); - if (content == null) { - throw new NotFoundException(); - } - - return getTextAnswersByContentId(contentId, offset, limit); - } - - @Override - @PreAuthorize("isAuthenticated()") - public int countAnswersByContentIdAndRound(final String contentId) { - final Content content = get(contentId); - if (content == null) { - return 0; - } - - if (content.getFormat() == Content.Format.TEXT) { - return answerRepository.countByContentId(content.getId()); - } else { - return answerRepository.countByContentIdRound(content.getId(), content.getState().getRound()); - } - } - - @Override - @PreAuthorize("isAuthenticated()") - public int countAnswersByContentIdAndRound(final String contentId, final int piRound) { - final Content content = get(contentId); - if (content == null) { - return 0; - } - - return answerRepository.countByContentIdRound(content.getId(), piRound); - } - - @Override - @PreAuthorize("isAuthenticated()") - public int countTotalAbstentionsByContentId(final String contentId) { - final Content content = get(contentId); - if (content == null) { - return 0; - } - - return answerRepository.countByContentId(contentId); - } - - @Override - @PreAuthorize("isAuthenticated()") - public int countTotalAnswersByContentId(final String contentId) { - final Content content = get(contentId); - if (content == null) { - return 0; - } - - return answerRepository.countByContentId(content.getId()); - } - - @Override - @PreAuthorize("isAuthenticated()") - public List<TextAnswer> getTextAnswersByContentId(final String contentId, final int offset, final int limit) { - final List<TextAnswer> answers = answerRepository.findByContentId(contentId, TextAnswer.class, offset, limit); - if (answers == null) { - throw new NotFoundException(); - } - - return answers; - } - - @Override - @PreAuthorize("isAuthenticated()") - public List<Answer> getMyAnswersByRoomId(final String roomId) { - // Load contents first because we are only interested in answers of the latest piRound. - final List<Content> contents = getByRoomId(roomId); - final Map<String, Content> contentIdToContent = new HashMap<>(); - for (final Content content : contents) { - contentIdToContent.put(content.getId(), content); - } - - /* filter answers by active piRound per content */ - final List<Answer> answers = answerRepository.findByUserRoomId(userService.getCurrentUser(), roomId); - final List<Answer> filteredAnswers = new ArrayList<>(); - for (final Answer answer : answers) { - final Content content = contentIdToContent.get(answer.getContentId()); - if (content == null) { - // Content is not present. Most likely it has been locked by the - // Room's creator. Locked Questions do not appear in this list. - continue; - } - - // discard all answers that aren't in the same piRound as the content - if (answer.getRound() == content.getState().getRound()) { - filteredAnswers.add(answer); - } - } - - return filteredAnswers; - } - - @Override - @PreAuthorize("isAuthenticated()") - public int countTotalAnswersByRoomId(final String roomId) { - return answerRepository.countByRoomId(roomId); - } - - @Override - @PreAuthorize("isAuthenticated()") - @CacheEvict(value = "answerlists", key = "#contentId") - public Answer saveAnswer(final String contentId, final Answer answer) { - final ClientAuthentication user = getCurrentUser(); - final Content content = get(contentId); - if (content == null) { - throw new NotFoundException(); - } - final Room room = roomRepository.findOne(content.getRoomId()); - - answer.setCreatorId(user.getId()); - answer.setContentId(content.getId()); - answer.setRoomId(room.getId()); - - /* FIXME: migrate - answer.setQuestionValue(content.calculateValue(answer)); - */ - - if (content.getFormat() == Content.Format.TEXT) { - answer.setRound(0); - /* FIXME: migrate - imageUtils.generateThumbnailImage(answer); - if (content.isFixedAnswer() && content.getBody() != null) { - answer.setAnswerTextRaw(answer.getAnswerText()); - - if (content.isStrictMode()) { - content.checkTextStrictOptions(answer); - } - answer.setQuestionValue(content.evaluateCorrectAnswerFixedText(answer.getAnswerTextRaw())); - answer.setSuccessfulFreeTextAnswer(content.isSuccessfulFreeTextAnswer(answer.getAnswerTextRaw())); - } - */ - } else { - answer.setRound(content.getState().getRound()); - } - - this.answerQueue.offer(new AnswerQueueElement(room, content, answer, user)); - - return answer; - } - - @Override - @PreAuthorize("isAuthenticated()") - @CacheEvict(value = "answerlists", allEntries = true) - public Answer updateAnswer(final Answer answer) { - final ClientAuthentication user = userService.getCurrentUser(); - final Answer realAnswer = this.getMyAnswer(answer.getContentId()); - if (user == null || realAnswer == null || !user.getId().equals(realAnswer.getCreatorId())) { - throw new UnauthorizedException(); - } - - final Content content = get(answer.getContentId()); - /* FIXME: migrate - if (content.getFormat() == Content.Format.TEXT) { - imageUtils.generateThumbnailImage(realAnswer); - content.checkTextStrictOptions(realAnswer); - } - */ - final Room room = roomRepository.findOne(content.getRoomId()); - answer.setCreatorId(user.getId()); - answer.setContentId(content.getId()); - answer.setRoomId(room.getId()); - answerRepository.save(realAnswer); - this.publisher.publishEvent(new NewAnswerEvent(this, room, answer, user, content)); - - return answer; - } - - @Override - @PreAuthorize("isAuthenticated()") - @CacheEvict(value = "answerlists", allEntries = true) - public void deleteAnswer(final String contentId, final String answerId) { - final Content content = contentRepository.findOne(contentId); - if (content == null) { - throw new NotFoundException(); - } - final ClientAuthentication user = userService.getCurrentUser(); - final Room room = roomRepository.findOne(content.getRoomId()); - if (user == null || room == null || !room.getOwnerId().equals(user.getId())) { - throw new UnauthorizedException(); - } - answerRepository.delete(answerId); - - this.publisher.publishEvent(new DeleteAnswerEvent(this, room, content)); - } - /* FIXME: caching */ @Override @PreAuthorize("isAuthenticated()") @@ -778,52 +448,6 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem return contentRepository.countPreparationVariantByRoomId(roomRepository.findOne(roomId).getId()); } - @Override - @PreAuthorize("isAuthenticated()") - public int countLectureContentAnswers(final String roomId) { - return this.countLectureQuestionAnswersInternal(roomId); - } - - /* - * The "internal" suffix means it is called by internal services that have no authentication! - * TODO: Find a better way of doing this... - */ - @Override - public int countLectureQuestionAnswersInternal(final String roomId) { - return answerRepository.countByRoomIdOnlyLectureVariant(roomRepository.findOne(roomId).getId()); - } - - @Override - public Map<String, Object> countAnswersAndAbstentionsInternal(final String contentId) { - final Content content = get(contentId); - HashMap<String, Object> map = new HashMap<>(); - - if (content == null) { - return null; - } - - map.put("_id", contentId); - map.put("answers", answerRepository.countByContentIdRound(content.getId(), content.getState().getRound())); - map.put("abstentions", answerRepository.countByContentId(contentId)); - - return map; - } - - @Override - @PreAuthorize("isAuthenticated()") - public int countPreparationContentAnswers(final String roomId) { - return this.countPreparationQuestionAnswersInternal(roomId); - } - - /* - * The "internal" suffix means it is called by internal services that have no authentication! - * TODO: Find a better way of doing this... - */ - @Override - public int countPreparationQuestionAnswersInternal(final String roomId) { - return answerRepository.countByRoomIdOnlyPreparationVariant(roomRepository.findOne(roomId).getId()); - } - /* * The "internal" suffix means it is called by internal services that have no authentication! * TODO: Find a better way of doing this... @@ -896,6 +520,7 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem this.publisher.publishEvent(event); } + /* TODO: Split and move answer part to AnswerService */ @Override @PreAuthorize("isAuthenticated()") @CacheEvict(value = "answerlists", allEntries = true) @@ -914,6 +539,7 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem this.publisher.publishEvent(new DeleteAllQuestionsAnswersEvent(this, room)); } + /* TODO: Split and move answer part to AnswerService */ /* TODO: Only evict cache entry for the answer's content. This requires some refactoring. */ @Override @PreAuthorize("hasPermission(#roomId, 'room', 'owner')") @@ -929,6 +555,7 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem this.publisher.publishEvent(new DeleteAllPreparationAnswersEvent(this, room)); } + /* TODO: Split and move answer part to AnswerService */ /* TODO: Only evict cache entry for the answer's content. This requires some refactoring. */ @Override @PreAuthorize("hasPermission(#roomId, 'room', 'owner')") diff --git a/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java index 456673b33..8ade69539 100644 --- a/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java +++ b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java @@ -36,6 +36,7 @@ import de.thm.arsnova.events.*; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; +import de.thm.arsnova.services.AnswerService; import de.thm.arsnova.services.CommentService; import de.thm.arsnova.services.FeedbackService; import de.thm.arsnova.services.ContentService; @@ -81,6 +82,9 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova @Autowired private ContentService contentService; + @Autowired + private AnswerService answerService; + @Autowired private CommentService commentService; @@ -216,7 +220,7 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova public void onData(SocketIOClient client, String answerId, AckRequest ackRequest) { final ClientAuthentication user = userService.getUserToSocketId(client.getSessionId()); try { - contentService.getFreetextAnswerAndMarkRead(answerId, user); + answerService.getFreetextAnswerAndMarkRead(answerId, user); } catch (NotFoundException | UnauthorizedException e) { logger.error("Marking answer {} as read failed for user {} with exception {}", answerId, user, e.getMessage()); } @@ -378,8 +382,8 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova client.sendEvent("unansweredLecturerQuestions", contentService.getUnAnsweredLectureContentIds(roomId, user)); client.sendEvent("unansweredPreparationQuestions", contentService.getUnAnsweredPreparationContentIds(roomId, user)); /* FIXME: Content variant is ignored for now */ - client.sendEvent("countLectureQuestionAnswers", contentService.countTotalAnswersByRoomId(roomId)); - client.sendEvent("countPreparationQuestionAnswers", contentService.countTotalAnswersByRoomId(roomId)); + client.sendEvent("countLectureQuestionAnswers", answerService.countTotalAnswersByRoomId(roomId)); + client.sendEvent("countPreparationQuestionAnswers", answerService.countTotalAnswersByRoomId(roomId)); client.sendEvent("activeUserCountData", roomService.activeUsers(roomId)); // client.sendEvent("learningProgressOptions", room.getLearningProgressOptions()); final de.thm.arsnova.entities.Feedback fb = feedbackService.getByRoomId(roomId); @@ -522,10 +526,10 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova public void visit(NewAnswerEvent event) { final String roomId = event.getRoom().getId(); this.reportAnswersToContentAvailable(event.getRoom(), new Content(event.getContent())); - broadcastInRoom(roomId, "countQuestionAnswersByQuestionId", contentService.countAnswersAndAbstentionsInternal(event.getContent().getId())); + broadcastInRoom(roomId, "countQuestionAnswersByQuestionId", answerService.countAnswersAndAbstentionsInternal(event.getContent().getId())); /* FIXME: Content variant is ignored for now */ - broadcastInRoom(roomId, "countLectureQuestionAnswers", contentService.countTotalAnswersByRoomId(roomId)); - broadcastInRoom(roomId, "countPreparationQuestionAnswers", contentService.countTotalAnswersByRoomId(roomId)); + broadcastInRoom(roomId, "countLectureQuestionAnswers", answerService.countTotalAnswersByRoomId(roomId)); + broadcastInRoom(roomId, "countPreparationQuestionAnswers", answerService.countTotalAnswersByRoomId(roomId)); // Update the unanswered count for the content variant that was answered. final de.thm.arsnova.entities.Content content = event.getContent(); @@ -544,8 +548,8 @@ public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, Arsnova this.reportAnswersToContentAvailable(event.getRoom(), new Content(event.getQuestion())); // We do not know which user's answer was deleted, so we can't update his 'unanswered' list of questions... /* FIXME: Content variant is ignored for now */ - broadcastInRoom(roomId, "countLectureQuestionAnswers", contentService.countTotalAnswersByRoomId(roomId)); - broadcastInRoom(roomId, "countPreparationQuestionAnswers", contentService.countTotalAnswersByRoomId(roomId)); + broadcastInRoom(roomId, "countLectureQuestionAnswers", answerService.countTotalAnswersByRoomId(roomId)); + broadcastInRoom(roomId, "countPreparationQuestionAnswers", answerService.countTotalAnswersByRoomId(roomId)); } @Async -- GitLab