/**
 * This class is the parent class for all *WorkerServlets. This class provides logging of exceptions
 * that are uncaught by child *WorkerServlet classes. Child classes should also perform their own
 * logging of exceptions that could occur during the execution of the servlet.
 */
@SuppressWarnings("serial")
public abstract class WorkerServlet extends HttpServlet {

  protected static Logger log = Utils.getLogger();

  protected String servletName = "unspecified";
  protected String action = "unspecified";

  public void doPost(HttpServletRequest req, HttpServletResponse resp) {
    try {
      doGet(req, resp);
    } catch (Exception e) {
      log.severe("Exception occured while performing " + servletName + e.getMessage());
    }
  }

  public abstract void doGet(HttpServletRequest req, HttpServletResponse resp);

  protected void logMessage(HttpServletRequest request, String message) {
    String url = HttpRequestHelper.getRequestedURL(request);
    ActivityLogEntry activityLogEntry =
        new ActivityLogEntry(servletName, action, null, message, url);
    log.log(Level.INFO, activityLogEntry.generateLogMessage());
  }
}
public class FeedbackSessionsDb extends EntitiesDb {

  public static final String ERROR_UPDATE_NON_EXISTENT =
      "Trying to update non-existent Feedback Session : ";
  private static final Logger log = Utils.getLogger();

  public void createFeedbackSessions(Collection<FeedbackSessionAttributes> feedbackSessionsToAdd)
      throws InvalidParametersException {
    List<EntityAttributes> feedbackSessionsToUpdate = createEntities(feedbackSessionsToAdd);
    for (EntityAttributes entity : feedbackSessionsToUpdate) {
      FeedbackSessionAttributes session = (FeedbackSessionAttributes) entity;
      try {
        updateFeedbackSession(session);
      } catch (EntityDoesNotExistException e) {
        // This situation is not tested as replicating such a situation is
        // difficult during testing
        Assumption.fail("Entity found be already existing and not existing simultaneously");
      }
    }
  }

  public List<FeedbackSessionAttributes> getAllOpenFeedbackSessions(
      Date start, Date end, double zone) {

    List<FeedbackSessionAttributes> list = new LinkedList<FeedbackSessionAttributes>();

    final Query endTimequery =
        getPM()
            .newQuery(
                "SELECT FROM teammates.storage.entity.FeedbackSession "
                    + "WHERE this.endTime>rangeStart && this.endTime<=rangeEnd "
                    + " PARAMETERS java.util.Date rangeStart, "
                    + "java.util.Date rangeEnd");

    final Query startTimequery =
        getPM()
            .newQuery(
                "SELECT FROM teammates.storage.entity.FeedbackSession "
                    + "WHERE this.startTime>=rangeStart && this.startTime<rangeEnd "
                    + "PARAMETERS java.util.Date rangeStart, "
                    + "java.util.Date rangeEnd");

    Calendar startCal = Calendar.getInstance();
    startCal.setTime(start);
    Calendar endCal = Calendar.getInstance();
    endCal.setTime(end);

    Date curStart = TimeHelper.convertToUserTimeZone(startCal, -25).getTime();
    Date curEnd = TimeHelper.convertToUserTimeZone(endCal, 25).getTime();

    @SuppressWarnings("unchecked")
    List<FeedbackSession> endEntities =
        (List<FeedbackSession>) endTimequery.execute(curStart, curEnd);
    @SuppressWarnings("unchecked")
    List<FeedbackSession> startEntities =
        (List<FeedbackSession>) startTimequery.execute(curStart, curEnd);

    List<FeedbackSession> endTimeEntities = new ArrayList<FeedbackSession>(endEntities);
    List<FeedbackSession> startTimeEntities = new ArrayList<FeedbackSession>(startEntities);

    endTimeEntities.removeAll(startTimeEntities);
    startTimeEntities.removeAll(endTimeEntities);
    endTimeEntities.addAll(startTimeEntities);

    Iterator<FeedbackSession> it = endTimeEntities.iterator();

    while (it.hasNext()) {
      startCal.setTime(start);
      endCal.setTime(end);
      FeedbackSessionAttributes fs = new FeedbackSessionAttributes(it.next());

      Date standardStart = TimeHelper.convertToUserTimeZone(startCal, fs.timeZone - zone).getTime();
      Date standardEnd = TimeHelper.convertToUserTimeZone(endCal, fs.timeZone - zone).getTime();

      if ((fs.startTime.getTime() >= standardStart.getTime()
              && fs.startTime.getTime() < standardEnd.getTime())
          || (fs.endTime.getTime() > standardStart.getTime()
              && fs.endTime.getTime() <= standardEnd.getTime())) list.add(fs);
    }

    return list;
  }

  /**
   * Preconditions: <br>
   * * All parameters are non-null.
   *
   * @return Null if not found.
   */
  public FeedbackSessionAttributes getFeedbackSession(String courseId, String feedbackSessionName) {

    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, feedbackSessionName);
    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, courseId);

    FeedbackSession fs = getFeedbackSessionEntity(feedbackSessionName, courseId);

    if (fs == null) {
      log.info("Trying to get non-existent Session: " + feedbackSessionName + "/" + courseId);
      return null;
    }
    return new FeedbackSessionAttributes(fs);
  }

  /**
   * @return empty list if none found.
   * @deprecated Not scalable. Created for data migration purposes.
   */
  @Deprecated
  public List<FeedbackSessionAttributes> getAllFeedbackSessions() {
    List<FeedbackSession> allFS = getAllFeedbackSessionEntities();
    List<FeedbackSessionAttributes> fsaList = new ArrayList<FeedbackSessionAttributes>();

    for (FeedbackSession fs : allFS) {
      fsaList.add(new FeedbackSessionAttributes(fs));
    }
    return fsaList;
  }

  /**
   * Preconditions: <br>
   * * All parameters are non-null.
   *
   * @return An empty list if no non-private sessions are found.
   */
  public List<FeedbackSessionAttributes> getNonPrivateFeedbackSessions() {

    List<FeedbackSession> fsList = getNonPrivateFeedbackSessionEntities();
    List<FeedbackSessionAttributes> fsaList = new ArrayList<FeedbackSessionAttributes>();

    for (FeedbackSession fs : fsList) {
      fsaList.add(new FeedbackSessionAttributes(fs));
    }
    return fsaList;
  }

  /**
   * Preconditions: <br>
   * * All parameters are non-null.
   *
   * @return An empty list if no sessions are found for the given course.
   */
  public List<FeedbackSessionAttributes> getFeedbackSessionsForCourse(String courseId) {

    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, courseId);

    List<FeedbackSession> fsList = getFeedbackSessionEntitiesForCourse(courseId);
    List<FeedbackSessionAttributes> fsaList = new ArrayList<FeedbackSessionAttributes>();

    for (FeedbackSession fs : fsList) {
      fsaList.add(new FeedbackSessionAttributes(fs));
    }
    return fsaList;
  }

  /**
   * Preconditions: <br>
   * * All parameters are non-null.
   *
   * @return An empty list if no sessions are found that have unsent open emails.
   */
  public List<FeedbackSessionAttributes> getFeedbackSessionsWithUnsentOpenEmail() {

    List<FeedbackSession> fsList = getFeedbackSessionEntitiesWithUnsentOpenEmail();
    List<FeedbackSessionAttributes> fsaList = new ArrayList<FeedbackSessionAttributes>();

    for (FeedbackSession fs : fsList) {
      fsaList.add(new FeedbackSessionAttributes(fs));
    }
    return fsaList;
  }

  /**
   * Preconditions: <br>
   * * All parameters are non-null.
   *
   * @return An empty list if no sessions are found that have unsent published emails.
   */
  public List<FeedbackSessionAttributes> getFeedbackSessionsWithUnsentPublishedEmail() {

    List<FeedbackSession> fsList = getFeedbackSessionEntitiesWithUnsentPublishedEmail();
    List<FeedbackSessionAttributes> fsaList = new ArrayList<FeedbackSessionAttributes>();

    for (FeedbackSession fs : fsList) {
      fsaList.add(new FeedbackSessionAttributes(fs));
    }
    return fsaList;
  }

  /**
   * Updates the feedback session identified by {@code newAttributes.feedbackSesionName} and {@code
   * newAttributes.courseId}. For the remaining parameters, the existing value is preserved if the
   * parameter is null (due to 'keep existing' policy).<br>
   * Preconditions: <br>
   * * {@code newAttributes.feedbackSesionName} and {@code newAttributes.courseId} are non-null and
   * correspond to an existing feedback session. <br>
   */
  public void updateFeedbackSession(FeedbackSessionAttributes newAttributes)
      throws InvalidParametersException, EntityDoesNotExistException {

    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, newAttributes);

    newAttributes.sanitizeForSaving();

    if (!newAttributes.isValid()) {
      throw new InvalidParametersException(newAttributes.getInvalidityInfo());
    }

    FeedbackSession fs = (FeedbackSession) getEntity(newAttributes);

    if (fs == null) {
      throw new EntityDoesNotExistException(ERROR_UPDATE_NON_EXISTENT + newAttributes.toString());
    }
    fs.setInstructions(newAttributes.instructions);
    fs.setStartTime(newAttributes.startTime);
    fs.setEndTime(newAttributes.endTime);
    fs.setSessionVisibleFromTime(newAttributes.sessionVisibleFromTime);
    fs.setResultsVisibleFromTime(newAttributes.resultsVisibleFromTime);
    fs.setTimeZone(newAttributes.timeZone);
    fs.setGracePeriod(newAttributes.gracePeriod);
    fs.setFeedbackSessionType(newAttributes.feedbackSessionType);
    fs.setSentOpenEmail(newAttributes.sentOpenEmail);
    fs.setSentPublishedEmail(newAttributes.sentPublishedEmail);
    fs.setIsOpeningEmailEnabled(newAttributes.isOpeningEmailEnabled);
    fs.setSendClosingEmail(newAttributes.isClosingEmailEnabled);
    fs.setSendPublishedEmail(newAttributes.isPublishedEmailEnabled);

    getPM().close();
  }

  public void deleteFeedbackSessionsForCourses(List<String> courseIds) {
    Assumption.assertNotNull(Const.StatusCodes.DBLEVEL_NULL_INPUT, courseIds);

    List<FeedbackSession> feedbackSessionList = getFeedbackSessionEntitiesForCourses(courseIds);

    getPM().deletePersistentAll(feedbackSessionList);
    getPM().flush();
  }

  private List<FeedbackSession> getFeedbackSessionEntitiesForCourses(List<String> courseIds) {
    Query q = getPM().newQuery(FeedbackSession.class);
    q.setFilter(":p.contains(courseId)");

    @SuppressWarnings("unchecked")
    List<FeedbackSession> feedbackSessionList = (List<FeedbackSession>) q.execute(courseIds);
    return feedbackSessionList;
  }

  private List<FeedbackSession> getAllFeedbackSessionEntities() {

    Query q = getPM().newQuery(FeedbackSession.class);

    @SuppressWarnings("unchecked")
    List<FeedbackSession> fsList = (List<FeedbackSession>) q.execute();

    return fsList;
  }

  private List<FeedbackSession> getNonPrivateFeedbackSessionEntities() {
    Query q = getPM().newQuery(FeedbackSession.class);
    q.declareParameters("Enum private");
    q.setFilter("feedbackSessionType != private");

    @SuppressWarnings("unchecked")
    List<FeedbackSession> fsList = (List<FeedbackSession>) q.execute(FeedbackSessionType.PRIVATE);
    return fsList;
  }

  private List<FeedbackSession> getFeedbackSessionEntitiesForCourse(String courseId) {
    Query q = getPM().newQuery(FeedbackSession.class);
    q.declareParameters("String courseIdParam");
    q.setFilter("courseId == courseIdParam");

    @SuppressWarnings("unchecked")
    List<FeedbackSession> fsList = (List<FeedbackSession>) q.execute(courseId);
    return fsList;
  }

  private List<FeedbackSession> getFeedbackSessionEntitiesWithUnsentOpenEmail() {
    Query q = getPM().newQuery(FeedbackSession.class);
    q.declareParameters("boolean sentParam, Enum notTypeParam");
    q.setFilter("sentOpenEmail == sentParam && feedbackSessionType != notTypeParam");

    @SuppressWarnings("unchecked")
    List<FeedbackSession> fsList =
        (List<FeedbackSession>) q.execute(false, FeedbackSessionType.PRIVATE);
    return fsList;
  }

  private List<FeedbackSession> getFeedbackSessionEntitiesWithUnsentPublishedEmail() {
    Query q = getPM().newQuery(FeedbackSession.class);
    q.declareParameters("boolean sentParam, Enum notTypeParam");
    q.setFilter("sentPublishedEmail == sentParam && feedbackSessionType != notTypeParam");

    @SuppressWarnings("unchecked")
    List<FeedbackSession> fsList =
        (List<FeedbackSession>) q.execute(false, FeedbackSessionType.PRIVATE);
    return fsList;
  }

  private FeedbackSession getFeedbackSessionEntity(String feedbackSessionName, String courseId) {

    Query q = getPM().newQuery(FeedbackSession.class);
    q.declareParameters("String feedbackSessionNameParam, String courseIdParam");
    q.setFilter("feedbackSessionName == feedbackSessionNameParam && courseId == courseIdParam");

    @SuppressWarnings("unchecked")
    List<FeedbackSession> feedbackSessionList =
        (List<FeedbackSession>) q.execute(feedbackSessionName, courseId);

    if (feedbackSessionList.isEmpty() || JDOHelper.isDeleted(feedbackSessionList.get(0))) {
      return null;
    }

    return feedbackSessionList.get(0);
  }

  @Override
  protected Object getEntity(EntityAttributes attributes) {
    FeedbackSessionAttributes feedbackSessionToGet = (FeedbackSessionAttributes) attributes;
    return getFeedbackSessionEntity(
        feedbackSessionToGet.feedbackSessionName, feedbackSessionToGet.courseId);
  }
}
public class FeedbackRubricResponseDetails extends FeedbackResponseDetails {

