/* * 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.apache.commons.lang.StringUtils; import org.b3log.latke.Keys; import org.b3log.latke.Latkes; 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.model.User; import org.b3log.latke.repository.*; import org.b3log.latke.service.LangPropsService; import org.b3log.latke.service.ServiceException; import org.b3log.latke.service.annotation.Service; import org.b3log.latke.util.CollectionUtils; import org.b3log.latke.util.Locales; import org.b3log.latke.util.Paginator; import org.b3log.latke.util.Stopwatchs; import org.b3log.symphony.model.*; import org.b3log.symphony.processor.advice.validate.UserRegisterValidation; import org.b3log.symphony.repository.ArticleRepository; import org.b3log.symphony.repository.CommentRepository; import org.b3log.symphony.repository.UserRepository; import org.b3log.symphony.util.*; import org.json.JSONArray; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.safety.Whitelist; import java.util.*; /** * Comment management service. * * @author <a href="http://88250.b3log.org">Liang Ding</a> * @version 2.11.9.27, May 12, 2017 * @since 0.2.0 */ @Service public class CommentQueryService { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(CommentQueryService.class); /** * Revision query service. */ @Inject private RevisionQueryService revisionQueryService; /** * Comment repository. */ @Inject private CommentRepository commentRepository; /** * Article repository. */ @Inject private ArticleRepository articleRepository; /** * User repository. */ @Inject private UserRepository userRepository; /** * User query service. */ @Inject private UserQueryService userQueryService; /** * Avatar query service. */ @Inject private AvatarQueryService avatarQueryService; /** * Language service. */ @Inject private LangPropsService langPropsService; /** * Short link query service. */ @Inject private ShortLinkQueryService shortLinkQueryService; /** * Gets the page number of a comment. * * @param articleId the specified article id * @param commentId the specified comment id * @param sortMode the specified sort mode * @param pageSize the specified comment page size * @return page number, return {@code 1} if occurs exception */ public int getCommentPage(final String articleId, final String commentId, final int sortMode, final int pageSize) { final Query numQuery = new Query() .setPageSize(Integer.MAX_VALUE).setCurrentPageNum(1).setPageCount(1); switch (sortMode) { case UserExt.USER_COMMENT_VIEW_MODE_C_TRADITIONAL: numQuery.setFilter(CompositeFilterOperator.and( new PropertyFilter(Comment.COMMENT_ON_ARTICLE_ID, FilterOperator.EQUAL, articleId), new PropertyFilter(Keys.OBJECT_ID, FilterOperator.LESS_THAN, commentId) )).addSort(Keys.OBJECT_ID, SortDirection.ASCENDING); break; case UserExt.USER_COMMENT_VIEW_MODE_C_REALTIME: numQuery.setFilter(CompositeFilterOperator.and( new PropertyFilter(Comment.COMMENT_ON_ARTICLE_ID, FilterOperator.EQUAL, articleId), new PropertyFilter(Keys.OBJECT_ID, FilterOperator.GREATER_THAN, commentId) )).addSort(Keys.OBJECT_ID, SortDirection.DESCENDING); break; } Stopwatchs.start("Get comment page"); try { final long num = commentRepository.count(numQuery); return (int) ((num / pageSize) + 1); } catch (final Exception e) { LOGGER.log(Level.ERROR, "Gets comment page failed", e); return 1; } finally { Stopwatchs.end(); } } /** * Gets original comment of a comment specified by the given comment id. * * @param avatarViewMode the specified avatar view mode * @param commentViewMode the specified comment view mode * @param commentId the given comment id * @return original comment, return {@code null} if not found */ public JSONObject getOriginalComment(final int avatarViewMode, final int commentViewMode, final String commentId) { try { final JSONObject comment = commentRepository.get(commentId); organizeComment(avatarViewMode, comment); final int pageSize = Symphonys.getInt("articleCommentsPageSize"); final JSONObject ret = new JSONObject(); final JSONObject commentAuthor = comment.optJSONObject(Comment.COMMENT_T_COMMENTER); if (UserExt.USER_XXX_STATUS_C_PRIVATE == commentAuthor.optInt(UserExt.USER_UA_STATUS)) { ret.put(Comment.COMMENT_UA, ""); } ret.put(Comment.COMMENT_T_AUTHOR_NAME, comment.optString(Comment.COMMENT_T_AUTHOR_NAME)); ret.put(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL, comment.optString(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL)); ret.put(Common.TIME_AGO, comment.optString(Common.TIME_AGO)); ret.put(Common.REWARED_COUNT, comment.optString(Common.REWARED_COUNT)); ret.put(Common.REWARDED, comment.optBoolean(Common.REWARDED)); ret.put(Keys.OBJECT_ID, commentId); ret.put(Comment.COMMENT_CONTENT, comment.optString(Comment.COMMENT_CONTENT)); ret.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, getCommentPage( comment.optString(Comment.COMMENT_ON_ARTICLE_ID), commentId, commentViewMode, pageSize)); return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Get replies failed", e); return null; } } /** * Gets replies of a comment specified by the given comment id. * * @param avatarViewMode the specified avatar view mode * @param commentViewMode the specified comment view mode * @param commentId the given comment id * @return a list of replies, return an empty list if not found */ public List<JSONObject> getReplies(final int avatarViewMode, final int commentViewMode, final String commentId) { final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING). setPageSize(Integer.MAX_VALUE).setCurrentPageNum(1).setPageCount(1) .setFilter(CompositeFilterOperator.and( new PropertyFilter(Comment.COMMENT_ORIGINAL_COMMENT_ID, FilterOperator.EQUAL, commentId), new PropertyFilter(Comment.COMMENT_STATUS, FilterOperator.EQUAL, Comment.COMMENT_STATUS_C_VALID) )); try { final List<JSONObject> comments = CollectionUtils.jsonArrayToList( commentRepository.get(query).optJSONArray(Keys.RESULTS)); organizeComments(avatarViewMode, comments); final int pageSize = Symphonys.getInt("articleCommentsPageSize"); final List<JSONObject> ret = new ArrayList<>(); for (final JSONObject comment : comments) { final JSONObject reply = new JSONObject(); ret.add(reply); final JSONObject commentAuthor = comment.optJSONObject(Comment.COMMENT_T_COMMENTER); if (UserExt.USER_XXX_STATUS_C_PRIVATE == commentAuthor.optInt(UserExt.USER_UA_STATUS)) { reply.put(Comment.COMMENT_UA, ""); } reply.put(Comment.COMMENT_T_AUTHOR_NAME, comment.optString(Comment.COMMENT_T_AUTHOR_NAME)); reply.put(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL, comment.optString(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL)); reply.put(Common.TIME_AGO, comment.optString(Common.TIME_AGO)); reply.put(Common.REWARED_COUNT, comment.optString(Common.REWARED_COUNT)); reply.put(Common.REWARDED, comment.optBoolean(Common.REWARDED)); reply.put(Keys.OBJECT_ID, comment.optString(Keys.OBJECT_ID)); reply.put(Comment.COMMENT_CONTENT, comment.optString(Comment.COMMENT_CONTENT)); reply.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, getCommentPage( comment.optString(Comment.COMMENT_ON_ARTICLE_ID), reply.optString(Keys.OBJECT_ID), commentViewMode, pageSize)); } return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Get replies failed", e); return Collections.emptyList(); } } /** * Gets nice comments of an article specified by the given article id. * * @param avatarViewMode the specified avatar view mode * @param commentViewMode the specified comment view mode * @param articleId the given article id * @param fetchSize the specified fetch size * @return a list of nice comments, return an empty list if not found */ public List<JSONObject> getNiceComments(final int avatarViewMode, final int commentViewMode, final String articleId, final int fetchSize) { Stopwatchs.start("Gets nice comments"); try { final Query query = new Query().addSort(Comment.COMMENT_SCORE, SortDirection.DESCENDING). setPageSize(fetchSize).setCurrentPageNum(1).setPageCount(1) .setFilter(CompositeFilterOperator.and( new PropertyFilter(Comment.COMMENT_ON_ARTICLE_ID, FilterOperator.EQUAL, articleId), new PropertyFilter(Comment.COMMENT_SCORE, FilterOperator.GREATER_THAN, 0D), new PropertyFilter(Comment.COMMENT_STATUS, FilterOperator.EQUAL, Comment.COMMENT_STATUS_C_VALID) )); try { final List<JSONObject> ret = CollectionUtils.jsonArrayToList( commentRepository.get(query).optJSONArray(Keys.RESULTS)); organizeComments(avatarViewMode, ret); final int pageSize = Symphonys.getInt("articleCommentsPageSize"); for (final JSONObject comment : ret) { comment.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, getCommentPage( articleId, comment.optString(Keys.OBJECT_ID), commentViewMode, pageSize)); } return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Get nice comments failed", e); return Collections.emptyList(); } } finally { Stopwatchs.end(); } } /** * Gets comment count of the specified day. * * @param day the specified day * @return comment count */ public int getCommentCntInDay(final Date day) { final long time = day.getTime(); final long start = Times.getDayStartTime(time); final long end = Times.getDayEndTime(time); final Query query = new Query().setFilter(CompositeFilterOperator.and( new PropertyFilter(Keys.OBJECT_ID, FilterOperator.GREATER_THAN_OR_EQUAL, start), new PropertyFilter(Keys.OBJECT_ID, FilterOperator.LESS_THAN, end), new PropertyFilter(Comment.COMMENT_STATUS, FilterOperator.EQUAL, Comment.COMMENT_STATUS_C_VALID) )); try { return (int) commentRepository.count(query); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Count day comment failed", e); return 1; } } /** * Gets comment count of the specified month. * * @param day the specified month * @return comment count */ public int getCommentCntInMonth(final Date day) { final long time = day.getTime(); final long start = Times.getMonthStartTime(time); final long end = Times.getMonthEndTime(time); final Query query = new Query().setFilter(CompositeFilterOperator.and( new PropertyFilter(Keys.OBJECT_ID, FilterOperator.GREATER_THAN_OR_EQUAL, start), new PropertyFilter(Keys.OBJECT_ID, FilterOperator.LESS_THAN, end), new PropertyFilter(Comment.COMMENT_STATUS, FilterOperator.EQUAL, Comment.COMMENT_STATUS_C_VALID) )); try { return (int) commentRepository.count(query); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Count month comment failed", e); return 1; } } /** * Gets a comment with {@link #organizeComment(int, JSONObject)} by the specified comment id. * * @param avatarViewMode the specified avatar view mode * @param commentId the specified comment id * @return comment, returns {@code null} if not found * @throws ServiceException service exception */ public JSONObject getCommentById(final int avatarViewMode, final String commentId) throws ServiceException { try { final JSONObject ret = commentRepository.get(commentId); if (null == ret) { return null; } organizeComment(avatarViewMode, ret); return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, e.getMessage(), e); throw new ServiceException("Gets comment[id=" + commentId + "] failed"); } } /** * Gets a comment by the specified id. * * @param commentId the specified id * @return comment, return {@code null} if not found * @throws ServiceException service exception */ public JSONObject getComment(final String commentId) throws ServiceException { try { final JSONObject ret = commentRepository.get(commentId); if (null == ret) { return null; } return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets a comment [commentId=" + commentId + "] failed", e); throw new ServiceException(e); } } /** * Gets the latest comments with the specified fetch size. * <p> * <p> * The returned comments content is plain text. * </p> * * @param avatarViewMode the specified avatar view mode * @param fetchSize the specified fetch size * @return the latest comments, returns an empty list if not found * @throws ServiceException service exception */ public List<JSONObject> getLatestComments(final int avatarViewMode, final int fetchSize) throws ServiceException { final Query query = new Query().addSort(Comment.COMMENT_CREATE_TIME, SortDirection.DESCENDING) .setCurrentPageNum(1).setPageSize(fetchSize).setPageCount(1); try { final JSONObject result = commentRepository.get(query); final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS)); for (final JSONObject comment : ret) { comment.put(Comment.COMMENT_CREATE_TIME, comment.optLong(Comment.COMMENT_CREATE_TIME)); final String articleId = comment.optString(Comment.COMMENT_ON_ARTICLE_ID); final JSONObject article = articleRepository.get(articleId); comment.put(Comment.COMMENT_T_ARTICLE_TITLE, Emotions.clear(article.optString(Article.ARTICLE_TITLE))); comment.put(Comment.COMMENT_T_ARTICLE_PERMALINK, article.optString(Article.ARTICLE_PERMALINK)); final String commenterId = comment.optString(Comment.COMMENT_AUTHOR_ID); final JSONObject commenter = userRepository.get(commenterId); if (UserExt.USER_STATUS_C_INVALID == commenter.optInt(UserExt.USER_STATUS) || Comment.COMMENT_STATUS_C_INVALID == comment.optInt(Comment.COMMENT_STATUS)) { comment.put(Comment.COMMENT_CONTENT, langPropsService.get("commentContentBlockLabel")); } if (Article.ARTICLE_TYPE_C_DISCUSSION == article.optInt(Article.ARTICLE_TYPE)) { comment.put(Comment.COMMENT_CONTENT, "...."); } String content = comment.optString(Comment.COMMENT_CONTENT); content = Emotions.clear(content); content = Jsoup.clean(content, Whitelist.none()); if (StringUtils.isBlank(content)) { comment.put(Comment.COMMENT_CONTENT, "...."); } else { comment.put(Comment.COMMENT_CONTENT, content); } final String commenterEmail = commenter.optString(User.USER_EMAIL); String avatarURL = Symphonys.get("defaultThumbnailURL"); if (!UserExt.DEFAULT_CMTER_EMAIL.equals(commenterEmail)) { avatarURL = avatarQueryService.getAvatarURLByUser(avatarViewMode, commenter, "20"); } commenter.put(UserExt.USER_AVATAR_URL, avatarURL); comment.put(Comment.COMMENT_T_COMMENTER, commenter); } return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets user comments failed", e); throw new ServiceException(e); } } /** * Gets the user comments with the specified user id, page number and page size. * * @param avatarViewMode the specified avatar view mode * @param userId the specified user id * @param anonymous the specified comment anonymous * @param currentPageNum the specified page number * @param pageSize the specified page size * @param viewer the specified viewer, may be {@code null} * @return user comments, return an empty list if not found * @throws ServiceException service exception */ public List<JSONObject> getUserComments(final int avatarViewMode, final String userId, final int anonymous, final int currentPageNum, final int pageSize, final JSONObject viewer) throws ServiceException { final Query query = new Query().addSort(Comment.COMMENT_CREATE_TIME, SortDirection.DESCENDING) .setCurrentPageNum(currentPageNum).setPageSize(pageSize). setFilter(CompositeFilterOperator.and( new PropertyFilter(Comment.COMMENT_AUTHOR_ID, FilterOperator.EQUAL, userId), new PropertyFilter(Comment.COMMENT_ANONYMOUS, FilterOperator.EQUAL, anonymous) )); try { final JSONObject result = commentRepository.get(query); final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS)); if (ret.isEmpty()) { return ret; } final JSONObject pagination = result.optJSONObject(Pagination.PAGINATION); final int recordCount = pagination.optInt(Pagination.PAGINATION_RECORD_COUNT); final int pageCount = pagination.optInt(Pagination.PAGINATION_PAGE_COUNT); final JSONObject first = ret.get(0); first.put(Pagination.PAGINATION_RECORD_COUNT, recordCount); first.put(Pagination.PAGINATION_PAGE_COUNT, pageCount); for (final JSONObject comment : ret) { comment.put(Comment.COMMENT_CREATE_TIME, new Date(comment.optLong(Comment.COMMENT_CREATE_TIME))); final String articleId = comment.optString(Comment.COMMENT_ON_ARTICLE_ID); final JSONObject article = articleRepository.get(articleId); comment.put(Comment.COMMENT_T_ARTICLE_TITLE, Article.ARTICLE_STATUS_C_INVALID == article.optInt(Article.ARTICLE_STATUS) ? langPropsService.get("articleTitleBlockLabel") : Emotions.convert(article.optString(Article.ARTICLE_TITLE))); comment.put(Comment.COMMENT_T_ARTICLE_TYPE, article.optInt(Article.ARTICLE_TYPE)); comment.put(Comment.COMMENT_T_ARTICLE_PERMALINK, article.optString(Article.ARTICLE_PERMALINK)); comment.put(Comment.COMMENT_T_ARTICLE_PERFECT, article.optInt(Article.ARTICLE_PERFECT)); final JSONObject commenter = userRepository.get(userId); comment.put(Comment.COMMENT_T_COMMENTER, commenter); final String articleAuthorId = article.optString(Article.ARTICLE_AUTHOR_ID); final JSONObject articleAuthor = userRepository.get(articleAuthorId); final String articleAuthorName = articleAuthor.optString(User.USER_NAME); if (Article.ARTICLE_ANONYMOUS_C_PUBLIC == article.optInt(Article.ARTICLE_ANONYMOUS)) { comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_NAME, articleAuthorName); comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_URL, "/member/" + articleAuthor.optString(User.USER_NAME)); final String articleAuthorThumbnailURL = avatarQueryService.getAvatarURLByUser( avatarViewMode, articleAuthor, "48"); comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_THUMBNAIL_URL, articleAuthorThumbnailURL); } else { comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_NAME, UserExt.ANONYMOUS_USER_NAME); comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_URL, ""); comment.put(Comment.COMMENT_T_ARTICLE_AUTHOR_THUMBNAIL_URL, avatarQueryService.getDefaultAvatarURL("48")); } if (Article.ARTICLE_TYPE_C_DISCUSSION == article.optInt(Article.ARTICLE_TYPE) && Article.ARTICLE_ANONYMOUS_C_PUBLIC == article.optInt(Article.ARTICLE_ANONYMOUS)) { final String msgContent = langPropsService.get("articleDiscussionLabel"). replace("{user}", "<a href='" + Latkes.getServePath() + "/member/" + articleAuthorName + "'>" + articleAuthorName + "</a>"); if (null == viewer) { comment.put(Comment.COMMENT_CONTENT, msgContent); } else { final String commenterName = commenter.optString(User.USER_NAME); final String viewerUserName = viewer.optString(User.USER_NAME); final String viewerRole = viewer.optString(User.USER_ROLE); if (!commenterName.equals(viewerUserName) && !Role.ROLE_ID_C_ADMIN.equals(viewerRole)) { final String articleContent = article.optString(Article.ARTICLE_CONTENT); final Set<String> userNames = userQueryService.getUserNames(articleContent); boolean invited = false; for (final String userName : userNames) { if (userName.equals(viewerUserName)) { invited = true; break; } } if (!invited) { comment.put(Comment.COMMENT_CONTENT, msgContent); } } } } processCommentContent(comment); } return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets user comments failed", e); throw new ServiceException(e); } } /** * Gets the article comments with the specified article id, page number and page size. * * @param avatarViewMode the specified avatar view mode * @param articleId the specified article id * @param currentPageNum the specified page number * @param pageSize the specified page size * @param sortMode the specified sort mode (traditional: 0, real time: 1) * @return comments, return an empty list if not found * @throws ServiceException service exception */ public List<JSONObject> getArticleComments(final int avatarViewMode, final String articleId, final int currentPageNum, final int pageSize, final int sortMode) throws ServiceException { Stopwatchs.start("Get comments"); final Query query = new Query() .setPageCount(1).setCurrentPageNum(currentPageNum).setPageSize(pageSize) .setFilter(new PropertyFilter(Comment.COMMENT_ON_ARTICLE_ID, FilterOperator.EQUAL, articleId)); if (UserExt.USER_COMMENT_VIEW_MODE_C_REALTIME == sortMode) { query.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING); } else { query.addSort(Keys.OBJECT_ID, SortDirection.ASCENDING); } try { Stopwatchs.start("Query comments"); JSONObject result; try { result = commentRepository.get(query); } finally { Stopwatchs.end(); } final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS)); organizeComments(avatarViewMode, ret); Stopwatchs.start("Revision, paging, original"); try { for (final JSONObject comment : ret) { final String commentId = comment.optString(Keys.OBJECT_ID); // Fill revision count comment.put(Comment.COMMENT_REVISION_COUNT, revisionQueryService.count(commentId, Revision.DATA_TYPE_C_COMMENT)); final String originalCmtId = comment.optString(Comment.COMMENT_ORIGINAL_COMMENT_ID); if (StringUtils.isBlank(originalCmtId)) { continue; } // Fill page number comment.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, getCommentPage(articleId, originalCmtId, sortMode, pageSize)); // Fill original comment final JSONObject originalCmt = commentRepository.get(originalCmtId); organizeComment(avatarViewMode, originalCmt); comment.put(Comment.COMMENT_T_ORIGINAL_AUTHOR_THUMBNAIL_URL, originalCmt.optString(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL)); } } finally { Stopwatchs.end(); } return ret; } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets article [" + articleId + "] comments failed", e); throw new ServiceException(e); } finally { Stopwatchs.end(); } } /** * Gets comments by the specified request json object. * * @param avatarViewMode the specified avatar view mode * @param requestJSONObject the specified request json object, for example, * "paginationCurrentPageNum": 1, * "paginationPageSize": 20, * "paginationWindowSize": 10, * , see {@link Pagination} for more details * @param commentFields the specified article fields to return * @return for example, <pre> * { * "pagination": { * "paginationPageCount": 100, * "paginationPageNums": [1, 2, 3, 4, 5] * }, * "comments": [{ * "oId": "", * "commentContent": "", * "commentCreateTime": "", * .... * }, ....] * } * </pre> * @throws ServiceException service exception * @see Pagination */ public JSONObject getComments(final int avatarViewMode, final JSONObject requestJSONObject, final Map<String, Class<?>> commentFields) throws ServiceException { final JSONObject ret = new JSONObject(); final int currentPageNum = requestJSONObject.optInt(Pagination.PAGINATION_CURRENT_PAGE_NUM); final int pageSize = requestJSONObject.optInt(Pagination.PAGINATION_PAGE_SIZE); final int windowSize = requestJSONObject.optInt(Pagination.PAGINATION_WINDOW_SIZE); final Query query = new Query().setCurrentPageNum(currentPageNum).setPageSize(pageSize) .addSort(Comment.COMMENT_CREATE_TIME, SortDirection.DESCENDING); for (final Map.Entry<String, Class<?>> commentField : commentFields.entrySet()) { query.addProjection(commentField.getKey(), commentField.getValue()); } JSONObject result = null; try { result = commentRepository.get(query); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Gets comments failed", e); throw new ServiceException(e); } final int pageCount = result.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_PAGE_COUNT); final JSONObject pagination = new JSONObject(); ret.put(Pagination.PAGINATION, pagination); final List<Integer> pageNums = Paginator.paginate(currentPageNum, pageSize, pageCount, windowSize); pagination.put(Pagination.PAGINATION_PAGE_COUNT, pageCount); pagination.put(Pagination.PAGINATION_PAGE_NUMS, pageNums); final JSONArray data = result.optJSONArray(Keys.RESULTS); final List<JSONObject> comments = CollectionUtils.<JSONObject>jsonArrayToList(data); try { for (final JSONObject comment : comments) { organizeComment(avatarViewMode, comment); final String articleId = comment.optString(Comment.COMMENT_ON_ARTICLE_ID); final JSONObject article = articleRepository.get(articleId); comment.put(Comment.COMMENT_T_ARTICLE_TITLE, Article.ARTICLE_STATUS_C_INVALID == article.optInt(Article.ARTICLE_STATUS) ? langPropsService.get("articleTitleBlockLabel") : Emotions.convert(article.optString(Article.ARTICLE_TITLE))); comment.put(Comment.COMMENT_T_ARTICLE_PERMALINK, article.optString(Article.ARTICLE_PERMALINK)); } } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Organizes comments failed", e); throw new ServiceException(e); } ret.put(Comment.COMMENTS, comments); return ret; } /** * Organizes the specified comments. * <p> * <ul> * <li>converts comment create time (long) to date type</li> * <li>generates comment author thumbnail URL</li> * <li>generates comment author URL</li> * <li>generates comment author name</li> * <li>generates @username home URL</li> * <li>markdowns comment content</li> * <li>block comment if need</li> * <li>generates emotion images</li> * <li>generates time ago text</li> * <li>anonymous process</li> * </ul> * * @param avatarViewMode the specified avatar view mode * @param comments the specified comments * @throws RepositoryException repository exception */ private void organizeComments(final int avatarViewMode, final List<JSONObject> comments) throws RepositoryException { Stopwatchs.start("Organizes comments"); try { for (final JSONObject comment : comments) { organizeComment(avatarViewMode, comment); } } finally { Stopwatchs.end(); } } /** * Organizes the specified comment. * <p> * <ul> * <li>converts comment create time (long) to date type</li> * <li>generates comment author thumbnail URL</li> * <li>generates comment author URL</li> * <li>generates comment author name</li> * <li>generates @username home URL</li> * <li>markdowns comment content</li> * <li>block comment if need</li> * <li>generates emotion images</li> * <li>generates time ago text</li> * <li>anonymous process</li> * </ul> * * @param avatarViewMode the specified avatar view mode * @param comment the specified comment * @throws RepositoryException repository exception */ private void organizeComment(final int avatarViewMode, final JSONObject comment) throws RepositoryException { Stopwatchs.start("Organize comment"); try { comment.put(Common.TIME_AGO, Times.getTimeAgo(comment.optLong(Comment.COMMENT_CREATE_TIME), Locales.getLocale())); comment.put(Comment.COMMENT_CREATE_TIME, new Date(comment.optLong(Comment.COMMENT_CREATE_TIME))); final String authorId = comment.optString(Comment.COMMENT_AUTHOR_ID); final JSONObject author = userRepository.get(authorId); comment.put(Comment.COMMENT_T_COMMENTER, author); if (Comment.COMMENT_ANONYMOUS_C_PUBLIC == comment.optInt(Comment.COMMENT_ANONYMOUS)) { comment.put(Comment.COMMENT_T_AUTHOR_NAME, author.optString(User.USER_NAME)); comment.put(Comment.COMMENT_T_AUTHOR_URL, author.optString(User.USER_URL)); final String thumbnailURL = avatarQueryService.getAvatarURLByUser(avatarViewMode, author, "48"); comment.put(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL, thumbnailURL); } else { comment.put(Comment.COMMENT_T_AUTHOR_NAME, UserExt.ANONYMOUS_USER_NAME); comment.put(Comment.COMMENT_T_AUTHOR_URL, ""); comment.put(Comment.COMMENT_T_AUTHOR_THUMBNAIL_URL, avatarQueryService.getDefaultAvatarURL("48")); } processCommentContent(comment); } finally { Stopwatchs.end(); } } /** * Processes the specified comment content. * <p> * <ul> * <li>Generates @username home URL</li> * <li>Markdowns</li> * <li>Blocks comment if need</li> * <li>Generates emotion images</li> * <li>Generates article link with article id</li> * </ul> * * @param comment the specified comment, for example, * "commentContent": "", * ...., * "commenter": {} */ private void processCommentContent(final JSONObject comment) { final JSONObject commenter = comment.optJSONObject(Comment.COMMENT_T_COMMENTER); final boolean sync = StringUtils.isNotBlank(comment.optString(Comment.COMMENT_CLIENT_COMMENT_ID)); comment.put(Common.FROM_CLIENT, sync); if (Comment.COMMENT_STATUS_C_INVALID == comment.optInt(Comment.COMMENT_STATUS) || UserExt.USER_STATUS_C_INVALID == commenter.optInt(UserExt.USER_STATUS)) { comment.put(Comment.COMMENT_CONTENT, langPropsService.get("commentContentBlockLabel")); return; } String commentContent = comment.optString(Comment.COMMENT_CONTENT); commentContent = shortLinkQueryService.linkArticle(commentContent); commentContent = shortLinkQueryService.linkTag(commentContent); commentContent = Emotions.convert(commentContent); commentContent = Markdowns.toHTML(commentContent); commentContent = Markdowns.clean(commentContent, ""); commentContent = MP3Players.render(commentContent); if (sync) { // "<i class='ft-small'>by 88250</i>" String syncCommenterName = StringUtils.substringAfter(commentContent, "<i class=\"ft-small\">by "); syncCommenterName = StringUtils.substringBefore(syncCommenterName, "</i>"); if (UserRegisterValidation.invalidUserName(syncCommenterName)) { syncCommenterName = UserExt.ANONYMOUS_USER_NAME; } commentContent = commentContent.replaceAll("<i class=\"ft-small\">by .*</i>", ""); comment.put(Comment.COMMENT_T_AUTHOR_NAME, syncCommenterName); } comment.put(Comment.COMMENT_CONTENT, commentContent); } }