/* Jug Management is a web application conceived to manage user groups or
* communities focused on a certain domain of knowledge, whose members are
* constantly sharing information and participating in social and educational
* events. Copyright (C) 2011 Ceara Java User Group - CEJUG.
*
* This application is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* This application 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 Lesser General Public
* License for more details.
*
* There is a full copy of the GNU Lesser General Public License along with
* this library. Look for the file license.txt at the root level. If you do not
* find it, write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307 USA.
* */
package org.cejug.yougi.business;
import org.cejug.yougi.entity.City;
import org.cejug.yougi.entity.Province;
import org.cejug.yougi.entity.ApplicationProperty;
import org.cejug.yougi.entity.AccessGroup;
import org.cejug.yougi.entity.UserGroup;
import org.cejug.yougi.entity.Authentication;
import org.cejug.yougi.entity.Properties;
import org.cejug.yougi.entity.Country;
import org.cejug.yougi.entity.DeactivationType;
import org.cejug.yougi.entity.UserAccount;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.*;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import org.cejug.yougi.exception.BusinessLogicException;
import org.cejug.yougi.knowledge.business.SubscriptionBsn;
import org.cejug.yougi.util.EntitySupport;
/**
* @author Hildeberto Mendonca - http://www.hildeberto.com
*/
@Stateless
@LocalBean
public class UserAccountBsn {
@EJB
private AccessGroupBsn accessGroupBsn;
@EJB
private UserGroupBsn userGroupBsn;
@EJB
private LocationBsn locationBsn;
@EJB
private MessengerBean messengerBean;
@EJB
private ApplicationPropertyBsn applicationPropertyBsn;
@EJB
private SubscriptionBsn subscriptionBsn;
@PersistenceContext
private EntityManager em;
static final Logger logger = Logger.getLogger("org.cejug.business.UserAccountBsn");
/**
* Checks whether an user account exists.
* @param username the username that unically identify users.
* @return true if the account already exists.
*/
public boolean existingAccount(String username) {
if(username == null || username.isEmpty()) {
throw new BusinessLogicException("It is not possible to check if the account exists because the username was not informed.");
}
UserAccount existing = findUserAccountByUsername(username);
return existing != null;
}
/**
* @return true if there is no account registered in the database.
* */
public boolean thereIsNoAccount() {
Long totalUserAccounts = (Long)em.createQuery("select count(u) from UserAccount u").getSingleResult();
if(totalUserAccounts == 0) {
return true;
}
else {
return false;
}
}
public UserAccount findUserAccount(String id) {
return em.find(UserAccount.class, id);
}
/**
* Check if the username has authentication data related to it. If there is
* no authentication data, then the user is considered as non-existing, even
* if an user account exists.
*/
public UserAccount findUserAccountByUsername(String username) {
try {
return (UserAccount) em.createQuery("select a.userAccount from Authentication a where a.username = :username")
.setParameter("username", username)
.getSingleResult();
}
catch(NoResultException nre) {
return null;
}
}
public UserAccount findUserAccountByEmail(String email) {
try {
return (UserAccount) em.createQuery("select ua from UserAccount ua where ua.email = :email")
.setParameter("email", email)
.getSingleResult();
}
catch(NoResultException nre) {
return null;
}
}
public UserAccount findUserAccountByConfirmationCode(String confirmationCode) {
try {
return (UserAccount) em.createQuery("select ua from UserAccount ua where ua.confirmationCode = :confirmationCode")
.setParameter("confirmationCode", confirmationCode)
.getSingleResult();
}
catch(NoResultException nre) {
return null;
}
}
/**
* @return All activated user accounts ordered by name.
*/
public List<UserAccount> findUserAccounts() {
return em.createQuery("select ua from UserAccount ua where ua.deactivated = :deactivated and ua.confirmationCode is null order by ua.firstName")
.setParameter("deactivated", Boolean.FALSE)
.getResultList();
}
/**
* @return All users that informed their websites.
*/
public List<UserAccount> findUserAccountsWithWebsite() {
return em.createQuery("select ua from UserAccount ua where ua.deactivated = false and ua.confirmationCode is null and ua.website is not null order by ua.firstName")
.getResultList();
}
/**
* Returns user accounts ordered by registration date and in which the
* registration date is between the informed period of time.
*/
@SuppressWarnings("unchecked")
public List<UserAccount> findConfirmedUserAccounts(Date from, Date to) {
return em.createQuery("select ua from UserAccount ua where ua.confirmationCode is null and ua.registrationDate >= :from and ua.registrationDate <= :to order by ua.registrationDate asc")
.setParameter("from", from)
.setParameter("to", to)
.getResultList();
}
@SuppressWarnings("unchecked")
public List<UserAccount> findNotVerifiedUsers() {
return em.createQuery("select ua from UserAccount ua where ua.verified = :verified and ua.deactivated = :deactivated order by ua.registrationDate desc")
.setParameter("verified", Boolean.FALSE)
.setParameter("deactivated", Boolean.FALSE)
.getResultList();
}
@SuppressWarnings("unchecked")
public List<UserAccount> findUserAccountsStartingWith(String firstLetter) {
return em.createQuery("select ua from UserAccount ua where ua.firstName like '"+ firstLetter +"%' and ua.deactivated = :deactivated order by ua.firstName")
.setParameter("deactivated", Boolean.FALSE)
.getResultList();
}
/**
* @return the list of deactivated user accounts that were deactivated by
* their own will or administratively.
*/
public List<UserAccount> findDeactivatedUserAccounts() {
return em.createQuery("select ua from UserAccount ua where ua.deactivated = :deactivated and ua.deactivationType <> :type order by ua.deactivationDate desc")
.setParameter("deactivated", Boolean.TRUE)
.setParameter("type", DeactivationType.UNREGISTERED)
.getResultList();
}
/**
* Find a user account that was previously deactivated or not activated yet.
* @param email The email address of the user.
* @return the user account of an unregistered user.
*/
public UserAccount findDeactivatedUserAccount(String email) {
try {
return (UserAccount) em.createQuery("select ua from UserAccount ua where ua.email = :email and ua.deactivationType is not null")
.setParameter("email", email)
.getSingleResult();
}
catch(NoResultException nre) {
return null;
}
}
/**
* Returns all users related to the informed city, independent of their
* confirmation, validation or deactivation status.
*/
@SuppressWarnings("unchecked")
public List<UserAccount> findInhabitantsFrom(City city) {
return em.createQuery("select u from UserAccount u where u.city = :city order by u.firstName")
.setParameter("city", city)
.getResultList();
}
/**
* @param userAccount the user who has authentication credentials registered.
* @return the user's authentication data.
*/
public Authentication findAuthenticationUser(UserAccount userAccount) {
try {
return (Authentication) em.createQuery("select a from Authentication a where a.userAccount = :userAccount")
.setParameter("userAccount", userAccount)
.getSingleResult();
}
catch(NoResultException nre) {
return null;
}
}
/**
* @param userAccount the id of the user who has authentication credentials registered.
* @return the user's authentication data.
*/
public Authentication findAuthenticationUser(String userAccount) {
try {
return (Authentication) em.createQuery("select a from Authentication a where a.userAccount.id = :userAccount")
.setParameter("userAccount", userAccount)
.getSingleResult();
}
catch(NoResultException nre) {
return null;
}
}
/** <p>Register new user accounts. For the moment, this is the only way an
* user account can be created. In the moment of the registration, data,
* such as country, city, website, etc., are saved as a contact record. This
* contact record is related to the new user and it is set as his/her main
* contact. In case the user inform a city that does not exist, then his/her
* suggestion is registered, but checked as not valid. The server address is
* informed just because it can only be detected automatically on the web
* container.</p>
* <p>When there is no user, the first registration creates a super user
* with administrative rights.</p> */
public void register(UserAccount newUserAccount, Authentication authentication, City newCity) {
// true if there is no account registered so far.
boolean noAccount = thereIsNoAccount();
/* In case there is at least one account, it checks if the current
* registration has a corresponding account that was deactivated before.
* If there is then the current registration updates the existing
* account. Otherwise, a new account is created. */
UserAccount userAccount = null;
boolean existingAccount = false;
if(!noAccount) {
userAccount = findDeactivatedUserAccount(newUserAccount.getUnverifiedEmail());
if(userAccount != null) {
existingAccount = true;
userAccount.setUnverifiedEmail(newUserAccount.getUnverifiedEmail());
userAccount.setFirstName(newUserAccount.getFirstName());
userAccount.setLastName(newUserAccount.getLastName());
userAccount.setGender(newUserAccount.getGender());
userAccount.setBirthDate(newUserAccount.getBirthDate());
userAccount.setWebsite(newUserAccount.getWebsite());
userAccount.setTwitter(newUserAccount.getTwitter());
userAccount.setPostalCode(newUserAccount.getPostalCode());
userAccount.setMailingList(newUserAccount.getMailingList());
userAccount.setPublicProfile(newUserAccount.getPublicProfile());
userAccount.setEvent(newUserAccount.getEvent());
userAccount.setNews(newUserAccount.getNews());
userAccount.setGeneralOffer(newUserAccount.getGeneralOffer());
userAccount.setJobOffer(newUserAccount.getJobOffer());
userAccount.setSponsor(newUserAccount.getSponsor());
userAccount.setSpeaker(newUserAccount.getSpeaker());
userAccount.setDeactivated(false);
userAccount.setDeactivationDate(null);
userAccount.setDeactivationReason(null);
userAccount.setDeactivationType(null);
userAccount.setVerified(false);
Country country = newUserAccount.getCountry();
country = em.merge(country);
userAccount.setCountry(country);
Province province = newUserAccount.getProvince();
if(province != null) {
province = em.merge(province);
}
userAccount.setProvince(province);
City city = newUserAccount.getCity();
if(city != null) {
city = em.merge(city);
}
userAccount.setCity(city);
}
}
if(userAccount == null) {
userAccount = newUserAccount;
}
ApplicationProperty timeZone = applicationPropertyBsn.findApplicationProperty(Properties.TIMEZONE);
// A potential new city was informed.
if(newCity != null) {
// Check if the informed city already exists.
City existingCity = locationBsn.findCityByName(newCity.getName());
// If the city exists it simply set the property of the user account.
if(existingCity != null) {
userAccount.setCity(existingCity);
if(existingCity.getTimeZone() != null) {
userAccount.setTimeZone(existingCity.getTimeZone());
}
else {
userAccount.setTimeZone(timeZone.getPropertyValue());
}
}
else { // If the city does not exist it is created and used to set the property of the user account.
newCity.setTimeZone(timeZone.getPropertyValue());
newCity.setCountry(userAccount.getCountry());
newCity.setProvince(userAccount.getProvince());
locationBsn.saveCity(newCity);
userAccount.setCity(newCity);
userAccount.setTimeZone(newCity.getTimeZone());
}
}
/* If no new city was informed, it just takes the selected one to set
* timezone of the user account. */
else {
if(userAccount.getCity() != null && userAccount.getCity().getTimeZone() != null) {
userAccount.setTimeZone(userAccount.getCity().getTimeZone());
}
else {
userAccount.setTimeZone(timeZone.getPropertyValue());
}
}
userAccount.defineNewConfirmationCode();
userAccount.setRegistrationDate(Calendar.getInstance().getTime());
if(!existingAccount) {
userAccount.setId(EntitySupport.INSTANCE.generateEntityId());
em.persist(userAccount);
}
authentication.setUserAccount(userAccount);
em.persist(authentication);
// In case there is no account, the user is added to the administrative group.
if(noAccount) {
userAccount.resetConfirmationCode();
AccessGroup adminGroup = accessGroupBsn.findAdministrativeGroup();
UserGroup userGroup = new UserGroup(adminGroup, authentication);
userGroupBsn.add(userGroup);
}
else {
ApplicationProperty appProp = applicationPropertyBsn.findApplicationProperty(Properties.SEND_EMAILS);
if(appProp.sendEmailsEnabled()) {
ApplicationProperty url = applicationPropertyBsn.findApplicationProperty(Properties.URL);
messengerBean.sendEmailConfirmationRequest(userAccount, url.getPropertyValue());
}
}
}
/**
* Finds the user account using the confirmation code, adds this user
* account in the default group, sends a welcome message to the user and a
* notification message to the leaders. The user has access to the
* application when he/she is added to the default group.
* @return The confirmed user account.
* */
public UserAccount confirmUser(String confirmationCode) {
if(confirmationCode == null || confirmationCode.isEmpty()) {
return null;
}
try {
UserAccount userAccount = (UserAccount)em.createQuery("select ua from UserAccount ua where ua.confirmationCode = :code")
.setParameter("code", confirmationCode)
.getSingleResult();
if(userAccount != null) {
userAccount.resetConfirmationCode();
userAccount.setEmail(userAccount.getUnverifiedEmail());
userAccount.setUnverifiedEmail(null);
userAccount.setRegistrationDate(Calendar.getInstance().getTime());
// This step effectively allows the user to access the application.
AccessGroup defaultGroup = accessGroupBsn.findUserDefaultGroup();
Authentication authentication = findAuthenticationUser(userAccount);
UserGroup userGroup = new UserGroup(defaultGroup, authentication);
userGroupBsn.add(userGroup);
ApplicationProperty appProp = applicationPropertyBsn.findApplicationProperty(Properties.SEND_EMAILS);
if(appProp.sendEmailsEnabled()) {
messengerBean.sendWelcomeMessage(userAccount);
AccessGroup administrativeGroup = accessGroupBsn.findAdministrativeGroup();
List<UserAccount> leaders = userGroupBsn.findUsersGroup(administrativeGroup);
messengerBean.sendNewMemberAlertMessage(userAccount, leaders);
}
}
return userAccount;
}
catch(NoResultException nre) {
return null;
}
}
public void save(UserAccount userAccount) {
userAccount.setLastUpdate(Calendar.getInstance().getTime());
em.merge(userAccount);
}
public void deactivateMembership(UserAccount userAccount, DeactivationType deactivationType) {
UserAccount existingUserAccount = findUserAccount(userAccount.getId());
existingUserAccount.setDeactivated(Boolean.TRUE);
existingUserAccount.setDeactivationDate(Calendar.getInstance().getTime());
existingUserAccount.setDeactivationReason(userAccount.getDeactivationReason());
existingUserAccount.setDeactivationType(deactivationType);
save(existingUserAccount);
userGroupBsn.removeUserFromAllGroups(existingUserAccount);
removeUserAuthentication(existingUserAccount);
ApplicationProperty appProp = applicationPropertyBsn.findApplicationProperty(Properties.SEND_EMAILS);
if(!existingUserAccount.getDeactivationReason().trim().isEmpty() && appProp.sendEmailsEnabled()) {
messengerBean.sendDeactivationReason(existingUserAccount);
}
AccessGroup administrativeGroup = accessGroupBsn.findAdministrativeGroup();
List<UserAccount> leaders = userGroupBsn.findUsersGroup(administrativeGroup);
if(appProp.sendEmailsEnabled()) {
messengerBean.sendDeactivationAlertMessage(existingUserAccount, leaders);
}
}
public void removeUserAuthentication(UserAccount userAccount) {
em.createQuery("delete from Authentication a where a.userAccount = :userAccount")
.setParameter("userAccount", userAccount)
.executeUpdate();
}
public void requestConfirmationPasswordChange(String username, String serverAddress) {
UserAccount userAccount = findUserAccountByUsername(username);
ApplicationProperty appProp = applicationPropertyBsn.findApplicationProperty(Properties.SEND_EMAILS);
if(userAccount != null) {
userAccount.defineNewConfirmationCode();
if(appProp.sendEmailsEnabled()) {
messengerBean.sendConfirmationCode(userAccount, serverAddress);
}
}
else {
throw new PersistenceException("Usuário inexistente:"+ username);
}
}
/**
* Compares the informed password with the one stored in the database.
* @param userAccount the user account that has authentication credentials.
* @param passwordToCheck the password to be compared with the one in the database.
* @return true if the password matches.
*/
public Boolean passwordMatches(UserAccount userAccount, String passwordToCheck) {
try {
Authentication authentication = (Authentication) em.createQuery("select a from Authentication a where a.userAccount = :userAccount and a.password = :password")
.setParameter("userAccount", userAccount)
.setParameter("password", (new Authentication()).hashPassword(passwordToCheck))
.getSingleResult();
if(authentication != null) {
return Boolean.TRUE;
}
}
catch(NoResultException nre) {
return Boolean.FALSE;
}
return Boolean.FALSE;
}
/**
* @param userAccount account of the user who wants to change his password.
* @param newPassword the new password of the user.
*/
public void changePassword(UserAccount userAccount, String newPassword) {
try {
// Retrieve the user authentication where the password is saved.
Authentication authentication = (Authentication) em.createQuery("select a from Authentication a where a.userAccount = :userAccount")
.setParameter("userAccount", userAccount)
.getSingleResult();
if(authentication != null) {
authentication.setPassword(newPassword);
userAccount.resetConfirmationCode();
save(userAccount);
}
}
catch(NoResultException nre) {
throw new BusinessLogicException("User account not found. It is not possible to change the password.");
}
}
/**
* Changes the email address of the user without having to repeat the
* registration process.
* @param userAccount the user account that intends to change its email address.
* @param newEmail the new email address of the user account.
* @exception BusinessLogicException in case the newEmail is already registered.
*/
public void changeEmail(UserAccount userAccount, String newEmail) {
// Check if the new email already exists in the UserAccounts
UserAccount existingUserAccount = findUserAccountByEmail(newEmail);
if(existingUserAccount != null) {
throw new BusinessLogicException("errorCode0001");
}
// Change the email address in the UserAccount
userAccount.setUnverifiedEmail(newEmail);
em.merge(userAccount);
// Since the email address is also the username, change the username in the Authentication and in the UserGroup
userGroupBsn.changeUsername(userAccount, newEmail);
// In the MailingListSubscription, we close the subscription of the previous email address and subscribe the new one, linked to the same user account.
subscriptionBsn.changeEmailAddress(userAccount);
// Send an email to the user to confirm the new email address
ApplicationProperty url = applicationPropertyBsn.findApplicationProperty(Properties.URL);
messengerBean.sendEmailVerificationRequest(userAccount, url.getPropertyValue());
}
public void confirmEmailChange(UserAccount userAccount) {
if(userAccount.getUnverifiedEmail() == null) {
throw new BusinessLogicException("errorCode0002");
}
userAccount.resetConfirmationCode();
userAccount.setEmail(userAccount.getUnverifiedEmail());
userAccount.setUnverifiedEmail(null);
save(userAccount);
}
@Schedules({ @Schedule(hour="*/12") })
public void removeNonConfirmedAccounts(Timer timer) {
logger.log(Level.INFO, "Timer to remove non confirmed accounts started.");
Calendar twoDaysAgo = Calendar.getInstance();
twoDaysAgo.add(Calendar.DAY_OF_YEAR, -2);
Format formatter = new SimpleDateFormat("dd/MM/yyyy HH:mm");
logger.log(Level.INFO, "Non confirmed accounts older than {0} will be removed.", formatter.format(twoDaysAgo.getTime()));
int i = em.createQuery("delete from UserAccount ua where ua.registrationDate <= :twoDaysAgo and ua.confirmationCode is not null")
.setParameter("twoDaysAgo", twoDaysAgo.getTime())
.executeUpdate();
logger.log(Level.INFO, "Number of removed non confirmed accounts: {0}", i);
}
/**
* Update the time zone of all users that inhabit the informed city.
*/
public void updateTimeZoneInhabitants(City city) {
if(city.getTimeZone() != null && !city.getTimeZone().isEmpty()) {
List<UserAccount> userAccounts = findInhabitantsFrom(city);
for(UserAccount user: userAccounts) {
user.setTimeZone(city.getTimeZone());
}
}
}
public void remove(String userId) {
UserAccount userAccount = em.find(UserAccount.class, userId);
em.remove(userAccount);
}
}