  private static final Logger log = Utils.getLogger();

  /**
   * List of integers, the size of the list corresponds to the number of sub-questions Each integer
   * at index i, represents the choice chosen for sub-question i
   */
  public List<Integer> answer;

  public FeedbackRubricResponseDetails() {
    super(FeedbackQuestionType.RUBRIC);
  }

  @Override
  public void extractResponseDetails(
      FeedbackQuestionType questionType, FeedbackQuestionDetails questionDetails, String[] answer) {

    /**
     * Example: a response in the form: "0-1,1-0" means that for sub-question 0, choice 1 is chosen,
     * and for sub-question 1, choice 0 is chosen.
     */
    String rawResponses = answer[0];
    FeedbackRubricQuestionDetails fqd = (FeedbackRubricQuestionDetails) questionDetails;

    initializeEmptyAnswerList(fqd.getNumOfRubricSubQuestions());

    // Parse and extract answers
    String[] subQuestionResponses = rawResponses.split(Pattern.quote(","));
    for (String subQuestionResponse : subQuestionResponses) {
      String[] subQuestionIndexAndChoice = subQuestionResponse.split(Pattern.quote("-"));

      if (subQuestionIndexAndChoice.length != 2) {
        // Expected length is 2.
        // Failed to parse, ignore response.
        continue;
      }

      try {
        int subQuestionIndex = Integer.parseInt(subQuestionIndexAndChoice[0]);
        int subQuestionChoice = Integer.parseInt(subQuestionIndexAndChoice[1]);
        if (subQuestionIndex >= 0
            && subQuestionIndex < fqd.getNumOfRubricSubQuestions()
            && subQuestionChoice >= 0
            && subQuestionChoice < fqd.getNumOfRubricChoices()) {
          setAnswer(subQuestionIndex, subQuestionChoice);
        } // else the indexes are invalid.
      } catch (NumberFormatException e) {
        // Failed to parse, ignore response.
        log.warning(TeammatesException.toStringWithStackTrace(e));
      }
    }
  }

  /**
   * Initializes the answer list to have empty responses -1 indicates no choice chosen
   *
   * @param numSubQuestions
   */
  private void initializeEmptyAnswerList(int numSubQuestions) {
    answer = new ArrayList<Integer>();
    for (int i = 0; i < numSubQuestions; i++) {
      answer.add(-1);
    }
  }

  @Override
  public String getAnswerString() {
    return this.answer.toString();
  }

  @Override
  public String getAnswerHtml(FeedbackQuestionDetails questionDetails) {
    FeedbackRubricQuestionDetails fqd = (FeedbackRubricQuestionDetails) questionDetails;
    StringBuilder html = new StringBuilder(100);
    for (int i = 0; i < answer.size(); i++) {
      int chosenIndex = answer.get(i);
      String chosenChoice = "";
      if (chosenIndex == -1) {
        chosenChoice =
            "<span class=\"color_neutral\"><i>"
                + Const.INSTRUCTOR_FEEDBACK_RESULTS_MISSING_RESPONSE
                + "</i></span>";
        html.append(
            StringHelper.integerToLowerCaseAlphabeticalIndex(i + 1) + ") " + chosenChoice + "<br>");
      } else {
        chosenChoice = Sanitizer.sanitizeForHtml(fqd.getRubricChoices().get(answer.get(i)));
        html.append(
            StringHelper.integerToLowerCaseAlphabeticalIndex(i + 1)
                + ") "
                + chosenChoice
                + " <span class=\"color_neutral\"><i>(Choice "
                + (chosenIndex + 1)
                + ")</i></span><br>");
      }
    }

    return html.toString();
  }

  @Override
  public String getAnswerCsv(FeedbackQuestionDetails questionDetails) {
    return answer.toString();
  }

  public int getAnswer(int subQuestionIndex) {
    return answer.get(subQuestionIndex);
  }

  public void setAnswer(int subQuestionIndex, int choice) {
    this.answer.set(subQuestionIndex, choice);
  }
}
Beispiel #4
0
/** Handles the logic related to {@link CommentAttributes}. */
public class CommentsLogic {

  private static CommentsLogic instance;

  @SuppressWarnings("unused") // used by test
  private static final Logger log = Utils.getLogger();

  private static final CommentsDb commentsDb = new CommentsDb();

  private static final CoursesLogic coursesLogic = CoursesLogic.inst();
  private static final InstructorsLogic instructorsLogic = InstructorsLogic.inst();
  private static final StudentsLogic studentsLogic = StudentsLogic.inst();
  private static final FeedbackQuestionsLogic fqLogic = FeedbackQuestionsLogic.inst();
  private static final FeedbackResponsesLogic frLogic = FeedbackResponsesLogic.inst();
  private static final FeedbackResponseCommentsLogic frcLogic =
      FeedbackResponseCommentsLogic.inst();

  public static CommentsLogic inst() {
    if (instance == null) {
      instance = new CommentsLogic();
    }
    return instance;
  }

  /** ********** CRUD *********** */
  public CommentAttributes createComment(CommentAttributes comment)
      throws InvalidParametersException, EntityAlreadyExistsException, EntityDoesNotExistException {
    verifyIsCoursePresent(comment.courseId, "create");
    verifyIsInstructorOfCourse(comment.courseId, comment.giverEmail);

    return commentsDb.createEntity(comment);
  }

  public CommentAttributes getComment(Long commentId) {
    return commentsDb.getComment(commentId);
  }

