/** * Copyright (C) 2011 JTalks.org Team * This library 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 library 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. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jtalks.poulpe.service.transactional; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.jtalks.common.model.entity.Component; import org.jtalks.common.model.entity.ComponentType; import org.jtalks.common.model.entity.Group; import org.jtalks.common.model.entity.User; import org.jtalks.common.security.acl.AclManager; import org.jtalks.common.security.acl.GroupAce; import org.jtalks.common.service.exceptions.NotFoundException; import org.jtalks.poulpe.model.dao.ComponentDao; import org.jtalks.poulpe.model.dao.UserDao; import org.jtalks.poulpe.model.entity.PoulpeUser; import org.jtalks.poulpe.model.logic.UserBanner; import org.jtalks.poulpe.model.logic.UserList; import org.jtalks.poulpe.model.pages.Pages; import org.jtalks.poulpe.model.pages.Pagination; import org.jtalks.poulpe.model.sorting.UserSearchRequest; import org.jtalks.poulpe.service.UserService; import org.jtalks.poulpe.service.exceptions.ValidationException; import org.springframework.transaction.interceptor.TransactionInterceptor; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; /** * User service class, contains methods needed to manipulate with {@code User} persistent entity. * * @author Guram Savinov * @author Vyacheslav Zhivaev * @author maxim reshetov * @author Mikhail Zaitsev */ public class TransactionalUserService implements UserService { private static final String NO_FILTER = ""; private final UserDao userDao; private final UserBanner userBanner; private final AclManager aclManager; private final ComponentDao componentDao; /** * Create an instance of user entity based service. * * @param userDao a DAO providing persistence operations over {@link org.jtalks.poulpe.model.entity.PoulpeUser} * entities */ public TransactionalUserService(UserDao userDao, UserBanner userBanner, AclManager aclManager, ComponentDao componentDao) { this.userDao = userDao; this.userBanner = userBanner; this.aclManager = aclManager; this.componentDao = componentDao; } /** * {@inheritDoc} */ @Override public List<PoulpeUser> getAll() { return userDao.findPoulpeUsersPaginated(NO_FILTER, Pages.NONE); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> findUsersPaginated(String searchString, int page, int itemsPerPage) { return userDao.findPoulpeUsersPaginated(searchString, Pages.paginate(page, itemsPerPage)); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> findUsersBySearchRequest(UserSearchRequest searchRequest) { return userDao.findPoulpeUsersBySearchRequest(searchRequest); } /** * {@inheritDoc} */ @Override public int countUsernameMatches(String searchString) { return userDao.countUsernameMatches(searchString); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> withUsernamesMatching(String searchString) { return userDao.findPoulpeUsersPaginated(searchString, Pages.NONE); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> findUsersNotInGroups(String availableFilterText, List<Group> groups, int page, int itemsPerPage) { return userDao.findUsersNotInGroups(availableFilterText, groups, Pages.paginate(page, itemsPerPage)); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> findUsersNotInGroups(String availableFilterText, List<Group> groups) { return userDao.findUsersNotInGroups(availableFilterText, groups, Pages.NONE); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> findUsersNotInList(String availableFilterText, List<PoulpeUser> listUsers, int page, int itemsPerPage) { return userDao.findUsersNotInList(availableFilterText, listUsers, Pages.paginate(page, itemsPerPage)); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> findUsersNotInList(String availableFilterText, List<PoulpeUser> listUsers) { return userDao.findUsersNotInList(availableFilterText, listUsers, Pages.NONE); } /** * {@inheritDoc} */ @Override public void updateUser(PoulpeUser user) { userDao.saveOrUpdate(user); } /** * {@inheritDoc} */ @Override public PoulpeUser get(long id) { return userDao.get(id); } /** * {@inheritDoc} */ @Override public PoulpeUser getByEmail(String email) { return userDao.getByEmail(email); } /** * {@inheritDoc} */ @Override public List<PoulpeUser> getAllBannedUsers() { return userBanner.getAllBannedUsers(); } /** * {@inheritDoc} */ @Override public void banUsers(PoulpeUser... usersToBan) { userBanner.banUsers(new UserList(usersToBan)); updateUsersAtGroup(new ArrayList<User>(Arrays.asList(usersToBan)), userBanner.getBannedUsersGroups().get(0)); } /** * {@inheritDoc} */ @Override public void revokeBan(PoulpeUser... bannedUsersToRevoke) { userBanner.revokeBan(new UserList(bannedUsersToRevoke)); updateUsersAtGroup(new ArrayList<User>(Arrays.asList(bannedUsersToRevoke)), userBanner.getBannedUsersGroups().get(0)); } /** * {@inheritDoc} */ @Override //TODO Rename method to 'get*' when delete creating a group 'ban users'! public List<PoulpeUser> loadNonBannedUsersByUsername(String availableFilterText, Pagination pagination) { return userBanner.getNonBannedUsersByUsername(availableFilterText, pagination); } @Override public boolean accessAllowedToComponentType(String username, ComponentType componentType) { PoulpeUser user = userDao.getByUsername(username); Component component = componentDao.getByType(componentType); if (component == null) { return false; } List<GroupAce> permissions = aclManager.getGroupPermissionsOn(component); boolean granting = false; for (GroupAce permission : permissions) { if (user.isInGroupWithId(permission.getGroupId())) { if (!permission.isGranting()) { return false; } granting = true; } } return granting; } /** * {@inheritDoc} */ @Override public void updateUsersAtGroup(List<User> users, Group group) { for(User u :users){ if(group.getUsers().contains(u)){ if(!u.getGroups().contains(group)){ u.getGroups().add(group); } }else{ if(u.getGroups().contains(group)){ u.getGroups().remove(group); } } updateUser((PoulpeUser) u); //TODO What is the performance? } } /** * {@inheritDoc} */ @Override public PoulpeUser authenticate(String username, String password) throws NotFoundException { if (StringUtils.isAnyBlank(username, password)) throw new NotFoundException(); List<PoulpeUser> users = requireNotNullNorEmpty(userDao.findUsersByUsernameAndPasswordHash(username, password)); if (users.size() == 1) return users.get(0); else return searchForUserCaseSensitive(users, username); } private PoulpeUser searchForUserCaseSensitive(List<PoulpeUser> users, String username) throws NotFoundException { for (PoulpeUser user : users) { if (user.getUsername().equals(username)) return user; } throw new NotFoundException("User wasn't found by case sensitive search during authentication, username = { "+ username +" }"); } /** * {@inheritDoc} */ @Override public void activate(String username) throws NotFoundException, ValidationException { PoulpeUser user = Validate.notNull(userDao.getByUsername(requireNotNullNorEmpty(username)), "User wasn't found during activation, username = {%s}", username); if (user.isEnabled()) throw new ValidationException(Collections.singletonList("user.already_active")); user.setEnabled(true); userDao.saveOrUpdate(user); } /** * {@inheritDoc} */ @Override public void registration(PoulpeUser user) throws ValidationException { List<String> errors = new ArrayList<String>(); if (userDao.getByUsername(user.getUsername()) != null) { errors.add(User.USER_ALREADY_EXISTS); } if (userDao.getByEmail(user.getEmail()) != null) { errors.add(User.EMAIL_ALREADY_EXISTS); } if (!errors.isEmpty()) { throw new ValidationException(errors); } try { user.setSalt(""); //because cannot be null userDao.save(user); } catch (ConstraintViolationException e){ List<String> messages = getConstraintViolationsMessages(e.getConstraintViolations()); throw new ValidationException(messages); } } /** * {@inheritDoc} */ @Override public void dryRunRegistration(PoulpeUser user) throws ValidationException { registration(user); TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); } /** * Returns list of template messages from the {@code ConstraintViolation} set * * @param violationSet the {@code ConstraintViolation} set * @return list of template messages */ private List<String> getConstraintViolationsMessages(Set<ConstraintViolation<?>> violationSet){ List<String> res = new ArrayList<String>(); for (ConstraintViolation violation : violationSet){ res.add(violation.getMessageTemplate()); } return res; } /** * Checks for null or emptiness of String and List * @param object Some object that needs to be checked * @param <T> The class of the object * @return The same object * @throws NotFoundException If object not passed validation */ private <T> T requireNotNullNorEmpty(T object) throws NotFoundException { if (object == null) throw new NotFoundException(); if (object instanceof String && ((String) object).isEmpty()) throw new NotFoundException(); if (object instanceof List && ((List) object).isEmpty()) throw new NotFoundException(); return object; } }