/*
* Symphony - A modern community (forum/SNS/blog) platform written in Java.
* Copyright (C) 2012-2017, b3log.org & hacpai.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.b3log.symphony.service;
import org.apache.commons.lang.math.RandomUtils;
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.User;
import org.b3log.latke.service.LangPropsService;
import org.b3log.latke.service.ServiceException;
import org.b3log.latke.service.annotation.Service;
import org.b3log.latke.util.Locales;
import org.b3log.latke.util.Stopwatchs;
import org.b3log.symphony.SymphonyServletListener;
import org.b3log.symphony.cache.DomainCache;
import org.b3log.symphony.model.*;
import org.b3log.symphony.util.Sessions;
import org.b3log.symphony.util.Symphonys;
import org.json.JSONObject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
/**
* Data model service.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.12.2.28, May 13, 2017
* @since 0.2.0
*/
@Service
public class DataModelService {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(DataModelService.class);
/**
* Icon configuration.
*/
private static final ResourceBundle ICON_CONF = ResourceBundle.getBundle("icon");
/**
* Icons.
*/
private static Map<String, String> ICONS;
/**
* Language service.
*/
@Inject
private LangPropsService langPropsService;
/**
* Follow query service.
*/
@Inject
private FollowQueryService followQueryService;
/**
* Article query service.
*/
@Inject
private ArticleQueryService articleQueryService;
/**
* Tag query service.
*/
@Inject
private TagQueryService tagQueryService;
/**
* Option query service.
*/
@Inject
private OptionQueryService optionQueryService;
/**
* User management service.
*/
@Inject
private UserMgmtService userMgmtService;
/**
* User query service.
*/
@Inject
private UserQueryService userQueryService;
/**
* Avatar query service.
*/
@Inject
private AvatarQueryService avatarQueryService;
/**
* Activity query service.
*/
@Inject
private ActivityQueryService activityQueryService;
/**
* Liveness query service.
*/
@Inject
private LivenessQueryService livenessQueryService;
/**
* Role query service.
*/
@Inject
private RoleQueryService roleQueryService;
/**
* Domain cache.
*/
@Inject
private DomainCache domainCache;
/**
* Fills relevant articles.
*
* @param avatarViewMode the specified avatar view mode
* @param dataModel the specified data model
* @param article the specified article
* @throws Exception exception
*/
public void fillRelevantArticles(final int avatarViewMode,
final Map<String, Object> dataModel, final JSONObject article) throws Exception {
final int articleStatus = article.optInt(Article.ARTICLE_STATUS);
if (Article.ARTICLE_STATUS_C_VALID != articleStatus) {
dataModel.put(Common.SIDE_RELEVANT_ARTICLES, Collections.emptyList());
return;
}
Stopwatchs.start("Fills relevant articles");
try {
dataModel.put(Common.SIDE_RELEVANT_ARTICLES,
articleQueryService.getRelevantArticles(
avatarViewMode, article, Symphonys.getInt("sideRelevantArticlesCnt")));
} finally {
Stopwatchs.end();
}
}
/**
* Fills the latest comments.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
public void fillLatestCmts(final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills latest comments");
try {
// dataModel.put(Common.SIDE_LATEST_CMTS, commentQueryService.getLatestComments(Symphonys.getInt("sizeLatestCmtsCnt")));
dataModel.put(Common.SIDE_LATEST_CMTS, (Object) Collections.emptyList());
} finally {
Stopwatchs.end();
}
}
/**
* Fills random articles.
*
* @param avatarViewMode the specified avatar view mode
* @param dataModel the specified data model
* @throws Exception exception
*/
public void fillRandomArticles(final int avatarViewMode, final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills random articles");
try {
final int fetchSize = Symphonys.getInt("sideRandomArticlesCnt");
if (fetchSize > 0) {
dataModel.put(Common.SIDE_RANDOM_ARTICLES, articleQueryService.getRandomArticles(avatarViewMode, fetchSize));
} else {
dataModel.put(Common.SIDE_RANDOM_ARTICLES, Collections.emptyList());
}
} finally {
Stopwatchs.end();
}
}
/**
* Fills side hot articles.
*
* @param avatarViewMode the specified avatar view mode
* @param dataModel the specified data model
* @throws Exception exception
*/
public void fillSideHotArticles(final int avatarViewMode, final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills hot articles");
try {
dataModel.put(Common.SIDE_HOT_ARTICLES,
articleQueryService.getSideHotArticles(avatarViewMode, Symphonys.getInt("sideHotArticlesCnt")));
} finally {
Stopwatchs.end();
}
}
/**
* Fills tags.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
public void fillSideTags(final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills side tags");
try {
dataModel.put(Common.SIDE_TAGS, tagQueryService.getTags(Symphonys.getInt("sideTagsCnt")));
if (!(Boolean) dataModel.get(Common.IS_MOBILE)) {
fillNewTags(dataModel);
}
} finally {
Stopwatchs.end();
}
}
/**
* Fills index tags.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
public void fillIndexTags(final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills index tags");
try {
for (int i = 0; i < 9; i++) {
final JSONObject tag = new JSONObject();
tag.put(Tag.TAG_URI, "Sym");
tag.put(Tag.TAG_ICON_PATH, "sym.png");
tag.put(Tag.TAG_TITLE, "Sym");
dataModel.put(Tag.TAG + i, tag);
}
final List<JSONObject> tags = tagQueryService.getTags(Symphonys.getInt("sideTagsCnt"));
for (int i = 0; i < tags.size(); i++) {
dataModel.put(Tag.TAG + i, tags.get(i));
}
dataModel.put(Tag.TAGS, tags);
} finally {
Stopwatchs.end();
}
}
/**
* Fills header.
*
* @param request the specified request
* @param response the specified response
* @param dataModel the specified data model
* @throws Exception exception
*/
private void fillHeader(final HttpServletRequest request, final HttpServletResponse response,
final Map<String, Object> dataModel) throws Exception {
fillMinified(dataModel);
dataModel.put(Common.STATIC_RESOURCE_VERSION, Latkes.getStaticResourceVersion());
dataModel.put("esEnabled", Symphonys.getBoolean("es.enabled"));
dataModel.put("algoliaEnabled", Symphonys.getBoolean("algolia.enabled"));
dataModel.put("algoliaAppId", Symphonys.get("algolia.appId"));
dataModel.put("algoliaSearchKey", Symphonys.get("algolia.searchKey"));
dataModel.put("algoliaIndex", Symphonys.get("algolia.index"));
// fillTrendTags(dataModel);
fillPersonalNav(request, response, dataModel);
fillLangs(dataModel);
fillIcons(dataModel);
fillSideAd(dataModel);
fillHeaderBanner(dataModel);
fillSideTips(dataModel);
fillDomainNav(dataModel);
}
/**
* Fills domain navigation.
*
* @param dataModel the specified data model
*/
private void fillDomainNav(final Map<String, Object> dataModel) {
Stopwatchs.start("Fills domain nav");
try {
dataModel.put(Domain.DOMAINS, domainCache.getDomains(Integer.MAX_VALUE));
} finally {
Stopwatchs.end();
}
}
/**
* Fills footer.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
private void fillFooter(final Map<String, Object> dataModel) throws Exception {
fillSysInfo(dataModel);
dataModel.put(Common.YEAR, String.valueOf(Calendar.getInstance().get(Calendar.YEAR)));
dataModel.put(Common.SITE_VISIT_STAT_CODE, Symphonys.get("siteVisitStatCode"));
dataModel.put(Common.MOUSE_EFFECTS, RandomUtils.nextDouble() > 0.95);
}
/**
* Fills header and footer.
*
* @param request the specified request
* @param response the specified response
* @param dataModel the specified data model
* @throws Exception exception
*/
public void fillHeaderAndFooter(final HttpServletRequest request, final HttpServletResponse response,
final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills header");
try {
final boolean isMobile = (Boolean) request.getAttribute(Common.IS_MOBILE);
dataModel.put(Common.IS_MOBILE, isMobile);
fillHeader(request, response, dataModel);
} finally {
Stopwatchs.end();
}
Stopwatchs.start("Fills footer");
try {
fillFooter(dataModel);
} finally {
Stopwatchs.end();
}
dataModel.put(Common.WEBSOCKET_SCHEME, Symphonys.get("websocket.scheme"));
}
/**
* Fills personal navigation.
*
* @param request the specified request
* @param response the specified response
* @param dataModel the specified data model
*/
private void fillPersonalNav(final HttpServletRequest request, final HttpServletResponse response,
final Map<String, Object> dataModel) {
Stopwatchs.start("Fills personal nav");
try {
dataModel.put(Common.IS_LOGGED_IN, false);
dataModel.put(Common.IS_ADMIN_LOGGED_IN, false);
if (null == Sessions.currentUser(request) && !userMgmtService.tryLogInWithCookie(request, response)) {
dataModel.put("loginLabel", langPropsService.get("loginLabel"));
return;
}
JSONObject curUser = null;
try {
curUser = userQueryService.getCurrentUser(request);
} catch (final ServiceException e) {
LOGGER.log(Level.ERROR, "Gets the current user failed", e);
}
if (null == curUser) {
dataModel.put("loginLabel", langPropsService.get("loginLabel"));
return;
}
dataModel.put(Common.IS_LOGGED_IN, true);
dataModel.put(Common.LOGOUT_URL, userQueryService.getLogoutURL("/"));
dataModel.put("logoutLabel", langPropsService.get("logoutLabel"));
final String userRole = curUser.optString(User.USER_ROLE);
dataModel.put(User.USER_ROLE, userRole);
dataModel.put(Common.IS_ADMIN_LOGGED_IN, Role.ROLE_ID_C_ADMIN.equals(userRole));
avatarQueryService.fillUserAvatarURL(curUser.optInt(UserExt.USER_AVATAR_VIEW_MODE), curUser);
final String userId = curUser.optString(Keys.OBJECT_ID);
final long followingArticleCnt = followQueryService.getFollowingCount(userId, Follow.FOLLOWING_TYPE_C_ARTICLE);
final long followingTagCnt = followQueryService.getFollowingCount(userId, Follow.FOLLOWING_TYPE_C_TAG);
final long followingUserCnt = followQueryService.getFollowingCount(userId, Follow.FOLLOWING_TYPE_C_USER);
curUser.put(Common.FOLLOWING_ARTICLE_CNT, followingArticleCnt);
curUser.put(Common.FOLLOWING_TAG_CNT, followingTagCnt);
curUser.put(Common.FOLLOWING_USER_CNT, followingUserCnt);
final int point = curUser.optInt(UserExt.USER_POINT);
final int appRole = curUser.optInt(UserExt.USER_APP_ROLE);
if (UserExt.USER_APP_ROLE_C_HACKER == appRole) {
curUser.put(UserExt.USER_T_POINT_HEX, Integer.toHexString(point));
} else {
curUser.put(UserExt.USER_T_POINT_CC, UserExt.toCCString(point));
}
dataModel.put(Common.CURRENT_USER, curUser);
final JSONObject role = roleQueryService.getRole(userRole);
curUser.put(Role.ROLE_NAME, role.optString(Role.ROLE_NAME));
// final int unreadNotificationCount = notificationQueryService.getUnreadNotificationCount(curUser.optString(Keys.OBJECT_ID));
dataModel.put(Notification.NOTIFICATION_T_UNREAD_COUNT, 0); // AJAX polling
dataModel.put(Common.IS_DAILY_CHECKIN, activityQueryService.isCheckedinToday(userId));
dataModel.put(Common.USE_CAPTCHA_CHECKIN, Symphonys.getBoolean("geetest.enabled"));
final int livenessMax = Symphonys.getInt("activitYesterdayLivenessReward.maxPoint");
final int currentLiveness = livenessQueryService.getCurrentLivenessPoint(userId);
dataModel.put(Liveness.LIVENESS, (float) (Math.round((float) currentLiveness / livenessMax * 100 * 100)) / 100);
} finally {
Stopwatchs.end();
}
}
/**
* Fills minified directory and file postfix for static JavaScript, CSS.
*
* @param dataModel the specified data model
*/
public void fillMinified(final Map<String, Object> dataModel) {
switch (Latkes.getRuntimeMode()) {
case DEVELOPMENT:
dataModel.put(Common.MINI_POSTFIX, "");
break;
case PRODUCTION:
dataModel.put(Common.MINI_POSTFIX, Common.MINI_POSTFIX_VALUE);
break;
default:
throw new AssertionError();
}
}
/**
* Fills the all language labels.
*
* @param dataModel the specified data model
*/
private void fillLangs(final Map<String, Object> dataModel) {
Stopwatchs.start("Fills lang");
try {
dataModel.putAll(langPropsService.getAll(Locales.getLocale()));
} finally {
Stopwatchs.end();
}
}
/**
* Fills the all icons.
*
* @param dataModel the specified data model
*/
private void fillIcons(final Map<String, Object> dataModel) {
Stopwatchs.start("Fills icons");
try {
if (null == ICONS) {
ICONS = new HashMap<>();
final Enumeration<String> keys = ICON_CONF.getKeys();
while (keys.hasMoreElements()) {
final String key = keys.nextElement();
String value = ICON_CONF.getString(key);
if ("logoIcon".equals(key)) {
value = value.replace("${servePath}", Latkes.getServePath());
}
ICONS.put(key, value);
}
}
dataModel.putAll(ICONS);
} finally {
Stopwatchs.end();
}
}
/**
* Fills the side ad labels.
*
* @param dataModel the specified data model
*/
private void fillSideAd(final Map<String, Object> dataModel) {
final JSONObject adOption = optionQueryService.getOption(Option.ID_C_SIDE_FULL_AD);
if (null == adOption) {
dataModel.put("ADLabel", "");
} else {
dataModel.put("ADLabel", adOption.optString(Option.OPTION_VALUE));
}
}
/**
* Fills the side tips.
*
* @param dataModel the specified data model
*/
private void fillSideTips(final Map<String, Object> dataModel) {
if (RandomUtils.nextFloat() < 0.8) {
return;
}
final Map<String, String> labels = langPropsService.getAll(Locales.getLocale());
final List<String> tipsLabels = new ArrayList<>();
for (final Map.Entry<String, String> entry : labels.entrySet()) {
final String key = entry.getKey();
if (key.startsWith("tips")) {
tipsLabels.add(entry.getValue());
}
}
dataModel.put("tipsLabel", tipsLabels.get(RandomUtils.nextInt(tipsLabels.size())));
}
/**
* Fills the header banner.
*
* @param dataModel the specified data model
*/
private void fillHeaderBanner(final Map<String, Object> dataModel) {
final JSONObject adOption = optionQueryService.getOption(Option.ID_C_HEADER_BANNER);
if (null == adOption) {
dataModel.put("HeaderBannerLabel", "");
} else {
dataModel.put("HeaderBannerLabel", adOption.optString(Option.OPTION_VALUE));
}
}
/**
* Fills trend tags.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
private void fillTrendTags(final Map<String, Object> dataModel) throws Exception {
Stopwatchs.start("Fills trend tags");
try {
// dataModel.put(Common.NAV_TREND_TAGS, tagQueryService.getTrendTags(Symphonys.getInt("trendTagsCnt")));
dataModel.put(Common.NAV_TREND_TAGS, Collections.emptyList());
} finally {
Stopwatchs.end();
}
}
/**
* Fils new tags.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
private void fillNewTags(final Map<String, Object> dataModel) throws Exception {
dataModel.put(Common.NEW_TAGS, tagQueryService.getNewTags());
}
/**
* Fills system info.
*
* @param dataModel the specified data model
* @throws Exception exception
*/
private void fillSysInfo(final Map<String, Object> dataModel) throws Exception {
dataModel.put(Common.VERSION, SymphonyServletListener.VERSION);
}
}