  public List<CommentAttributes> getCommentsForGiver(String courseId, String giverEmail)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "get");
    return commentsDb.getCommentsForGiver(courseId, giverEmail);
  }

  public List<CommentAttributes> getCommentsForReceiver(
      String courseId,
      String giverEmail,
      CommentParticipantType recipientType,
      String receiverEmail)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "get");
    List<CommentAttributes> comments =
        commentsDb.getCommentsForReceiver(courseId, recipientType, receiverEmail);
    Iterator<CommentAttributes> iterator = comments.iterator();
    while (iterator.hasNext()) {
      CommentAttributes c = iterator.next();
      if (!c.giverEmail.equals(giverEmail)) {
        iterator.remove();
      }
    }
    return comments;
  }

  public List<CommentAttributes> getCommentsForReceiver(
      String courseId, CommentParticipantType recipientType, String receiverEmail)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "get");
    return commentsDb.getCommentsForReceiver(courseId, recipientType, receiverEmail);
  }

  public List<CommentAttributes> getCommentsForSendingState(
      String courseId, CommentSendingState sendingState) throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "get");
    return commentsDb.getCommentsForSendingState(courseId, sendingState);
  }

  public void updateCommentsSendingState(
      String courseId, CommentSendingState oldState, CommentSendingState newState)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "clear pending");
    commentsDb.updateComments(courseId, oldState, newState);
  }

  public CommentAttributes updateComment(CommentAttributes comment)
      throws InvalidParametersException, EntityDoesNotExistException {
    verifyIsCoursePresent(comment.courseId, "update");

    return commentsDb.updateComment(comment);
  }

  /**
   * update comment's giver email (assume to be an instructor)
   *
   * @param courseId
   * @param oldInstrEmail
   * @param updatedInstrEmail
   */
  public void updateInstructorEmail(
      String courseId, String oldInstrEmail, String updatedInstrEmail) {
    commentsDb.updateInstructorEmail(courseId, oldInstrEmail, updatedInstrEmail);
  }

  /**
   * update comment's recipient email (assume to be a student)
   *
   * @param courseId
   * @param oldStudentEmail
   * @param updatedStudentEmail
   */
  public void updateStudentEmail(
      String courseId, String oldStudentEmail, String updatedStudentEmail) {
    commentsDb.updateStudentEmail(courseId, oldStudentEmail, updatedStudentEmail);
  }

  public void deleteCommentsForInstructor(String courseId, String instructorEmail) {
    commentsDb.deleteCommentsByInstructorEmail(courseId, instructorEmail);
  }

  public void deleteCommentsForStudent(String courseId, String studentEmail) {
    commentsDb.deleteCommentsByStudentEmail(courseId, studentEmail);
  }

  public void deleteCommentsForTeam(String courseId, String teamName) {
    commentsDb.deleteCommentsForTeam(courseId, teamName);
  }

  public void deleteCommentsForSection(String courseId, String sectionName) {
    commentsDb.deleteCommentsForSection(courseId, sectionName);
  }

  public void deleteCommentsForCourse(String courseId) {
    commentsDb.deleteCommentsForCourse(courseId);
  }

  public void deleteCommentAndDocument(CommentAttributes comment) {
    this.deleteComment(comment);
    this.deleteDocument(comment);
  }

  public void deleteComment(CommentAttributes comment) {
    commentsDb.deleteEntity(comment);
  }

  public void deleteDocument(CommentAttributes comment) {
    commentsDb.deleteDocument(comment);
  }

  public List<CommentAttributes> getCommentDrafts(String giverEmail) {
    return commentsDb.getCommentDrafts(giverEmail);
  }

  /**
   * Create or update document for comment
   *
   * @param comment
   */
  public void putDocument(CommentAttributes comment) {
    commentsDb.putDocument(comment);
  }

  public CommentSearchResultBundle searchComment(
      String queryString, List<InstructorAttributes> instructors, String cursorString) {
    return commentsDb.search(queryString, instructors, cursorString);
  }

  private void verifyIsCoursePresent(String courseId, String action)
      throws EntityDoesNotExistException {
    if (!coursesLogic.isCoursePresent(courseId)) {
      throw new EntityDoesNotExistException(
          "Trying to " + action + " comments for a course that does not exist.");
    }
  }

  private void verifyIsInstructorOfCourse(String courseId, String email)
      throws EntityDoesNotExistException {
    InstructorAttributes instructor = instructorsLogic.getInstructorForEmail(courseId, email);
    if (instructor == null) {
      throw new EntityDoesNotExistException(
          "User " + email + " is not a registered instructor for course " + courseId + ".");
    }
  }

  /** ********** Get Comments For an Instructor *********** */

  /**
   * Get comments visible for the given instructor
   *
   * @param instructor
   * @return list of {@link CommentAttributes}
   * @throws EntityDoesNotExistException when the course doesn't exist
   */
  public List<CommentAttributes> getCommentsForInstructor(InstructorAttributes instructor)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(instructor.courseId, "get");
    verifyIsInstructorOfCourse(instructor.courseId, instructor.email);
    HashSet<String> commentsVisitedSet = new HashSet<String>();

    // When the given instructor is the comment giver,
    List<CommentAttributes> comments =
        getCommentsForGiverAndStatus(instructor.courseId, instructor.email, CommentStatus.FINAL);
    for (CommentAttributes c : comments) {
      preventAppendingThisCommentAgain(commentsVisitedSet, c);
    }

    // When other giver's comments are visible to the given instructor
    List<CommentAttributes> commentsForOtherInstructor =
        getCommentsForCommentViewer(instructor.courseId, CommentParticipantType.INSTRUCTOR);
    removeNonVisibleCommentsForInstructor(commentsForOtherInstructor, commentsVisitedSet, comments);

    java.util.Collections.sort(comments);

    return comments;
  }

  private void removeNonVisibleCommentsForInstructor(
      List<CommentAttributes> commentsForInstructor,
      HashSet<String> commentsVisitedSet,
      List<CommentAttributes> comments) {
    for (CommentAttributes c : commentsForInstructor) {
      removeGiverAndRecipientNameByVisibilityOptions(c, CommentParticipantType.INSTRUCTOR);
      appendComments(c, comments, commentsVisitedSet);
    }
  }

  private List<CommentAttributes> getCommentsForCommentViewer(
      String courseId, CommentParticipantType commentViewerType)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "get");
    return commentsDb.getCommentsForCommentViewer(courseId, commentViewerType);
  }

  private List<CommentAttributes> getCommentsForGiverAndStatus(
      String courseId, String giverEmail, CommentStatus status) throws EntityDoesNotExistException {
    verifyIsCoursePresent(courseId, "get");
    return commentsDb.getCommentsForGiverAndStatus(courseId, giverEmail, status);
  }

  /** ********** Get Comments For a Student *********** */

  /**
   * Get comments visible to the given student
   *
   * @param student
   * @return list of {@link CommentAttributes}
   * @throws EntityDoesNotExistException when the course doesn't exist
   */
  public List<CommentAttributes> getCommentsForStudent(StudentAttributes student)
      throws EntityDoesNotExistException {
    verifyIsCoursePresent(student.course, "get");
    List<StudentAttributes> teammates =
        studentsLogic.getStudentsForTeam(student.team, student.course);
    List<StudentAttributes> studentsInTheSameSection =
        studentsLogic.getStudentsForSection(student.section, student.course);
    List<String> teammatesEmails = getTeammatesEmails(teammates);
    List<String> sectionStudentsEmails = getSectionStudentsEmails(studentsInTheSameSection);
    List<String> teamsInThisSection = getTeamsForSection(studentsInTheSameSection);

    List<CommentAttributes> comments = new ArrayList<CommentAttributes>();
    HashSet<String> commentsVisitedSet = new HashSet<String>();

    // Get comments sent to the given student
    List<CommentAttributes> commentsForStudent =
        getCommentsForReceiver(student.course, CommentParticipantType.PERSON, student.email);
    removeNonVisibleCommentsForStudent(commentsForStudent, commentsVisitedSet, comments);

    // Get comments visible to the given student's teammates
    List<CommentAttributes> commentsForTeam =
        getCommentsForCommentViewer(student.course, CommentParticipantType.TEAM);
    removeNonVisibleCommentsForTeam(
        commentsForTeam, student, teammatesEmails, commentsVisitedSet, comments);

    // Get comments visible to the given student's section
    List<CommentAttributes> commentsForSection =
        getCommentsForCommentViewer(student.course, CommentParticipantType.SECTION);
    removeNonVisibleCommentsForSection(
        commentsForSection,
        student,
        teammatesEmails,
        sectionStudentsEmails,
        teamsInThisSection,
        commentsVisitedSet,
        comments);

    // Get comments visible to the whole course
    List<CommentAttributes> commentsForCourse =
        getCommentsForCommentViewer(student.course, CommentParticipantType.COURSE);
    removeNonVisibleCommentsForCourse(
        commentsForCourse,
        student,
        teammatesEmails,
        sectionStudentsEmails,
        teamsInThisSection,
        commentsVisitedSet,
        comments);

    java.util.Collections.sort(comments);

    return comments;
  }

  private List<String> getTeamsForSection(List<StudentAttributes> studentsInTheSameSection) {
    List<String> teams = new ArrayList<String>();
    for (StudentAttributes stu : studentsInTheSameSection) {
      teams.add(stu.team);
    }
    return teams;
  }

  private List<String> getSectionStudentsEmails(List<StudentAttributes> studentsInTheSameSection) {
    List<String> sectionStudentsEmails = new ArrayList<String>();
    for (StudentAttributes stu : studentsInTheSameSection) {
      sectionStudentsEmails.add(stu.email);
    }
    return sectionStudentsEmails;
  }

  private List<String> getTeammatesEmails(List<StudentAttributes> teammates) {
    List<String> teammatesEmails = new ArrayList<String>();
    for (StudentAttributes teammate : teammates) {
      teammatesEmails.add(teammate.email);
    }
    return teammatesEmails;
  }

  private void removeNonVisibleCommentsForCourse(
      List<CommentAttributes> commentsForCourse,
      StudentAttributes student,
      List<String> teammates,
      List<String> sectionStudentsEmails,
      List<String> teamsInThisSection,
      HashSet<String> commentsVisitedSet,
      List<CommentAttributes> comments) {
    removeNonVisibleCommentsForSection(
        commentsForCourse,
        student,
        teammates,
        sectionStudentsEmails,
        teamsInThisSection,
        commentsVisitedSet,
        comments);

    for (CommentAttributes c : commentsForCourse) {
      if (c.courseId.equals(student.course)) {
        if (c.recipientType == CommentParticipantType.COURSE) {
          removeGiverNameByVisibilityOptions(c, CommentParticipantType.COURSE);
        } else {
          removeGiverAndRecipientNameByVisibilityOptions(c, CommentParticipantType.COURSE);
        }
        appendComments(c, comments, commentsVisitedSet);
      }
    }
  }

  private void removeNonVisibleCommentsForSection(
      List<CommentAttributes> commentsForSection,
      StudentAttributes student,
      List<String> teammatesEmails,
      List<String> sectionStudentsEmails,
      List<String> teamsInThisSection,
      HashSet<String> commentsVisitedSet,
      List<CommentAttributes> comments) {
    removeNonVisibleCommentsForTeam(
        commentsForSection, student, teammatesEmails, commentsVisitedSet, comments);

    for (CommentAttributes c : commentsForSection) {
      // for teammates
      if (c.recipientType == CommentParticipantType.PERSON
          && isCommentRecipientsWithinGroup(sectionStudentsEmails, c)) {
        if (c.showCommentTo.contains(CommentParticipantType.SECTION)) {
          removeGiverAndRecipientNameByVisibilityOptions(c, CommentParticipantType.SECTION);
          appendComments(c, comments, commentsVisitedSet);
        } else {
          preventAppendingThisCommentAgain(commentsVisitedSet, c);
        }
        // for team
      } else if (c.recipientType == CommentParticipantType.TEAM
          && isCommentRecipientsWithinGroup(teamsInThisSection, c)) {
        if (c.showCommentTo.contains(CommentParticipantType.SECTION)) {
          removeGiverNameByVisibilityOptions(c, CommentParticipantType.SECTION);
          appendComments(c, comments, commentsVisitedSet);
        } else {
          preventAppendingThisCommentAgain(commentsVisitedSet, c);
        }
        // for section
      } else if (c.recipientType == CommentParticipantType.SECTION
          && c.recipients.contains(student.section)) {
        if (c.showCommentTo.contains(CommentParticipantType.SECTION)) {
          removeGiverNameByVisibilityOptions(c, CommentParticipantType.SECTION);
          appendComments(c, comments, commentsVisitedSet);
        } else {
          preventAppendingThisCommentAgain(commentsVisitedSet, c);
        }
      }
    }
  }

  private void removeNonVisibleCommentsForTeam(
      List<CommentAttributes> commentsForTeam,
      StudentAttributes student,
      List<String> teammates,
      HashSet<String> commentsVisitedSet,
      List<CommentAttributes> comments) {
    for (CommentAttributes c : commentsForTeam) {
      // for teammates
      if (c.recipientType == CommentParticipantType.PERSON
          && isCommentRecipientsWithinGroup(teammates, c)) {
        if (c.showCommentTo.contains(CommentParticipantType.TEAM)) {
          removeGiverAndRecipientNameByVisibilityOptions(c, CommentParticipantType.TEAM);
          appendComments(c, comments, commentsVisitedSet);
        } else {
          preventAppendingThisCommentAgain(commentsVisitedSet, c);
        }
        // for team
      } else if (c.recipientType == CommentParticipantType.TEAM
          && c.recipients.contains(Sanitizer.sanitizeForHtml(student.team))) {
        if (c.showCommentTo.contains(CommentParticipantType.TEAM)) {
          removeGiverNameByVisibilityOptions(c, CommentParticipantType.TEAM);
          appendComments(c, comments, commentsVisitedSet);
        } else {
          preventAppendingThisCommentAgain(commentsVisitedSet, c);
        }
      }
    }
  }

  private void removeNonVisibleCommentsForStudent(
      List<CommentAttributes> commentsForStudent,
      HashSet<String> commentsVisitedSet,
      List<CommentAttributes> comments) {
    for (CommentAttributes c : commentsForStudent) {
      if (c.showCommentTo.contains(CommentParticipantType.PERSON)) {
        removeGiverNameByVisibilityOptions(c, CommentParticipantType.PERSON);
        appendComments(c, comments, commentsVisitedSet);
      } else {
        preventAppendingThisCommentAgain(commentsVisitedSet, c);
      }
    }
  }

  private void removeGiverNameByVisibilityOptions(
      CommentAttributes c, CommentParticipantType viewerType) {
    if (!c.showGiverNameTo.contains(viewerType)) {
      c.giverEmail = "Anonymous";
    }
  }

  private void removeGiverAndRecipientNameByVisibilityOptions(
      CommentAttributes c, CommentParticipantType viewerType) {
    removeGiverNameByVisibilityOptions(c, viewerType);
    if (!c.showRecipientNameTo.contains(viewerType)) {
      c.recipients = new HashSet<String>();
      c.recipients.add("Anonymous");
    }
  }

  private void appendComments(
      CommentAttributes c,
      List<CommentAttributes> toThisCommentList,
      HashSet<String> commentsVisitedSet) {
    if (!commentsVisitedSet.contains(c.getCommentId().toString())) {
      toThisCommentList.add(c);
      preventAppendingThisCommentAgain(commentsVisitedSet, c);
    }
  }

  private void preventAppendingThisCommentAgain(
      HashSet<String> commentsVisitedSet, CommentAttributes c) {
    commentsVisitedSet.add(c.getCommentId().toString());
  }

  private boolean isCommentRecipientsWithinGroup(List<String> group, CommentAttributes c) {
    for (String recipient : c.recipients) {
      if (group.contains(recipient)) {
        return true;
      }
    }
    return false;
  }

  /** ********** Send Email For Pending Comments *********** */

  /**
   * Get recipient emails for comments with sending state. When pending comments are cleared,
   * they'll become sending comments.
   *
   * @param courseId
   * @return set of emails for recipients who can see the sending comments
   * @throws EntityDoesNotExistException when the course doesn't exist
   */
  public Set<String> getRecipientEmailsForSendingComments(String courseId)
      throws EntityDoesNotExistException {
    List<StudentAttributes> allStudents = new StudentsDb().getStudentsForCourse(courseId);

    CourseRoster roster =
        new CourseRoster(allStudents, new InstructorsDb().getInstructorsForCourse(courseId));

    Map<String, List<StudentAttributes>> teamStudentTable =
        new HashMap<String, List<StudentAttributes>>();
    Map<String, List<StudentAttributes>> sectionStudentTable =
        new HashMap<String, List<StudentAttributes>>();
    populateTeamSectionStudentTables(allStudents, teamStudentTable, sectionStudentTable);

    Set<String> recipientEmailsList =
        populateRecipientEmails(
            courseId, allStudents, roster, teamStudentTable, sectionStudentTable);

    return recipientEmailsList;
  }

  private Set<String> populateRecipientEmails(
      String courseId,
      List<StudentAttributes> allStudents,
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Map<String, List<StudentAttributes>> sectionStudentTable)
      throws EntityDoesNotExistException {
    Set<String> recipientEmailsList = new HashSet<String>();

    List<CommentAttributes> sendingCommentsList =
        commentsDb.getCommentsForSendingState(courseId, CommentSendingState.SENDING);
    populateRecipientEmailsFromPendingComments(
        sendingCommentsList,
        allStudents,
        roster,
        teamStudentTable,
        sectionStudentTable,
        recipientEmailsList);

    List<FeedbackResponseCommentAttributes> sendingResponseCommentsList =
        frcLogic.getFeedbackResponseCommentsForSendingState(courseId, CommentSendingState.SENDING);
    populateRecipientEmailsFromPendingResponseComments(
        sendingResponseCommentsList, allStudents, roster, teamStudentTable, recipientEmailsList);

    return recipientEmailsList;
  }

  private void populateTeamSectionStudentTables(
      List<StudentAttributes> allStudents,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Map<String, List<StudentAttributes>> sectionStudentTable) {
    for (StudentAttributes student : allStudents) {
      List<StudentAttributes> teammates = teamStudentTable.get(student.team);
      if (teammates == null) {
        teammates = new ArrayList<StudentAttributes>();
        teamStudentTable.put(student.team, teammates);
      }
      teammates.add(student);
      List<StudentAttributes> studentsInTheSameSection = sectionStudentTable.get(student.section);
      if (studentsInTheSameSection == null) {
        studentsInTheSameSection = new ArrayList<StudentAttributes>();
        sectionStudentTable.put(student.section, studentsInTheSameSection);
      }
      studentsInTheSameSection.add(student);
    }
  }

  /**
   * ********** Send Email For Pending Comments : populate recipients emails from Feedback Response
   * Comments ***********
   */
  private void populateRecipientEmailsFromPendingResponseComments(
      List<FeedbackResponseCommentAttributes> sendingResponseCommentsList,
      List<StudentAttributes> allStudents,
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Set<String> recipientEmailsList) {

    Map<String, FeedbackQuestionAttributes> feedbackQuestionsTable =
        new HashMap<String, FeedbackQuestionAttributes>();
    Map<String, FeedbackResponseAttributes> feedbackResponsesTable =
        new HashMap<String, FeedbackResponseAttributes>();
    Map<String, Set<String>> responseCommentsAddedTable = new HashMap<String, Set<String>>();

    for (FeedbackResponseCommentAttributes frc : sendingResponseCommentsList) {
      FeedbackQuestionAttributes relatedQuestion = getRelatedQuestion(feedbackQuestionsTable, frc);
      FeedbackResponseAttributes relatedResponse = getRelatedResponse(feedbackResponsesTable, frc);

      if (relatedQuestion != null && relatedResponse != null) {
        populateRecipientEmailsForGiver(
            roster,
            teamStudentTable,
            recipientEmailsList,
            responseCommentsAddedTable,
            frc,
            relatedQuestion,
            relatedResponse);
        populateRecipientEmailsForReceiver(
            roster,
            teamStudentTable,
            recipientEmailsList,
            responseCommentsAddedTable,
            frc,
            relatedResponse);
        populateRecipientEmailsForTeamMember(
            roster,
            teamStudentTable,
            recipientEmailsList,
            responseCommentsAddedTable,
            frc,
            relatedResponse);
        populateRecipientEmailsForAllStudents(
            allStudents, recipientEmailsList, responseCommentsAddedTable, frc);
      }
    }
  }

  private void populateRecipientEmailsForAllStudents(
      List<StudentAttributes> allStudents,
      Set<String> recipientEmailsList,
      Map<String, Set<String>> responseCommentsAddedTable,
      FeedbackResponseCommentAttributes frc) {
    if (frc.isVisibleTo(FeedbackParticipantType.STUDENTS)) {
      for (StudentAttributes student : allStudents) {
        addRecipientEmailsToList(
            responseCommentsAddedTable, recipientEmailsList, frc.getId().toString(), student.email);
      }
    }
  }

  private void populateRecipientEmailsForTeamMember(
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Set<String> recipientEmailsList,
      Map<String, Set<String>> responseCommentsAddedTable,
      FeedbackResponseCommentAttributes frc,
      FeedbackResponseAttributes relatedResponse) {
    if (frc.isVisibleTo(FeedbackParticipantType.RECEIVER_TEAM_MEMBERS)) {
      StudentAttributes studentOfThisEmail = roster.getStudentForEmail(relatedResponse.recipient);
      if (studentOfThisEmail == null) {
        addRecipientEmailsForTeam(
            teamStudentTable,
            recipientEmailsList,
            responseCommentsAddedTable,
            frc.getId().toString(),
            relatedResponse.recipient);
      } else {
        addRecipientEmailsForTeam(
            teamStudentTable,
            recipientEmailsList,
            responseCommentsAddedTable,
            frc.getId().toString(),
            studentOfThisEmail.team);
      }
    }
  }

  private void populateRecipientEmailsForReceiver(
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Set<String> recipientEmailsList,
      Map<String, Set<String>> responseCommentsAddedTable,
      FeedbackResponseCommentAttributes frc,
      FeedbackResponseAttributes relatedResponse) {
    if (frc.isVisibleTo(FeedbackParticipantType.RECEIVER)) {
      // recipientEmail is email
      if (roster.getStudentForEmail(relatedResponse.recipient) == null) {
        addRecipientEmailsForTeam(
            teamStudentTable,
            recipientEmailsList,
            responseCommentsAddedTable,
            frc.getId().toString(),
            relatedResponse.recipient);
      } else {
        addRecipientEmailsToList(
            responseCommentsAddedTable,
            recipientEmailsList,
            frc.getId().toString(),
            relatedResponse.recipient);
      }
    }
  }

  private void populateRecipientEmailsForGiver(
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Set<String> recipientEmailsList,
      Map<String, Set<String>> responseCommentsAddedTable,
      FeedbackResponseCommentAttributes frc,
      FeedbackQuestionAttributes relatedQuestion,
      FeedbackResponseAttributes relatedResponse) {
    StudentAttributes giver = roster.getStudentForEmail(relatedResponse.giver);
    if (giver == null) {
      return;
    }

    if (frc.isVisibleTo(FeedbackParticipantType.GIVER)) {
      addRecipientEmailsToList(
          responseCommentsAddedTable,
          recipientEmailsList,
          frc.getId().toString(),
          relatedResponse.giver);
    }

    if (relatedQuestion.giverType == FeedbackParticipantType.TEAMS
        || frc.isVisibleTo(FeedbackParticipantType.OWN_TEAM_MEMBERS)) {
      addRecipientEmailsForTeam(
          teamStudentTable,
          recipientEmailsList,
          responseCommentsAddedTable,
          frc.getId().toString(),
          giver.team);
    }
  }

  private FeedbackResponseAttributes getRelatedResponse(
      Map<String, FeedbackResponseAttributes> feedbackResponsesTable,
      FeedbackResponseCommentAttributes frc) {
    FeedbackResponseAttributes relatedResponse = feedbackResponsesTable.get(frc.feedbackResponseId);
    if (relatedResponse == null) {
      relatedResponse = frLogic.getFeedbackResponse(frc.feedbackResponseId);
      feedbackResponsesTable.put(frc.feedbackResponseId, relatedResponse);
    }
    return relatedResponse;
  }

  private FeedbackQuestionAttributes getRelatedQuestion(
      Map<String, FeedbackQuestionAttributes> feedbackQuestionsTable,
      FeedbackResponseCommentAttributes frc) {
    FeedbackQuestionAttributes relatedQuestion = feedbackQuestionsTable.get(frc.feedbackQuestionId);
    if (relatedQuestion == null) {
      relatedQuestion = fqLogic.getFeedbackQuestion(frc.feedbackQuestionId);
      feedbackQuestionsTable.put(frc.feedbackQuestionId, relatedQuestion);
    }
    return relatedQuestion;
  }

  /**
   * ********** Send Email For Pending Comments : populate recipients emails from Student Comments
   * ***********
   */
  private void populateRecipientEmailsFromPendingComments(
      List<CommentAttributes> sendingCommentsList,
      List<StudentAttributes> allStudents,
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Map<String, List<StudentAttributes>> sectionStudentTable,
      Set<String> recipientEmailList) {

    Map<String, Set<String>> studentCommentsAddedTable = new HashMap<String, Set<String>>();

    for (CommentAttributes pendingComment : sendingCommentsList) {
      populateRecipientEmailsForPerson(
          recipientEmailList, studentCommentsAddedTable, pendingComment);
      populateRecipientEmailsForTeam(
          recipientEmailList, roster, teamStudentTable, studentCommentsAddedTable, pendingComment);
      populateRecipientEmailsForSection(
          recipientEmailList,
          roster,
          teamStudentTable,
          sectionStudentTable,
          studentCommentsAddedTable,
          pendingComment);
      populateRecipientEmailsForCourse(
          recipientEmailList, allStudents,
          studentCommentsAddedTable, pendingComment);
    }
  }

  private void populateRecipientEmailsForCourse(
      Set<String> recipientEmailList,
      List<StudentAttributes> allStudents,
      Map<String, Set<String>> studentCommentsAddedTable,
      CommentAttributes pendingComment) {
    if (pendingComment.isVisibleTo(CommentParticipantType.COURSE)) {
      for (StudentAttributes student : allStudents) {
        addRecipientEmailsToList(
            studentCommentsAddedTable,
            recipientEmailList,
            pendingComment.getCommentId().toString(),
            student.email);
      }
    }
  }

  private void populateRecipientEmailsForSection(
      Set<String> recipientEmailList,
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Map<String, List<StudentAttributes>> sectionStudentTable,
      Map<String, Set<String>> studentCommentsAddedTable,
      CommentAttributes pendingComment) {
    String commentId = pendingComment.getCommentId().toString();
    if (pendingComment.isVisibleTo(CommentParticipantType.SECTION)) {
      if (pendingComment.recipientType == CommentParticipantType.PERSON) {
        for (String recipientEmail : pendingComment.recipients) {
          StudentAttributes student = roster.getStudentForEmail(recipientEmail);
          if (student == null) {
            continue;
          }
          addRecipientEmailsForSection(
              sectionStudentTable,
              recipientEmailList,
              studentCommentsAddedTable,
              commentId,
              student.section);
        }
      } else if (pendingComment.recipientType == CommentParticipantType.TEAM) {
        for (String team : pendingComment.recipients) {
          List<StudentAttributes> students = teamStudentTable.get(team);
          if (students == null) {
            continue;
          }
          for (StudentAttributes stu : students) {
            addRecipientEmailsForSection(
                sectionStudentTable,
                recipientEmailList,
                studentCommentsAddedTable,
                commentId,
                stu.section);
          }
        }
      } else if (pendingComment.recipientType == CommentParticipantType.SECTION) {
        for (String section : pendingComment.recipients) {
          addRecipientEmailsForSection(
              sectionStudentTable,
              recipientEmailList,
              studentCommentsAddedTable,
              commentId,
              section);
        }
      }
    } else { // not visible to SECTION
      if (pendingComment.recipientType == CommentParticipantType.PERSON) {
        for (String recipientEmail : pendingComment.recipients) {
          StudentAttributes student = roster.getStudentForEmail(recipientEmail);
          if (student == null) {
            continue;
          }
          preventAddRecipientEmailsForSection(
              teamStudentTable, studentCommentsAddedTable, commentId, student.section);
        }
      } else if (pendingComment.recipientType == CommentParticipantType.TEAM) {
        for (String team : pendingComment.recipients) {
          List<StudentAttributes> students = teamStudentTable.get(team);
          if (students == null) {
            continue;
          }
          for (StudentAttributes stu : students) {
            preventAddRecipientEmailsForSection(
                teamStudentTable, studentCommentsAddedTable, commentId, stu.section);
          }
        }
      } else if (pendingComment.recipientType == CommentParticipantType.SECTION) {
        for (String section : pendingComment.recipients) {
          preventAddRecipientEmailsForSection(
              teamStudentTable, studentCommentsAddedTable,
              commentId, section);
        }
      }
    }
  }

  private void populateRecipientEmailsForTeam(
      Set<String> recipientEmailList,
      CourseRoster roster,
      Map<String, List<StudentAttributes>> teamStudentTable,
      Map<String, Set<String>> studentCommentsAddedTable,
      CommentAttributes pendingComment) {
    String commentId = pendingComment.getCommentId().toString();
    if (pendingComment.isVisibleTo(CommentParticipantType.TEAM)) {
      if (pendingComment.recipientType == CommentParticipantType.PERSON) {
        for (String recipientEmail : pendingComment.recipients) {
          StudentAttributes student = roster.getStudentForEmail(recipientEmail);
          if (student == null) {
            continue;
          }
          addRecipientEmailsForTeam(
              teamStudentTable,
              recipientEmailList,
              studentCommentsAddedTable,
              commentId,
              student.team);
        }
      } else if (pendingComment.recipientType == CommentParticipantType.TEAM) {
        for (String team : pendingComment.recipients) {
          addRecipientEmailsForTeam(
              teamStudentTable, recipientEmailList, studentCommentsAddedTable, commentId, team);
        }
      }
    } else { // not visible to TEAM
      if (pendingComment.recipientType == CommentParticipantType.PERSON) {
        for (String recipientEmail : pendingComment.recipients) {
          StudentAttributes student = roster.getStudentForEmail(recipientEmail);
          if (student == null) {
            continue;
          }
          preventAddRecipientEmailsForTeam(
              teamStudentTable, studentCommentsAddedTable, commentId, student.team);
        }
      } else if (pendingComment.recipientType == CommentParticipantType.TEAM) {
        for (String team : pendingComment.recipients) {
          preventAddRecipientEmailsForTeam(
              teamStudentTable, studentCommentsAddedTable,
              commentId, team);
        }
      }
    }
  }

  private void populateRecipientEmailsForPerson(
      Set<String> recipientEmailList,
      Map<String, Set<String>> studentCommentsAddedTable,
      CommentAttributes pendingComment) {
    String commentId = pendingComment.getCommentId().toString();
    if (pendingComment.isVisibleTo(CommentParticipantType.PERSON)) {
      for (String recipientEmail : pendingComment.recipients) {
        addRecipientEmailsToList(
            studentCommentsAddedTable, recipientEmailList,
            commentId, recipientEmail);
      }
    } else { // not visible to PERSON
      for (String recipientEmail : pendingComment.recipients) {
        preventAddRecipientEmailsToList(studentCommentsAddedTable, commentId, recipientEmail);
      }
    }
  }

  private void addRecipientEmailsToList(
      Map<String, Set<String>> isAddedTable, Set<String> targetTable, String subKey, String key) {
    // prevent re-entry
    Set<String> commentIdsSet = isAddedTable.get(key);
    if (commentIdsSet == null) {
      commentIdsSet = new HashSet<String>();
      isAddedTable.put(key, commentIdsSet);
    }
    if (!commentIdsSet.contains(subKey)) {
      commentIdsSet.add(subKey);
      targetTable.add(key);
    }
  }

  private void addRecipientEmailsForSection(
      Map<String, List<StudentAttributes>> sectionStudentTable,
      Set<String> recipientEmailsList,
      Map<String, Set<String>> responseCommentsAddedTable,
      String commentId,
      String sectionName) {
    List<StudentAttributes> students = sectionStudentTable.get(sectionName);
    if (students == null) {
      return;
    }

    for (StudentAttributes stu : students) {
      addRecipientEmailsToList(
          responseCommentsAddedTable, recipientEmailsList, commentId, stu.email);
    }
  }

  private void addRecipientEmailsForTeam(
      Map<String, List<StudentAttributes>> teamStudentTable,
      Set<String> recipientEmailsList,
      Map<String, Set<String>> responseCommentsAddedTable,
      String commentId,
      String teamName) {
    List<StudentAttributes> students = teamStudentTable.get(teamName);
    if (students == null) {
      return;
    }

    for (StudentAttributes stu : students) {
      addRecipientEmailsToList(
          responseCommentsAddedTable, recipientEmailsList, commentId, stu.email);
    }
  }

  private void preventAddRecipientEmailsToList(
      Map<String, Set<String>> isAddedTable, String subKey, String key) {
    Set<String> commentIdsSet = isAddedTable.get(key);
    if (commentIdsSet == null) {
      commentIdsSet = new HashSet<String>();
      isAddedTable.put(key, commentIdsSet);
    }
    commentIdsSet.add(subKey);
  }

  private void preventAddRecipientEmailsForSection(
      Map<String, List<StudentAttributes>> sectionStudentTable,
      Map<String, Set<String>> isAddedTable,
      String commentId,
      String section) {
    List<StudentAttributes> students = sectionStudentTable.get(section);
    if (students == null) {
      return;
    }

    for (StudentAttributes stu : students) {
      preventAddRecipientEmailsToList(isAddedTable, commentId, stu.email);
    }
  }

  private void preventAddRecipientEmailsForTeam(
      Map<String, List<StudentAttributes>> teamStudentTable,
      Map<String, Set<String>> isAddedTable,
      String commentId,
      String team) {
    List<StudentAttributes> teammates = teamStudentTable.get(team);
    if (teammates == null) {
      return;
    }

    for (StudentAttributes teamMember : teammates) {
      preventAddRecipientEmailsToList(isAddedTable, commentId, teamMember.email);
    }
  }

  @SuppressWarnings("deprecation")
  public List<CommentAttributes> getAllComments() {
    return commentsDb.getAllComments();
  }

  /**
   * Sends notifications to students in course {@code courseId} who have received comments and not
   * yet been notified.
   */
  public void sendCommentNotification(String courseId) {
    Map<String, String> paramMap = new HashMap<String, String>();
    paramMap.put(Const.ParamsNames.EMAIL_COURSE, courseId);
    paramMap.put(Const.ParamsNames.EMAIL_TYPE, EmailType.PENDING_COMMENT_CLEARED.toString());

    TaskQueuesLogic taskQueueLogic = TaskQueuesLogic.inst();
    taskQueueLogic.createAndAddTask(
        Const.SystemParams.EMAIL_TASK_QUEUE, Const.ActionURIs.EMAIL_WORKER, paramMap);
  }
}
public class FeedbackResponsesLogic {

