/*
* Copyright 2011-2015 Eric F. Savage, code@efsavage.com
*
* 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.ajah.user.data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ajah.crypto.CryptoException;
import com.ajah.crypto.Password;
import com.ajah.spring.jdbc.DataOperationResult;
import com.ajah.spring.jdbc.criteria.Order;
import com.ajah.spring.jdbc.err.DataOperationException;
import com.ajah.user.AuthenticationFailureException;
import com.ajah.user.User;
import com.ajah.user.UserId;
import com.ajah.user.UserImpl;
import com.ajah.user.UserNotFoundException;
import com.ajah.user.UserStatus;
import com.ajah.user.UserStatusReason;
import com.ajah.user.UserType;
import com.ajah.user.audit.UserAuditField;
import com.ajah.user.audit.UserAuditType;
import com.ajah.user.audit.data.UserAuditManager;
import com.ajah.user.email.Email;
import com.ajah.user.email.EmailStatus;
import com.ajah.user.email.EmailType;
import com.ajah.user.email.data.EmailManager;
import com.ajah.user.info.UserInfo;
import com.ajah.user.info.UserInfoImpl;
import com.ajah.user.info.UserSourceId;
import com.ajah.util.AjahUtils;
import com.ajah.util.Validate;
import com.ajah.util.data.Month;
import com.ajah.util.data.format.EmailAddress;
import lombok.extern.java.Log;
/**
* Persistence manager for Users.
*
* @author Eric F. Savage <code@efsavage.com>
*
*/
@Service
@Log
public class UserManager {
@Autowired
private UserDao userDao;
@Autowired
private UserAuditManager userAuditManager;
@Autowired
private UserInfoDao userInfoDao;
@Autowired
EmailManager emailManager;
/**
* Activates a user for the specified reason.
*
* @param user
* The user to deactivate.
* @param statusReason
* The reason for deactivation.
* @param type
* @param ip
* @param headers
* @throws DataOperationException
* If a query could not be executed.
* @throws UserNotFoundException
* If the specified user was not found.
*/
public void activate(final User user, final UserId staffUserId, final UserStatusReason statusReason, final UserAuditType type, String userComment, String staffComment, String ip, String headers)
throws DataOperationException, UserNotFoundException {
final UserStatus oldStatus = this.userDao.getStatus(user.getId());
user.setStatus(UserStatus.ACTIVE);
user.setStatusReason(statusReason);
this.userAuditManager.create(user.getId(), staffUserId, UserAuditField.STATUS, null, oldStatus.getId(), UserStatus.ACTIVE.getId(), type, userComment, staffComment, ip, headers);
this.userDao.update(user);
}
/**
* Blocks a user for the specified reason.
*
* @param user
* The user to block.
* @param statusReason
* The reason for blocking.
* @param type
* @param ip
* @param headers
* @throws DataOperationException
* If a query could not be executed.
* @throws UserNotFoundException
* If the specified user was not found.
*/
public void block(final User user, final UserId staffUserId, final UserStatusReason statusReason, final UserAuditType type, String userComment, String staffComment, String ip, String headers)
throws DataOperationException, UserNotFoundException {
final UserStatus oldStatus = this.userDao.getStatus(user.getId());
user.setStatus(UserStatus.BLOCKED);
user.setStatusReason(statusReason);
this.userAuditManager.create(user.getId(), staffUserId, UserAuditField.STATUS, null, oldStatus.getId(), UserStatus.BLOCKED.getId(), type, userComment, staffComment, ip, headers);
this.userDao.update(user);
}
/**
* Changes a user's password.
*
* @param userId
* The ID of the user to update.
* @param password
* The new password.
* @param type
* The type of change this is.
* @param ip
* @param headers
* @throws DataOperationException
* If the query could not be executed.
* @throws CryptoException
* If there was a problem hashing the password.
*/
public void changePassword(final UserId userId, final UserId staffUserId, final Password password, final UserAuditType type, String userComment, String staffComment, String ip, String headers)
throws DataOperationException, CryptoException {
final Password oldPassword = this.userDao.getPassword(userId);
this.userDao.updatePassword(userId, password);
this.userAuditManager.create(userId, staffUserId, UserAuditField.PASSWORD, password.getClass().getSimpleName(), oldPassword.toString(), password.toString(), type, userComment, staffComment,
ip, headers);
}
/**
* Changes a user's username.
*
* @param userId
* The ID of the user to update.
* @param username
* The new username.
* @param type
* The type of change this is.
* @param ip
* @param headers
* @throws DataOperationException
* If the query could not be executed.
*/
public void changeUsername(final UserId userId, final UserId staffUserId, final String username, final UserAuditType type, String userComment, String staffComment, String ip, String headers)
throws DataOperationException {
final String oldUsername = this.userDao.getUsername(userId);
this.userDao.updateUsername(userId, username);
this.userAuditManager.create(userId, staffUserId, UserAuditField.USERNAME, null, oldUsername, username, type, userComment, staffComment, ip, headers);
}
/**
* Returns a count of all records.
*
* @return Count of all records.
* @throws DataOperationException
* If the query could not be executed.
*/
public int count() throws DataOperationException {
return count(null, null);
}
/**
* Counts the records available that match the criteria.
*
* @param type
* The user type to limit to, optional.
* @param status
* The status to limit to, optional.
* @return The number of matching records.
* @throws DataOperationException
* If the query could not be executed.
*/
public int count(final UserType type, final UserStatus status) throws DataOperationException {
return this.userDao.count(type, status);
}
/**
* Creates a new user.
*
* @param username
* The username, used to login. May be the same as email address
* for some applications.
* @param emailAddress
* Email address, will be used as provisional username, required.
* @param password
* Password, required.
* @param ip
* IP of the signup
* @param source
* source of the signup
* @param type
* Type of user to create, required.
* @return New user, if save was successful.
* @throws DataOperationException
* If the queries could not be completed.
*/
public User createUser(final String username, final EmailAddress emailAddress, final Password password, final String ip, final UserSourceId source, final UserType type)
throws DataOperationException {
final User user = new UserImpl();
user.setId(new UserId(UUID.randomUUID().toString()));
user.setUsername(username);
user.setStatus(UserStatus.NEW);
user.setType(type);
this.userDao.insert(user, password);
final Email email = this.emailManager.create(user.getId(), emailAddress.toString(), EmailType.STANDARD, EmailStatus.ACTIVE).getEntity();
final UserInfo userInfo = new UserInfoImpl(user.getId());
userInfo.setPrimaryEmailId(email.getId());
userInfo.setCreated(new Date());
this.userInfoDao.insert(userInfo);
return user;
}
/**
* Deactivates a user for the specified reason.
*
* @param user
* The user to deactivate.
* @param statusReason
* The reason for deactivation.
* @param type
* @throws DataOperationException
* If a query could not be executed.
* @throws UserNotFoundException
* If the specified user was not found.
*/
public void deactivate(final User user, final UserId staffUserId, final UserStatusReason statusReason, final UserAuditType type, String userComment, String staffComment, String ip, String headers)
throws DataOperationException, UserNotFoundException {
final UserStatus oldStatus = this.userDao.getStatus(user.getId());
user.setStatus(UserStatus.INACTIVE);
user.setStatusReason(statusReason);
this.userDao.update(user);
log.info("User " + user.getUsername() + " is now status " + user.getStatus() + " (" + user.getStatusReason() + ")");
this.userAuditManager.create(user.getId(), staffUserId, UserAuditField.STATUS, null, oldStatus.getId(), UserStatus.INACTIVE.getId(), type, userComment, staffComment, ip, headers);
this.userDao.update(user);
}
/**
* Blocks a user for the specified reason.
*
* @param user
* The user to disable.
* @param statusReason
* The reason for disabling.
* @throws DataOperationException
* If a query could not be executed.
* @throws UserNotFoundException
* If the specified user was not found.
*/
public void disable(final User user, final UserId staffUserId, final UserStatusReason statusReason, final UserAuditType type, String userComment, String staffComment, String ip, String headers)
throws DataOperationException, UserNotFoundException {
final UserStatus oldStatus = this.userDao.getStatus(user.getId());
user.setStatus(UserStatus.DISABLED);
user.setStatusReason(statusReason);
this.userAuditManager.create(user.getId(), staffUserId, UserAuditField.STATUS, null, oldStatus.getId(), UserStatus.DISABLED.getId(), type, userComment, staffComment, ip, headers);
this.userDao.update(user);
}
/**
* Finds a user by email.
*
* @param emailAddress
* The email address, as entered by user.
* @return User, if found.
* @throws UserNotFoundException
* If user is not found.
* @throws DataOperationException
* If the query could not be executed.
*/
public User findUserByEmail(final EmailAddress emailAddress) throws UserNotFoundException, DataOperationException {
final Email email = this.emailManager.find(emailAddress);
if (email != null) {
log.fine("Found email " + email.getAddress());
final User user = this.userDao.load(email.getUserId());
if (user != null) {
log.fine("Found user by email: " + user.getUsername());
return user;
}
}
throw new UserNotFoundException(emailAddress);
}
/**
* Finds a user by username.
*
* @param username
* Username as entered by user.
* @return User, if found.
* @throws UserNotFoundException
* If user is not found.
* @throws DataOperationException
* If the query could not be executed.
*/
public User findUserByUsername(final String username) throws UserNotFoundException, DataOperationException {
final User user = this.userDao.findByUsername(username);
if (user != null) {
log.fine("Found user by username: " + user.getUsername());
return user;
}
throw new UserNotFoundException(username);
}
/**
* Finds a user by username or email. Tries by email if the parameter is a
* valid address, otherwise tries by username.
*
* @param usernameOrEmail
* Username or email, as entered by user.
* @return User, if found.
* @throws UserNotFoundException
* If user is not found.
* @throws DataOperationException
* If the query could not be executed.
*/
public User findUserByUsernameOrEmail(final String usernameOrEmail) throws UserNotFoundException, DataOperationException {
if (Validate.isEmail(usernameOrEmail)) {
return findUserByEmail(new EmailAddress(usernameOrEmail));
}
return findUserByUsername(usernameOrEmail);
}
/**
* Returns a random active.
*
* @return A random user, may be null if no users satisfy the criteria.
* @throws DataOperationException
* If the query could not be executed.
*/
public User getRandomUser() throws DataOperationException {
return this.userDao.getRandomUser(UserStatus.ACTIVE);
}
/**
* Attempt to find a user and authenticate.
*
* @param username
* A valid username for a user.
* @param password
* The user's password, unencrypted.
* @return User if found and authenticated correctly, will never return
* null.
* @throws AuthenticationFailureException
* If the password was not correct
* @throws UserNotFoundException
* If no user could be found for the username supplied
* @throws DataOperationException
* If the query could not be executed.
*/
public User getUser(final String username, final Password password) throws AuthenticationFailureException, UserNotFoundException, DataOperationException {
AjahUtils.requireParam(username, "username");
final User user = this.userDao.findByUsernameAndPassword(username, password.toString());
if (user != null) {
log.fine("getUser successful");
return user;
}
log.fine("getUser failed");
throw new AuthenticationFailureException(username + " authentication failed");
}
/**
* Attempt to find a user and authenticate.
*
* @param userId
* The ID of the user to attempt to fetch.
* @param password
* The user's password, unencrypted.
* @return User if found and authenticated correctly, will never return
* null.
* @throws AuthenticationFailureException
* If the password was not correct
* @throws UserNotFoundException
* If no user could be found for the username supplied
* @throws DataOperationException
* If the query could not be executed.
*/
public User getUser(final UserId userId, final Password password) throws AuthenticationFailureException, UserNotFoundException, DataOperationException {
AjahUtils.requireParam(userId, "userId");
final User user = this.userDao.findByUserIdAndPassword(userId, password.toString());
if (user != null) {
log.fine("getUser successful");
return user;
}
log.fine("getUser failed");
throw new AuthenticationFailureException(userId + " authentication failed");
}
/**
* Find a user info object if possible, otherwise create one.
*
* @param userId
* User ID of the user that we want the info about. *
* @return UserInfo from the database if possible, otherwise a new/empty one
* will be returned with the UserId set.
* @throws DataOperationException
*/
public UserInfo getUserInfo(final UserId userId) throws DataOperationException {
final UserInfo userInfo = this.userInfoDao.load(userId);
if (userInfo != null) {
return userInfo;
}
return new UserInfoImpl(userId);
}
public List<User> list(final int page, final int count) throws DataOperationException {
return this.userDao.list("username", Order.ASC, page, count);
}
public List<User> list(final String username, final String firstName, final String lastName, final UserStatus status, final String sort, final Order order, final int page, final int count)
throws DataOperationException {
return this.userDao.list(username, firstName, lastName, status, sort, order, page, count);
}
public List<User> load(final List<UserId> userIds) throws UserNotFoundException, DataOperationException {
// TODO Optimize this to a single query?
final List<User> users = new ArrayList<User>();
for (final UserId userId : userIds) {
users.add(load(userId));
}
return users;
}
/**
* Loads a user by unique ID.
*
* @param userId
* The ID of the user, required.
* @return The matching user, will not be null.
* @throws UserNotFoundException
* If the user could not be found.
* @throws DataOperationException
* If the query could not be executed.
*/
public User load(final UserId userId) throws UserNotFoundException, DataOperationException {
final User user = this.userDao.load(userId);
if (user == null) {
throw new UserNotFoundException(userId);
}
return user;
}
public DataOperationResult<UserInfo> save(final UserInfo userInfo) throws DataOperationException {
if (userInfo.getCreated() == null) {
userInfo.setCreated(new Date());
}
final DataOperationResult<UserInfo> result = this.userInfoDao.update(userInfo);
return result;
}
public int searchCount(final String username, final String firstName, final String lastName, final UserStatus status) throws DataOperationException {
return this.userDao.searchCount(username, firstName, lastName, status);
}
/**
* Is this username already in use?
*
* @param username
* @return true if the username exists, otherwise false.
* @throws DataOperationException
* If the query could not be executed.
*/
public boolean usernameExists(final String username) throws DataOperationException {
return this.userDao.findByUsername(username) != null;
}
/**
* Converts a user to a test user. Only users that are
* {@link UserType#NORMAL} can be converted, other types must be converted
* to normal first.
*
* @param user
* The user to convert.
* @param type
* The type of audit to record.
* @throws DataOperationException
* If the query could not be executed.
*/
public void convertToTest(User user, final UserId staffUserId, UserAuditType type, String userComment, String staffComment, String ip, String headers) throws DataOperationException {
if (user.getType() == UserType.NORMAL) {
this.userAuditManager.create(user.getId(), staffUserId, UserAuditField.TYPE, null, user.getType().getId(), UserType.TEST.getId(), type, userComment, staffComment, ip, headers);
user.setType(UserType.TEST);
this.userDao.update(user);
}
}
public List<UserInfo> listBySource(String source, int page, int count) throws DataOperationException {
return this.userInfoDao.listBySource(source, page, count);
}
public List<UserInfo> list(String firstName, String lastName, Integer birthYear, Month birthMonth, Integer birthDay) throws DataOperationException {
return this.userInfoDao.list(firstName, lastName, birthYear, birthMonth, birthDay);
}
}