/**
* Copyright (C) 2011 JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jtalks.jcommune.service.transactional;
import org.apache.commons.collections.ListUtils;
import org.joda.time.DateTime;
import org.jtalks.jcommune.model.dao.LastReadPostDao;
import org.jtalks.jcommune.model.dao.BranchReadedMarkerDao;
import org.jtalks.jcommune.model.dao.UserDao;
import org.jtalks.jcommune.model.entity.*;
import org.jtalks.jcommune.plugin.api.service.PluginLastReadPostService;
import org.jtalks.jcommune.service.LastReadPostService;
import org.jtalks.jcommune.service.UserService;
import org.springframework.security.access.prepost.PreAuthorize;
import java.util.ArrayList;
import java.util.List;
/**
* Performs last read posts management to track topic updates
* since user's last visit.
*
* @author Evgeniy Naumenko
* @author Anuar_Nurmakanov
*/
public class TransactionalLastReadPostService implements LastReadPostService, PluginLastReadPostService {
private final UserService userService;
private final LastReadPostDao lastReadPostDao;
private final UserDao userDao;
private final BranchReadedMarkerDao branchReadedMarkerDao;
/**
* Constructs an instance with required fields.
*
* @param userService to figure out the current user logged in
* @param lastReadPostDao to save/read last read post information from a database
* @param userDao to save an information about user of forum
*/
public TransactionalLastReadPostService(
UserService userService,
LastReadPostDao lastReadPostDao,
UserDao userDao,
BranchReadedMarkerDao branchReadedMarkerDao) {
this.userService = userService;
this.lastReadPostDao = lastReadPostDao;
this.userDao = userDao;
this.branchReadedMarkerDao = branchReadedMarkerDao;
}
/**
* {@inheritDoc}
*/
@Override
public List<Topic> fillLastReadPostForTopics(List<Topic> topics) {
JCUser currentUser = userService.getCurrentUser();
if (!currentUser.isAnonymous()) {
List<Topic> notModifiedTopics = extractNotModifiedTopicsSinceForumMarkedAsRead(
currentUser, topics);
for (Topic notModifiedTopic : notModifiedTopics) {
Post lastPost = notModifiedTopic.getLastPost();
notModifiedTopic.setLastReadPostDate(lastPost.getCreationDate());
}
//
@SuppressWarnings("unchecked")
List<Topic> modifiedTopics = ListUtils.removeAll(topics, notModifiedTopics);
fillLastReadPostsForModifiedTopics(modifiedTopics, currentUser);
}
return topics;
}
/**
* Extract topics that don't have modifications after marking all forum as read
* or after marking their branch as read.
*
* @param currentUser the current user of application
* @param sourceTopics the list of topics that must be processed
* @return topics that don't have modification after marking all forum as read
*/
private List<Topic> extractNotModifiedTopicsSinceForumMarkedAsRead(
JCUser currentUser,
List<Topic> sourceTopics) {
DateTime forumMarkAsReadDate = currentUser.getAllForumMarkedAsReadTime();
List<Topic> topics = new ArrayList<>();
if (!sourceTopics.isEmpty()) {
Branch branch = sourceTopics.get(0).getBranch();
BranchReadedMarker markBranch = branchReadedMarkerDao.getMarkerFor(currentUser, branch);
DateTime markTime = getLastMarkDateTime(markBranch, forumMarkAsReadDate);
for (Topic topic : sourceTopics) {
if (!topic.getBranch().equals(branch)) {
branch = topic.getBranch();
markBranch = branchReadedMarkerDao.getMarkerFor(currentUser, branch);
markTime = getLastMarkDateTime(markBranch, forumMarkAsReadDate);
}
if(markTime != null && topic.getModificationDate().isBefore(markTime)) {
topics.add(topic);
}
}
}
return topics;
}
/**
* Compares date from marker with specified date and returns greater value
*
* @param marker marker object
* @param date date to compare
*
* @return greater value if both not null
* null if both null
* not null one if another null
*/
private DateTime getLastMarkDateTime(BranchReadedMarker marker, DateTime date) {
if (marker == null) {
return date;
} else if (date == null) {
return marker.getMarkTime();
} else {
return marker.getMarkTime().isBefore(date) ? date : marker.getMarkTime();
}
}
/**
* For topics modified since forum was marked as all read we need to calculate
* last read posts from data that were saved in repository.
*
* @param modifiedTopics the list of modified topics
* @param currentUser the current user of application
*/
private void fillLastReadPostsForModifiedTopics(List<Topic> modifiedTopics, JCUser currentUser) {
List<LastReadPost> lastReadPosts = lastReadPostDao.getLastReadPosts(currentUser, modifiedTopics);
for (Topic topic : modifiedTopics) {
LastReadPost lastReadPost = findLastReadPost(lastReadPosts, topic.getId());
if (lastReadPost != null) {
topic.setLastReadPostDate(lastReadPost.getPostCreationDate());
}
}
}
/**
* Find last read post in the list for given topic.
*
* @param lastReadPosts the list of last read posts where we are going to search
* @param topicId an identifier of topic for which we find last read post in list
* @return last read post for given topic
*/
private LastReadPost findLastReadPost(List<LastReadPost> lastReadPosts, long topicId) {
for (LastReadPost lastReadPost : lastReadPosts) {
if (lastReadPost.getTopic().getId() == topicId) {
return lastReadPost;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
@PreAuthorize("hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.VIEW_TOPICS')")
public void markTopicPageAsRead(Topic topic, int pageNum) {
JCUser current = userService.getCurrentUser();
// topics are always unread for anonymous users
if (!current.isAnonymous()) {
Post lastPostOnPage = this.calculatePostOnPage(current, topic, pageNum);
saveLastReadPost(current, topic, lastPostOnPage);
}
}
/**
* Computes new last read post on the page based on the topic size and
* current pagination settings.
*
* @param user user to calculate post for
* @param topic topic to calculate post for
* @param pageNum page number co calculate last post seen by the user
* @return last post on the page
*/
private Post calculatePostOnPage(JCUser user, Topic topic, int pageNum) {
int maxPostIndex = user.getPageSize() * pageNum - 1;
maxPostIndex = Math.min(topic.getPostCount() - 1, maxPostIndex);
return topic.getPosts().get(maxPostIndex);
}
/**
* {@inheritDoc}
*/
@Override
@PreAuthorize("hasPermission(#topic.branch.id, 'BRANCH', 'BranchPermission.VIEW_TOPICS')")
public void markTopicAsRead(Topic topic) {
JCUser current = userService.getCurrentUser();
if (!current.isAnonymous()) { // topics are always unread for anonymous users
saveLastReadPost(current, topic, topic.getLastPost());
}
}
/**
* Stores last read post info into database for the particular topic and user.
*
* @param user user to save last read post data for
* @param topic topic to store info for
* @param lastPost last post in the topic (or in the last read page of the topic)
*/
private void saveLastReadPost(JCUser user, Topic topic, Post lastPost) {
DateTime lastTimeForumWasMarkedRead = user.getAllForumMarkedAsReadTime();
DateTime topicModifiedDate = topic.getModificationDate();
if (lastTimeForumWasMarkedRead != null && topicModifiedDate.isBefore(lastTimeForumWasMarkedRead)) {
return;
}
LastReadPost lastReadPost = lastReadPostDao.getLastReadPost(user, topic);
if (lastReadPost == null) {
lastReadPost = new LastReadPost(user, topic, lastPost.getCreationDate());
} else {
if (lastPost.getCreationDate().isAfter(lastReadPost.getPostCreationDate())) {
lastReadPost.setPostCreationDate(lastPost.getCreationDate());
} else {
return;
}
}
lastReadPostDao.saveOrUpdate(lastReadPost);
}
/**
* {@inheritDoc}
*/
@Override
@PreAuthorize("hasPermission(#branch.id, 'BRANCH', 'BranchPermission.VIEW_TOPICS')")
public void markAllTopicsAsRead(Branch branch) {
JCUser user = userService.getCurrentUser();
if (!user.isAnonymous()) {
// would be logical to remove per-topic Last Read Post records from DB,
// but it's not worth it since most people will anyway press on global Mark All As Read
// at some point and this will clean the records for user. Ergo, it's not expected
// that the DB will be overwhelmed with per-topic Last Read Post records.
branchReadedMarkerDao.markBranchAsRead(user, branch);
}
}
/**
* {@inheritDoc}
*/
@Override
public void markAllForumAsReadForCurrentUser() {
JCUser currentUser = userService.getCurrentUser();
currentUser.setAllForumMarkedAsReadTime(new DateTime());
userDao.saveOrUpdate(currentUser);
lastReadPostDao.deleteLastReadPostsFor(currentUser);
}
}