org.jtalks.jcommune.service.nontransactional.MentionedUsers.java Source code

Java tutorial

Introduction

Here is the source code for org.jtalks.jcommune.service.nontransactional.MentionedUsers.java

Source

/**
 * 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.nontransactional;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.jtalks.jcommune.model.dao.PostDao;
import org.jtalks.jcommune.model.dao.UserDao;
import org.jtalks.jcommune.model.entity.JCUser;
import org.jtalks.jcommune.model.entity.Post;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.String.format;

/**
 * This service provides send email notifications to all mentioned users
 * in some components of forum:topics, posts. Also it provides an ability
 * to extract users mentioning from text.
 *
 * @author Anuar_Nurmakanov
 * @author Andrei Alikov
 */
public class MentionedUsers {
    public static final String MENTIONED_NOT_NOTIFIED_USER_TEMPLATE = "[user]%s[/user]";
    public static final String MENTIONED_AND_NOTIFIED_USER_TEMPLATE = "[user notified=true]%s[/user]";
    public static final String USER_WITH_LINK_TO_PROFILE_TEMPLATE = "[user=%s]%s[/user]";
    private static final Logger LOGGER = LoggerFactory.getLogger(MentionedUsers.class);
    private static final Pattern ALL_MENTIONED_USERS_PATTERN = Pattern
            .compile("\\[user\\].+?(\\[/user\\])+|\\[user notified=true\\].+?(\\[/user\\])+");
    private static final Pattern MENTIONED_AND_NOT_NOTIFIED_USERS_PATTERN = Pattern
            .compile("\\[user\\].+?(\\[/user\\])+");
    private static final String CLOSE_BRACKET_CODE_PLACEHOLDER = "@w0956756wo@";
    private static final String OPEN_BRACKET_CODE_PLACEHOLDER = "@ywdffgg434y@";
    private static final String SLASH_CODE_PLACEHOLDER = "14@123435vggv4f";
    private static final String LOWER_THEN_PLACEHOLDER = "gertfgertgf@@@@@#4324234";
    private static final Map<String, String> CHARS_PLACEHOLDERS = new HashMap<>();

    static {
        CHARS_PLACEHOLDERS.put("[", OPEN_BRACKET_CODE_PLACEHOLDER);
        CHARS_PLACEHOLDERS.put("]", CLOSE_BRACKET_CODE_PLACEHOLDER);
        CHARS_PLACEHOLDERS.put("\\", SLASH_CODE_PLACEHOLDER);
        CHARS_PLACEHOLDERS.put("<", LOWER_THEN_PLACEHOLDER);
    }

    private final Map<String, String> encodedUserNames = new HashMap<>();
    /**
     * Content of the post
     */
    private String postContent;
    /**
     * Post with mentioned users
     */
    private Post post;

    private MentionedUsers(String postContent) {
        this.postContent = postContent;
    }

    private MentionedUsers(Post post) {
        this.post = post;
        this.postContent = post.getPostContent();
    }

    /**
     * Creates new instance of MentionedUsers based on the Post data
     *
     * @param postContent content of the post where user was mentioned
     */
    public static MentionedUsers parse(String postContent) {
        return new MentionedUsers(postContent);
    }

    /**
     * Creates new instance of MentionedUsers based on the Post data
     *
     * @param post the post where user was mentioned
     */
    public static MentionedUsers parse(Post post) {
        return new MentionedUsers(post);
    }

    /**
     * Get list of the users which have to receive notification
     *
     * @param userDao service for user related operations
     * @return list of the users which should be notified that they were mentioned
     * @throws IllegalStateException when instance was not created based on Post object
     */
    public List<JCUser> getNewUsersToNotify(UserDao userDao) {
        if (post == null) {
            throw new IllegalStateException("To call this method you should create class with Post type parameter");
        }

        Set<String> mentionedUsersNames = extractNotNotifiedMentionedUsers(postContent);
        if (!CollectionUtils.isEmpty(mentionedUsersNames)) {
            return getNewUsersToNotify(mentionedUsersNames, userDao);
        }

        return new ArrayList<>();
    }

    /**
     * Marks all users in user BB codes as already notified
     *
     * @param postDao service for post related operations
     * @throws IllegalStateException when instance was not created based on Post object
     */
    public void markUsersAsAlreadyNotified(PostDao postDao) {
        if (post == null) {
            throw new IllegalStateException("To call this method you should create class with Post type parameter");
        }

        Set<String> mentionedUsersNames = extractNotNotifiedMentionedUsers(postContent);
        if (!CollectionUtils.isEmpty(mentionedUsersNames)) {
            markUsersAsAlreadyNotified(mentionedUsersNames, postDao);
        }
    }

    /**
     * Returns post text with BB codes replaced by user profile links
     *
     * @param userDao service for working with user objects
     * @return text with BB codes replaced by user profile links
     */
    public String getTextWithProcessedUserTags(UserDao userDao) {
        Set<String> mentionedUsers = extractAllMentionedUsers(postContent);
        Map<String, String> userToUserProfileLinkMap = new HashMap<>();
        for (String mentionedUser : mentionedUsers) {
            String mentionedUserProfileLink = getLinkToUserProfile(mentionedUser, userDao);
            userToUserProfileLinkMap.put(mentionedUser, mentionedUserProfileLink);
        }
        return addLinksToUserProfileForMentionedUsers(postContent, userToUserProfileLinkMap);
    }

    /**
     * Extract names of all users that were mentioned in passed text.
     *
     * @return extracted users' names
     */
    public Set<String> extractAllMentionedUsers(String canContainMentionedUsers) {
        return extractMentionedUsers(canContainMentionedUsers, ALL_MENTIONED_USERS_PATTERN);
    }

    /**
     * Extract names of users that were mentioned but not notified yet
     *
     * @return names of users that were mentioned but not notified yet
     */
    private Set<String> extractNotNotifiedMentionedUsers(String canContainMentionedUsers) {
        return extractMentionedUsers(canContainMentionedUsers, MENTIONED_AND_NOT_NOTIFIED_USERS_PATTERN);
    }

    /**
     * Extract names of users that were mentioned in passed text.
     *
     * @param canContainMentionedUsers can contain users mentioning
     * @param mentionedUserPattern     pattern to extract mentioned user in given text
     * @return extracted users' names
     */
    private Set<String> extractMentionedUsers(String canContainMentionedUsers, Pattern mentionedUserPattern) {
        if (!StringUtils.isEmpty(canContainMentionedUsers)) {
            Matcher matcher = mentionedUserPattern.matcher(canContainMentionedUsers);
            Set<String> mentionedUsernames = new HashSet<>();
            while (matcher.find()) {
                String userBBCode = matcher.group();
                String mentionedUser;
                if (userBBCode.contains("[user notified=true]")) {
                    mentionedUser = StringUtils.substring(userBBCode, 20, userBBCode.length() - 7);
                } else {
                    mentionedUser = StringUtils.substring(userBBCode, 6, userBBCode.length() - 7);
                }
                mentionedUsernames.add(replacePlaceholdersWithChars(mentionedUser));
            }
            return mentionedUsernames;
        }
        return Collections.emptySet();
    }

    private String replacePlaceholdersWithChars(String userNameWithPlaceholders) {
        String formattedUserName = userNameWithPlaceholders;
        for (Map.Entry<String, String> decodeEntry : CHARS_PLACEHOLDERS.entrySet()) {
            formattedUserName = formattedUserName.replace(decodeEntry.getValue(), decodeEntry.getKey());
        }
        encodedUserNames.put(formattedUserName, userNameWithPlaceholders);
        return formattedUserName;
    }

    private String encodeUsername(String decodedUsername) {
        return encodedUserNames.get(decodedUsername);
    }

    /**
     * Gets list of users which should be notified
     *
     * @param mentionedUsernames the set of names of mentioned users
     * @param userDao            service for working with JCUser objects
     * @return list of users which should be notified
     */
    private List<JCUser> getNewUsersToNotify(Set<String> mentionedUsernames, UserDao userDao) {
        List<JCUser> mentionedUsers = userDao.getByUsernames(mentionedUsernames);
        List<JCUser> usersToNotify = new ArrayList<>();

        for (JCUser mentionedUser : mentionedUsers) {
            if (shouldNotificationBeSent(mentionedUser)) {
                usersToNotify.add(mentionedUser);
            }
        }

        return usersToNotify;
    }

    /**
     * Determines if it is needed to send notification to the user
     *
     * @param mentionedUser this user was mentioned
     * @return true if we need to send notification and false otherwise
     */
    private boolean shouldNotificationBeSent(JCUser mentionedUser) {
        boolean isOtherNotificationAlreadySent = post.getSubscribers().contains(mentionedUser);
        return !isOtherNotificationAlreadySent && mentionedUser.isMentioningNotificationsEnabled();
    }

    /**
     * Mark user tags as already notified
     *
     * @param mentionedUsernames the set of names of mentioned users
     * @param postDao            service for working with Post objects
     */
    private void markUsersAsAlreadyNotified(Set<String> mentionedUsernames, PostDao postDao) {
        for (String user : mentionedUsernames) {
            markUserAsAlreadyNotified(user, postDao);
        }
    }

    /**
     * Change BB user tag to mark user as already notified
     *
     * @param username this user was mentioned
     * @param postDao  service for working with Post objects
     */
    private void markUserAsAlreadyNotified(String username, PostDao postDao) {
        String initialUserMentioning = format(MENTIONED_NOT_NOTIFIED_USER_TEMPLATE, username);
        String notifiedUserMentioning = format(MENTIONED_AND_NOTIFIED_USER_TEMPLATE, username);

        String newPostContent = post.getPostContent().replace(initialUserMentioning, notifiedUserMentioning);
        post.setPostContent(newPostContent);
        postDao.saveOrUpdate(post);
    }

    /**
     * Get link to user's profile.
     *
     * @param username user's name
     * @return null when user doesn't exist, otherwise link to user's profile
     */
    private String getLinkToUserProfile(String username, UserDao userDao) {
        String userPofileLink = null;

        JCUser user = userDao.getByUsername(username);
        if (user != null && user.getUsername().equals(username)) {
            userPofileLink = getApplicationNameAsContextPath() + "/users/" + user.getId();
            LOGGER.trace("{} has the following url of profile - {}", username, userPofileLink);
        } else {
            LOGGER.trace("Mentioned user wasn't find: {}", username);
        }

        return userPofileLink;
    }

    /**
     * Get the name of application as context path.
     *
     * @return forum application name
     */
    private String getApplicationNameAsContextPath() {
        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
        return request.getContextPath();
    }

    /**
     * Add links to users' profiles for mentioned users.
     *
     * @param source                   will be changed and all mentioned users in it will contain links to their
     *                                 profiles
     * @param userToUserProfileLinkMap user to it links of profile map
     * @return source with users with attached links to profiles
     */
    private String addLinksToUserProfileForMentionedUsers(String source,
            Map<String, String> userToUserProfileLinkMap) {
        String changedSource = source;
        for (Map.Entry<String, String> userToLinkMap : userToUserProfileLinkMap.entrySet()) {
            String username = encodeUsername(userToLinkMap.getKey());
            String userNotNotifiedBBCode = format(MENTIONED_NOT_NOTIFIED_USER_TEMPLATE, username);
            String userNotifiedBBCode = format(MENTIONED_AND_NOTIFIED_USER_TEMPLATE, username);
            String userBBCodeWithLink = username;
            if (userToLinkMap.getValue() != null) {
                userBBCodeWithLink = format(USER_WITH_LINK_TO_PROFILE_TEMPLATE, userToLinkMap.getValue(), username);
            }
            changedSource = changedSource.replace(userNotNotifiedBBCode, userBBCodeWithLink);
            changedSource = changedSource.replace(userNotifiedBBCode, userBBCodeWithLink);
        }
        return changedSource;
    }
}