/* * 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.qiniu.storage.UploadManager; import com.qiniu.util.Auth; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; 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.User; import org.b3log.latke.repository.*; import org.b3log.latke.repository.annotation.Transactional; import org.b3log.latke.service.LangPropsService; import org.b3log.latke.service.ServiceException; import org.b3log.latke.service.annotation.Service; import org.b3log.latke.util.Ids; import org.b3log.latke.util.MD5; import org.b3log.latke.util.Requests; import org.b3log.latke.util.Strings; import org.b3log.symphony.model.*; import org.b3log.symphony.repository.*; import org.b3log.symphony.util.Crypts; import org.b3log.symphony.util.Geos; import org.b3log.symphony.util.Sessions; import org.b3log.symphony.util.Symphonys; import org.json.JSONArray; import org.json.JSONObject; import javax.imageio.ImageIO; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.*; import java.net.URLEncoder; import java.util.*; import java.util.regex.Pattern; /** * User management service. * * @author <a href="http://88250.b3log.org">Liang Ding</a> * @author Bill Ho * @version 1.15.20.24, Apr 21, 2017 * @since 0.2.0 */ @Service public class UserMgmtService { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(UserMgmtService.class.getName()); /** * User repository. */ @Inject private UserRepository userRepository; /** * Comment repository. */ @Inject private CommentRepository commentRepository; /** * Article repository. */ @Inject private ArticleRepository articleRepository; /** * Option repository. */ @Inject private OptionRepository optionRepository; /** * Tag repository. */ @Inject private TagRepository tagRepository; /** * User-Tag repository. */ @Inject private UserTagRepository userTagRepository; /** * Language service. */ @Inject private LangPropsService langPropsService; /** * Pointtransfer management service. */ @Inject private PointtransferMgmtService pointtransferMgmtService; /** * Avatar query service. */ @Inject private AvatarQueryService avatarQueryService; /** * Notification management service. */ @Inject private NotificationMgmtService notificationMgmtService; /** * Tries to login with cookie. * * @param request the specified request * @param response the specified response * @return returns {@code true} if logged in, returns {@code false} otherwise */ public boolean tryLogInWithCookie(final HttpServletRequest request, final HttpServletResponse response) { final Cookie[] cookies = request.getCookies(); if (null == cookies || 0 == cookies.length) { return false; } try { for (final Cookie cookie : cookies) { if (!"b3log-latke".equals(cookie.getName())) { continue; } final String value = Crypts.decryptByAES(cookie.getValue(), Symphonys.get("cookie.secret")); final JSONObject cookieJSONObject = new JSONObject(value); final String userId = cookieJSONObject.optString(Keys.OBJECT_ID); if (Strings.isEmptyOrNull(userId)) { break; } final JSONObject user = userRepository.get(userId); if (null == user) { break; } final String ip = Requests.getRemoteAddr(request); if (UserExt.USER_STATUS_C_INVALID == user.optInt(UserExt.USER_STATUS) || UserExt.USER_STATUS_C_INVALID_LOGIN == user.optInt(UserExt.USER_STATUS)) { Sessions.logout(request, response); updateOnlineStatus(userId, ip, false); return false; } final String userPassword = user.optString(User.USER_PASSWORD); final String token = cookieJSONObject.optString(Common.TOKEN); final String password = StringUtils.substringBeforeLast(token, ":"); if (userPassword.equals(password)) { Sessions.login(request, response, user, cookieJSONObject.optBoolean(Common.REMEMBER_LOGIN)); updateOnlineStatus(userId, ip, true); LOGGER.log(Level.TRACE, "Logged in with cookie[userId={0}]", userId); return true; } } } catch (final Exception e) { LOGGER.log(Level.WARN, "Parses cookie failed, clears the cookie[name=b3log-latke]"); final Cookie cookie = new Cookie("b3log-latke", null); cookie.setMaxAge(0); cookie.setPath("/"); response.addCookie(cookie); } return false; } /** * Updates a user's online status and saves the login time and IP. * * @param userId the specified user id * @param ip the specified IP, could be "" if the {@code onlineFlag} is {@code false} * @param onlineFlag the specified online flag * @throws ServiceException service exception */ public void updateOnlineStatus(final String userId, final String ip, final boolean onlineFlag) throws ServiceException { Transaction transaction = null; try { final JSONObject address = Geos.getAddress(ip); final JSONObject user = userRepository.get(userId); if (null == user) { return; } if (null != address) { final String country = address.optString(Common.COUNTRY); final String province = address.optString(Common.PROVINCE); final String city = address.optString(Common.CITY); user.put(UserExt.USER_COUNTRY, country); user.put(UserExt.USER_PROVINCE, province); user.put(UserExt.USER_CITY, city); } transaction = userRepository.beginTransaction(); user.put(UserExt.USER_ONLINE_FLAG, onlineFlag); user.put(UserExt.USER_LATEST_LOGIN_TIME, System.currentTimeMillis()); if (onlineFlag) { user.put(UserExt.USER_LATEST_LOGIN_IP, ip); } userRepository.update(userId, user); transaction.commit(); } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Updates user online status failed [id=" + userId + "]", e); if (null != transaction && transaction.isActive()) { transaction.rollback(); } throw new ServiceException(e); } } /** * Updates a user's profiles by the specified request json object. * * @param requestJSONObject the specified request json object (user), for example, * "oId": "", * "userNickname": "", * "userTags": "", * "userURL": "", * "userQQ": "", * "userIntro": "", * "userAvatarType": int, * "userAvatarURL": "", * "userCommentViewMode": int * @throws ServiceException service exception */ public void updateProfiles(final JSONObject requestJSONObject) throws ServiceException { final Transaction transaction = userRepository.beginTransaction(); try { final String oldUserId = requestJSONObject.optString(Keys.OBJECT_ID); final JSONObject oldUser = userRepository.get(oldUserId); if (null == oldUser) { throw new ServiceException(langPropsService.get("updateFailLabel")); } // Tag final String userTags = requestJSONObject.optString(UserExt.USER_TAGS); oldUser.put(UserExt.USER_TAGS, userTags); tag(oldUser); // Update oldUser.put(UserExt.USER_NICKNAME, requestJSONObject.optString(UserExt.USER_NICKNAME)); oldUser.put(User.USER_URL, requestJSONObject.optString(User.USER_URL)); oldUser.put(UserExt.USER_QQ, requestJSONObject.optString(UserExt.USER_QQ)); oldUser.put(UserExt.USER_INTRO, requestJSONObject.optString(UserExt.USER_INTRO)); oldUser.put(UserExt.USER_AVATAR_TYPE, requestJSONObject.optString(UserExt.USER_AVATAR_TYPE)); oldUser.put(UserExt.USER_AVATAR_URL, requestJSONObject.optString(UserExt.USER_AVATAR_URL)); oldUser.put(UserExt.USER_COMMENT_VIEW_MODE, requestJSONObject.optInt(UserExt.USER_COMMENT_VIEW_MODE)); oldUser.put(UserExt.USER_UPDATE_TIME, System.currentTimeMillis()); userRepository.update(oldUserId, oldUser); transaction.commit(); } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Updates user profiles failed", e); throw new ServiceException(langPropsService.get("updateFailLabel")); } } /** * Updates a user's sync B3log settings by the specified request json object. * * @param requestJSONObject the specified request json object (user), for example, * "oId": "", * "userB3Key": "", * "userB3ClientAddArticleURL": "", * "userB3ClientUpdateArticleURL": "", * "userB3ClientAddCommentURL": "", * "syncWithSymphonyClient": boolean // optional, default to false * @throws ServiceException service exception */ public void updateSyncB3(final JSONObject requestJSONObject) throws ServiceException { final Transaction transaction = userRepository.beginTransaction(); try { final String oldUserId = requestJSONObject.optString(Keys.OBJECT_ID); final JSONObject oldUser = userRepository.get(oldUserId); if (null == oldUser) { throw new ServiceException(langPropsService.get("updateFailLabel")); } // Update oldUser.put(UserExt.USER_B3_KEY, requestJSONObject.optString(UserExt.USER_B3_KEY)); oldUser.put(UserExt.USER_B3_CLIENT_ADD_ARTICLE_URL, requestJSONObject.optString(UserExt.USER_B3_CLIENT_ADD_ARTICLE_URL)); oldUser.put(UserExt.USER_B3_CLIENT_UPDATE_ARTICLE_URL, requestJSONObject.optString(UserExt.USER_B3_CLIENT_UPDATE_ARTICLE_URL)); oldUser.put(UserExt.USER_B3_CLIENT_ADD_COMMENT_URL, requestJSONObject.optString(UserExt.USER_B3_CLIENT_ADD_COMMENT_URL)); oldUser.put(UserExt.SYNC_TO_CLIENT, requestJSONObject.optBoolean(UserExt.SYNC_TO_CLIENT, false)); userRepository.update(oldUserId, oldUser); transaction.commit(); } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Updates user sync b3log settings failed", e); throw new ServiceException(e); } } /** * Updates a user's password by the specified request json object. * * @param requestJSONObject the specified request json object (user), for example, * "oId": "", * "userPassword": "", // Hashed * @throws ServiceException service exception */ public void updatePassword(final JSONObject requestJSONObject) throws ServiceException { final Transaction transaction = userRepository.beginTransaction(); try { final String oldUserId = requestJSONObject.optString(Keys.OBJECT_ID); final JSONObject oldUser = userRepository.get(oldUserId); if (null == oldUser) { throw new ServiceException(langPropsService.get("updateFailLabel")); } // Update oldUser.put(User.USER_PASSWORD, requestJSONObject.optString(User.USER_PASSWORD)); userRepository.update(oldUserId, oldUser); transaction.commit(); } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Updates user password failed", e); throw new ServiceException(e); } } /** * Adds a user with the specified request json object. * * @param requestJSONObject the specified request json object, for example, * "userName": "", * "userEmail": "", * "userPassword": "", // Hashed * "userLanguage": "", * "userAppRole": int, // optional, default to 0 * "userRole": "", // optional, uses {@value Role#ROLE_ID_C_DEFAULT} instead if not specified * "userStatus": int, // optional, uses {@value UserExt#USER_STATUS_C_NOT_VERIFIED} instead if not specified * "userGuideStep": int // optional, uses {@value UserExt#USER_GUIDE_STEP_UPLOAD_AVATAR} instead if not specified * ,see {@link User} for more details * @return generated user id * @throws ServiceException if user name or email duplicated, or repository exception */ public synchronized String addUser(final JSONObject requestJSONObject) throws ServiceException { final Transaction transaction = userRepository.beginTransaction(); try { final String userEmail = requestJSONObject.optString(User.USER_EMAIL).trim().toLowerCase(); final String userName = requestJSONObject.optString(User.USER_NAME); JSONObject user = userRepository.getByName(userName); if (null != user && (UserExt.USER_STATUS_C_VALID == user.optInt(UserExt.USER_STATUS) || UserExt.NULL_USER_NAME.equals(userName))) { if (transaction.isActive()) { transaction.rollback(); } throw new ServiceException(langPropsService.get("duplicatedUserNameLabel") + " [" + userName + "]"); } boolean toUpdate = false; String ret = null; String avatarURL = null; user = userRepository.getByEmail(userEmail); int userNo = 0; if (null != user) { if (UserExt.USER_STATUS_C_VALID == user.optInt(UserExt.USER_STATUS)) { if (transaction.isActive()) { transaction.rollback(); } throw new ServiceException(langPropsService.get("duplicatedEmailLabel")); } toUpdate = true; ret = user.optString(Keys.OBJECT_ID); userNo = user.optInt(UserExt.USER_NO); avatarURL = user.optString(UserExt.USER_AVATAR_URL); } user = new JSONObject(); user.put(User.USER_NAME, userName); user.put(User.USER_EMAIL, userEmail); user.put(UserExt.USER_APP_ROLE, requestJSONObject.optInt(UserExt.USER_APP_ROLE)); user.put(User.USER_PASSWORD, requestJSONObject.optString(User.USER_PASSWORD)); user.put(User.USER_ROLE, requestJSONObject.optString(User.USER_ROLE, Role.ROLE_ID_C_DEFAULT)); user.put(User.USER_URL, ""); user.put(UserExt.USER_ARTICLE_COUNT, 0); user.put(UserExt.USER_COMMENT_COUNT, 0); user.put(UserExt.USER_TAG_COUNT, 0); user.put(UserExt.USER_B3_KEY, ""); user.put(UserExt.USER_B3_CLIENT_ADD_ARTICLE_URL, ""); user.put(UserExt.USER_B3_CLIENT_UPDATE_ARTICLE_URL, ""); user.put(UserExt.USER_B3_CLIENT_ADD_COMMENT_URL, ""); user.put(UserExt.USER_INTRO, ""); user.put(UserExt.USER_NICKNAME, ""); user.put(UserExt.USER_AVATAR_TYPE, UserExt.USER_AVATAR_TYPE_C_UPLOAD); user.put(UserExt.USER_QQ, ""); user.put(UserExt.USER_ONLINE_FLAG, false); user.put(UserExt.USER_LATEST_ARTICLE_TIME, 0L); user.put(UserExt.USER_LATEST_CMT_TIME, 0L); user.put(UserExt.USER_LATEST_LOGIN_TIME, 0L); user.put(UserExt.USER_LATEST_LOGIN_IP, ""); user.put(UserExt.USER_CHECKIN_TIME, 0); user.put(UserExt.USER_CURRENT_CHECKIN_STREAK_START, 0); user.put(UserExt.USER_CURRENT_CHECKIN_STREAK_END, 0); user.put(UserExt.USER_LONGEST_CHECKIN_STREAK_START, 0); user.put(UserExt.USER_LONGEST_CHECKIN_STREAK_END, 0); user.put(UserExt.USER_LONGEST_CHECKIN_STREAK, 0); user.put(UserExt.USER_CURRENT_CHECKIN_STREAK, 0); user.put(UserExt.USER_POINT, 0); user.put(UserExt.USER_USED_POINT, 0); user.put(UserExt.USER_JOIN_POINT_RANK, UserExt.USER_JOIN_POINT_RANK_C_JOIN); user.put(UserExt.USER_JOIN_USED_POINT_RANK, UserExt.USER_JOIN_USED_POINT_RANK_C_JOIN); user.put(UserExt.USER_TAGS, ""); user.put(UserExt.USER_SKIN, Symphonys.get("skinDirName")); // TODO: set default skin by app role user.put(UserExt.USER_MOBILE_SKIN, Symphonys.get("mobileSkinDirName")); user.put(UserExt.USER_COUNTRY, ""); user.put(UserExt.USER_PROVINCE, ""); user.put(UserExt.USER_CITY, ""); user.put(UserExt.USER_UPDATE_TIME, 0L); user.put(UserExt.USER_GEO_STATUS, UserExt.USER_GEO_STATUS_C_PUBLIC); user.put(UserExt.SYNC_TO_CLIENT, false); final int status = requestJSONObject.optInt(UserExt.USER_STATUS, UserExt.USER_STATUS_C_NOT_VERIFIED); user.put(UserExt.USER_STATUS, status); user.put(UserExt.USER_COMMENT_VIEW_MODE, UserExt.USER_COMMENT_VIEW_MODE_C_REALTIME); user.put(UserExt.USER_ONLINE_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_ARTICLE_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_COMMENT_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_FOLLOWING_ARTICLE_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_WATCHING_ARTICLE_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_FOLLOWING_TAG_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_FOLLOWING_USER_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_FOLLOWER_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_POINT_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_TIMELINE_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_UA_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_FORGE_LINK_STATUS, UserExt.USER_XXX_STATUS_C_PUBLIC); user.put(UserExt.USER_NOTIFY_STATUS, UserExt.USER_XXX_STATUS_C_ENABLED); user.put(UserExt.USER_SUB_MAIL_STATUS, UserExt.USER_XXX_STATUS_C_ENABLED); user.put(UserExt.USER_LIST_PAGE_SIZE, Symphonys.getInt("indexArticlesCnt")); user.put(UserExt.USER_AVATAR_VIEW_MODE, UserExt.USER_AVATAR_VIEW_MODE_C_ORIGINAL); user.put(UserExt.USER_SUB_MAIL_SEND_TIME, System.currentTimeMillis()); user.put(UserExt.USER_KEYBOARD_SHORTCUTS_STATUS, UserExt.USER_XXX_STATUS_C_DISABLED); final JSONObject optionLanguage = optionRepository.get(Option.ID_C_MISC_LANGUAGE); final String adminSpecifiedLang = optionLanguage.optString(Option.OPTION_VALUE); if ("0".equals(adminSpecifiedLang)) { user.put(UserExt.USER_LANGUAGE, requestJSONObject.optString(UserExt.USER_LANGUAGE, "zh_CN")); } else { user.put(UserExt.USER_LANGUAGE, adminSpecifiedLang); } user.put(UserExt.USER_TIMEZONE, requestJSONObject.optString(UserExt.USER_TIMEZONE, TimeZone.getDefault().getID())); user.put(UserExt.USER_GUIDE_STEP, requestJSONObject.optInt(UserExt.USER_GUIDE_STEP, UserExt.USER_GUIDE_STEP_UPLOAD_AVATAR)); if (toUpdate) { user.put(UserExt.USER_NO, userNo); if (!Symphonys.get("defaultThumbnailURL").equals(avatarURL)) { // generate/upload avatar succ if (Symphonys.getBoolean("qiniu.enabled")) { user.put(UserExt.USER_AVATAR_URL, Symphonys.get("qiniu.domain") + "/avatar/" + ret + "?" + new Date().getTime()); } else { user.put(UserExt.USER_AVATAR_URL, avatarURL + "?" + new Date().getTime()); } userRepository.update(ret, user); } } else { ret = Ids.genTimeMillisId(); user.put(Keys.OBJECT_ID, ret); try { final BufferedImage img = avatarQueryService.createAvatar(MD5.hash(ret), 512); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(img, "jpg", baos); baos.flush(); final byte[] bytes = baos.toByteArray(); baos.close(); if (Symphonys.getBoolean("qiniu.enabled")) { final Auth auth = Auth.create(Symphonys.get("qiniu.accessKey"), Symphonys.get("qiniu.secretKey")); final UploadManager uploadManager = new UploadManager(); uploadManager.put(bytes, "avatar/" + ret, auth.uploadToken(Symphonys.get("qiniu.bucket")), null, "image/jpeg", false); user.put(UserExt.USER_AVATAR_URL, Symphonys.get("qiniu.domain") + "/avatar/" + ret + "?" + new Date().getTime()); } else { final String fileName = UUID.randomUUID().toString().replaceAll("-", "") + ".jpg"; final OutputStream output = new FileOutputStream(Symphonys.get("upload.dir") + fileName); IOUtils.write(bytes, output); IOUtils.closeQuietly(output); user.put(UserExt.USER_AVATAR_URL, Latkes.getServePath() + "/upload/" + fileName); } } catch (final IOException e) { LOGGER.log(Level.ERROR, "Generates avatar error, using default thumbnail instead", e); user.put(UserExt.USER_AVATAR_URL, Symphonys.get("defaultThumbnailURL")); } final JSONObject memberCntOption = optionRepository.get(Option.ID_C_STATISTIC_MEMBER_COUNT); final int memberCount = memberCntOption.optInt(Option.OPTION_VALUE) + 1; // Updates stat. (member count +1) user.put(UserExt.USER_NO, memberCount); userRepository.add(user); memberCntOption.put(Option.OPTION_VALUE, String.valueOf(memberCount)); optionRepository.update(Option.ID_C_STATISTIC_MEMBER_COUNT, memberCntOption); } transaction.commit(); if (UserExt.USER_STATUS_C_VALID == status) { // Point pointtransferMgmtService.transfer(Pointtransfer.ID_C_SYS, ret, Pointtransfer.TRANSFER_TYPE_C_INIT, Pointtransfer.TRANSFER_SUM_C_INIT, ret, System.currentTimeMillis()); // Occupy the username, defeat others final Transaction trans = userRepository.beginTransaction(); try { final Query query = new Query(); final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(User.USER_NAME, FilterOperator.EQUAL, userName)); filters.add(new PropertyFilter(UserExt.USER_STATUS, FilterOperator.EQUAL, UserExt.USER_STATUS_C_NOT_VERIFIED)); query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)); final JSONArray others = userRepository.get(query).optJSONArray(Keys.RESULTS); for (int i = 0; i < others.length(); i++) { final JSONObject u = others.optJSONObject(i); final String id = u.optString(Keys.OBJECT_ID); u.put(User.USER_NAME, UserExt.NULL_USER_NAME); u.put(UserExt.USER_STATUS, UserExt.USER_STATUS_C_NOT_VERIFIED); userRepository.update(id, u); LOGGER.log(Level.INFO, "Defeated a user [email=" + u.optString(User.USER_EMAIL) + "]"); } trans.commit(); } catch (final RepositoryException e) { if (trans.isActive()) { trans.rollback(); } LOGGER.log(Level.ERROR, "Defeat others error", e); } final JSONObject notification = new JSONObject(); notification.put(Notification.NOTIFICATION_USER_ID, ret); notification.put(Notification.NOTIFICATION_DATA_ID, ""); notificationMgmtService.addSysAnnounceNewUserNotification(notification); // Refresh usernames final JSONObject u = new JSONObject(); u.put(User.USER_NAME, user.optString(User.USER_NAME)); u.put(UserExt.USER_T_NAME_LOWER_CASE, user.optString(User.USER_NAME).toLowerCase()); final String avatar = avatarQueryService.getAvatarURLByUser(UserExt.USER_AVATAR_VIEW_MODE_C_STATIC, user, "20"); u.put(UserExt.USER_AVATAR_URL, avatar); UserQueryService.USER_NAMES.add(u); Collections.sort(UserQueryService.USER_NAMES, (u1, u2) -> { final String u1Name = u1.optString(UserExt.USER_T_NAME_LOWER_CASE); final String u2Name = u2.optString(UserExt.USER_T_NAME_LOWER_CASE); return u1Name.compareTo(u2Name); }); } return ret; } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Adds a user failed", e); throw new ServiceException(e); } } /** * Removes a user specified by the given user id. * * @param userId the given user id * @throws ServiceException service exception */ public void removeUser(final String userId) throws ServiceException { final Transaction transaction = userRepository.beginTransaction(); try { userRepository.remove(userId); transaction.commit(); } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Removes a user[id=" + userId + "] failed", e); throw new ServiceException(e); } } /** * Updates the specified user by the given user id. * * @param userId the given user id * @param user the specified user * @throws ServiceException service exception */ public void updateUser(final String userId, final JSONObject user) throws ServiceException { final Transaction transaction = userRepository.beginTransaction(); try { final JSONObject old = userRepository.get(userId); final String oldRoleId = old.optString(User.USER_ROLE); final String newRoleId = user.optString(User.USER_ROLE); userRepository.update(userId, user); transaction.commit(); if (!oldRoleId.equals(newRoleId)) { final JSONObject notification = new JSONObject(); notification.put(Notification.NOTIFICATION_USER_ID, userId); notification.put(Notification.NOTIFICATION_DATA_ID, oldRoleId + "-" + newRoleId); notificationMgmtService.addSysAnnounceRoleChangedNotification(notification); } } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Updates a user[id=" + userId + "] failed", e); throw new ServiceException(e); } } /** * Updates the specified user's email by the given user id. * * @param userId the given user id * @param user the specified user, contains the new email * @throws ServiceException service exception */ public void updateUserEmail(final String userId, final JSONObject user) throws ServiceException { final String newEmail = user.optString(User.USER_EMAIL); final Transaction transaction = userRepository.beginTransaction(); try { if (null != userRepository.getByEmail(newEmail)) { throw new ServiceException(langPropsService.get("duplicatedEmailLabel") + " [" + newEmail + "]"); } // Update the user userRepository.update(userId, user); transaction.commit(); } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Updates email of the user[id=" + userId + "] failed", e); throw new ServiceException(e); } } /** * Updates the specified user's username by the given user id. * * @param userId the given user id * @param user the specified user, contains the new username * @throws ServiceException service exception */ public void updateUserName(final String userId, final JSONObject user) throws ServiceException { final String newUserName = user.optString(User.USER_NAME); final Transaction transaction = userRepository.beginTransaction(); try { if (!UserExt.NULL_USER_NAME.equals(newUserName) && null != userRepository.getByName(newUserName)) { throw new ServiceException(langPropsService.get("duplicatedUserNameLabel") + " [" + newUserName + "]"); } // Update the user userRepository.update(userId, user); transaction.commit(); } catch (final RepositoryException e) { if (transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Updates username of the user[id=" + userId + "] failed", e); throw new ServiceException(e); } } /** * Resets unverified users. */ @Transactional public void resetUnverifiedUsers() { final Date now = new Date(); final long yesterdayTime = DateUtils.addDays(now, -1).getTime(); final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(UserExt.USER_STATUS, FilterOperator.EQUAL, UserExt.USER_STATUS_C_NOT_VERIFIED)); filters.add(new PropertyFilter(Keys.OBJECT_ID, FilterOperator.LESS_THAN_OR_EQUAL, yesterdayTime)); filters.add(new PropertyFilter(User.USER_NAME, FilterOperator.NOT_EQUAL, UserExt.NULL_USER_NAME)); final Query query = new Query().setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)); try { final JSONObject result = userRepository.get(query); final JSONArray users = result.optJSONArray(Keys.RESULTS); for (int i = 0; i < users.length(); i++) { final JSONObject user = users.optJSONObject(i); final String id = user.optString(Keys.OBJECT_ID); user.put(User.USER_NAME, UserExt.NULL_USER_NAME); userRepository.update(id, user); LOGGER.log(Level.INFO, "Reset unverified user [email=" + user.optString(User.USER_EMAIL)); } } catch (final RepositoryException e) { LOGGER.log(Level.ERROR, "Reset unverified users failed", e); } } /** * Tags the specified user with the specified tag titles. * * @param user the specified article * @throws RepositoryException repository exception */ private synchronized void tag(final JSONObject user) throws RepositoryException { // Clear final List<Filter> filters = new ArrayList<>(); filters.add(new PropertyFilter(User.USER + '_' + Keys.OBJECT_ID, FilterOperator.EQUAL, user.optString(Keys.OBJECT_ID))); filters.add(new PropertyFilter(Common.TYPE, FilterOperator.EQUAL, Tag.TAG_TYPE_C_USER_SELF)); final Query query = new Query(); query.setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters)); final JSONArray results = userTagRepository.get(query).optJSONArray(Keys.RESULTS); for (int i = 0; i < results.length(); i++) { final JSONObject rel = results.optJSONObject(i); final String id = rel.optString(Keys.OBJECT_ID); userTagRepository.remove(id); } // Add String tagTitleStr = user.optString(UserExt.USER_TAGS); final String[] tagTitles = tagTitleStr.split(","); for (final String title : tagTitles) { final String tagTitle = title.trim(); JSONObject tag = tagRepository.getByTitle(tagTitle); String tagId; if (null == tag) { LOGGER.log(Level.TRACE, "Found a new tag[title={0}] in user [name={1}]", new Object[]{tagTitle, user.optString(User.USER_NAME)}); tag = new JSONObject(); tag.put(Tag.TAG_TITLE, tagTitle); String tagURI = tagTitle; try { tagURI = URLEncoder.encode(tagTitle, "UTF-8"); } catch (final UnsupportedEncodingException e) { LOGGER.log(Level.ERROR, "Encode tag title [" + tagTitle + "] error", e); } tag.put(Tag.TAG_URI, tagURI); tag.put(Tag.TAG_CSS, ""); tag.put(Tag.TAG_REFERENCE_CNT, 0); tag.put(Tag.TAG_COMMENT_CNT, 0); tag.put(Tag.TAG_FOLLOWER_CNT, 0); tag.put(Tag.TAG_LINK_CNT, 0); tag.put(Tag.TAG_DESCRIPTION, ""); tag.put(Tag.TAG_ICON_PATH, ""); tag.put(Tag.TAG_STATUS, 0); tag.put(Tag.TAG_GOOD_CNT, 0); tag.put(Tag.TAG_BAD_CNT, 0); tag.put(Tag.TAG_SEO_TITLE, tagTitle); tag.put(Tag.TAG_SEO_KEYWORDS, tagTitle); tag.put(Tag.TAG_SEO_DESC, ""); tag.put(Tag.TAG_RANDOM_DOUBLE, Math.random()); tagId = tagRepository.add(tag); final JSONObject tagCntOption = optionRepository.get(Option.ID_C_STATISTIC_TAG_COUNT); final int tagCnt = tagCntOption.optInt(Option.OPTION_VALUE); tagCntOption.put(Option.OPTION_VALUE, tagCnt + 1); optionRepository.update(Option.ID_C_STATISTIC_TAG_COUNT, tagCntOption); // User-Tag relation (creator) final JSONObject userTagRelation = new JSONObject(); userTagRelation.put(Tag.TAG + '_' + Keys.OBJECT_ID, tagId); userTagRelation.put(User.USER + '_' + Keys.OBJECT_ID, user.optString(Keys.OBJECT_ID)); userTagRelation.put(Common.TYPE, Tag.TAG_TYPE_C_CREATOR); userTagRepository.add(userTagRelation); } else { tagId = tag.optString(Keys.OBJECT_ID); LOGGER.log(Level.TRACE, "Found a existing tag[title={0}, id={1}] in user[name={2}]", tag.optString(Tag.TAG_TITLE), tag.optString(Keys.OBJECT_ID), user.optString(User.USER_NAME)); tagTitleStr = tagTitleStr.replaceAll("(?i)" + Pattern.quote(tagTitle), tag.optString(Tag.TAG_TITLE)); } // User-Tag relation (userself) final JSONObject userTagRelation = new JSONObject(); userTagRelation.put(Tag.TAG + '_' + Keys.OBJECT_ID, tagId); userTagRelation.put(User.USER + '_' + Keys.OBJECT_ID, user.optString(Keys.OBJECT_ID)); userTagRelation.put(Common.TYPE, Tag.TAG_TYPE_C_USER_SELF); userTagRepository.add(userTagRelation); } user.put(UserExt.USER_TAGS, tagTitleStr); } }