  private static final Logger log = Utils.getLogger();

  private static FeedbackResponsesLogic instance = null;
  private static final StudentsLogic studentsLogic = StudentsLogic.inst();
  private static final FeedbackQuestionsLogic fqLogic = FeedbackQuestionsLogic.inst();
  private static final FeedbackResponseCommentsLogic frcLogic =
      FeedbackResponseCommentsLogic.inst();
  private static final FeedbackResponsesDb frDb = new FeedbackResponsesDb();

  public static FeedbackResponsesLogic inst() {
    if (instance == null) instance = new FeedbackResponsesLogic();
    return instance;
  }

  public void createFeedbackResponse(FeedbackResponseAttributes fra)
      throws InvalidParametersException {
    try {
      frDb.createEntity(fra);
    } catch (Exception EntityAlreadyExistsException) {
      try {
        FeedbackResponseAttributes existingFeedback = new FeedbackResponseAttributes();

        existingFeedback =
            frDb.getFeedbackResponse(fra.feedbackQuestionId, fra.giverEmail, fra.recipientEmail);
        fra.setId(existingFeedback.getId());

        frDb.updateFeedbackResponse(fra);
      } catch (Exception EntityDoesNotExistException) {
        Assumption.fail();
      }
    }
  }

  public FeedbackResponseAttributes getFeedbackResponse(String feedbackResponseId) {
    return frDb.getFeedbackResponse(feedbackResponseId);
  }

