/* * This file is part of ARSnova Backend. * Copyright (C) 2012-2017 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.services; import de.thm.arsnova.FeedbackStorage; import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Feedback; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.events.DeleteFeedbackForSessionsEvent; import de.thm.arsnova.events.NewFeedbackEvent; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Performs all feedback related operations. */ @Service public class FeedbackService implements IFeedbackService, ApplicationEventPublisherAware { private static final int DEFAULT_SCHEDULER_DELAY = 5000; private static final double Z_THRESHOLD = 0.1; /** * minutes, after which the feedback is deleted */ @Value("${feedback.cleanup}") private int cleanupFeedbackDelay; @Autowired private IDatabaseDao databaseDao; private FeedbackStorage feedbackStorage; private ApplicationEventPublisher publisher; public void setDatabaseDao(final IDatabaseDao newDatabaseDao) { databaseDao = newDatabaseDao; } @PostConstruct public void init() { feedbackStorage = new FeedbackStorage(); } @Override @Scheduled(fixedDelay = DEFAULT_SCHEDULER_DELAY) public void cleanFeedbackVotes() { Map<Session, List<User>> deletedFeedbackOfUsersInSession = feedbackStorage.cleanFeedbackVotes(cleanupFeedbackDelay); /* * mapping (Session -> Users) is not suitable for web sockets, because we want to sent all affected * sessions to a single user in one go instead of sending multiple messages for each session. Hence, * we need the mapping (User -> Sessions) */ final Map<User, Set<Session>> affectedSessionsOfUsers = new HashMap<>(); for (Map.Entry<Session, List<User>> entry : deletedFeedbackOfUsersInSession.entrySet()) { final Session session = entry.getKey(); final List<User> users = entry.getValue(); for (User user : users) { Set<Session> affectedSessions; if (affectedSessionsOfUsers.containsKey(user)) { affectedSessions = affectedSessionsOfUsers.get(user); } else { affectedSessions = new HashSet<>(); } affectedSessions.add(session); affectedSessionsOfUsers.put(user, affectedSessions); } } // Send feedback reset event to all affected users for (Map.Entry<User, Set<Session>> entry : affectedSessionsOfUsers.entrySet()) { final User user = entry.getKey(); final Set<Session> arsSessions = entry.getValue(); this.publisher.publishEvent(new DeleteFeedbackForSessionsEvent(this, arsSessions, user)); } // For each session that has deleted feedback, send the new feedback to all clients for (Session session : deletedFeedbackOfUsersInSession.keySet()) { this.publisher.publishEvent(new NewFeedbackEvent(this, session)); } } @Override public void cleanFeedbackVotesInSession(final String keyword, final int cleanupFeedbackDelayInMins) { final Session session = databaseDao.getSessionFromKeyword(keyword); List<User> affectedUsers = feedbackStorage.cleanFeedbackVotesInSession(session, cleanupFeedbackDelayInMins); Set<Session> sessionSet = new HashSet<>(); sessionSet.add(session); // Send feedback reset event to all affected users for (User user : affectedUsers) { this.publisher.publishEvent(new DeleteFeedbackForSessionsEvent(this, sessionSet, user)); } // send the new feedback to all clients in affected session this.publisher.publishEvent(new NewFeedbackEvent(this, session)); } @Override public Feedback getFeedback(final String keyword) { final Session session = databaseDao.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } return feedbackStorage.getFeedback(session); } @Override public int getFeedbackCount(final String keyword) { final Feedback feedback = this.getFeedback(keyword); final List<Integer> values = feedback.getValues(); return values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY); } @Override public double getAverageFeedback(final String sessionkey) { final Session session = databaseDao.getSessionFromKeyword(sessionkey); if (session == null) { throw new NotFoundException(); } final Feedback feedback = feedbackStorage.getFeedback(session); final List<Integer> values = feedback.getValues(); final double count = values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY); final double sum = values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) * 2 + values.get(Feedback.FEEDBACK_AWAY) * 3; if (Math.abs(count) < Z_THRESHOLD) { throw new NoContentException(); } return sum / count; } @Override public long getAverageFeedbackRounded(final String sessionkey) { return Math.round(getAverageFeedback(sessionkey)); } @Override public boolean saveFeedback(final String keyword, final int value, final User user) { final Session session = databaseDao.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } feedbackStorage.saveFeedback(session, value, user); this.publisher.publishEvent(new NewFeedbackEvent(this, session)); return true; } @Override public Integer getMyFeedback(final String keyword, final User user) { final Session session = databaseDao.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } return feedbackStorage.getMyFeedback(session, user); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } }