/*
* 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 com.vdurmont.emoji.EmojiParser;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
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.*;
import org.b3log.symphony.cache.ArticleCache;
import org.b3log.symphony.model.*;
import org.b3log.symphony.processor.advice.validate.UserRegisterValidation;
import org.b3log.symphony.processor.channel.ArticleChannel;
import org.b3log.symphony.repository.*;
import org.b3log.symphony.util.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;
import org.jsoup.safety.Whitelist;
import org.jsoup.select.Elements;
import javax.servlet.http.HttpServletRequest;
import java.text.DecimalFormat;
import java.util.*;
/**
* Article query service.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 2.27.30.58, May 13, 2017
* @since 0.2.0
*/
@Service
public class ArticleQueryService {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(ArticleQueryService.class);
/**
* Count to fetch article tags for relevant articles.
*/
private static final int RELEVANT_ARTICLE_RANDOM_FETCH_TAG_CNT = 3;
/**
* Article repository.
*/
@Inject
private ArticleRepository articleRepository;
/**
* Comment repository.
*/
@Inject
private CommentRepository commentRepository;
/**
* Tag-Article repository.
*/
@Inject
private TagArticleRepository tagArticleRepository;
/**
* Tag repository.
*/
@Inject
private TagRepository tagRepository;
/**
* User repository.
*/
@Inject
private UserRepository userRepository;
/**
* Domain tag repository.
*/
@Inject
private DomainTagRepository domainTagRepository;
/**
* Comment query service.
*/
@Inject
private CommentQueryService commentQueryService;
/**
* User query service.
*/
@Inject
private UserQueryService userQueryService;
/**
* Avatar query service.
*/
@Inject
private AvatarQueryService avatarQueryService;
/**
* Short link query service.
*/
@Inject
private ShortLinkQueryService shortLinkQueryService;
/**
* Follow query service.
*/
@Inject
private FollowQueryService followQueryService;
/**
* Language service.
*/
@Inject
private LangPropsService langPropsService;
/**
* Article cache.
*/
@Inject
private ArticleCache articleCache;
/**
* Gets following user articles.
*
* @param avatarViewMode the specified avatar view mode
* @param userId the specified user id
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return following tag articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getFollowingUserArticles(final int avatarViewMode, final String userId,
final int currentPageNum, final int pageSize) throws ServiceException {
final List<JSONObject> users = (List<JSONObject>) followQueryService.getFollowingUsers(
avatarViewMode, userId, 1, Integer.MAX_VALUE).opt(Keys.RESULTS);
if (users.isEmpty()) {
return Collections.emptyList();
}
final Query query = new Query()
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageSize(pageSize).setCurrentPageNum(currentPageNum);
final List<String> followingUserIds = new ArrayList<>();
for (final JSONObject user : users) {
followingUserIds.add(user.optString(Keys.OBJECT_ID));
}
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID));
filters.add(new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION));
filters.add(new PropertyFilter(Article.ARTICLE_AUTHOR_ID, FilterOperator.IN, followingUserIds));
query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));
query.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class).
addProjection(Article.ARTICLE_CONTENT, String.class);
JSONObject result = null;
try {
Stopwatchs.start("Query following user articles");
result = articleRepository.get(query);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets following user articles failed", e);
throw new ServiceException(e);
} finally {
Stopwatchs.end();
}
final JSONArray data = result.optJSONArray(Keys.RESULTS);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(data);
try {
organizeArticles(avatarViewMode, ret);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Organizes articles failed", e);
throw new ServiceException(e);
}
return ret;
}
/**
* Gets following tag articles.
*
* @param avatarViewMode the specified avatar view mode
* @param userId the specified user id
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return following tag articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getFollowingTagArticles(final int avatarViewMode, final String userId,
final int currentPageNum, final int pageSize) throws ServiceException {
final List<JSONObject> tags = (List<JSONObject>) followQueryService.getFollowingTags(
userId, 1, Integer.MAX_VALUE).opt(Keys.RESULTS);
if (tags.isEmpty()) {
return Collections.emptyList();
}
final Map<String, Class<?>> articleFields = new HashMap<>();
articleFields.put(Keys.OBJECT_ID, String.class);
articleFields.put(Article.ARTICLE_STICK, Long.class);
articleFields.put(Article.ARTICLE_CREATE_TIME, Long.class);
articleFields.put(Article.ARTICLE_UPDATE_TIME, Long.class);
articleFields.put(Article.ARTICLE_LATEST_CMT_TIME, Long.class);
articleFields.put(Article.ARTICLE_AUTHOR_ID, String.class);
articleFields.put(Article.ARTICLE_TITLE, String.class);
articleFields.put(Article.ARTICLE_STATUS, Integer.class);
articleFields.put(Article.ARTICLE_VIEW_CNT, Integer.class);
articleFields.put(Article.ARTICLE_TYPE, Integer.class);
articleFields.put(Article.ARTICLE_PERMALINK, String.class);
articleFields.put(Article.ARTICLE_TAGS, String.class);
articleFields.put(Article.ARTICLE_LATEST_CMTER_NAME, String.class);
articleFields.put(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class);
articleFields.put(Article.ARTICLE_COMMENT_CNT, Integer.class);
articleFields.put(Article.ARTICLE_ANONYMOUS, Integer.class);
articleFields.put(Article.ARTICLE_PERFECT, Integer.class);
articleFields.put(Article.ARTICLE_CONTENT, String.class);
return getArticlesByTags(avatarViewMode, currentPageNum, pageSize, articleFields, tags.toArray(new JSONObject[0]));
}
/**
* Gets the next article.
*
* @param articleId the specified article id
* @return permalink and title, <pre>
* {
* "articlePermalink": "",
* "articleTitle": "",
* "articleTitleEmoj": "",
* "articleTitleEmojUnicode": ""
* }
* </pre>, returns {@code null} if not found
*/
public JSONObject getNextPermalink(final String articleId) {
Stopwatchs.start("Get next");
try {
final Query query = new Query().setFilter(
new PropertyFilter(Keys.OBJECT_ID, FilterOperator.GREATER_THAN, articleId)).
addSort(Keys.OBJECT_ID, SortDirection.ASCENDING).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
setCurrentPageNum(1).setPageCount(1).setPageSize(1);
final JSONArray result = articleRepository.get(query).optJSONArray(Keys.RESULTS);
if (0 == result.length()) {
return null;
}
final JSONObject ret = result.optJSONObject(0);
if (null == ret) {
return null;
}
String title = ret.optString(Article.ARTICLE_TITLE);
ret.put(Article.ARTICLE_T_TITLE_EMOJI, Emotions.convert(title));
ret.put(Article.ARTICLE_T_TITLE_EMOJI_UNICODE, EmojiParser.parseToUnicode(title));
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets next article permalink failed", e);
return null;
} finally {
Stopwatchs.end();
}
}
/**
* Gets the previous article.
*
* @param articleId the specified article id
* @return permalink and title, <pre>
* {
* "articlePermalink": "",
* "articleTitle": "",
* "articleTitleEmoj": "",
* "articleTitleEmojUnicode": ""
* }
* </pre>, returns {@code null} if not found
*/
public JSONObject getPreviousPermalink(final String articleId) {
Stopwatchs.start("Get previous");
try {
final Query query = new Query().setFilter(
new PropertyFilter(Keys.OBJECT_ID, FilterOperator.LESS_THAN, articleId)).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
setCurrentPageNum(1).setPageCount(1).setPageSize(1);
final JSONArray result = articleRepository.get(query).optJSONArray(Keys.RESULTS);
if (0 == result.length()) {
return null;
}
final JSONObject ret = result.optJSONObject(0);
if (null == ret) {
return null;
}
String title = ret.optString(Article.ARTICLE_TITLE);
ret.put(Article.ARTICLE_T_TITLE_EMOJI, Emotions.convert(title));
ret.put(Article.ARTICLE_T_TITLE_EMOJI_UNICODE, EmojiParser.parseToUnicode(title));
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets previous article permalink failed", e);
return null;
} finally {
Stopwatchs.end();
}
}
/**
* Get an articles by the specified title.
*
* @param title the specified title
* @return article, returns {@code null} if not found
*/
public JSONObject getArticleByTitle(final String title) {
try {
return articleRepository.getByTitle(title);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets article by title [" + title + "] failed", e);
return null;
}
}
/**
* Gets article count of the specified day.
*
* @param day the specified day
* @return article count
*/
public int getArticleCntInDay(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(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID)
));
try {
return (int) articleRepository.count(query);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Count day article failed", e);
return 1;
}
}
/**
* Gets article count of the specified month.
*
* @param day the specified month
* @return article count
*/
public int getArticleCntInMonth(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(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID)
));
try {
return (int) articleRepository.count(query);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Count month article failed", e);
return 1;
}
}
/**
* Gets articles by the specified page number and page size.
*
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @param types the specified types
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getValidArticles(final int currentPageNum, final int pageSize, final int... types) throws ServiceException {
try {
final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
if (null != types && types.length > 0) {
final List<Filter> typeFilters = new ArrayList<>();
for (int i = 0; i < types.length; i++) {
final int type = types[i];
typeFilters.add(new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.EQUAL, type));
}
final CompositeFilter typeFilter = new CompositeFilter(CompositeFilterOperator.OR, typeFilters);
final List<Filter> filters = new ArrayList<>();
filters.add(typeFilter);
filters.add(new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID));
query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));
} else {
query.setFilter(new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID));
}
final JSONObject result = articleRepository.get(query);
return CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets domain articles.
*
* @param avatarViewMode the specified avatar view mode
* @param domainId the specified domain id
* @param currentPageNum the specified current page number
* @param pageSize the specified page size
* @return result
* @throws ServiceException service exception
*/
public JSONObject getDomainArticles(final int avatarViewMode, final String domainId,
final int currentPageNum, final int pageSize) throws ServiceException {
final JSONObject ret = new JSONObject();
ret.put(Article.ARTICLES, (Object) Collections.emptyList());
final JSONObject pagination = new JSONObject();
ret.put(Pagination.PAGINATION, pagination);
pagination.put(Pagination.PAGINATION_PAGE_COUNT, 0);
pagination.put(Pagination.PAGINATION_PAGE_NUMS, (Object) Collections.emptyList());
try {
final JSONArray domainTags = domainTagRepository.getByDomainId(domainId, 1, Integer.MAX_VALUE)
.optJSONArray(Keys.RESULTS);
if (domainTags.length() <= 0) {
return ret;
}
final List<String> tagIds = new ArrayList<>();
for (int i = 0; i < domainTags.length(); i++) {
tagIds.add(domainTags.optJSONObject(i).optString(Tag.TAG + "_" + Keys.OBJECT_ID));
}
Query query = new Query().setFilter(
new PropertyFilter(Tag.TAG + "_" + Keys.OBJECT_ID, FilterOperator.IN, tagIds)).
setCurrentPageNum(currentPageNum).setPageSize(pageSize).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING);
JSONObject result = tagArticleRepository.get(query);
final JSONArray tagArticles = result.optJSONArray(Keys.RESULTS);
if (tagArticles.length() <= 0) {
return ret;
}
final int pageCount = result.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_PAGE_COUNT);
final int windowSize = Symphonys.getInt("latestArticlesWindowSize");
final List<Integer> pageNums = Paginator.paginate(currentPageNum, pageSize, pageCount, windowSize);
pagination.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
pagination.put(Pagination.PAGINATION_PAGE_NUMS, (Object) pageNums);
final Set<String> articleIds = new HashSet<>();
for (int i = 0; i < tagArticles.length(); i++) {
articleIds.add(tagArticles.optJSONObject(i).optString(Article.ARTICLE + "_" + Keys.OBJECT_ID));
}
query = new Query().setFilter(CompositeFilterOperator.and(
new PropertyFilter(Keys.OBJECT_ID, FilterOperator.IN, articleIds),
new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID))).
setPageCount(1).addSort(Keys.OBJECT_ID, SortDirection.DESCENDING);
final List<JSONObject> articles
= CollectionUtils.<JSONObject>jsonArrayToList(articleRepository.get(query).optJSONArray(Keys.RESULTS));
try {
organizeArticles(avatarViewMode, articles);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Organizes articles failed", e);
throw new ServiceException(e);
}
final Integer participantsCnt = Symphonys.getInt("latestArticleParticipantsCnt");
genParticipants(avatarViewMode, articles, participantsCnt);
ret.put(Article.ARTICLES, (Object) articles);
return ret;
} catch (final RepositoryException | ServiceException e) {
LOGGER.log(Level.ERROR, "Gets domain articles error", e);
throw new ServiceException(e);
}
}
/**
* Gets the relevant articles of the specified article with the specified fetch size.
* <p>
* The relevant articles exist the same tag with the specified article.
* </p>
*
* @param avatarViewMode the specified avatar view mode
* @param article the specified article
* @param fetchSize the specified fetch size
* @return relevant articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getRelevantArticles(final int avatarViewMode, final JSONObject article, final int fetchSize)
throws ServiceException {
final String tagsString = article.optString(Article.ARTICLE_TAGS);
final String[] tagTitles = tagsString.split(",");
final int tagTitlesLength = tagTitles.length;
final int subCnt = tagTitlesLength > RELEVANT_ARTICLE_RANDOM_FETCH_TAG_CNT
? RELEVANT_ARTICLE_RANDOM_FETCH_TAG_CNT : tagTitlesLength;
final List<Integer> tagIdx = CollectionUtils.getRandomIntegers(0, tagTitlesLength, subCnt);
final int subFetchSize = fetchSize / subCnt;
final Set<String> fetchedArticleIds = new HashSet<>();
final List<JSONObject> ret = new ArrayList<>();
try {
for (int i = 0; i < tagIdx.size(); i++) {
final String tagTitle = tagTitles[tagIdx.get(i)].trim();
final JSONObject tag = tagRepository.getByTitle(tagTitle);
final String tagId = tag.optString(Keys.OBJECT_ID);
JSONObject result = tagArticleRepository.getByTagId(tagId, 1, subFetchSize);
final JSONArray tagArticleRelations = result.optJSONArray(Keys.RESULTS);
final Set<String> articleIds = new HashSet<>();
for (int j = 0; j < tagArticleRelations.length(); j++) {
final String articleId = tagArticleRelations.optJSONObject(j).optString(Article.ARTICLE + '_' + Keys.OBJECT_ID);
if (fetchedArticleIds.contains(articleId)) {
continue;
}
articleIds.add(articleId);
fetchedArticleIds.add(articleId);
}
articleIds.remove(article.optString(Keys.OBJECT_ID));
final Query query = new Query().setFilter(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.IN, articleIds));
result = articleRepository.get(query);
ret.addAll(CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS)));
}
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets relevant articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets broadcasts (articles permalink equals to "aBroadcast").
*
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getBroadcasts(final int currentPageNum, final int pageSize) throws ServiceException {
try {
final Query query = new Query().setCurrentPageNum(currentPageNum).setPageSize(pageSize).setFilter(
new PropertyFilter(Article.ARTICLE_CLIENT_ARTICLE_ID, FilterOperator.EQUAL, "aBroadcast")).
addSort(Article.ARTICLE_CREATE_TIME, SortDirection.DESCENDING);
final JSONObject result = articleRepository.get(query);
final JSONArray articles = result.optJSONArray(Keys.RESULTS);
if (0 == articles.length()) {
return Collections.emptyList();
}
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(articles);
for (final JSONObject article : ret) {
article.put(Article.ARTICLE_PERMALINK, Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK));
article.remove(Article.ARTICLE_CONTENT);
}
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets broadcasts [currentPageNum=" + currentPageNum + ", pageSize=" + pageSize + "] failed", e);
throw new ServiceException(e);
}
}
/**
* Gets interest articles.
*
* @param currentPageNum the specified current page number
* @param pageSize the specified fetch size
* @param tagTitles the specified tag titles
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getInterests(final int currentPageNum, final int pageSize, final String... tagTitles)
throws ServiceException {
try {
final List<JSONObject> tagList = new ArrayList<>();
for (final String tagTitle : tagTitles) {
final JSONObject tag = tagRepository.getByTitle(tagTitle);
if (null == tag) {
continue;
}
tagList.add(tag);
}
final Map<String, Class<?>> articleFields = new HashMap<>();
articleFields.put(Article.ARTICLE_TITLE, String.class);
articleFields.put(Article.ARTICLE_PERMALINK, String.class);
articleFields.put(Article.ARTICLE_CREATE_TIME, Long.class);
articleFields.put(Article.ARTICLE_AUTHOR_ID, String.class);
final List<JSONObject> ret = new ArrayList<>();
if (!tagList.isEmpty()) {
final List<JSONObject> tagArticles
= getArticlesByTags(UserExt.USER_AVATAR_VIEW_MODE_C_STATIC,
currentPageNum, pageSize, articleFields, tagList.toArray(new JSONObject[0]));
ret.addAll(tagArticles);
}
if (ret.size() < pageSize) {
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID));
filters.add(new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION));
final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageCount(currentPageNum).setPageSize(pageSize).setCurrentPageNum(1);
query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));
for (final Map.Entry<String, Class<?>> articleField : articleFields.entrySet()) {
query.addProjection(articleField.getKey(), articleField.getValue());
}
final JSONObject result = articleRepository.get(query);
final List<JSONObject> recentArticles = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
ret.addAll(recentArticles);
}
final Iterator<JSONObject> iterator = ret.iterator();
int i = 0;
while (iterator.hasNext()) {
final JSONObject article = iterator.next();
article.put(Article.ARTICLE_PERMALINK, Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK));
article.remove(Article.ARTICLE_T_AUTHOR);
article.remove(Article.ARTICLE_AUTHOR_ID);
article.remove(Article.ARTICLE_T_PARTICIPANTS);
article.remove(Article.ARTICLE_T_PARTICIPANT_NAME);
article.remove(Article.ARTICLE_T_PARTICIPANT_THUMBNAIL_URL);
article.remove(Article.ARTICLE_LATEST_CMT_TIME);
article.remove(Article.ARTICLE_LATEST_CMTER_NAME);
article.remove(Article.ARTICLE_UPDATE_TIME);
article.remove(Article.ARTICLE_T_HEAT);
article.remove(Article.ARTICLE_T_TITLE_EMOJI);
article.remove(Article.ARTICLE_T_TITLE_EMOJI_UNICODE);
article.remove(Common.TIME_AGO);
article.remove(Common.CMT_TIME_AGO);
article.remove(Article.ARTICLE_T_TAG_OBJS);
article.remove(Article.ARTICLE_STICK);
article.remove(Article.ARTICLE_T_PREVIEW_CONTENT);
article.remove(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "20");
article.remove(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "48");
article.remove(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "210");
article.remove(Article.ARTICLE_T_STICK_REMAINS);
long createTime = 0;
final Object time = article.get(Article.ARTICLE_CREATE_TIME);
if (time instanceof Date) {
createTime = ((Date) time).getTime();
} else {
createTime = (Long) time;
}
article.put(Article.ARTICLE_CREATE_TIME, createTime);
i++;
if (i > pageSize) {
iterator.remove();
}
}
return ret;
} catch (final RepositoryException | ServiceException | JSONException e) {
LOGGER.log(Level.ERROR, "Gets interests failed", e);
throw new ServiceException(e);
}
}
/**
* Gets news (perfect articles).
*
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getNews(final int currentPageNum, final int pageSize) throws ServiceException {
try {
final Query query = new Query().
setFilter(new PropertyFilter(Article.ARTICLE_PERFECT, FilterOperator.EQUAL, Article.ARTICLE_PERFECT_C_PERFECT)).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addSort(Article.ARTICLE_CREATE_TIME, SortDirection.DESCENDING);
final JSONObject result = articleRepository.get(query);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
for (final JSONObject article : ret) {
article.put(Article.ARTICLE_PERMALINK, Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK));
}
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets news failed", e);
throw new ServiceException(e);
}
}
/**
* Gets articles by the specified tags (order by article create date desc).
*
* @param avatarViewMode the specified avatar view mode
* @param tags the specified tags
* @param currentPageNum the specified page number
* @param articleFields the specified article fields to return
* @param pageSize the specified page size
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getArticlesByTags(final int avatarViewMode, final int currentPageNum, final int pageSize,
final Map<String, Class<?>> articleFields, final JSONObject... tags) throws ServiceException {
try {
final List<Filter> filters = new ArrayList<>();
for (final JSONObject tag : tags) {
filters.add(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)));
}
Filter filter;
if (filters.size() >= 2) {
filter = new CompositeFilter(CompositeFilterOperator.OR, filters);
} else {
filter = filters.get(0);
}
// XXX: 这里的分页是有问题的,后面取文章的时候会少(因为一篇文章可以有多个标签,但是文章 id 一样)
Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(filter).setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
JSONObject result = tagArticleRepository.get(query);
final JSONArray tagArticleRelations = result.optJSONArray(Keys.RESULTS);
final Set<String> articleIds = new HashSet<>();
for (int i = 0; i < tagArticleRelations.length(); i++) {
articleIds.add(tagArticleRelations.optJSONObject(i).optString(Article.ARTICLE + '_' + Keys.OBJECT_ID));
}
query = new Query().setFilter(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.IN, articleIds)).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING);
for (final Map.Entry<String, Class<?>> articleField : articleFields.entrySet()) {
query.addProjection(articleField.getKey(), articleField.getValue());
}
result = articleRepository.get(query);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles by tags [tagLength=" + tags.length + "] failed", e);
throw new ServiceException(e);
}
}
/**
* Gets articles by the specified city (order by article create date desc).
*
* @param avatarViewMode the specified avatar view mode
* @param city the specified city
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getArticlesByCity(final int avatarViewMode, final String city,
final int currentPageNum, final int pageSize) throws ServiceException {
try {
final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Article.ARTICLE_CITY, FilterOperator.EQUAL, city))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
final JSONObject result = articleRepository.get(query);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
organizeArticles(avatarViewMode, ret);
final Integer participantsCnt = Symphonys.getInt("cityArticleParticipantsCnt");
genParticipants(avatarViewMode, ret, participantsCnt);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles by city [" + city + "] failed", e);
throw new ServiceException(e);
}
}
/**
* Gets articles by the specified tag (order by article create date desc).
*
* @param avatarViewMode the specified avatar view mode
* @param sortMode the specified sort mode, 0: default, 1: hot, 2: score, 3: reply, 4: perfect
* @param tag the specified tag
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getArticlesByTag(final int avatarViewMode, final int sortMode, final JSONObject tag,
final int currentPageNum, final int pageSize) throws ServiceException {
try {
Query query = new Query();
switch (sortMode) {
case 0:
query.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
break;
case 1:
query.addSort(Article.ARTICLE_COMMENT_CNT, SortDirection.DESCENDING).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
break;
case 2:
query.addSort(Article.REDDIT_SCORE, SortDirection.DESCENDING).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
break;
case 3:
query.addSort(Article.ARTICLE_LATEST_CMT_TIME, SortDirection.DESCENDING).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
break;
case 4:
query.addSort(Article.ARTICLE_PERFECT, SortDirection.DESCENDING).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
break;
default:
LOGGER.warn("Unknown sort mode [" + sortMode + "]");
query.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).
setFilter(new PropertyFilter(Tag.TAG + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, tag.optString(Keys.OBJECT_ID)))
.setPageCount(1).setPageSize(pageSize).setCurrentPageNum(currentPageNum);
}
JSONObject result = tagArticleRepository.get(query);
final JSONArray tagArticleRelations = result.optJSONArray(Keys.RESULTS);
final List<String> articleIds = new ArrayList<>();
for (int i = 0; i < tagArticleRelations.length(); i++) {
articleIds.add(tagArticleRelations.optJSONObject(i).optString(Article.ARTICLE + '_' + Keys.OBJECT_ID));
}
query = new Query().setFilter(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.IN, articleIds)).
addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class).
addProjection(Article.ARTICLE_CONTENT, String.class);
result = articleRepository.get(query);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
switch (sortMode) {
default:
LOGGER.warn("Unknown sort mode [" + sortMode + "]");
case 0:
Collections.sort(ret, (o1, o2) -> o2.optString(Keys.OBJECT_ID).compareTo(o1.optString(Keys.OBJECT_ID)));
break;
case 1:
Collections.sort(ret, (o1, o2) -> {
final int v = o2.optInt(Article.ARTICLE_COMMENT_CNT) - o1.optInt(Article.ARTICLE_COMMENT_CNT);
if (0 == v) {
return o2.optString(Keys.OBJECT_ID).compareTo(o1.optString(Keys.OBJECT_ID));
}
return v > 0 ? 1 : -1;
});
break;
case 2:
Collections.sort(ret, (o1, o2) -> {
final double v = o2.optDouble(Article.REDDIT_SCORE) - o1.optDouble(Article.REDDIT_SCORE);
if (0 == v) {
return o2.optString(Keys.OBJECT_ID).compareTo(o1.optString(Keys.OBJECT_ID));
}
return v > 0 ? 1 : -1;
});
break;
case 3:
Collections.sort(ret, (o1, o2) -> {
final long v = (o2.optLong(Article.ARTICLE_LATEST_CMT_TIME)
- o1.optLong(Article.ARTICLE_LATEST_CMT_TIME));
if (0 == v) {
return o2.optString(Keys.OBJECT_ID).compareTo(o1.optString(Keys.OBJECT_ID));
}
return v > 0 ? 1 : -1;
});
break;
case 4:
Collections.sort(ret, (o1, o2) -> {
final long v = (o2.optLong(Article.ARTICLE_PERFECT) - o1.optLong(Article.ARTICLE_PERFECT));
if (0 == v) {
return o2.optString(Keys.OBJECT_ID).compareTo(o1.optString(Keys.OBJECT_ID));
}
return v > 0 ? 1 : -1;
});
break;
}
organizeArticles(avatarViewMode, ret);
final Integer participantsCnt = Symphonys.getInt("tagArticleParticipantsCnt");
genParticipants(avatarViewMode, ret, participantsCnt);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles by tag [tagTitle=" + tag.optString(Tag.TAG_TITLE) + "] failed", e);
throw new ServiceException(e);
}
}
/**
* Gets an article by the specified client article id.
*
* @param authorId the specified author id
* @param clientArticleId the specified client article id
* @return article, return {@code null} if not found
* @throws ServiceException service exception
*/
public JSONObject getArticleByClientArticleId(final String authorId, final String clientArticleId) throws ServiceException {
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Article.ARTICLE_CLIENT_ARTICLE_ID, FilterOperator.EQUAL, clientArticleId));
filters.add(new PropertyFilter(Article.ARTICLE_AUTHOR_ID, FilterOperator.EQUAL, authorId));
final Query query = new Query().setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));
try {
final JSONObject result = articleRepository.get(query);
final JSONArray array = result.optJSONArray(Keys.RESULTS);
if (0 == array.length()) {
return null;
}
return array.optJSONObject(0);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets article [clientArticleId=" + clientArticleId + "] failed", e);
throw new ServiceException(e);
}
}
/**
* Gets an article with {@link #organizeArticle(int, JSONObject)} by the specified id.
*
* @param avatarViewMode the specified avatar view mode
* @param articleId the specified id
* @return article, return {@code null} if not found
* @throws ServiceException service exception
*/
public JSONObject getArticleById(final int avatarViewMode, final String articleId) throws ServiceException {
Stopwatchs.start("Get article by id");
try {
final JSONObject ret = articleRepository.get(articleId);
if (null == ret) {
return null;
}
organizeArticle(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets an article [articleId=" + articleId + "] failed", e);
throw new ServiceException(e);
} finally {
Stopwatchs.end();
}
}
/**
* Gets an article by the specified id.
*
* @param articleId the specified id
* @return article, return {@code null} if not found
* @throws ServiceException service exception
*/
public JSONObject getArticle(final String articleId) throws ServiceException {
try {
final JSONObject ret = articleRepository.get(articleId);
if (null == ret) {
return null;
}
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets an article [articleId=" + articleId + "] failed", e);
throw new ServiceException(e);
}
}
/**
* Gets preview content of the article specified with the given article id.
*
* @param articleId the given article id
* @param request the specified request
* @return preview content
* @throws ServiceException service exception
*/
public String getArticlePreviewContent(final String articleId, final HttpServletRequest request) throws ServiceException {
final JSONObject article = getArticle(articleId);
if (null == article) {
return null;
}
final int articleType = article.optInt(Article.ARTICLE_TYPE);
if (Article.ARTICLE_TYPE_C_THOUGHT == articleType) {
return null;
}
Stopwatchs.start("Get preview content");
try {
final int length = Integer.valueOf("150");
String ret = article.optString(Article.ARTICLE_CONTENT);
final String authorId = article.optString(Article.ARTICLE_AUTHOR_ID);
final JSONObject author = userQueryService.getUser(authorId);
if (null != author && UserExt.USER_STATUS_C_INVALID == author.optInt(UserExt.USER_STATUS)
|| Article.ARTICLE_STATUS_C_INVALID == article.optInt(Article.ARTICLE_STATUS)) {
return langPropsService.get("articleContentBlockLabel");
}
final Set<String> userNames = userQueryService.getUserNames(ret);
final JSONObject currentUser = userQueryService.getCurrentUser(request);
final String currentUserName = null == currentUser ? "" : currentUser.optString(User.USER_NAME);
final String authorName = author.optString(User.USER_NAME);
if (Article.ARTICLE_TYPE_C_DISCUSSION == articleType
&& !authorName.equals(currentUserName)) {
boolean invited = false;
for (final String userName : userNames) {
if (userName.equals(currentUserName)) {
invited = true;
break;
}
}
if (!invited) {
String blockContent = langPropsService.get("articleDiscussionLabel");
blockContent = blockContent.replace("{user}", "<a href='" + Latkes.getServePath()
+ "/member/" + authorName + "'>" + authorName + "</a>");
return blockContent;
}
}
ret = Emotions.convert(ret);
ret = Markdowns.toHTML(ret);
ret = Jsoup.clean(ret, Whitelist.none());
if (ret.length() >= length) {
ret = StringUtils.substring(ret, 0, length)
+ " ....";
}
return ret;
} finally {
Stopwatchs.end();
}
}
/**
* Gets the user articles 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 article anonymous
* @param currentPageNum the specified page number
* @param pageSize the specified page size
* @return user articles, return an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getUserArticles(final int avatarViewMode, final String userId, final int anonymous,
final int currentPageNum, final int pageSize) throws ServiceException {
final Query query = new Query().addSort(Article.ARTICLE_CREATE_TIME, SortDirection.DESCENDING)
.setCurrentPageNum(currentPageNum).setPageSize(pageSize).
setFilter(CompositeFilterOperator.and(
new PropertyFilter(Article.ARTICLE_AUTHOR_ID, FilterOperator.EQUAL, userId),
new PropertyFilter(Article.ARTICLE_ANONYMOUS, FilterOperator.EQUAL, anonymous),
new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID)));
try {
final JSONObject result = articleRepository.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);
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets user articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets side hot articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param fetchSize the specified fetch size
* @return recent articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getSideHotArticles(final int avatarViewMode, final int fetchSize) throws ServiceException {
final String id = String.valueOf(DateUtils.addDays(new Date(), -7).getTime());
try {
final Query query = new Query().addSort(Article.ARTICLE_COMMENT_CNT, SortDirection.DESCENDING).
addSort(Keys.OBJECT_ID, SortDirection.ASCENDING).setCurrentPageNum(1).setPageSize(fetchSize);
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.GREATER_THAN_OR_EQUAL, id));
filters.add(new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION));
filters.add(new PropertyFilter(Article.ARTICLE_TAGS, FilterOperator.NOT_EQUAL, Tag.TAG_TITLE_C_SANDBOX));
query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));
final JSONObject result = articleRepository.get(query);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets hot articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets the random articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param fetchSize the specified fetch size
* @return random articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getRandomArticles(final int avatarViewMode, final int fetchSize) throws ServiceException {
try {
final List<JSONObject> ret = articleRepository.getRandomly(fetchSize);
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets random articles failed", e);
throw new ServiceException(e);
}
}
/**
* Makes article showing filters.
*
* @return filter the article showing to user
*/
private CompositeFilter makeArticleShowingFilter() {
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID));
filters.add(new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION));
return new CompositeFilter(CompositeFilterOperator.AND, filters);
}
/**
* Makes recent article showing filters.
*
* @return filter the article showing to user
*/
private CompositeFilter makeRecentArticleShowingFilter() {
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Article.ARTICLE_STATUS, FilterOperator.EQUAL, Article.ARTICLE_STATUS_C_VALID));
filters.add(new PropertyFilter(Article.ARTICLE_TYPE, FilterOperator.NOT_EQUAL, Article.ARTICLE_TYPE_C_DISCUSSION));
filters.add(new PropertyFilter(Article.ARTICLE_TAGS, FilterOperator.NOT_LIKE, "B3log%"));
filters.add(new PropertyFilter(Article.ARTICLE_TAGS, FilterOperator.NOT_LIKE, Tag.TAG_TITLE_C_SANDBOX + "%"));
return new CompositeFilter(CompositeFilterOperator.AND, filters);
}
/**
* Makes the recent (sort by create time desc) articles with the specified fetch size.
*
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return recent articles query
*/
private Query makeRecentDefaultQuery(final int currentPageNum, final int fetchSize) {
final Query ret = new Query()
.addSort(Article.ARTICLE_STICK, SortDirection.DESCENDING)
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageSize(fetchSize).setCurrentPageNum(currentPageNum);
ret.setFilter(makeRecentArticleShowingFilter());
ret.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class).
addProjection(Article.ARTICLE_BAD_CNT, Integer.class).
addProjection(Article.ARTICLE_GOOD_CNT, Integer.class).
addProjection(Article.ARTICLE_COLLECT_CNT, Integer.class).
addProjection(Article.ARTICLE_WATCH_CNT, Integer.class).
addProjection(Article.ARTICLE_UA, String.class).
addProjection(Article.ARTICLE_CONTENT, String.class);
return ret;
}
/**
* Makes the recent (sort by comment count desc) articles with the specified fetch size.
*
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return recent articles query
*/
private Query makeRecentHotQuery(final int currentPageNum, final int fetchSize) {
final String id = String.valueOf(DateUtils.addMonths(new Date(), -1).getTime());
final Query ret = new Query()
.addSort(Article.ARTICLE_STICK, SortDirection.DESCENDING)
.addSort(Article.ARTICLE_COMMENT_CNT, SortDirection.DESCENDING)
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageSize(fetchSize).setCurrentPageNum(currentPageNum);
final CompositeFilter compositeFilter = makeRecentArticleShowingFilter();
final List<Filter> filters = new ArrayList<>();
filters.add(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.GREATER_THAN_OR_EQUAL, id));
filters.addAll(compositeFilter.getSubFilters());
ret.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));
ret.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class).
addProjection(Article.ARTICLE_BAD_CNT, Integer.class).
addProjection(Article.ARTICLE_GOOD_CNT, Integer.class).
addProjection(Article.ARTICLE_COLLECT_CNT, Integer.class).
addProjection(Article.ARTICLE_WATCH_CNT, Integer.class).
addProjection(Article.ARTICLE_UA, String.class).
addProjection(Article.ARTICLE_CONTENT, String.class);
return ret;
}
/**
* Makes the recent (sort by score desc) articles with the specified fetch size.
*
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return recent articles query
*/
private Query makeRecentGoodQuery(final int currentPageNum, final int fetchSize) {
final Query ret = new Query()
.addSort(Article.ARTICLE_STICK, SortDirection.DESCENDING)
.addSort(Article.REDDIT_SCORE, SortDirection.DESCENDING)
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageSize(fetchSize).setCurrentPageNum(currentPageNum);
ret.setFilter(makeRecentArticleShowingFilter());
ret.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class).
addProjection(Article.ARTICLE_BAD_CNT, Integer.class).
addProjection(Article.ARTICLE_GOOD_CNT, Integer.class).
addProjection(Article.ARTICLE_COLLECT_CNT, Integer.class).
addProjection(Article.ARTICLE_WATCH_CNT, Integer.class).
addProjection(Article.ARTICLE_UA, String.class).
addProjection(Article.ARTICLE_CONTENT, String.class);
return ret;
}
/**
* Makes the recent (sort by latest comment time desc) articles with the specified fetch size.
*
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return recent articles query
*/
private Query makeRecentReplyQuery(final int currentPageNum, final int fetchSize) {
final Query ret = new Query()
.addSort(Article.ARTICLE_STICK, SortDirection.DESCENDING)
.addSort(Article.ARTICLE_LATEST_CMT_TIME, SortDirection.DESCENDING)
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageSize(fetchSize).setCurrentPageNum(currentPageNum);
ret.setFilter(makeRecentArticleShowingFilter());
ret.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class).
addProjection(Article.ARTICLE_BAD_CNT, Integer.class).
addProjection(Article.ARTICLE_GOOD_CNT, Integer.class).
addProjection(Article.ARTICLE_COLLECT_CNT, Integer.class).
addProjection(Article.ARTICLE_WATCH_CNT, Integer.class).
addProjection(Article.ARTICLE_UA, String.class).
addProjection(Article.ARTICLE_CONTENT, String.class);
return ret;
}
/**
* Makes the top articles with the specified fetch size.
*
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return top articles query
*/
private Query makeTopQuery(final int currentPageNum, final int fetchSize) {
final Query query = new Query()
.addSort(Article.REDDIT_SCORE, SortDirection.DESCENDING)
.addSort(Article.ARTICLE_LATEST_CMT_TIME, SortDirection.DESCENDING)
.setPageCount(1).setPageSize(fetchSize).setCurrentPageNum(currentPageNum);
query.setFilter(makeArticleShowingFilter());
return query;
}
/**
* Gets the recent (sort by create time) articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param sortMode the specified sort mode, 0: default, 1: hot, 2: score, 3: reply
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return for example, <pre>
* {
* "pagination": {
* "paginationPageCount": 100,
* "paginationPageNums": [1, 2, 3, 4, 5]
* },
* "articles": [{
* "oId": "",
* "articleTitle": "",
* "articleContent": "",
* ....
* }, ....]
* }
* </pre>
* @throws ServiceException service exception
*/
public JSONObject getRecentArticles(final int avatarViewMode, final int sortMode,
final int currentPageNum, final int fetchSize)
throws ServiceException {
final JSONObject ret = new JSONObject();
Query query;
switch (sortMode) {
case 0:
query = makeRecentDefaultQuery(currentPageNum, fetchSize);
break;
case 1:
query = makeRecentHotQuery(currentPageNum, fetchSize);
break;
case 2:
query = makeRecentGoodQuery(currentPageNum, fetchSize);
break;
case 3:
query = makeRecentReplyQuery(currentPageNum, fetchSize);
break;
default:
LOGGER.warn("Unknown sort mode [" + sortMode + "]");
query = makeRecentDefaultQuery(currentPageNum, fetchSize);
}
JSONObject result = null;
try {
Stopwatchs.start("Query recent articles");
result = articleRepository.get(query);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles failed", e);
throw new ServiceException(e);
} finally {
Stopwatchs.end();
}
final int pageCount = result.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_PAGE_COUNT);
final JSONObject pagination = new JSONObject();
ret.put(Pagination.PAGINATION, pagination);
final int windowSize = Symphonys.getInt("latestArticlesWindowSize");
final List<Integer> pageNums = Paginator.paginate(currentPageNum, fetchSize, pageCount, windowSize);
pagination.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
pagination.put(Pagination.PAGINATION_PAGE_NUMS, (Object) pageNums);
final JSONArray data = result.optJSONArray(Keys.RESULTS);
final List<JSONObject> articles = CollectionUtils.<JSONObject>jsonArrayToList(data);
try {
organizeArticles(avatarViewMode, articles);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Organizes articles failed", e);
throw new ServiceException(e);
}
//final Integer participantsCnt = Symphonys.getInt("latestArticleParticipantsCnt");
//genParticipants(articles, participantsCnt);
ret.put(Article.ARTICLES, (Object) articles);
return ret;
}
/**
* Gets the index recent (sort by create time) articles.
*
* @param avatarViewMode the specified avatar view mode
* @return recent articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getIndexRecentArticles(final int avatarViewMode) throws ServiceException {
try {
List<JSONObject> ret;
Stopwatchs.start("Query index recent articles");
try {
ret = articleRepository.select("SELECT\n"
+ " oId,\n"
+ " articleStick,\n"
+ " articleCreateTime,\n"
+ " articleUpdateTime,\n"
+ " articleLatestCmtTime,\n"
+ " articleAuthorId,\n"
+ " articleTitle,\n"
+ " articleStatus,\n"
+ " articleViewCount,\n"
+ " articleType,\n"
+ " articlePermalink,\n"
+ " articleTags,\n"
+ " articleLatestCmterName,\n"
+ " syncWithSymphonyClient,\n"
+ " articleCommentCount,\n"
+ " articleAnonymous,\n"
+ " articlePerfect,\n"
+ " articleContent,\n"
+ " CASE\n"
+ "WHEN articleLatestCmtTime = 0 THEN\n"
+ " oId\n"
+ "ELSE\n"
+ " articleLatestCmtTime\n"
+ "END AS flag\n"
+ "FROM\n"
+ " `" + articleRepository.getName() + "`\n"
+ " WHERE `articleType` != 1 AND `articleStatus` = 0 AND `articleTags` != '" + Tag.TAG_TITLE_C_SANDBOX + "'\n"
+ " ORDER BY\n"
+ " articleStick DESC,\n"
+ " flag DESC\n"
+ "LIMIT ?", Symphonys.getInt("indexListCnt"));
} finally {
Stopwatchs.end();
}
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets index recent articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets the hot articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param fetchSize the specified fetch size
* @return hot articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getHotArticles(final int avatarViewMode, final int fetchSize) throws ServiceException {
final Query query = makeTopQuery(1, fetchSize);
try {
List<JSONObject> ret;
Stopwatchs.start("Query hot articles");
try {
final JSONObject result = articleRepository.get(query);
ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
} finally {
Stopwatchs.end();
}
organizeArticles(avatarViewMode, ret);
Stopwatchs.start("Checks author status");
try {
for (final JSONObject article : ret) {
final String authorId = article.optString(Article.ARTICLE_AUTHOR_ID);
final JSONObject author = userRepository.get(authorId);
if (UserExt.USER_STATUS_C_INVALID == author.optInt(UserExt.USER_STATUS)) {
article.put(Article.ARTICLE_TITLE, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_T_TITLE_EMOJI, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_T_TITLE_EMOJI_UNICODE, langPropsService.get("articleTitleBlockLabel"));
}
}
} finally {
Stopwatchs.end();
}
// final Integer participantsCnt = Symphonys.getInt("indexArticleParticipantsCnt");
// genParticipants(ret, participantsCnt);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets index articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets the perfect articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return for example, <pre>
* {
* "pagination": {
* "paginationPageCount": 100,
* "paginationPageNums": [1, 2, 3, 4, 5]
* },
* "articles": [{
* "oId": "",
* "articleTitle": "",
* "articleContent": "",
* ....
* }, ....]
* }
* </pre>
* @throws ServiceException service exception
*/
public JSONObject getPerfectArticles(final int avatarViewMode, final int currentPageNum, final int fetchSize)
throws ServiceException {
final Query query = new Query()
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setCurrentPageNum(currentPageNum).setPageSize(fetchSize);
query.setFilter(new PropertyFilter(Article.ARTICLE_PERFECT, FilterOperator.EQUAL, Article.ARTICLE_PERFECT_C_PERFECT));
final JSONObject ret = new JSONObject();
JSONObject result = null;
try {
Stopwatchs.start("Query recent articles");
result = articleRepository.get(query);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles failed", e);
throw new ServiceException(e);
} finally {
Stopwatchs.end();
}
final int pageCount = result.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_PAGE_COUNT);
final JSONObject pagination = new JSONObject();
ret.put(Pagination.PAGINATION, pagination);
final int windowSize = Symphonys.getInt("latestArticlesWindowSize");
final List<Integer> pageNums = Paginator.paginate(currentPageNum, fetchSize, pageCount, windowSize);
pagination.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
pagination.put(Pagination.PAGINATION_PAGE_NUMS, (Object) pageNums);
final JSONArray data = result.optJSONArray(Keys.RESULTS);
final List<JSONObject> articles = CollectionUtils.<JSONObject>jsonArrayToList(data);
try {
organizeArticles(avatarViewMode, articles);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Organizes articles failed", e);
throw new ServiceException(e);
}
//final Integer participantsCnt = Symphonys.getInt("latestArticleParticipantsCnt");
//genParticipants(articles, participantsCnt);
ret.put(Article.ARTICLES, (Object) articles);
return ret;
}
/**
* Gets the index hot articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @return hot articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getIndexHotArticles(final int avatarViewMode) throws ServiceException {
final Query query = new Query()
.addSort(Article.REDDIT_SCORE, SortDirection.DESCENDING)
.addSort(Article.ARTICLE_LATEST_CMT_TIME, SortDirection.DESCENDING)
.setPageCount(1).setPageSize(Symphonys.getInt("indexListCnt")).setCurrentPageNum(1);
query.setFilter(makeArticleShowingFilter());
query.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class);
try {
List<JSONObject> ret;
Stopwatchs.start("Query index hot articles");
try {
final JSONObject result = articleRepository.get(query);
ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
} finally {
Stopwatchs.end();
}
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets index hot articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets the index perfect articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @return hot articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getIndexPerfectArticles(final int avatarViewMode) throws ServiceException {
final Query query = new Query()
.addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setPageCount(1).setPageSize(36).setCurrentPageNum(1);
query.setFilter(new PropertyFilter(Article.ARTICLE_PERFECT, FilterOperator.EQUAL, Article.ARTICLE_PERFECT_C_PERFECT));
query.addProjection(Keys.OBJECT_ID, String.class).
addProjection(Article.ARTICLE_STICK, Long.class).
addProjection(Article.ARTICLE_CREATE_TIME, Long.class).
addProjection(Article.ARTICLE_UPDATE_TIME, Long.class).
addProjection(Article.ARTICLE_LATEST_CMT_TIME, Long.class).
addProjection(Article.ARTICLE_AUTHOR_ID, String.class).
addProjection(Article.ARTICLE_TITLE, String.class).
addProjection(Article.ARTICLE_STATUS, Integer.class).
addProjection(Article.ARTICLE_VIEW_CNT, Integer.class).
addProjection(Article.ARTICLE_TYPE, Integer.class).
addProjection(Article.ARTICLE_PERMALINK, String.class).
addProjection(Article.ARTICLE_TAGS, String.class).
addProjection(Article.ARTICLE_LATEST_CMTER_NAME, String.class).
addProjection(Article.ARTICLE_SYNC_TO_CLIENT, Boolean.class).
addProjection(Article.ARTICLE_COMMENT_CNT, Integer.class).
addProjection(Article.ARTICLE_ANONYMOUS, Integer.class).
addProjection(Article.ARTICLE_PERFECT, Integer.class);
try {
List<JSONObject> ret;
Stopwatchs.start("Query index perfect articles");
try {
final JSONObject result = articleRepository.get(query);
ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
} finally {
Stopwatchs.end();
}
organizeArticles(avatarViewMode, ret);
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets index perfect articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets the recent articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return recent articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getRecentArticlesWithComments(final int avatarViewMode,
final int currentPageNum, final int fetchSize) throws ServiceException {
return getArticles(avatarViewMode, makeRecentDefaultQuery(currentPageNum, fetchSize));
}
/**
* Gets the index articles with the specified fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param currentPageNum the specified current page number
* @param fetchSize the specified fetch size
* @return recent articles, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getTopArticlesWithComments(final int avatarViewMode,
final int currentPageNum, final int fetchSize) throws ServiceException {
return getArticles(avatarViewMode, makeTopQuery(currentPageNum, fetchSize));
}
/**
* The specific articles.
*
* @param avatarViewMode the specified avatar view mode
* @param query conditions
* @return articles
* @throws ServiceException service exception
*/
private List<JSONObject> getArticles(final int avatarViewMode, final Query query) throws ServiceException {
try {
final JSONObject result = articleRepository.get(query);
final List<JSONObject> ret = CollectionUtils.<JSONObject>jsonArrayToList(result.optJSONArray(Keys.RESULTS));
organizeArticles(avatarViewMode, ret);
final List<JSONObject> stories = new ArrayList<>();
for (final JSONObject article : ret) {
final JSONObject story = new JSONObject();
final String authorId = article.optString(Article.ARTICLE_AUTHOR_ID);
final JSONObject author = userRepository.get(authorId);
if (UserExt.USER_STATUS_C_INVALID == author.optInt(UserExt.USER_STATUS)) {
story.put("title", langPropsService.get("articleTitleBlockLabel"));
} else {
story.put("title", article.optString(Article.ARTICLE_TITLE));
}
story.put("id", article.optLong("oId"));
story.put("url", Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK));
story.put("user_display_name", article.optString(Article.ARTICLE_T_AUTHOR_NAME));
story.put("user_job", author.optString(UserExt.USER_INTRO));
story.put("comment_html", article.optString(Article.ARTICLE_CONTENT));
story.put("comment_count", article.optInt(Article.ARTICLE_COMMENT_CNT));
story.put("vote_count", article.optInt(Article.ARTICLE_GOOD_CNT));
story.put("created_at", formatDate(article.get(Article.ARTICLE_CREATE_TIME)));
story.put("user_portrait_url", article.optString(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL));
story.put("comments", getAllComments(avatarViewMode, article.optString("oId")));
final String tagsString = article.optString(Article.ARTICLE_TAGS);
String[] tags = null;
if (!Strings.isEmptyOrNull(tagsString)) {
tags = tagsString.split(",");
}
story.put("badge", tags == null ? "" : tags[0]);
stories.add(story);
}
final Integer participantsCnt = Symphonys.getInt("indexArticleParticipantsCnt");
genParticipants(avatarViewMode, stories, participantsCnt);
return stories;
} catch (final RepositoryException | JSONException e) {
LOGGER.log(Level.ERROR, "Gets index articles failed", e);
throw new ServiceException(e);
}
}
/**
* Gets the article comments with the specified article id.
*
* @param avatarViewMode the specified avatar view mode
* @param articleId the specified article id
* @return comments, return an empty list if not found
* @throws ServiceException service exception
* @throws JSONException json exception
* @throws RepositoryException repository exception
*/
private List<JSONObject> getAllComments(final int avatarViewMode, final String articleId)
throws ServiceException, JSONException, RepositoryException {
final List<JSONObject> commments = new ArrayList<>();
final List<JSONObject> articleComments = commentQueryService.getArticleComments(
avatarViewMode, articleId, 1, Integer.MAX_VALUE, UserExt.USER_COMMENT_VIEW_MODE_C_TRADITIONAL);
for (final JSONObject ac : articleComments) {
final JSONObject comment = new JSONObject();
final JSONObject author = userRepository.get(ac.optString(Comment.COMMENT_AUTHOR_ID));
comment.put("id", ac.optLong("oId"));
comment.put("body_html", ac.optString(Comment.COMMENT_CONTENT));
comment.put("depth", 0);
comment.put("user_display_name", ac.optString(Comment.COMMENT_T_AUTHOR_NAME));
comment.put("user_job", author.optString(UserExt.USER_INTRO));
comment.put("vote_count", 0);
comment.put("created_at", formatDate(ac.get(Comment.COMMENT_CREATE_TIME)));
comment.put("user_portrait_url", ac.optString(Comment.COMMENT_T_ARTICLE_AUTHOR_THUMBNAIL_URL));
commments.add(comment);
}
return commments;
}
/**
* The demand format date.
*
* @param date the original date
* @return the format date like "2015-08-03T07:26:57Z"
*/
private String formatDate(final Object date) {
return DateFormatUtils.format(((Date) date).getTime(), "yyyy-MM-dd")
+ "T" + DateFormatUtils.format(((Date) date).getTime(), "HH:mm:ss") + "Z";
}
/**
* Organizes the specified articles.
*
* @param avatarViewMode the specified avatarViewMode
* @param articles the specified articles
* @throws RepositoryException repository exception
* @see #organizeArticle(int, org.json.JSONObject)
*/
public void organizeArticles(final int avatarViewMode, final List<JSONObject> articles) throws RepositoryException {
Stopwatchs.start("Organize articles");
try {
for (final JSONObject article : articles) {
organizeArticle(avatarViewMode, article);
}
} finally {
Stopwatchs.end();
}
}
/**
* Organizes the specified article.
* <p>
* <ul>
* <li>converts create/update/latest comment time (long) to date type</li>
* <li>generates author thumbnail URL</li>
* <li>generates author name</li>
* <li>escapes article title < and ></li>
* <li>generates article heat</li>
* <li>generates article view count display format(1k+/1.5k+...)</li>
* <li>generates time ago text</li>
* <li>generates comment time ago text</li>
* <li>generates stick remains minutes</li>
* <li>anonymous process</li>
* <li>builds tag objects</li>
* <li>generates article preview content</li>
* <li>extracts the first image URL</li>
* <li>image processing if using Qiniu</li>
* </ul>
* </p>
*
* @param avatarViewMode the specified avatar view mode
* @param article the specified article
* @throws RepositoryException repository exception
*/
public void organizeArticle(final int avatarViewMode, final JSONObject article) throws RepositoryException {
toArticleDate(article);
genArticleAuthor(avatarViewMode, article);
final String previewContent = getArticleMetaDesc(article);
article.put(Article.ARTICLE_T_PREVIEW_CONTENT, previewContent);
if (StringUtils.length(previewContent) > 100) {
article.put(Article.ARTICLE_T_THUMBNAIL_URL, getArticleThumbnail(article));
} else {
article.put(Article.ARTICLE_T_THUMBNAIL_URL, "");
}
qiniuImgProcessing(article);
String title = article.optString(Article.ARTICLE_TITLE).replace("<", "<").replace(">", ">");
title = Markdowns.clean(title, "");
article.put(Article.ARTICLE_TITLE, title);
article.put(Article.ARTICLE_T_TITLE_EMOJI, Emotions.convert(title));
article.put(Article.ARTICLE_T_TITLE_EMOJI_UNICODE, EmojiParser.parseToUnicode(title));
if (Article.ARTICLE_STATUS_C_INVALID == article.optInt(Article.ARTICLE_STATUS)) {
article.put(Article.ARTICLE_TITLE, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_T_TITLE_EMOJI, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_T_TITLE_EMOJI_UNICODE, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_CONTENT, langPropsService.get("articleContentBlockLabel"));
}
final String articleId = article.optString(Keys.OBJECT_ID);
Integer viewingCnt = ArticleChannel.ARTICLE_VIEWS.get(articleId);
if (null == viewingCnt) {
viewingCnt = 0;
}
article.put(Article.ARTICLE_T_HEAT, viewingCnt);
final int viewCnt = article.optInt(Article.ARTICLE_VIEW_CNT);
final double views = (double) viewCnt / 1000;
if (views >= 1) {
final DecimalFormat df = new DecimalFormat("#.#");
article.put(Article.ARTICLE_T_VIEW_CNT_DISPLAY_FORMAT, df.format(views) + "K");
}
final long stick = article.optLong(Article.ARTICLE_STICK);
long expired;
if (stick > 0) {
expired = stick + Symphonys.getLong("stickArticleTime");
final long remainsMills = Math.abs(System.currentTimeMillis() - expired);
article.put(Article.ARTICLE_T_STICK_REMAINS, (int) Math.floor((double) remainsMills / 1000 / 60));
} else {
article.put(Article.ARTICLE_T_STICK_REMAINS, 0);
}
String articleLatestCmterName = article.optString(Article.ARTICLE_LATEST_CMTER_NAME);
if (StringUtils.isNotBlank(articleLatestCmterName)
&& UserRegisterValidation.invalidUserName(articleLatestCmterName)) {
articleLatestCmterName = UserExt.ANONYMOUS_USER_NAME;
article.put(Article.ARTICLE_LATEST_CMTER_NAME, articleLatestCmterName);
}
final Query query = new Query()
.setPageCount(1).setCurrentPageNum(1).setPageSize(1)
.setFilter(new PropertyFilter(Comment.COMMENT_ON_ARTICLE_ID, FilterOperator.EQUAL, articleId)).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING);
final JSONArray cmts = commentRepository.get(query).optJSONArray(Keys.RESULTS);
if (cmts.length() > 0) {
final JSONObject latestCmt = cmts.optJSONObject(0);
latestCmt.put(Comment.COMMENT_CLIENT_COMMENT_ID, latestCmt.optString(Comment.COMMENT_CLIENT_COMMENT_ID));
article.put(Article.ARTICLE_T_LATEST_CMT, latestCmt);
}
// builds tag objects
final String tagsStr = article.optString(Article.ARTICLE_TAGS);
final String[] tagTitles = tagsStr.split(",");
final List<JSONObject> tags = new ArrayList<>();
for (final String tagTitle : tagTitles) {
final JSONObject tag = new JSONObject();
tag.put(Tag.TAG_TITLE, tagTitle);
final String uri = tagRepository.getURIByTitle(tagTitle);
if (null != uri) {
tag.put(Tag.TAG_URI, uri);
} else {
tag.put(Tag.TAG_URI, tagTitle);
tagRepository.getURIByTitle(tagTitle);
}
tags.add(tag);
}
article.put(Article.ARTICLE_T_TAG_OBJS, (Object) tags);
}
/**
* Gets the first image URL of the specified article.
*
* @param article the specified article
* @return the first image URL, returns {@code ""} if not found
*/
private String getArticleThumbnail(final JSONObject article) {
final int articleType = article.optInt(Article.ARTICLE_TYPE);
if (Article.ARTICLE_TYPE_C_THOUGHT == articleType) {
return "";
}
final String content = article.optString(Article.ARTICLE_CONTENT);
final String html = Markdowns.toHTML(content);
String ret = StringUtils.substringBetween(html, "<img src=\"", "\"");
final boolean qiniuEnabled = Symphonys.getBoolean("qiniu.enabled");
if (qiniuEnabled) {
final String qiniuDomain = Symphonys.get("qiniu.domain");
if (StringUtils.startsWith(ret, qiniuDomain)) {
ret += "?imageView2/1/w/" + 180 + "/h/" + 135 + "/format/jpg/interlace/1/q";
} else {
ret = "";
}
} else {
if (!StringUtils.startsWith(ret, Latkes.getServePath())) {
ret = "";
}
}
if (StringUtils.isBlank(ret)) {
ret = "";
}
return ret;
}
/**
* Qiniu image processing.
*
* @param article the specified article
* @return the first image URL, returns {@code ""} if not found
*/
private void qiniuImgProcessing(final JSONObject article) {
final boolean qiniuEnabled = Symphonys.getBoolean("qiniu.enabled");
if (!qiniuEnabled) {
return;
}
final String qiniuDomain = Symphonys.get("qiniu.domain");
String content = article.optString(Article.ARTICLE_CONTENT);
final String html = Markdowns.toHTML(content);
final String[] imgSrcs = StringUtils.substringsBetween(html, "<img src=\"", "\"");
if (null == imgSrcs) {
return;
}
for (final String imgSrc : imgSrcs) {
if (!StringUtils.startsWith(imgSrc, qiniuDomain) || StringUtils.contains(imgSrc, ".gif")) {
continue;
}
content = StringUtils.replaceOnce(content, imgSrc, imgSrc + "?imageView2/2/w/768/format/jpg/interlace/0/q");
}
article.put(Article.ARTICLE_CONTENT, content);
}
/**
* Converts the specified article create/update/latest comment time (long) to date type.
*
* @param article the specified article
*/
private void toArticleDate(final JSONObject article) {
article.put(Common.TIME_AGO,
Times.getTimeAgo(article.optLong(Article.ARTICLE_CREATE_TIME), Locales.getLocale()));
article.put(Common.CMT_TIME_AGO,
Times.getTimeAgo(article.optLong(Article.ARTICLE_LATEST_CMT_TIME), Locales.getLocale()));
article.put(Article.ARTICLE_CREATE_TIME, new Date(article.optLong(Article.ARTICLE_CREATE_TIME)));
article.put(Article.ARTICLE_UPDATE_TIME, new Date(article.optLong(Article.ARTICLE_UPDATE_TIME)));
article.put(Article.ARTICLE_LATEST_CMT_TIME, new Date(article.optLong(Article.ARTICLE_LATEST_CMT_TIME)));
}
/**
* Generates the specified article author name and thumbnail URL.
*
* @param avatarViewMode the specified avatar view mode
* @param article the specified article
* @throws RepositoryException repository exception
*/
private void genArticleAuthor(final int avatarViewMode, final JSONObject article) throws RepositoryException {
final String authorId = article.optString(Article.ARTICLE_AUTHOR_ID);
final JSONObject author = userRepository.get(authorId);
article.put(Article.ARTICLE_T_AUTHOR, author);
if (Article.ARTICLE_ANONYMOUS_C_ANONYMOUS == article.optInt(Article.ARTICLE_ANONYMOUS)) {
article.put(Article.ARTICLE_T_AUTHOR_NAME, UserExt.ANONYMOUS_USER_NAME);
article.put(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "210", avatarQueryService.getDefaultAvatarURL("210"));
article.put(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "48", avatarQueryService.getDefaultAvatarURL("48"));
article.put(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "20", avatarQueryService.getDefaultAvatarURL("20"));
} else {
article.put(Article.ARTICLE_T_AUTHOR_NAME, author.optString(User.USER_NAME));
article.put(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "210",
avatarQueryService.getAvatarURLByUser(avatarViewMode, author, "210"));
article.put(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "48",
avatarQueryService.getAvatarURLByUser(avatarViewMode, author, "48"));
article.put(Article.ARTICLE_T_AUTHOR_THUMBNAIL_URL + "20",
avatarQueryService.getAvatarURLByUser(avatarViewMode, author, "20"));
}
}
/**
* Generates participants for the specified articles.
*
* @param avatarViewMode the specified avatar view mode
* @param articles the specified articles
* @param participantsCnt the specified generate size
* @throws ServiceException service exception
*/
public void genParticipants(final int avatarViewMode,
final List<JSONObject> articles, final Integer participantsCnt) throws ServiceException {
Stopwatchs.start("Generates participants");
try {
for (final JSONObject article : articles) {
article.put(Article.ARTICLE_T_PARTICIPANTS, (Object) Collections.emptyList());
if (article.optInt(Article.ARTICLE_COMMENT_CNT) < 1) {
continue;
}
final List<JSONObject> articleParticipants = getArticleLatestParticipants(
avatarViewMode, article.optString(Keys.OBJECT_ID), participantsCnt);
article.put(Article.ARTICLE_T_PARTICIPANTS, (Object) articleParticipants);
}
} finally {
Stopwatchs.end();
}
}
/**
* Gets the article participants (commenters) with the specified article article id and fetch size.
*
* @param avatarViewMode the specified avatar view mode
* @param articleId the specified article id
* @param fetchSize the specified fetch size
* @return article participants, for example, <pre>
* [
* {
* "oId": "",
* "articleParticipantName": "",
* "articleParticipantThumbnailURL": "",
* "articleParticipantThumbnailUpdateTime": long,
* "commentId": ""
* }, ....
* ]
* </pre>, returns an empty list if not found
* @throws ServiceException service exception
*/
public List<JSONObject> getArticleLatestParticipants(final int avatarViewMode,
final String articleId, final int fetchSize) throws ServiceException {
final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
.setFilter(new PropertyFilter(Comment.COMMENT_ON_ARTICLE_ID, FilterOperator.EQUAL, articleId))
.addProjection(Keys.OBJECT_ID, String.class)
.addProjection(Comment.COMMENT_AUTHOR_ID, String.class)
.setPageCount(1).setCurrentPageNum(1).setPageSize(fetchSize);
final List<JSONObject> ret = new ArrayList<>();
try {
final JSONObject result = commentRepository.get(query);
final List<JSONObject> comments = new ArrayList<>();
final JSONArray records = result.optJSONArray(Keys.RESULTS);
for (int i = 0; i < records.length(); i++) {
final JSONObject comment = records.optJSONObject(i);
boolean exist = false;
// deduplicate
for (final JSONObject c : comments) {
if (comment.optString(Comment.COMMENT_AUTHOR_ID).equals(
c.optString(Comment.COMMENT_AUTHOR_ID))) {
exist = true;
break;
}
}
if (!exist) {
comments.add(comment);
}
}
for (final JSONObject comment : comments) {
final String userId = comment.optString(Comment.COMMENT_AUTHOR_ID);
final JSONObject commenter = userRepository.get(userId);
final String email = commenter.optString(User.USER_EMAIL);
String thumbnailURL = Symphonys.get("defaultThumbnailURL");
if (!UserExt.DEFAULT_CMTER_EMAIL.equals(email)) {
thumbnailURL = avatarQueryService.getAvatarURLByUser(avatarViewMode, commenter, "48");
}
final JSONObject participant = new JSONObject();
participant.put(Article.ARTICLE_T_PARTICIPANT_NAME, commenter.optString(User.USER_NAME));
participant.put(Article.ARTICLE_T_PARTICIPANT_THUMBNAIL_URL, thumbnailURL);
participant.put(Article.ARTICLE_T_PARTICIPANT_THUMBNAIL_UPDATE_TIME,
commenter.optLong(UserExt.USER_UPDATE_TIME));
participant.put(Article.ARTICLE_T_PARTICIPANT_URL, commenter.optString(User.USER_URL));
participant.put(Keys.OBJECT_ID, commenter.optString(Keys.OBJECT_ID));
participant.put(Comment.COMMENT_T_ID, comment.optString(Keys.OBJECT_ID));
ret.add(participant);
}
return ret;
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets article [" + articleId + "] participants failed", e);
throw new ServiceException(e);
}
}
/**
* Processes the specified article content.
* <p>
* <ul>
* <li>Generates @username home URL</li>
* <li>Markdowns</li>
* <li>Generates secured article content</li>
* <li>Blocks the article if need</li>
* <li>Generates emotion images</li>
* <li>Generates article link with article id</li>
* <li>Generates article abstract (preview content)</li>
* <li>Generates article ToC</li>
* </ul>
*
* @param article the specified article, for example,
* "articleTitle": "",
* ....,
* "author": {}
* @param request the specified request
* @throws ServiceException service exception
*/
public void processArticleContent(final JSONObject article, final HttpServletRequest request)
throws ServiceException {
Stopwatchs.start("Process content");
try {
final JSONObject author = article.optJSONObject(Article.ARTICLE_T_AUTHOR);
if (null != author && UserExt.USER_STATUS_C_INVALID == author.optInt(UserExt.USER_STATUS)
|| Article.ARTICLE_STATUS_C_INVALID == article.optInt(Article.ARTICLE_STATUS)) {
article.put(Article.ARTICLE_TITLE, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_T_TITLE_EMOJI, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_T_TITLE_EMOJI_UNICODE, langPropsService.get("articleTitleBlockLabel"));
article.put(Article.ARTICLE_CONTENT, langPropsService.get("articleContentBlockLabel"));
article.put(Article.ARTICLE_T_PREVIEW_CONTENT, langPropsService.get("articleContentBlockLabel"));
article.put(Article.ARTICLE_T_TOC, "");
article.put(Article.ARTICLE_REWARD_CONTENT, "");
article.put(Article.ARTICLE_REWARD_POINT, 0);
return;
}
article.put(Article.ARTICLE_T_PREVIEW_CONTENT, article.optString(Article.ARTICLE_TITLE));
String articleContent = article.optString(Article.ARTICLE_CONTENT);
article.put(Common.DISCUSSION_VIEWABLE, true);
final JSONObject currentUser = userQueryService.getCurrentUser(request);
final String currentUserName = null == currentUser ? "" : currentUser.optString(User.USER_NAME);
final String currentRole = null == currentUser ? "" : currentUser.optString(User.USER_ROLE);
final String authorName = article.optString(Article.ARTICLE_T_AUTHOR_NAME);
final int articleType = article.optInt(Article.ARTICLE_TYPE);
if (Article.ARTICLE_TYPE_C_DISCUSSION == articleType
&& !authorName.equals(currentUserName) && !Role.ROLE_ID_C_ADMIN.equals(currentRole)) {
boolean invited = false;
final Set<String> userNames = userQueryService.getUserNames(articleContent);
for (final String userName : userNames) {
if (userName.equals(currentUserName)) {
invited = true;
break;
}
}
if (!invited) {
String blockContent = langPropsService.get("articleDiscussionLabel");
blockContent = blockContent.replace("{user}", "<a href='" + Latkes.getServePath()
+ "/member/" + authorName + "'>" + authorName + "</a>");
article.put(Article.ARTICLE_CONTENT, blockContent);
article.put(Common.DISCUSSION_VIEWABLE, false);
article.put(Article.ARTICLE_REWARD_CONTENT, "");
article.put(Article.ARTICLE_REWARD_POINT, 0);
article.put(Article.ARTICLE_T_TOC, "");
article.put(Article.ARTICLE_AUDIO_URL, "");
return;
}
}
if (Article.ARTICLE_TYPE_C_THOUGHT != articleType) {
articleContent = shortLinkQueryService.linkArticle(articleContent);
articleContent = shortLinkQueryService.linkTag(articleContent);
articleContent = Emotions.convert(articleContent);
article.put(Article.ARTICLE_CONTENT, articleContent);
}
markdown(article);
articleContent = article.optString(Article.ARTICLE_CONTENT);
if (Article.ARTICLE_TYPE_C_THOUGHT != articleType) {
articleContent = MP3Players.render(articleContent);
}
article.put(Article.ARTICLE_CONTENT, articleContent);
article.put(Article.ARTICLE_T_PREVIEW_CONTENT, getArticleMetaDesc(article));
article.put(Article.ARTICLE_T_TOC, getArticleToC(article));
} finally {
Stopwatchs.end();
}
}
/**
* Gets articles by the specified request json object.
*
* @param avatarViewMode the specified avatar view mode
* @param requestJSONObject the specified request json object, for example
* "oId": "", // optional
* "paginationCurrentPageNum": 1,
* "paginationPageSize": 20,
* "paginationWindowSize": 10
* @param articleFields the specified article fields to return
* @return for example, <pre>
* {
* "pagination": {
* "paginationPageCount": 100,
* "paginationPageNums": [1, 2, 3, 4, 5]
* },
* "articles": [{
* "oId": "",
* "articleTitle": "",
* "articleContent": "",
* ....
* }, ....]
* }
* </pre>
* @throws ServiceException service exception
* @see Pagination
*/
public JSONObject getArticles(final int avatarViewMode,
final JSONObject requestJSONObject, final Map<String, Class<?>> articleFields) 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(Article.ARTICLE_STICK, SortDirection.DESCENDING).
addSort(Keys.OBJECT_ID, SortDirection.DESCENDING);
for (final Map.Entry<String, Class<?>> articleField : articleFields.entrySet()) {
query.addProjection(articleField.getKey(), articleField.getValue());
}
if (requestJSONObject.has(Keys.OBJECT_ID)) {
query.setFilter(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.EQUAL, requestJSONObject.optString(Keys.OBJECT_ID)));
}
JSONObject result = null;
try {
result = articleRepository.get(query);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Gets articles 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> articles = CollectionUtils.<JSONObject>jsonArrayToList(data);
try {
organizeArticles(avatarViewMode, articles);
} catch (final RepositoryException e) {
LOGGER.log(Level.ERROR, "Organizes articles failed", e);
throw new ServiceException(e);
}
ret.put(Article.ARTICLES, articles);
return ret;
}
/**
* Markdowns the specified article content.
* <p>
* <ul>
* <li>Markdowns article content/reward content</li>
* <li>Generates secured article content/reward content</li>
* </ul>
*
* @param article the specified article content
*/
private void markdown(final JSONObject article) {
String content = article.optString(Article.ARTICLE_CONTENT);
final int articleType = article.optInt(Article.ARTICLE_TYPE);
if (Article.ARTICLE_TYPE_C_THOUGHT != articleType) {
content = Markdowns.toHTML(content);
content = Markdowns.clean(content, Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK));
} else {
final Document.OutputSettings outputSettings = new Document.OutputSettings();
outputSettings.prettyPrint(false);
content = Jsoup.clean(content, Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK),
Whitelist.relaxed().addAttributes(":all", "id", "target", "class").
addTags("span", "hr").addAttributes("iframe", "src", "width", "height")
.addAttributes("audio", "controls", "src"), outputSettings);
content = content.replace("\n", "\\n").replace("'", "\\'")
.replace("\"", "\\\"");
}
article.put(Article.ARTICLE_CONTENT, content);
if (article.optInt(Article.ARTICLE_REWARD_POINT) > 0) {
String rewardContent = article.optString(Article.ARTICLE_REWARD_CONTENT);
rewardContent = Markdowns.toHTML(rewardContent);
rewardContent = Markdowns.clean(rewardContent,
Latkes.getServePath() + article.optString(Article.ARTICLE_PERMALINK));
article.put(Article.ARTICLE_REWARD_CONTENT, rewardContent);
}
}
/**
* Gets meta description content of the specified article.
*
* @param article the specified article
* @return meta description
*/
public String getArticleMetaDesc(final JSONObject article) {
final String articleId = article.optString(Keys.OBJECT_ID);
String articleAbstract = articleCache.getArticleAbstract(articleId);
if (StringUtils.isNotBlank(articleAbstract)) {
return articleAbstract;
}
Stopwatchs.start("Meta Desc");
try {
final int articleType = article.optInt(Article.ARTICLE_TYPE);
if (Article.ARTICLE_TYPE_C_THOUGHT == articleType) {
return "....";
}
if (Article.ARTICLE_TYPE_C_DISCUSSION == articleType) {
return langPropsService.get("articleAbstractDiscussionLabel", Latkes.getLocale());
}
final int length = Integer.valueOf("150");
String ret = article.optString(Article.ARTICLE_CONTENT);
ret = Emotions.clear(ret);
try {
ret = Markdowns.toHTML(ret);
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Parses article abstract failed [id=" + articleId + ", md=" + ret + "]");
throw e;
}
final Whitelist whitelist = Whitelist.basicWithImages();
whitelist.addTags("object", "video");
ret = Jsoup.clean(ret, whitelist);
final int threshold = 20;
String[] pics = StringUtils.substringsBetween(ret, "<img", ">");
if (null != pics) {
if (pics.length > threshold) {
pics = Arrays.copyOf(pics, threshold);
}
final String[] picsRepl = new String[pics.length];
for (int i = 0; i < picsRepl.length; i++) {
picsRepl[i] = langPropsService.get("picTagLabel", Latkes.getLocale());
pics[i] = "<img" + pics[i] + ">";
if (i > threshold) {
break;
}
}
ret = StringUtils.replaceEach(ret, pics, picsRepl);
}
String[] objs = StringUtils.substringsBetween(ret, "<object>", "</object>");
if (null != objs) {
if (objs.length > threshold) {
objs = Arrays.copyOf(objs, threshold);
}
final String[] objsRepl = new String[objs.length];
for (int i = 0; i < objsRepl.length; i++) {
objsRepl[i] = langPropsService.get("objTagLabel", Latkes.getLocale());
objs[i] = "<object>" + objs[i] + "</object>";
if (i > threshold) {
break;
}
}
ret = StringUtils.replaceEach(ret, objs, objsRepl);
}
objs = StringUtils.substringsBetween(ret, "<video", "</video>");
if (null != objs) {
if (objs.length > threshold) {
objs = Arrays.copyOf(objs, threshold);
}
final String[] objsRepl = new String[objs.length];
for (int i = 0; i < objsRepl.length; i++) {
objsRepl[i] = langPropsService.get("objTagLabel", Latkes.getLocale());
objs[i] = "<video" + objs[i] + "</video>";
if (i > threshold) {
break;
}
}
ret = StringUtils.replaceEach(ret, objs, objsRepl);
}
String tmp = Jsoup.clean(Jsoup.parse(ret).text(), Whitelist.none());
if (tmp.length() >= length && null != pics) {
tmp = StringUtils.substring(tmp, 0, length) + " ....";
ret = tmp.replaceAll("\"", "'");
articleCache.putArticleAbstract(articleId, ret);
return ret;
}
String[] urls = StringUtils.substringsBetween(ret, "<a", "</a>");
if (null != urls) {
if (urls.length > threshold) {
urls = Arrays.copyOf(urls, threshold);
}
final String[] urlsRepl = new String[urls.length];
for (int i = 0; i < urlsRepl.length; i++) {
urlsRepl[i] = langPropsService.get("urlTagLabel", Latkes.getLocale());
urls[i] = "<a" + urls[i] + "</a>";
}
ret = StringUtils.replaceEach(ret, urls, urlsRepl);
}
tmp = Jsoup.clean(Jsoup.parse(ret).text(), Whitelist.none());
if (tmp.length() >= length) {
tmp = StringUtils.substring(tmp, 0, length) + " ....";
}
ret = tmp.replaceAll("\"", "'");
articleCache.putArticleAbstract(articleId, ret);
return ret;
} finally {
Stopwatchs.end();
}
}
/**
* Gets ToC of the specified article.
*
* @param article the specified article
* @return ToC
*/
private String getArticleToC(final JSONObject article) {
Stopwatchs.start("ToC");
if (Article.ARTICLE_TYPE_C_THOUGHT == article.optInt(Article.ARTICLE_TYPE)) {
return "";
}
try {
final String content = article.optString(Article.ARTICLE_CONTENT);
final Document doc = Jsoup.parse(content, StringUtils.EMPTY, Parser.htmlParser());
doc.outputSettings().prettyPrint(false);
final Elements hs = doc.select("h1, h2, h3, h4, h5");
if (hs.size() < 3) {
return "";
}
final StringBuilder listBuilder = new StringBuilder();
listBuilder.append("<ul class=\"article-toc\">");
for (int i = 0; i < hs.size(); i++) {
final Element element = hs.get(i);
final String tagName = element.tagName().toLowerCase();
final String text = element.text();
final String id = "toc_" + tagName + "_" + i;
element.before("<span id='" + id + "'></span>");
listBuilder.append("<li class='toc-").append(tagName).append("'><a data-id=\"").append(id).append("\" href=\"javascript:Comment._bgFade($('#").append(id).append("'))\">").append(text).append(
"</a></li>");
}
listBuilder.append("</ul>");
article.put(Article.ARTICLE_CONTENT, doc.select("body").html());
return listBuilder.toString();
} finally {
Stopwatchs.end();
}
}
}