  public FeedbackResponseAttributes getFeedbackResponse(
      String feedbackQuestionId, String giverEmail, String recipient) {
    // TODO: check what is this line doing here!!!
    log.warning(feedbackQuestionId);
    return frDb.getFeedbackResponse(feedbackQuestionId, giverEmail, recipient);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSession(
      String feedbackSessionName, String courseId) {
    return frDb.getFeedbackResponsesForSession(feedbackSessionName, courseId);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionInSection(
      String feedbackSessionName, String courseId, String section) {
    if (section == null) {
      return getFeedbackResponsesForSession(feedbackSessionName, courseId);
    } else {
      return frDb.getFeedbackResponsesForSessionInSection(feedbackSessionName, courseId, section);
    }
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionFromSection(
      String feedbackSessionName, String courseId, String section) {
    if (section == null) {
      return getFeedbackResponsesForSession(feedbackSessionName, courseId);
    } else {
      return frDb.getFeedbackResponsesForSessionFromSection(feedbackSessionName, courseId, section);
    }
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionToSection(
      String feedbackSessionName, String courseId, String section) {
    if (section == null) {
      return getFeedbackResponsesForSession(feedbackSessionName, courseId);
    } else {
      return frDb.getFeedbackResponsesForSessionToSection(feedbackSessionName, courseId, section);
    }
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionWithinRange(
      String feedbackSessionName, String courseId, long range) {
    return frDb.getFeedbackResponsesForSessionWithinRange(feedbackSessionName, courseId, range);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionInSectionWithinRange(
      String feedbackSessionName, String courseId, String section, long range) {
    if (section == null) {
      return getFeedbackResponsesForSessionWithinRange(feedbackSessionName, courseId, range);
    } else {
      return frDb.getFeedbackResponsesForSessionInSectionWithinRange(
          feedbackSessionName, courseId, section, range);
    }
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionFromSectionWithinRange(
      String feedbackSessionName, String courseId, String section, long range) {
    if (section == null) {
      return getFeedbackResponsesForSessionWithinRange(feedbackSessionName, courseId, range);
    } else {
      return frDb.getFeedbackResponsesForSessionFromSectionWithinRange(
          feedbackSessionName, courseId, section, range);
    }
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForSessionToSectionWithinRange(
      String feedbackSessionName, String courseId, String section, long range) {
    if (section == null) {
      return getFeedbackResponsesForSessionWithinRange(feedbackSessionName, courseId, range);
    } else {
      return frDb.getFeedbackResponsesForSessionToSectionWithinRange(
          feedbackSessionName, courseId, section, range);
    }
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForQuestion(
      String feedbackQuestionId) {
    return frDb.getFeedbackResponsesForQuestion(feedbackQuestionId);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForQuestionWithinRange(
      String feedbackQuestionId, long range) {
    return frDb.getFeedbackResponsesForQuestionWithinRange(feedbackQuestionId, range);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForQuestionInSection(
      String feedbackQuestionId, String section) {
    if (section == null) {
      return getFeedbackResponsesForQuestion(feedbackQuestionId);
    }
    return frDb.getFeedbackResponsesForQuestionInSection(feedbackQuestionId, section);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForReceiverForQuestion(
      String feedbackQuestionId, String userEmail) {
    return frDb.getFeedbackResponsesForReceiverForQuestion(feedbackQuestionId, userEmail);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForReceiverForQuestionInSection(
      String feedbackQuestionId, String userEmail, String section) {

    if (section == null) {
      return getFeedbackResponsesForReceiverForQuestion(feedbackQuestionId, userEmail);
    }
    return frDb.getFeedbackResponsesForReceiverForQuestionInSection(
        feedbackQuestionId, userEmail, section);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesFromGiverForQuestion(
      String feedbackQuestionId, String userEmail) {
    return frDb.getFeedbackResponsesFromGiverForQuestion(feedbackQuestionId, userEmail);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesFromGiverForQuestionInSection(
      String feedbackQuestionId, String userEmail, String section) {

    if (section == null) {
      return getFeedbackResponsesFromGiverForQuestion(feedbackQuestionId, userEmail);
    }
    return frDb.getFeedbackResponsesFromGiverForQuestionInSection(
        feedbackQuestionId, userEmail, section);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesForReceiverForCourse(
      String courseId, String userEmail) {
    return frDb.getFeedbackResponsesForReceiverForCourse(courseId, userEmail);
  }

  public List<FeedbackResponseAttributes> getFeedbackResponsesFromGiverForCourse(
      String courseId, String userEmail) {
    return frDb.getFeedbackResponsesFromGiverForCourse(courseId, userEmail);
  }

  /** Get existing feedback responses from student or his team for the given question. */
  public List<FeedbackResponseAttributes> getFeedbackResponsesFromStudentOrTeamForQuestion(
      FeedbackQuestionAttributes question, StudentAttributes student) {
    if (question.giverType == FeedbackParticipantType.TEAMS) {
      return getFeedbackResponsesFromTeamForQuestion(
          question.getId(), question.courseId, student.team);
    } else {
      return frDb.getFeedbackResponsesFromGiverForQuestion(question.getId(), student.email);
    }
  }

  public List<FeedbackResponseAttributes> getViewableFeedbackResponsesForQuestionInSection(
      FeedbackQuestionAttributes question, String userEmail, UserType.Role role, String section)
      throws EntityDoesNotExistException {

    List<FeedbackResponseAttributes> viewableResponses =
        new ArrayList<FeedbackResponseAttributes>();

    // Add responses that the user submitted himself
    addNewResponses(
        viewableResponses,
        getFeedbackResponsesFromGiverForQuestionInSection(question.getId(), userEmail, section));

    // Add responses that user is a receiver of when question is visible to
    // receiver.
    if (question.isResponseVisibleTo(FeedbackParticipantType.RECEIVER)) {
      addNewResponses(
          viewableResponses,
          getFeedbackResponsesForReceiverForQuestionInSection(
              question.getId(), userEmail, section));
    }

    switch (role) {
      case STUDENT:
        addNewResponses(
            viewableResponses,
            // many queries
            getViewableFeedbackResponsesForStudentForQuestion(question, userEmail));
        break;
      case INSTRUCTOR:
        if (question.isResponseVisibleTo(FeedbackParticipantType.INSTRUCTORS)) {
          addNewResponses(
              viewableResponses,
              getFeedbackResponsesForQuestionInSection(question.getId(), section));
        }
        break;
      default:
        Assumption.fail("The role of the requesting use has to be Student or Instructor");
    }

    return viewableResponses;
  }

  public boolean isNameVisibleTo(
      FeedbackQuestionAttributes question,
      FeedbackResponseAttributes response,
      String userEmail,
      UserType.Role role,
      boolean isGiverName,
      CourseRoster roster) {

    if (question == null) {
      return false;
    }

    List<FeedbackParticipantType> showNameTo =
        isGiverName ? question.showGiverNameTo : question.showRecipientNameTo;

    // Giver can always see giver and recipient.(because he answered.)
    if (response.giverEmail.equals(userEmail)) {
      return true;
    }

    for (FeedbackParticipantType type : showNameTo) {
      switch (type) {
        case INSTRUCTORS:
          if (roster.getInstructorForEmail(userEmail) != null && role == UserType.Role.INSTRUCTOR) {
            return true;
          } else {
            break;
          }
        case OWN_TEAM_MEMBERS:
        case OWN_TEAM_MEMBERS_INCLUDING_SELF:
          // Refers to Giver's Team Members
          if (roster.isStudentsInSameTeam(response.giverEmail, userEmail)) {
            return true;
          } else {
            break;
          }
        case RECEIVER:
          // Response to team
          if (question.recipientType == FeedbackParticipantType.TEAMS) {
            if (roster.isStudentInTeam(
                userEmail, /* this is a team name */ response.recipientEmail)) {
              return true;
            }
            // Response to individual
          } else if (response.recipientEmail.equals(userEmail)) {
            return true;
          } else {
            break;
          }
        case RECEIVER_TEAM_MEMBERS:
          // Response to team; recipient = teamName
          if (question.recipientType == FeedbackParticipantType.TEAMS) {
            if (roster.isStudentInTeam(
                userEmail, /* this is a team name */ response.recipientEmail)) {
              return true;
            }
            // Response to individual
          } else if (roster.isStudentsInSameTeam(response.recipientEmail, userEmail)) {
            return true;
          } else {
            break;
          }
        case STUDENTS:
          if (roster.isStudentInCourse(userEmail)) {
            return true;
          } else {
            break;
          }
        default:
          Assumption.fail(
              "Invalid FeedbackPariticipantType for showNameTo in "
                  + "FeedbackResponseLogic.isNameVisible()");
          break;
      }
    }
    return false;
  }

  /**
   * Updates a {@link FeedbackResponse} based on it's {@code id}.<br>
   * If the giver/recipient field is changed, the {@link FeedbackResponse} is updated by recreating
   * the response<br>
   * in order to prevent an id clash if the previous email is reused later on.
   */
  public void updateFeedbackResponse(FeedbackResponseAttributes responseToUpdate)
      throws InvalidParametersException, EntityDoesNotExistException, EntityAlreadyExistsException {

    // Create a copy.
    FeedbackResponseAttributes newResponse = new FeedbackResponseAttributes(responseToUpdate);
    FeedbackResponseAttributes oldResponse = null;
    if (newResponse.getId() == null) {
      oldResponse =
          frDb.getFeedbackResponse(
              newResponse.feedbackQuestionId, newResponse.giverEmail, newResponse.recipientEmail);
    } else {
      oldResponse = frDb.getFeedbackResponse(newResponse.getId());
    }

    if (oldResponse == null) {
      throw new EntityDoesNotExistException(
          "Trying to update a feedback response that does not exist.");
    }

    // Copy values that cannot be changed to defensively avoid invalid
    // parameters.
    newResponse.courseId = oldResponse.courseId;
    newResponse.feedbackSessionName = oldResponse.feedbackSessionName;
    newResponse.feedbackQuestionId = oldResponse.feedbackQuestionId;
    newResponse.feedbackQuestionType = oldResponse.feedbackQuestionType;

    if (newResponse.responseMetaData == null) {
      newResponse.responseMetaData = oldResponse.responseMetaData;
    }
    if (newResponse.giverEmail == null) {
      newResponse.giverEmail = oldResponse.giverEmail;
    }
    if (newResponse.recipientEmail == null) {
      newResponse.recipientEmail = oldResponse.recipientEmail;
    }
    if (newResponse.giverSection == null) {
      newResponse.giverSection = oldResponse.giverSection;
    }
    if (newResponse.recipientSection == null) {
      newResponse.recipientSection = oldResponse.recipientSection;
    }

    if (!newResponse.recipientEmail.equals(oldResponse.recipientEmail)
        || !newResponse.giverEmail.equals(oldResponse.giverEmail)) {
      // Recreate response to prevent possible future id conflict.
      try {
        newResponse.setId(null);
        frDb.createEntity(newResponse);
        frDb.deleteEntity(oldResponse);
      } catch (EntityAlreadyExistsException e) {
        log.warning("Trying to update an existing response to one that already exists.");
        throw new EntityAlreadyExistsException(
            e.getMessage()
                + Const.EOL
                + "Trying to update recipient for response to one that already exists for this giver.");
      }
    } else {
      frDb.updateFeedbackResponse(newResponse);
    }
  }

  /**
   * Updates responses for a student when his team changes. This is done by deleting responses that
   * are no longer relevant to him in his new team.
   */
  public void updateFeedbackResponsesForChangingTeam(
      String courseId, String userEmail, String oldTeam, String newTeam)
      throws EntityDoesNotExistException {

    FeedbackQuestionAttributes question;

    List<FeedbackResponseAttributes> responsesFromUser =
        getFeedbackResponsesFromGiverForCourse(courseId, userEmail);

    for (FeedbackResponseAttributes response : responsesFromUser) {
      question = fqLogic.getFeedbackQuestion(response.feedbackQuestionId);
      if (question.giverType == FeedbackParticipantType.TEAMS
          || question.recipientType == FeedbackParticipantType.OWN_TEAM_MEMBERS
          || question.recipientType == FeedbackParticipantType.OWN_TEAM_MEMBERS_INCLUDING_SELF) {
        frDb.deleteEntity(response);
      }
    }

    List<FeedbackResponseAttributes> responsesToUser =
        getFeedbackResponsesForReceiverForCourse(courseId, userEmail);

    for (FeedbackResponseAttributes response : responsesToUser) {
      question = fqLogic.getFeedbackQuestion(response.feedbackQuestionId);
      if (question.recipientType == FeedbackParticipantType.OWN_TEAM_MEMBERS
          || question.recipientType == FeedbackParticipantType.OWN_TEAM_MEMBERS_INCLUDING_SELF) {
        frDb.deleteEntity(response);
      }
    }

    if (studentsLogic.getStudentsForTeam(oldTeam, courseId).isEmpty()) {
      List<FeedbackResponseAttributes> responsesToTeam =
          getFeedbackResponsesForReceiverForCourse(courseId, oldTeam);
      for (FeedbackResponseAttributes response : responsesToTeam) {
        frDb.deleteEntity(response);
      }
    }
  }

  public void updateFeedbackResponsesForChangingSection(
      String courseId, String userEmail, String oldSection, String newSection)
      throws EntityDoesNotExistException, InvalidParametersException {

    List<FeedbackResponseAttributes> responsesFromUser =
        getFeedbackResponsesFromGiverForCourse(courseId, userEmail);

    for (FeedbackResponseAttributes response : responsesFromUser) {
      response.giverSection = newSection;
      frDb.updateFeedbackResponse(response);
      frcLogic.updateFeedbackResponseCommentsForResponse(response.getId());
    }

    List<FeedbackResponseAttributes> responsesToUser =
        getFeedbackResponsesForReceiverForCourse(courseId, userEmail);

    for (FeedbackResponseAttributes response : responsesToUser) {
      response.recipientSection = newSection;
      frDb.updateFeedbackResponse(response);
      frcLogic.updateFeedbackResponseCommentsForResponse(response.getId());
    }
  }

  public boolean updateFeedbackResponseForChangingTeam(
      StudentEnrollDetails enrollment, FeedbackResponseAttributes response) {

    FeedbackQuestionAttributes question = fqLogic.getFeedbackQuestion(response.feedbackQuestionId);

    boolean isGiverSameForResponseAndEnrollment = response.giverEmail.equals(enrollment.email);
    boolean isReceiverSameForResponseAndEnrollment =
        response.recipientEmail.equals(enrollment.email);

    boolean shouldDeleteByChangeOfGiver =
        (isGiverSameForResponseAndEnrollment
            && (question.giverType == FeedbackParticipantType.TEAMS
                || question.recipientType == FeedbackParticipantType.OWN_TEAM_MEMBERS));
    boolean shouldDeleteByChangeOfRecipient =
        (isReceiverSameForResponseAndEnrollment
            && question.recipientType == FeedbackParticipantType.OWN_TEAM_MEMBERS);

    boolean shouldDeleteResponse = shouldDeleteByChangeOfGiver || shouldDeleteByChangeOfRecipient;

    if (shouldDeleteResponse) {
      frDb.deleteEntity(response);
    }

    return shouldDeleteResponse;
  }

  public void updateFeedbackResponseForChangingSection(
      StudentEnrollDetails enrollment, FeedbackResponseAttributes response)
      throws InvalidParametersException, EntityDoesNotExistException {

    FeedbackResponse feedbackResponse = frDb.getFeedbackResponseEntityOptimized(response);
    boolean isGiverSameForResponseAndEnrollment =
        feedbackResponse.getGiverEmail().equals(enrollment.email);
    boolean isReceiverSameForResponseAndEnrollment =
        feedbackResponse.getRecipientEmail().equals(enrollment.email);

    if (isGiverSameForResponseAndEnrollment) {
      feedbackResponse.setGiverSection(enrollment.newSection);
    }

    if (isReceiverSameForResponseAndEnrollment) {
      feedbackResponse.setRecipientSection(enrollment.newSection);
    }

    frDb.commitOutstandingChanges();

    if (isGiverSameForResponseAndEnrollment || isReceiverSameForResponseAndEnrollment) {
      frcLogic.updateFeedbackResponseCommentsForResponse(response.getId());
    }
  }

  /** Updates responses for a student when his email changes. */
  // TODO: cascade the update to response comments
  public void updateFeedbackResponsesForChangingEmail(
      String courseId, String oldEmail, String newEmail)
      throws InvalidParametersException, EntityDoesNotExistException {

    List<FeedbackResponseAttributes> responsesFromUser =
        getFeedbackResponsesFromGiverForCourse(courseId, oldEmail);

    for (FeedbackResponseAttributes response : responsesFromUser) {
      response.giverEmail = newEmail;
      try {
        updateFeedbackResponse(response);
      } catch (EntityAlreadyExistsException e) {
        Assumption.fail(
            "Feedback response failed to update successfully" + "as email was already in use.");
      }
    }

    List<FeedbackResponseAttributes> responsesToUser =
        getFeedbackResponsesForReceiverForCourse(courseId, oldEmail);

    for (FeedbackResponseAttributes response : responsesToUser) {
      response.recipientEmail = newEmail;
      try {
        updateFeedbackResponse(response);
      } catch (EntityAlreadyExistsException e) {
        Assumption.fail(
            "Feedback response failed to update successfully" + "as email was already in use.");
      }
    }
  }

  public void deleteFeedbackResponseAndCascade(FeedbackResponseAttributes responseToDelete) {
    frcLogic.deleteFeedbackResponseCommentsForResponse(responseToDelete.getId());
    frDb.deleteEntity(responseToDelete);
  }

  public void deleteFeedbackResponsesForQuestionAndCascade(String feedbackQuestionId) {
    List<FeedbackResponseAttributes> responsesForQuestion =
        getFeedbackResponsesForQuestion(feedbackQuestionId);
    for (FeedbackResponseAttributes response : responsesForQuestion) {
      this.deleteFeedbackResponseAndCascade(response);
    }
  }

  public void deleteFeedbackResponsesForStudentAndCascade(String courseId, String studentEmail) {

    String studentTeam = "";
    StudentAttributes student = studentsLogic.getStudentForEmail(courseId, studentEmail);

    if (student != null) {
      studentTeam = student.team;
    }

    List<FeedbackResponseAttributes> responses =
        getFeedbackResponsesFromGiverForCourse(courseId, studentEmail);
    responses.addAll(getFeedbackResponsesForReceiverForCourse(courseId, studentEmail));
    // Delete responses to team as well if student is last person in team.
    if (studentsLogic.getStudentsForTeam(studentTeam, courseId).size() <= 1) {
      responses.addAll(getFeedbackResponsesForReceiverForCourse(courseId, studentTeam));
    }

    for (FeedbackResponseAttributes response : responses) {
      this.deleteFeedbackResponseAndCascade(response);
    }
  }

  /**
   * Adds {@link FeedbackResponseAttributes} in {@code newResponses} that are not already in to
   * {@code existingResponses} to {@code existingResponses}.
   */
  private void addNewResponses(
      List<FeedbackResponseAttributes> existingResponses,
      List<FeedbackResponseAttributes> newResponses) {

    Map<String, FeedbackResponseAttributes> responses =
        new HashMap<String, FeedbackResponseAttributes>();

    for (FeedbackResponseAttributes existingResponse : existingResponses) {
      responses.put(existingResponse.getId(), existingResponse);
    }
    for (FeedbackResponseAttributes newResponse : newResponses) {
      if (!responses.containsKey(newResponse.getId())) {
        responses.put(newResponse.getId(), newResponse);
        existingResponses.add(newResponse);
      }
    }
  }

  private List<FeedbackResponseAttributes> getFeedbackResponsesFromTeamForQuestion(
      String feedbackQuestionId, String courseId, String teamName) {

    List<FeedbackResponseAttributes> responses = new ArrayList<FeedbackResponseAttributes>();
    List<StudentAttributes> studentsInTeam = studentsLogic.getStudentsForTeam(teamName, courseId);

    for (StudentAttributes student : studentsInTeam) {
      responses.addAll(
          frDb.getFeedbackResponsesFromGiverForQuestion(feedbackQuestionId, student.email));
    }

    return responses;
  }

  private List<FeedbackResponseAttributes> getFeedbackResponsesForTeamMembersOfStudent(
      String feedbackQuestionId, StudentAttributes student) {

    List<StudentAttributes> studentsInTeam =
        studentsLogic.getStudentsForTeam(student.team, student.course);

    List<FeedbackResponseAttributes> teamResponses = new ArrayList<FeedbackResponseAttributes>();

    for (StudentAttributes studentInTeam : studentsInTeam) {
      if (studentInTeam.email.equals(student.email)) {
        continue;
      }
      List<FeedbackResponseAttributes> responses =
          frDb.getFeedbackResponsesForReceiverForQuestion(feedbackQuestionId, studentInTeam.email);
      teamResponses.addAll(responses);
    }

    return teamResponses;
  }

  private List<FeedbackResponseAttributes> getViewableFeedbackResponsesForStudentForQuestion(
      FeedbackQuestionAttributes question, String studentEmail) {

    List<FeedbackResponseAttributes> viewableResponses =
        new ArrayList<FeedbackResponseAttributes>();

    StudentAttributes student = studentsLogic.getStudentForEmail(question.courseId, studentEmail);

    if (question.isResponseVisibleTo(FeedbackParticipantType.STUDENTS)) {
      addNewResponses(viewableResponses, getFeedbackResponsesForQuestion(question.getId()));

      // Early return as STUDENTS covers all other student types.
      return viewableResponses;
    }

    if (question.recipientType == FeedbackParticipantType.TEAMS
        && question.isResponseVisibleTo(FeedbackParticipantType.RECEIVER)) {
      addNewResponses(
          viewableResponses,
          getFeedbackResponsesForReceiverForQuestion(question.getId(), student.team));
    }

    if (question.giverType == FeedbackParticipantType.TEAMS
        || question.isResponseVisibleTo(FeedbackParticipantType.OWN_TEAM_MEMBERS)) {
      addNewResponses(
          viewableResponses,
          getFeedbackResponsesFromTeamForQuestion(
              question.getId(), question.courseId, student.team));
    }
    if (question.isResponseVisibleTo(FeedbackParticipantType.RECEIVER_TEAM_MEMBERS)) {
      addNewResponses(
          viewableResponses,
          getFeedbackResponsesForTeamMembersOfStudent(question.getId(), student));
    }

    return viewableResponses;
  }
}