/* * Symphony - A modern community (forum/SNS/blog) platform written in Java. * Copyright (C) 2012-2017, b3log.org & hacpai.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.b3log.symphony.service; import org.b3log.latke.Keys; import org.b3log.latke.ioc.Lifecycle; import org.b3log.latke.ioc.inject.Inject; import org.b3log.latke.logging.Level; import org.b3log.latke.logging.Logger; import org.b3log.latke.model.Pagination; import org.b3log.latke.repository.*; import org.b3log.latke.service.ServiceException; import org.b3log.latke.service.annotation.Service; import org.b3log.latke.util.CollectionUtils; import org.b3log.latke.util.Stopwatchs; import org.b3log.symphony.model.Follow; import org.b3log.symphony.repository.ArticleRepository; import org.b3log.symphony.repository.FollowRepository; import org.b3log.symphony.repository.TagRepository; import org.b3log.symphony.repository.UserRepository; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * Follow query service. * * @author <a href="http://88250.b3log.org">Liang Ding</a> * @version 1.4.0.4, Jan 19, 2017 * @since 0.2.5 */ @Service public class FollowQueryService { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(FollowQueryService.class.getName()); /** * Follow repository. */ @Inject private FollowRepository followRepository; /** * User repository. */ @Inject private UserRepository userRepository; /** * Tag repository. */ @Inject private TagRepository tagRepository; /** * Article repository. */ @Inject private ArticleRepository articleRepository; /** * Avatar query service. */ @Inject private AvatarQueryService avatarQueryService; /** * Determines whether exists a follow relationship for the specified follower and the specified following entity. * * @param followerId the specified follower id * @param followingId the specified following entity id * @param followingType the specified following type * @return {@code true} if exists, returns {@code false} otherwise */ public boolean isFollowing(final String followerId, final String followingId, final int followingType) { Stopwatchs.start("Is following"); try { return followRepository.exists(followerId, followingId, followingType); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Determines following failed[followerId=" + followerId + ", followingId=" + followingId + ']', e); return false; } finally { Stopwatchs.end(); } } /** * Gets following users of the specified follower. * * @param avatarViewMode the specified avatar view mode * @param followerId the specified follower id * @param currentPageNum the specified page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * User * }, ....] * } * </pre> * @throws ServiceException service exception */ public JSONObject getFollowingUsers(final int avatarViewMode, final String followerId, final int currentPageNum, final int pageSize) throws ServiceException { final JSONObject ret = new JSONObject(); final List<JSONObject> records = new ArrayList<>(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, 0); try { final JSONObject result = getFollowings(followerId, Follow.FOLLOWING_TYPE_C_USER, currentPageNum, pageSize); @SuppressWarnings("unchecked") final List<JSONObject> followings = (List<JSONObject>) result.opt(Keys.RESULTS); for (final JSONObject follow : followings) { final String followingId = follow.optString(Follow.FOLLOWING_ID); final JSONObject user = userRepository.get(followingId); if (null == user) { LOGGER.log(Level.WARN, "Not found user[id=" + followingId + ']'); continue; } avatarQueryService.fillUserAvatarURL(avatarViewMode, user); records.add(user); } ret.put(Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT)); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets following users of follower[id=" + followerId + "] failed", e); } return ret; } /** * Gets following tags of the specified follower. * * @param followerId the specified follower id * @param currentPageNum the specified page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * Tag * }, ....] * } * </pre> * @throws ServiceException service exception */ public JSONObject getFollowingTags(final String followerId, final int currentPageNum, final int pageSize) throws ServiceException { final JSONObject ret = new JSONObject(); final List<JSONObject> records = new ArrayList<>(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, 0); try { final JSONObject result = getFollowings(followerId, Follow.FOLLOWING_TYPE_C_TAG, currentPageNum, pageSize); @SuppressWarnings("unchecked") final List<JSONObject> followings = (List<JSONObject>) result.opt(Keys.RESULTS); for (final JSONObject follow : followings) { final String followingId = follow.optString(Follow.FOLLOWING_ID); final JSONObject tag = tagRepository.get(followingId); if (null == tag) { LOGGER.log(Level.WARN, "Not found tag[id=" + followingId + ']'); continue; } records.add(tag); } ret.put(Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT)); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets following tags of follower[id=" + followerId + "] failed", e); } return ret; } /** * Gets following articles of the specified follower. * * @param avatarViewMode the specified avatar view mode * @param followerId the specified follower id * @param currentPageNum the specified page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * Article * }, ....] * } * </pre> * @throws ServiceException service exception */ public JSONObject getFollowingArticles(final int avatarViewMode, final String followerId, final int currentPageNum, final int pageSize) throws ServiceException { final JSONObject ret = new JSONObject(); final List<JSONObject> records = new ArrayList<>(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, 0); try { final JSONObject result = getFollowings(followerId, Follow.FOLLOWING_TYPE_C_ARTICLE, currentPageNum, pageSize); @SuppressWarnings("unchecked") final List<JSONObject> followings = (List<JSONObject>) result.opt(Keys.RESULTS); final ArticleQueryService articleQueryService = Lifecycle.getBeanManager().getReference(ArticleQueryService.class); for (final JSONObject follow : followings) { final String followingId = follow.optString(Follow.FOLLOWING_ID); final JSONObject article = articleRepository.get(followingId); if (null == article) { LOGGER.log(Level.WARN, "Not found article [id=" + followingId + ']'); continue; } articleQueryService.organizeArticle(avatarViewMode, article); records.add(article); } ret.put(Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT)); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Get following articles of follower [id=" + followerId + "] failed", e); } return ret; } /** * Gets watching articles of the specified follower. * * @param avatarViewMode the specified avatar view mode * @param followerId the specified follower id * @param currentPageNum the specified page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * Article * }, ....] * } * </pre> * @throws ServiceException service exception */ public JSONObject getWatchingArticles(final int avatarViewMode, final String followerId, final int currentPageNum, final int pageSize) throws ServiceException { final JSONObject ret = new JSONObject(); final List<JSONObject> records = new ArrayList<>(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, 0); try { final JSONObject result = getFollowings(followerId, Follow.FOLLOWING_TYPE_C_ARTICLE_WATCH, currentPageNum, pageSize); @SuppressWarnings("unchecked") final List<JSONObject> followings = (List<JSONObject>) result.opt(Keys.RESULTS); final ArticleQueryService articleQueryService = Lifecycle.getBeanManager().getReference(ArticleQueryService.class); for (final JSONObject follow : followings) { final String followingId = follow.optString(Follow.FOLLOWING_ID); final JSONObject article = articleRepository.get(followingId); if (null == article) { LOGGER.log(Level.WARN, "Not found article [id=" + followingId + ']'); continue; } articleQueryService.organizeArticle(avatarViewMode, article); records.add(article); } ret.put(Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT)); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Get watching articles of follower [id=" + followerId + "] failed", e); } return ret; } /** * Gets watcher users of the specified watching article. * * @param avatarViewMode the specified avatar view mode * @param watchingArticleId the specified watching article id * @param currentPageNum the specified page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * User * }, ....] * } * </pre> * @throws ServiceException service exception */ public JSONObject getArticleWatchers(final int avatarViewMode, final String watchingArticleId, final int currentPageNum, final int pageSize) throws ServiceException { final JSONObject ret = new JSONObject(); final List<JSONObject> records = new ArrayList<>(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, 0); try { final JSONObject result = getFollowers(watchingArticleId, Follow.FOLLOWING_TYPE_C_ARTICLE_WATCH, currentPageNum, pageSize); @SuppressWarnings("unchecked") final List<JSONObject> followers = (List<JSONObject>) result.opt(Keys.RESULTS); for (final JSONObject follow : followers) { final String followerId = follow.optString(Follow.FOLLOWER_ID); final JSONObject user = userRepository.get(followerId); if (null == user) { LOGGER.log(Level.WARN, "Not found user[id=" + followerId + ']'); continue; } avatarQueryService.fillUserAvatarURL(avatarViewMode, user); records.add(user); } ret.put(Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT)); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets watcher users of watching article [id=" + watchingArticleId + "] failed", e); } return ret; } /** * Gets follower users of the specified following user. * * @param avatarViewMode the specified avatar view mode * @param followingUserId the specified following user id * @param currentPageNum the specified page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * User * }, ....] * } * </pre> * @throws ServiceException service exception */ public JSONObject getFollowerUsers(final int avatarViewMode, final String followingUserId, final int currentPageNum, final int pageSize) throws ServiceException { final JSONObject ret = new JSONObject(); final List<JSONObject> records = new ArrayList<>(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, 0); try { final JSONObject result = getFollowers(followingUserId, Follow.FOLLOWING_TYPE_C_USER, currentPageNum, pageSize); @SuppressWarnings("unchecked") final List<JSONObject> followers = (List<JSONObject>) result.opt(Keys.RESULTS); for (final JSONObject follow : followers) { final String followerId = follow.optString(Follow.FOLLOWER_ID); final JSONObject user = userRepository.get(followerId); if (null == user) { LOGGER.log(Level.WARN, "Not found user[id=" + followerId + ']'); continue; } avatarQueryService.fillUserAvatarURL(avatarViewMode, user); records.add(user); } ret.put(Pagination.PAGINATION_RECORD_COUNT, result.optInt(Pagination.PAGINATION_RECORD_COUNT)); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets follower users of following user[id=" + followingUserId + "] failed", e); } return ret; } /** * Gets the following count of a follower specified by the given follower id and following type. * * @param followerId the given follower id * @param followingType the given following type * @return count */ public long getFollowingCount(final String followerId, final int followingType) { Stopwatchs.start("Gets following count [" + followingType + "]"); try { final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(Follow.FOLLOWER_ID, FilterOperator.EQUAL, followerId)); filters.add(new PropertyFilter(Follow.FOLLOWING_TYPE, FilterOperator.EQUAL, followingType)); final Query query = new Query().setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)); try { return followRepository.count(query); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Counts following count error", e); return 0; } } finally { Stopwatchs.end(); } } /** * Gets the follower count of a following specified by the given following id and following type. * * @param followingId the given following id * @param followingType the given following type * @return count */ public long getFollowerCount(final String followingId, final int followingType) { final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(Follow.FOLLOWING_ID, FilterOperator.EQUAL, followingId)); filters.add(new PropertyFilter(Follow.FOLLOWING_TYPE, FilterOperator.EQUAL, followingType)); final Query query = new Query().setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)); try { return followRepository.count(query); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Counts follower count error", e); return 0; } } /** * Gets the followings of a follower specified by the given follower id and following type. * * @param followerId the given follower id * @param followingType the specified following type * @param currentPageNum the specified current page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * "oId": "", * "followerId": "", * "followingId": "", * "followingType": int * }, ....] * } * </pre> * @throws RepositoryException repository exception */ private JSONObject getFollowings(final String followerId, final int followingType, final int currentPageNum, final int pageSize) throws RepositoryException { final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(Follow.FOLLOWER_ID, FilterOperator.EQUAL, followerId)); filters.add(new PropertyFilter(Follow.FOLLOWING_TYPE, FilterOperator.EQUAL, followingType)); final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING). setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)) .setPageSize(pageSize).setCurrentPageNum(currentPageNum); final JSONObject result = followRepository.get(query); final List<JSONObject> records = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS)); final int recordCnt = result.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_RECORD_COUNT); final JSONObject ret = new JSONObject(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, recordCnt); return ret; } /** * Gets the followers of a following specified by the given following id and follow type. * * @param followingId the given following id * @param followingType the specified following type * @param currentPageNum the specified current page number * @param pageSize the specified page size * @return result json object, for example, <pre> * { * "paginationRecordCount": int, * "rslts": java.util.List[{ * "oId": "", * "followerId": "", * "followingId": "", * "followingType": int * }, ....] * } * </pre> * @throws RepositoryException repository exception */ private JSONObject getFollowers(final String followingId, final int followingType, final int currentPageNum, final int pageSize) throws RepositoryException { final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(Follow.FOLLOWING_ID, FilterOperator.EQUAL, followingId)); filters.add(new PropertyFilter(Follow.FOLLOWING_TYPE, FilterOperator.EQUAL, followingType)); final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING). setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)) .setPageSize(pageSize).setCurrentPageNum(currentPageNum); final JSONObject result = followRepository.get(query); final List<JSONObject> records = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS)); final int recordCnt = result.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_RECORD_COUNT); final JSONObject ret = new JSONObject(); ret.put(Keys.RESULTS, (Object) records); ret.put(Pagination.PAGINATION_RECORD_COUNT, recordCnt); return ret; } }