/*******************************************************************************
* Copyright [2015] [Onboard team of SERC, Peking University]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.onboard.service.account.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.onboard.domain.mapper.CompanyMapper;
import com.onboard.domain.mapper.DepartmentMapper;
import com.onboard.domain.mapper.ProjectMapper;
import com.onboard.domain.mapper.UserCompanyMapper;
import com.onboard.domain.mapper.UserMapper;
import com.onboard.domain.mapper.UserProjectMapper;
import com.onboard.domain.mapper.base.BaseMapper;
import com.onboard.domain.mapper.model.DepartmentExample;
import com.onboard.domain.mapper.model.ProjectExample;
import com.onboard.domain.mapper.model.UserCompanyExample;
import com.onboard.domain.mapper.model.UserExample;
import com.onboard.domain.mapper.model.UserProjectExample;
import com.onboard.domain.model.Company;
import com.onboard.domain.model.Department;
import com.onboard.domain.model.Project;
import com.onboard.domain.model.User;
import com.onboard.domain.model.UserCompany;
import com.onboard.domain.model.UserProject;
import com.onboard.service.account.UserService;
import com.onboard.service.account.redis.Repository;
import com.onboard.service.account.redis.TokenType;
import com.onboard.service.account.utils.PasswordUtils;
import com.onboard.service.base.AbstractBaseService;
import com.onboard.service.email.EmailService;
import com.onboard.service.email.TemplateEngineService;
import com.onboard.service.file.ImageService;
@Transactional
@Service("userServiceBean")
public class UserServiceImpl extends AbstractBaseService<User, UserExample> implements UserService {
public static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
private static final String FORGET_PASSWORD_TPL = "templates/ForgetPassword.html";
private static final String CONFIRMAION_TPL = "templates/Confirmation.html";
@Autowired
private AccountConfigure configurer;
@Autowired
private UserMapper userMapper;
@Autowired
private CompanyMapper companyMapper;
@Autowired
private UserCompanyMapper userCompanyMapper;
@Autowired
private DepartmentMapper departmentMapper;
@Autowired
private UserProjectMapper userProjectMapper;
@Autowired
private ProjectMapper projectMapper;
@Autowired
private EmailService emailService;
@Autowired
private TemplateEngineService templateEngineService;
@Autowired
private ImageService fileService;
@Autowired
private Repository repository;
private final Function<UserCompany, User> userCompanyMapping = new Function<UserCompany, User>() {
@Override
public User apply(UserCompany input) {
return getById(input.getUserId());
}
};
@Override
public User signUp(User user, String companyName) {
assert(!isEmailRegistered(user.getEmail()));
// create new user
Date current = new Date();
user.setNewPassword(PasswordUtils.createPassword(user.getPassword(), current.toString()));
user.setPassword(null);
user.setCreated(current);
user.setUpdated(user.getCreated());
user.setActivated(false);
userMapper.insertSelective(user);
// create a company belonged to the user
Company company = new Company();
company.setCreated(new Date());
company.setDeleted(false);
company.setCreatorId(user.getId());
company.setName(companyName);
company.setPrivileged(false);
companyMapper.insertSelective(company);
// add creator to the company
UserCompany userCompany = new UserCompany();
userCompany.setUserId(user.getId());
userCompany.setCompanyId(company.getId());
userCompanyMapper.insert(userCompany);
this.sendConfirmationEmail(user);
return user;
}
@Override
public User getUserWithPasswordByEmail(String email) {
User sample = new User();
sample.setEmail(email);
UserExample example = new UserExample(sample);
List<User> users = userMapper.selectByExample(example);
if (users.isEmpty()) {
return null;
}
return new User(users.get(0));
}
@Override
public User getUserByEmailOrUsernameWithPassword(String emailOrUsername) {
UserExample example = new UserExample();
example.or().andEmailEqualTo(emailOrUsername);
example.or().andUsernameEqualTo(emailOrUsername);
List<User> users = userMapper.selectByExample(example);
if (users.isEmpty()) {
return null;
}
return new User(users.get(0));
}
@Override
public User login(String email, String password) {
logger.debug("Current Thread Class Loader is {}", Thread.currentThread().getContextClassLoader());
User user = this.getUserByEmailOrUsernameWithPassword(email);
if (user != null) {
String salt = user.getCreated().toString();
return !PasswordUtils.isPasswordValid(user.getNewPassword(), password, salt) ? null : user;
}
return null;
}
@Override
public boolean isEmailRegistered(String email) {
if (!StringUtils.hasText(email)) {
return false;
}
return getUserByEmail(email) != null;
}
@Override
public void forgetPassword(String email) {
User user = getUserByEmail(email);
String token = repository.addToken(TokenType.FORGET_PASSWORD, user.getId(), configurer.getTokenExpired());
Map<String, Serializable> model = ImmutableMap.of("host", configurer.getProtocol().concat(configurer.getHost()), "user", user,
"token", token);
String text = this.templateEngineService.process(getClass(), FORGET_PASSWORD_TPL, model);
emailService.sendEmail(email, null, null, "[OnBoard]忘记密码", text, null);
}
@Override
public boolean resetPassword(int uid, String password, String token) {
if (!repository.authenticateToken(TokenType.FORGET_PASSWORD, uid, token)) {
return false;
}
User user = new User(uid);
String salt = userMapper.selectByPrimaryKey(user.getId()).getCreated().toString();
user.setNewPassword(PasswordUtils.createPassword(password, salt));
userMapper.updateByPrimaryKeySelective(user);
repository.delToken(TokenType.FORGET_PASSWORD, uid);
return true;
}
@Override
public User getById(int id) {
User origin = userMapper.selectByPrimaryKey(id);
if (origin == null) {
return null;
}
User user = new User(origin);
user.setPassword(null);
user.setNewPassword(null);
return user;
}
/**
* 存在n+1问题
*/
@Override
public List<User> getUserByProjectId(int projectId) {
UserProject sample = new UserProject();
sample.setProjectId(projectId);
UserProjectExample example = new UserProjectExample(sample);
List<UserProject> userProjectList = userProjectMapper.selectByExample(example);
Function<UserProject, User> mapping = new Function<UserProject, User>() {
@Override
public User apply(UserProject input) {
return getById(input.getUserId());
}
};
return Lists.transform(userProjectList, mapping);
}
@Override
public Map<Department, List<User>> getDepartmentedUserByCompanyId(Integer companyId) {
Department sample = new Department();
sample.setCompanyId(companyId);
DepartmentExample example = new DepartmentExample(sample);
List<Department> groups = departmentMapper.selectByExample(example);
Map<Department, List<User>> map = new TreeMap<Department, List<User>>(new Comparator<Department>() {
@Override
public int compare(Department o1, Department o2) {
if (o1.getCustomOrder() == null || o2.getCustomOrder() == null) {
return o1.getId().compareTo(o2.getId());
} else {
return o1.getCustomOrder().compareTo(o2.getCustomOrder());
}
}
});
for (Department g : groups) {
if (!map.containsKey(g)) {
map.put(g, getUserByCompanyIdByDepartmentId(g.getId(), companyId));
}
}
return map;
}
@Override
public List<User> getUnDepartmentedUsersByCompanyId(Integer companyId) {
UserCompanyExample example = new UserCompanyExample();
example.or().andCompanyIdEqualTo(companyId).andGroupIdIsNull();
List<UserCompany> userCompanies = userCompanyMapper.selectByExample(example);
List<User> unDepartmentedUsers = new ArrayList<User>();
for (UserCompany userCompany : userCompanies) {
unDepartmentedUsers.add(this.getById(userCompany.getUserId()));
}
return unDepartmentedUsers;
}
@Override
public User getUserByEmail(String email) {
User sample = new User();
sample.setEmail(email);
UserExample example = new UserExample(sample);
List<User> users = userMapper.selectByExample(example);
if (users.isEmpty()) {
return null;
}
User user = new User(users.get(0));
user.setPassword(null);
user.setNewPassword(null);
return user;
}
@Override
public void updateUser(User user, byte[] avatar, String filename) {
Preconditions.checkNotNull(user);
if (avatar != null && avatar.length > 0) {
String path = Joiner.on("/").join("/avatar", user.getId(), filename);
fileService.writeFile(path, avatar);
user.setAvatar(path);
}
if (Strings.isNullOrEmpty(user.getNewPassword())) {
user.setNewPassword(null);
} else {
String salt = userMapper.selectByPrimaryKey(user.getId()).getCreated().toString();
user.setNewPassword(PasswordUtils.createPassword(user.getNewPassword(), salt));
}
user.setUpdated(new Date());
userMapper.updateByPrimaryKeySelective(user);
}
@Override
public void sendConfirmationEmail(User user) {
if (user.getActivated()) {
return;
}
String token = repository.addToken(TokenType.CONFIRMATION, user.getId(), configurer.getTokenExpired());
Map<String, Object> model = new HashMap<String, Object>();
model.put("host", configurer.getProtocol() + configurer.getHost());
model.put("user", user);
model.put("token", token);
String text = this.templateEngineService.process(getClass(), CONFIRMAION_TPL, model);
emailService.sendEmail(user.getEmail(), null, null, "[OnBoard]注册确认", text, null);
}
@Override
public boolean confirmRegisteredUser(int uid, String token) {
boolean result = repository.authenticateToken(TokenType.CONFIRMATION, uid, token);
if (result) {
User user = new User(uid);
user.setActivated(true);
userMapper.updateByPrimaryKeySelective(user);
repository.delToken(TokenType.CONFIRMATION, uid);
}
return result;
}
@Override
public List<User> getUserByCompanyId(int companyId) {
// TODO 存在N+1问题:即先查一次UserCompany表然后再查N次User表,这样对效率有影响
UserCompany sample = new UserCompany();
sample.setCompanyId(companyId);
UserCompanyExample example = new UserCompanyExample(sample);
List<UserCompany> userCompanyList = userCompanyMapper.selectByExample(example);
List<User> users = Lists.newArrayList();
for (UserCompany userCompany : userCompanyList) {
users.add(getById(userCompany.getUserId()));
}
return users;
}
/**
* 获取一个分组内所有的用户
* @param groupId
* @return
*/
@Override
// TODO 函数名顺序和参数顺序不一致
public List<User> getUserByCompanyIdByDepartmentId(int groupId, int companyId) {
UserCompany sample = new UserCompany();
sample.setDepartmentId(groupId);
sample.setCompanyId(companyId);
UserCompanyExample example = new UserCompanyExample(sample);
List<UserCompany> userCompanyList = userCompanyMapper.selectByExample(example);
List<User> users = Lists.newArrayList();
for (UserCompany userCompany : userCompanyList) {
users.add(getById(userCompany.getUserId()));
}
return users;
}
@Override
public Map<Integer, List<User>> getAllProjectUsersInCompany(int companyId) {
Project sample = new Project(false);
sample.setArchived(false);
sample.setCompanyId(companyId);
ProjectExample example = new ProjectExample(sample);
List<Project> projects = projectMapper.selectByExample(example);
Map<Integer, List<User>> map = new HashMap<Integer, List<User>>();
for (Project project : projects) {
map.put(project.getId(), getUserByProjectId(project.getId()));
}
return map;
}
@Override
public boolean isUserInCompany(int userId, int companyId) {
UserCompany sample = new UserCompany();
sample.setUserId(userId);
sample.setCompanyId(companyId);
return userCompanyMapper.countByExample(new UserCompanyExample(sample)) > 0;
}
@Override
public boolean isUserInProject(int userId, int companyId, int projectId) {
UserProject sample = new UserProject();
sample.setUserId(userId);
sample.setCompanyId(companyId);
sample.setProjectId(projectId);
return userProjectMapper.countByExample(new UserProjectExample(sample)) > 0;
}
@Override
public User getUserByEmailOrUsername(String emailOrUsername) {
User user = getUserByEmailOrUsernameWithPassword(emailOrUsername);
if (user == null) {
return null;
}
user.setPassword(null);
user.setNewPassword(null);
return user;
}
@Override
public Boolean containUsername(String username) {
User user = new User();
user.setUsername(username);
return userMapper.countByExample(new UserExample(user)) > 0;
}
/**
* @author Chenlong to migrate password encoding schema
*/
@PostConstruct
public void updatePassword() {
UserExample example = new UserExample();
List<User> users = userMapper.selectByExample(example);
for (User user : users) {
if (user.getNewPassword() == null) {
user.setNewPassword(PasswordUtils
.updateOldEncPass(user.getPassword().toUpperCase(), user.getCreated().toString()));
userMapper.updateByPrimaryKeySelective(user);
}
}
}
@Override
public String createPassword(String password, String salt) {
return PasswordUtils.createPassword(password, salt);
}
@Override
public List<User> filterProjectMembers(List<User> users, int projectId) {
List<User> members = getUserByProjectId(projectId);
SetView<User> intersection = Sets.intersection(new HashSet<User>(users), new HashSet<User>(members));
List<User> legalUsers = Lists.newArrayList();
for (User user : intersection)
legalUsers.add(user);
return legalUsers;
}
@Override
protected BaseMapper<User, UserExample> getBaseMapper() {
return userMapper;
}
@Override
public User newItem() {
return new User();
}
@Override
public UserExample newExample() {
return new UserExample();
}
@Override
public UserExample newExample(User item) {
return new UserExample(item);
}
}