/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero 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 Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.jaspersoft.jasperserver.api.metadata.user.service.impl;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.util.IlikeEscapeAwareExpression;
import com.jaspersoft.jasperserver.api.metadata.common.util.DatabaseCharactersEscapeResolver;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.ui.switchuser.SwitchUserGrantedAuthority;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.*;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;

import com.jaspersoft.jasperserver.api.common.domain.ExecutionContext;
import com.jaspersoft.jasperserver.api.common.domain.impl.ExecutionContextImpl;
import com.jaspersoft.jasperserver.api.metadata.common.service.ResourceFactory;
import com.jaspersoft.jasperserver.api.metadata.common.service.impl.HibernateDaoImpl;
import com.jaspersoft.jasperserver.api.metadata.tenant.service.TenantPersistenceResolver;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Role;
import com.jaspersoft.jasperserver.api.metadata.user.domain.User;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Tenant;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.client.MetadataUserDetails;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.hibernate.RepoRole;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.hibernate.RepoTenant;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.hibernate.RepoUser;
import com.jaspersoft.jasperserver.api.metadata.user.service.ProfileAttributeService;
import com.jaspersoft.jasperserver.api.metadata.user.service.TenantService;
import com.jaspersoft.jasperserver.api.metadata.view.domain.FilterCriteria;
import com.jaspersoft.jasperserver.api.logging.audit.context.AuditContext;
import com.jaspersoft.jasperserver.api.logging.audit.domain.AuditEvent;

/**
 * @author swood
 * @version $Id: UserAuthorityServiceImpl.java 20460 2011-05-09 18:18:38Z schubar $
 */
public class UserAuthorityServiceImpl extends HibernateDaoImpl implements UserDetailsService, ExternalUserService, UserAuthorityPersistenceService {

	protected static final Log log = LogFactory.getLog(UserAuthorityServiceImpl.class);
	private ResourceFactory objectFactory;
	private ResourceFactory persistentClassFactory;
	private ProfileAttributeService profileAttributeService;
    private TenantPersistenceResolver tenantPersistenceResolver;
    private AuditContext auditContext;

	private List defaultInternalRoles;
    private DatabaseCharactersEscapeResolver databaseCharactersEscapeResolver;
    
    public void setDatabaseCharactersEscapeResolver(DatabaseCharactersEscapeResolver databaseCharactersEscapeResolver) {
        this.databaseCharactersEscapeResolver = databaseCharactersEscapeResolver;
    }

    public DatabaseCharactersEscapeResolver getDatabaseCharactersEscapeResolver() {
        return this.databaseCharactersEscapeResolver;
    }

	public ResourceFactory getObjectMappingFactory() {
		return objectFactory;
	}

	public void setObjectMappingFactory(ResourceFactory objectFactory) {
		this.objectFactory = objectFactory;
	}

	public ResourceFactory getPersistentClassFactory() {
		return persistentClassFactory;
	}

	public void setPersistentClassFactory(ResourceFactory persistentClassFactory) {
		this.persistentClassFactory = persistentClassFactory;
	}

    public ProfileAttributeService getProfileAttributeService() {
	    return profileAttributeService;
	}

    public void setProfileAttributeService(ProfileAttributeService pas) {
	    this.profileAttributeService = pas;
	}


    public void setAuditContext(AuditContext auditContext) {
        this.auditContext = auditContext;
    }

    protected RepoUser getRepoUser(ExecutionContext context, String username) {
		return getRepoUser(username, null);
	}

	protected RepoUser getRepoUser(String username, String tenantId) {
		RepoTenant tenant = getPersistentTenant(tenantId, false);
		if (tenant == null && !isNullTenant(tenantId)) {
			//if the requested tenant was not found, return null
			if (log.isDebugEnabled()) {
				log.debug("Tenant " + tenantId + " not found, returning null user");
			}
			return null;
		}
		
		DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentUserClass());
		criteria.add(Restrictions.naturalId()
				.set("username", username)
				.set("tenant", tenant));

		List userList = getHibernateTemplate().findByCriteria(criteria);
		RepoUser user = null;
		if (userList.isEmpty()) {
			log.debug("User not found with username \"" + username
					+ "\" in tenant " + tenantId);
		} else {
			user = (RepoUser) userList.get(0);
		}
		return user;
	}
	
	protected RepoUser getRepoUser(ExecutionContext context, User user) {
		return getRepoUser(user.getUsername(), user.getTenantId());
	}

	protected Class getPersistentUserClass() {
		return getPersistentClassFactory().getImplementationClass(User.class);
	}

    protected Class getPersistentTenantClass() {
        return getPersistentClassFactory().getImplementationClass(Tenant.class);
    }

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.String)
	 */
	public User getUser(ExecutionContext context, String username) {
		RepoUser user = getRepoUser(context, username);
		User userDTO = null;
		if (user != null) {
			userDTO = (User) user.toClient(getObjectMappingFactory());
			List attrs = getProfileAttributeService().
			    getProfileAttributesForPrincipal(null, user);
			userDTO.setAttributes(attrs);
		} else {
			log.debug("No such user as: " + username);
		}
		return userDTO;
	}

	protected RepoUser getRepoUser(ExecutionContext context, Long id) {
		RepoUser user = (RepoUser) getHibernateTemplate().load(getPersistentUserClass(), id);
		return user;
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.Long)
	 */
	protected User getUser(ExecutionContext context, Long id) {
		RepoUser user = getRepoUser(context, id);
		User userDTO = null;
		if (user != null) {
			userDTO = (User) user.toClient(getObjectMappingFactory());
		}
		return userDTO;
	}                                                        

	/* (non-Javadoc)
	 * @see org.springframework.security.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
		User u = getUser(null, username);

		if (u == null) {
			throw new UsernameNotFoundException("User not found with username \"" + username + "\"");
		} else {
			return makeUserDetails(u);
		}
	}

	protected MetadataUserDetails makeUserDetails(User user) {
		return new MetadataUserDetails(user);
	}

	/*
	 * 11-11-08 bob
	 * Modified to deal with bogus behavior allowed by a method signature that isn't specific enough.
	 * This fixes bug 12382.
	 * This method expects a client object (UserImpl),
	 * but its signature just says User which has both RepoUser and UserImpl (client) implementations.
	 * When you pass in a RepoUser, it does a copyFromClient() on it, which is wrong, but doesn't
	 * burn anyone most of the time.
	 * It DOES burn you when you have password encryption turned on, in which case it encrypts your already-encrypted password.
	 * Guess what, you can't log in anymore!
	 *
	 * TODO Per Sherman, if there are methods calling putUser() with a RepoUser, they need to be fixed.
	 * I looked at all the callers (about 20) and found three that do this: addRole(), removeRole(), and removeAllRoles().
	 * We should probably change the interface so it can't be called with RepoUser, but we should probably look at other API's.
	 *  (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#putUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, com.jaspersoft.jasperserver.api.metadata.user.domain.User)
	 */
	public void putUser(ExecutionContext context, User aUser) {
		RepoUser existingUser;
		if (aUser instanceof RepoUser) {
			existingUser = (RepoUser) aUser;
		} else {
			existingUser = getRepoUser(context, aUser);
			if (existingUser == null) {
				existingUser = (RepoUser) getPersistentClassFactory().newObject(User.class);
			}
			updatePersistentUser(aUser, existingUser);
		}

        addPropertiesToUserEvent(new String[] {"createUser", "updateUser"}, existingUser);
		getHibernateTemplate().saveOrUpdate(existingUser);
	}

	protected void updatePersistentUser(User user, RepoUser persistentUser) {
		persistentUser.copyFromClient(user, this);
	}

	/**
	 * return everything for now
	 *
	 *  (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getUsers(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, com.jaspersoft.jasperserver.api.metadata.view.domain.FilterCriteria)
	 */
	public List<User> getUsers(ExecutionContext context, FilterCriteria filterCriteria) {
		// make User DTOs
		List results = getHibernateTemplate().loadAll(getPersistentUserClass());
		List userDTOs = null;

		if (results != null) {
			userDTOs = new ArrayList(results.size());
			Iterator it = results.iterator();
			while (it.hasNext()) {
				RepoUser u = (RepoUser) it.next();
				User newUser = (User) u.toClient(getObjectMappingFactory());
				userDTOs.add(newUser);
			}
		}
		return userDTOs;
	}

    /**
	 * return everything for now
	 *
	 *  (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getUsersByCriteria(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, org.hibernate.criterion.DetachedCriteria)
	 */
	public List<User> getUsersByCriteria(ExecutionContext context, DetachedCriteria detachedCriteria) {
		// make User DTOs
		List results = getHibernateTemplate().findByCriteria(detachedCriteria);
		List userDTOs = null;

		if (results != null) {
			userDTOs = new ArrayList(results.size());
			Iterator it = results.iterator();
			while (it.hasNext()) {
				RepoUser u = (RepoUser) it.next();
				User newUser = (User) u.toClient(getObjectMappingFactory());
				userDTOs.add(newUser);
			}
		}
		return userDTOs;
	}

    public int getUsersCountExceptExcluded(ExecutionContext executionContext, final Set<String> excludedUserNames) {
        return (Integer) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
		        Criteria criteria = session.createCriteria(getPersistentUserClass());
                if (excludedUserNames != null && excludedUserNames.size() > 0) {
                    criteria.add(Restrictions.not(Restrictions.in("username", excludedUserNames)));
                }
                criteria.setProjection(Projections.count("id"));
                return criteria.uniqueResult();
			}
    	});
    }

	/**
	 * DTO for the User interface
	 *
	 * (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#newUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext)
	 */
	public User newUser(ExecutionContext context) {
		return (User) getObjectMappingFactory().newObject(User.class);
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#disableUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.Long)
	 */
	protected boolean disableUser(ExecutionContext context, Long id) {
		RepoUser user = getRepoUser(context, id);
		if (user != null && user.isEnabled()) {
			user.setEnabled(false);
			return true;
		} else {
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#disableUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.String)
	 */
	public boolean disableUser(ExecutionContext context, String username) {
		RepoUser user = getRepoUser(context, username);
		if (user != null && user.isEnabled()) {
			user.setEnabled(false);
			return true;
		} else {
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#enableUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.Long)
	 */
	protected boolean enableUser(ExecutionContext context, Long id) {
		RepoUser user = getRepoUser(context, id);
		if (user != null && !user.isEnabled()) {
			user.setEnabled(true);
			return true;
		} else {
			return false;
		}
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#enableUser(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.String)
	 */
	public boolean enableUser(ExecutionContext context, String username) {
		RepoUser user = getRepoUser(context, username);
		if (user != null && !user.isEnabled()) {
			user.setEnabled(true);
			return true;
		} else {
			return false;
		}
	}

    private void addPropertiesToUserEvent(final String[] auditEventTypes, final User user) {
        auditContext.doInAuditContext(auditEventTypes, new AuditContext.AuditContextCallbackWithEvent() {
            public void execute(AuditEvent auditEvent) {
                if (user != null) {
                    auditContext.addPropertyToAuditEvent("username", user.getUsername(), auditEvent);
                    auditContext.addPropertyToAuditEvent("tenantId", user.getTenantId(), auditEvent);
                    auditContext.addPropertyToAuditEvent("email", user.getEmailAddress(), auditEvent);
                    auditContext.addPropertyToAuditEvent("fullName", user.getFullName(), auditEvent);
                    auditContext.addPropertyToAuditEvent("passwordChangeTime", user.getPreviousPasswordChangeTime(), auditEvent);
                    auditContext.addPropertyToAuditEvent("enabled", user.isEnabled(), auditEvent);
                    auditContext.addPropertyToAuditEvent("externallyDefined", user.isExternallyDefined(), auditEvent);

                    List attrs = getProfileAttributeService().
                        getProfileAttributesForPrincipal(null, user);

                    if (attrs != null && !attrs.isEmpty()) {
                        for (Object attribute: attrs) {
                            auditContext.addPropertyToAuditEvent("attribute", attribute, auditEvent);
                        }
                    }

                    if (user.getRoles() != null && !user.getRoles().isEmpty()) {
                        for (Object roleObject: user.getRoles()) {
                            Role role = (Role) roleObject;
                            auditContext.addPropertyToAuditEvent("roleName", role.getRoleName(), auditEvent);
                        }
                    }
                }
            }
        });
    }

	public void deleteUser (ExecutionContext context, String username) {
		RepoUser user = getRepoUser(context, username);
		if (user == null) {
			return;
		}

        addPropertiesToUserEvent(new String[] {"deleteUser"}, user);

		removeAllRoles(context, (User) user);

		getHibernateTemplate().delete(user);
	}

	public void addRole(ExecutionContext context, User user, Role role) {
		if (user == null) {
			return;
		}

		RepoUser existingUser = getRepoUser(context, user);
		if (existingUser != null) {
            RepoRole existingRole = getRepoRole(role);
			existingUser.addRole(existingRole);
			putUser(null, existingUser);
		}
		user.addRole(role);
	}


	public void removeRole(ExecutionContext context, User user, Role role) {
		if (user == null || role == null) {
			return;
		}

		RepoUser existingUser = getRepoUser(context, user);

		if (existingUser != null) {
			RepoRole r = getRepoRole(role);
			if (r != null) {
				existingUser.removeRole(r);
				putUser(null, existingUser);
			} else {
				log.debug("removeRole: No role such as " + role.getRoleName());
			}
		} else {
			log.debug("removeRole: No user such as " + user.getUsername());
		}
		user.removeRole(role);
	}

	public void removeAllRoles(ExecutionContext context, User user) {
		if (user == null) {
			return;
		}

		RepoUser existingUser = getRepoUser(context, user);
		if (existingUser == null) {
			return;
		}

		/*
			for (Iterator it = existingUser.getRoles().iterator(); it.hasNext(); ) {
				Role role = (Role) it.next();
				existingUser.removeRole(role);
				user.removeRole(role);
			}
		*/

		existingUser.getRoles().clear(); //to avoid ConcurrentModificationException
		putUser(null, existingUser);

	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getRole(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.String)
	 */
	public Role getRole(ExecutionContext context, String roleName) {
		RepoRole repoRole = getRepoRole(context, roleName);
		Role role = null;
		if (repoRole != null) {
			role = (Role) repoRole.toClient((ResourceFactory) getObjectMappingFactory());
		}
		return role;
	}

	protected Class getPersistentRoleClass() {
		return getPersistentClassFactory().getImplementationClass(Role.class);
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getRole(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, java.lang.Long)
	 */
	protected RepoRole getRepoRole(ExecutionContext context, String roleName) {
		return getRepoRole(roleName, (String) null);
	}

	protected RepoRole getRepoRole(Role role) {
		return getRepoRole(role.getRoleName(), role.getTenantId());
	}
	
	protected RepoRole getRepoRole(String roleName, String tenantId) {
		RepoTenant tenant = getPersistentTenant(tenantId, false);
		if (tenant == null && !isNullTenant(tenantId)) {
			//if the requested tenant was not found, return null
			if (log.isDebugEnabled()) {
				log.debug("Tenant " + tenantId + " not found, returning null role");
			}
			return null;
		}
		
		DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentRoleClass());
		if (tenant == null) {
			criteria.
					add(Restrictions.isNull("tenant")).
					add(Restrictions.eq("roleName", roleName));
		} else {
			criteria.add(Restrictions.naturalId().
					set("tenant", tenant).
					set("roleName", roleName));
		}
		List roleList = getHibernateTemplate().findByCriteria(criteria);
		RepoRole role = null;
		if (roleList.isEmpty()) {
			if (log.isDebugEnabled()) {
				log.debug("Role not found with role name \"" + roleName + "\""
						+ (tenantId == null ? "" : (", tenant \"" + tenantId + "\"")));
			}
		} else {
			role = (RepoRole) roleList.get(0);
		}
		return role;
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#putRole(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, com.jaspersoft.jasperserver.api.metadata.user.domain.Role)
	 */
	public void putRole(ExecutionContext context, Role aRole) {
		RepoRole existingRole = getRepoRole(aRole);
		log.debug("putRole: " + aRole.getRoleName() + ", " + existingRole);
		if (existingRole == null) {
			existingRole = (RepoRole) getPersistentClassFactory().newObject(Role.class);
			log.debug("New Object");
		}
        Set existingRoleUserIds = getIdsFromUserSet(existingRole.getUsers());

		existingRole.copyFromClient(aRole, this);

        Set newRoleUserIds = getIdsFromUserSet(existingRole.getUsers());
        addParametersToRoleManagementAuditEvent(new String[] {"createRole", "updateRole"}, existingRole, false);
        addUserIdsToRoleManagementAuditEvent(existingRoleUserIds, newRoleUserIds);

		getHibernateTemplate().saveOrUpdate(existingRole);

        updateRoleUsers(context, existingRole, aRole);
	}

    private void updateRoleUsers(ExecutionContext context, RepoRole existingRole, Role aRole) {

        Set repoUsers = existingRole.getUsers();
        for (Iterator it = repoUsers.iterator(); it.hasNext();) {
            RepoUser repoUser = (RepoUser) it.next();
            repoUser.getRoles().remove(getPersistentObject(aRole));
        }

        Set users = aRole.getUsers();
        for (Iterator it = users.iterator(); it.hasNext();) {
            User user = (User) it.next();
            addRole(context, user, aRole);
        }
    }

	/**
	 * Return everything for now
	 *
	 *  (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#getRoles(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, com.jaspersoft.jasperserver.api.metadata.view.domain.FilterCriteria)
	 */
	public List getRoles(ExecutionContext context, FilterCriteria filterCriteria) {
		List results = getHibernateTemplate().loadAll(getPersistentRoleClass());
		List roleDTOs = null;

		if (results != null) {
			roleDTOs = new ArrayList(results.size());
			Iterator it = results.iterator();
			while (it.hasNext()) {
				RepoRole r = (RepoRole) it.next();
				Role newRole = (Role) r.toClient((ResourceFactory) getObjectMappingFactory());
				roleDTOs.add(newRole);
			}
		}
		return roleDTOs;
	}

	/* (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#newRole(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext)
	 */
	public Role newRole(ExecutionContext context) {
		// return a Role DTO
		return (Role) getObjectMappingFactory().newObject(Role.class);
	}

    private void addParametersToRoleManagementAuditEvent(final String[] auditEventTypes, final RepoRole role, final boolean logUsers) {
        auditContext.doInAuditContext(auditEventTypes, new AuditContext.AuditContextCallbackWithEvent() {
            public void execute(AuditEvent auditEvent) {
                auditContext.addPropertyToAuditEvent("roleName", role.getRoleName(), auditEvent);
                auditContext.addPropertyToAuditEvent("tenantId", role.getTenantId(), auditEvent);
                auditContext.addPropertyToAuditEvent("externallyDefined", role.isExternallyDefined(), auditEvent);
                if (role.getAttributes() != null && !role.getAttributes().isEmpty()) {
                    for (Object attribute: role.getAttributes()) {
                        auditContext.addPropertyToAuditEvent("attribute", attribute, auditEvent);
                    }
                }
                if (logUsers && role.getUsers() != null && !role.getUsers().isEmpty()) {
                    for (Object userObject: role.getUsers()) {
                        RepoUser user = (RepoUser)userObject;
                        auditContext.addPropertyToAuditEvent("userId", user.getId(), auditEvent);
                    }
                }
            }
        });
    }

    private Set getIdsFromUserSet(Set userSet) {
        if (userSet != null && !userSet.isEmpty()) {
            Set userIds = new HashSet(userSet.size());
            for (Object user: userSet) {
                userIds.add(((RepoUser)user).getId());
            }
            return userIds;
        } else {
            return null;
        }
    }

    private void addUserIdsToRoleManagementAuditEvent(final Set usersIdsBeforeUpdate, final Set usersIdsAfterUpdate) {
        auditContext.doInAuditContext(new String[] {"createRole", "updateRole"},
                new AuditContext.AuditContextCallbackWithEvent() {
            public void execute(AuditEvent auditEvent) {
                if (usersIdsAfterUpdate != null && !usersIdsAfterUpdate.isEmpty()) {
                    for (Iterator i = usersIdsAfterUpdate.iterator(); i.hasNext(); ) {
                        Long id = (Long)i.next();
                        if (usersIdsBeforeUpdate != null && usersIdsBeforeUpdate.contains(id)) {
                            usersIdsBeforeUpdate.remove(id);
                            i.remove();
                        }
                    }
                }

                if (usersIdsBeforeUpdate != null && !usersIdsBeforeUpdate.isEmpty()) {
                    for (Object removedId: usersIdsBeforeUpdate) {
                        auditContext.addPropertyToAuditEvent("removedUserId", removedId, auditEvent);
                    }
                }
                if (usersIdsAfterUpdate != null && !usersIdsAfterUpdate.isEmpty()) {
                    for (Object addedId: usersIdsAfterUpdate) {
                        auditContext.addPropertyToAuditEvent("addedUserId", addedId, auditEvent);
                    }
                }
            }
        });
    }
    
	public void deleteRole(ExecutionContext context, String roleName) {
		RepoRole role = getRepoRole(context, roleName);
		if (role == null) {
			return;
		}

        addParametersToRoleManagementAuditEvent(new String[] {"deleteRole"}, role, true);

		// Get all users that have this role and remove the role from them
		Set userList = role.getUsers();
		for (Iterator it = userList.iterator(); it.hasNext(); ) {
			RepoUser u = (RepoUser) it.next();
			u.removeRole(role);
		}

//		role.getUsers().clear();

		// then delete the role
		getHibernateTemplate().delete(role);
	}

	public List getUsersNotInRole(ExecutionContext context, String roleName)
	{
		List allUsers = getUsers(context, null);
		List usersInRole = getUsersInRole(context, roleName);
		allUsers.removeAll(usersInRole);

		return allUsers;
	}

	public List getUsersInRole(ExecutionContext context, String roleName)
	{
		RepoRole repoRole = getRepoRole(context, roleName);
		Set repoUsers = repoRole.getUsers();
		List users = new ArrayList();

		for (Iterator it = repoUsers.iterator(); it.hasNext();)
		{
			RepoUser repoUser = (RepoUser) it.next();
			User user = (User) repoUser.toClient(getObjectMappingFactory());
			users.add(user);
		}

		return users;
	}


	public List getAssignedRoles(ExecutionContext context, String userName)
	{
		RepoUser repoUser = getRepoUser(context, userName);
		Set repoRoles = repoUser.getRoles();

		List roles = new ArrayList();

		for (Iterator it = repoRoles.iterator(); it.hasNext();) {
			RepoRole repoRole = (RepoRole) it.next();
			Role role = (Role) repoRole.toClient(getObjectMappingFactory());
			roles.add(role);
		}

		return roles;
	}


	public List getAvailableRoles(ExecutionContext context, String userName)
	{
		List allRoles = getRoles(context, null);
		List assignedRoles = getAssignedRoles(null, userName);
		allRoles.removeAll(assignedRoles);
		return allRoles;
	}

	public boolean roleExists(ExecutionContext context, String roleName)
	{
		return (getRole(context, roleName) != null);
	}

	/*
	 * TODO this should be generalized. Maybe get the Repo* objects to return a
	 * DetachedCriteria filled with the key from the client object?
	 *
	 *  (non-Javadoc)
	 * @see com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.PersistentObjectResolver#getPersistentObject(java.lang.Object)
	 */
	public Object getPersistentObject(Object clientObject) {
		if (clientObject instanceof Role) {
			Role r = (Role) clientObject;
			return getRepoRole(r);
		} else if (clientObject instanceof User) {
			User u = (User) clientObject;
			return getRepoUser(null, u);
		}
		return null;
	}

	/**
	 * From an external UserDetails + GrantedAuthority[], maintain the shadow internal user
	 *
	 * @param externalUserDetails
	 */
	public User maintainInternalUser(UserDetails externalUserDetails, GrantedAuthority[] authorities) {

    	log.debug("External user: " + externalUserDetails.getUsername());

        User user = getUser(new ExecutionContextImpl(), externalUserDetails.getUsername());

		if (user == null) {
			user = createNewExternalUser(externalUserDetails.getUsername());
		}

        Set roles = persistRoles(getRolesFromGrantedAuthorities(authorities));
		alignInternalAndExternalUser(roles, user);

        return user;
	}

	/**
	 * From an external user (string user name) + GrantedAuthority[], maintain the shadow internal user
	 *
     * @param userName
     * @param authorities
	 */
	public User maintainInternalUser(String userName, GrantedAuthority[] authorities) {

    	log.debug("External user(String): " + userName);

        User user = getUser(new ExecutionContextImpl(), userName);

		if (user == null) {
			user = createNewExternalUser(userName);
		}

        Set roles = persistRoles(getRolesFromGrantedAuthorities(authorities));
		alignInternalAndExternalUser(roles, user);

        return user;
	}
	/**
	 * New user created from given authentication details. No password is set or needed.
	 * Roles are set elsewhere.
	 *
	 * @param userName
	 * @return created User
	 */
	protected User createNewExternalUser(String userName) {
    	User user = newUser(new ExecutionContextImpl());
    	user.setUsername(userName);
    	// If it is externally authenticated, no save of password
    	//user.setPassword(userDetails.getPassword());
    	user.setFullName(userName); // We don't know the real name
    	user.setExternallyDefined(true);
    	user.setEnabled(true);
		log.warn("Created new external user: " + user.getUsername());
		return user;
	}

	/**
	 * Ensure the external user has the right roles. Roles attached to the userDetails are the definitive list
	 * of externally defined roles.
	 *
	 * @param externalRoles
	 * @param user
	 */
	protected void alignInternalAndExternalUser(Set externalRoles, User user) {

    	final Predicate externallyDefinedRoles = new Predicate() {
    		public boolean evaluate(Object input) {
    			if (!(input instanceof Role)) {
    				return false;
    			}
    			return ((Role) input).isExternallyDefined();
    		}
    	};

    	Set currentRoles = user.getRoles();

    	// we may have a new user, so always persist them
    	boolean persistUserNeeded = (currentRoles.size() == 0);
/*
    	// If it is externally authenticated, no save of password
    	if (!user.getPassword().equals(userDetails.getPassword())) {
    		user.setPassword(userDetails.getPassword());
    		persistUserNeeded = true;
    	}

*/    	Collection currentExternalRoles = CollectionUtils.select(user.getRoles(), externallyDefinedRoles);
		if (log.isDebugEnabled()) {
			log.debug("Login of external User: " + user.getUsername() );
			log.debug("Roles from authentication:\n" + roleCollectionToString(externalRoles));
			log.debug("Current roles from metadata:\n" + roleCollectionToString(user.getRoles()));
			log.debug("Current external roles for user from metadata: " + user.getUsername() + "\n" + roleCollectionToString(currentExternalRoles));
		}

		/*
		 * If we have new external roles, we want to add them
		 */
    	Collection newExternalRoles = CollectionUtils.subtract(externalRoles, currentExternalRoles);

    	if (newExternalRoles.size() > 0) {
    		currentRoles.addAll(newExternalRoles);
    		if (log.isWarnEnabled()) {
    			log.warn("Added following external roles to: " + user.getUsername() + "\n" + roleCollectionToString(newExternalRoles));
    		}
    		persistUserNeeded = true;
    	}

		/*
		 * If external roles have been removed, we need to remove them
		 */
    	Collection rolesNeedingRemoval = CollectionUtils.subtract(currentExternalRoles, externalRoles);

    	if (rolesNeedingRemoval.size() > 0) {
    		currentRoles.removeAll(rolesNeedingRemoval);
    		if (log.isWarnEnabled()) {
    			log.warn("Removed following external roles from: " + user.getUsername() + "\n" + roleCollectionToString(rolesNeedingRemoval));
    		}
    		persistUserNeeded = true;
    	}

		/*
		 * If we have new default internal roles, we want to add them
		 */
    	Collection defaultInternalRolesToAdd = CollectionUtils.subtract(getNewDefaultInternalRoles(), currentRoles);

    	if (defaultInternalRolesToAdd.size() > 0) {
    		if (log.isDebugEnabled()) {
    			log.debug("Default internal roles: " + roleCollectionToString(getNewDefaultInternalRoles()));
    		}
    		currentRoles.addAll(defaultInternalRolesToAdd);
    		if (log.isWarnEnabled()) {
    			log.warn("Added following new default internal roles to: " + user.getUsername() + "\n" + roleCollectionToString(defaultInternalRolesToAdd));
    		}
    		persistUserNeeded = true;
    	}

    	if (persistUserNeeded) {
    		if (log.isWarnEnabled()) {
    			log.warn("Updated user: " + user.getUsername() + ". Roles are now:\n" + roleCollectionToString(currentRoles));
    		}
        	user.setRoles(currentRoles);
        	// persist user and roles
        	putUser(new ExecutionContextImpl(), user);
    		if (log.isWarnEnabled()) {
    			log.warn("Updated user: " + user.getUsername() + ". Roles are now:\n" + roleCollectionToString(currentRoles));
    		}
    	}

	}

	private String roleCollectionToString(Collection coll) {
		Iterator it = coll.iterator();
		StringBuffer rolesPrint = new StringBuffer();
		while (it.hasNext()) {
			String s = ((Role) it.next()).getRoleName();
			rolesPrint.append(s).append("\n");
		}
		return rolesPrint.toString();
	}

	/**
	 * Get a set of roles based on the given GrantedAuthority[]. Roles are created
	 * in the metadata if they do not exist.
	 *
	 * @param authorities from authenticated user
	 * @return Set of externally defined Roles
	 */
	protected Set getRolesFromGrantedAuthorities(GrantedAuthority[] authorities) {
		Set set = new HashSet();

		if (authorities == null || authorities.length == 0)
			return set;

		for (int i = 0; i < authorities.length; i++) {
			GrantedAuthority auth = authorities[i];

			String authorityName = auth.getAuthority();

			// Make spaces in the authority name be underscores

			authorityName = authorityName.replace(' ', '_');

            if (!authorityName.startsWith("ROLE_")) {
                authorityName = "ROLE_" + authorityName;
            }

            Role r = newRole(new ExecutionContextImpl());
            r.setRoleName(authorityName);
            r.setExternallyDefined(true);
			set.add(r);
		}
		return set;
	}
    
    /*
     * 
     */
    protected Set persistRoles(Set roles) {
        Set persistedRoles = new HashSet();
        for (Iterator iter = roles.iterator(); iter.hasNext(); ) {
            Role r = (Role) iter.next();
            persistedRoles.add(getOrCreateRole(r.getRoleName(), r.isExternallyDefined())); 
        }
        return persistedRoles;
    }

    /**
     * @return the Authentication corresponding to the principal who used
     * the "Switch User" feature to login as the current principal if any,
     * or null, if the current principal is not a switched user.
     */
    public static Authentication getSourceAuthentication() {
	Authentication current = SecurityContextHolder.getContext().getAuthentication();
	Authentication original = null;

	// iterate over granted authorities and find the 'switch user' authority
	GrantedAuthority[] authorities = current.getAuthorities();

	for (int i = 0; i < authorities.length; i++) {
	    // check for switch user type of authority
	    if (authorities[i] instanceof SwitchUserGrantedAuthority) {
		original = ((SwitchUserGrantedAuthority) authorities[i]).getSource();
		log.debug("Found original switch user granted authority [" + original + "]");
	    }
         }

	return original;
    }

    public static boolean isUserSwitched() {
	return (getSourceAuthentication() != null);
    }

	/**
	 * Get a set of roles that are the defaults for a new external user. Roles are created
	 * in the metadata if they do not exist.
	 *
	 * @return Set of internally defined Roles
	 */
	private Set getNewDefaultInternalRoles() {
		Set set = new HashSet();

		if (getDefaultInternalRoles() == null || getDefaultInternalRoles().size() == 0)
			return set;

		for (int i = 0; i < getDefaultInternalRoles().size(); i++) {
			String roleName = (String) getDefaultInternalRoles().get(i);

			set.add(getOrCreateRole(roleName, false));
		}
		return set;
	}

	private Role getOrCreateRole(String roleName, boolean externallyDefined) {
		Role r = getRole(new ExecutionContextImpl(), roleName);
		if (r == null) {
			r = newRole(new ExecutionContextImpl());
			r.setRoleName(roleName);
			r.setExternallyDefined(externallyDefined);
			putRole(new ExecutionContextImpl(), r);
			log.warn("Created new " + (externallyDefined ? "external" : "internal") + " role: " + roleName);
		}

		return r;
	}

	public void makeUserLoggedIn(User user) {

		try {
			// Make our user the Authentication!

			UserDetails ourUserDetails = makeUserDetails(user);

			// Don't set the authentication if we have no roles

			if (ourUserDetails.getAuthorities().length != 0) {
				UsernamePasswordAuthenticationToken ourAuthentication = new UsernamePasswordAuthenticationToken(ourUserDetails,
						ourUserDetails.getPassword(), ourUserDetails.getAuthorities());

				if (log.isDebugEnabled()) {
					log.debug("Setting Authentication to: " + ourAuthentication);
				}
				SecurityContextHolder.getContext().setAuthentication(ourAuthentication);
			} else {

	            // There was some error - maybe no roles?
	            // Remove authentication to allow anonymous access to catch things
	            // later in the filter chain
				SecurityContextHolder.getContext().setAuthentication(null);
			}
		} catch (UsernameNotFoundException e) {
			log.warn("User: " + user.getUsername() + " was not found to make them logged in");
		}
	}

	/**
	 * @return Returns the defaultInternalRoles.
	 */
	public List getDefaultInternalRoles() {
		return defaultInternalRoles;
	}

	/**
	 * @param defaultInternalRoles The defaultInternalRoles to set.
	 */
	public void setDefaultInternalRoles(List defaultInternalRoles) {
		this.defaultInternalRoles = defaultInternalRoles;
	}

	public boolean userExists(ExecutionContext context, String username)
	{
		return (getUser(context, username) != null);
	}

    protected boolean isDateExpired(int nDate, Date previousExpirationDate) {
        long during = nDate*3600*24;
        return ((previousExpirationDate.getTime() / 1000) + during) <= ((new Date()).getTime() / 1000);
    }

	public boolean isPasswordExpired(ExecutionContext context, String username, int nDate) {
		Date previousExpirationDate = getUser(context, username).getPreviousPasswordChangeTime();
		// TO-DO what if previousExpirationDate is empty
		if ((previousExpirationDate == null) || ("".equals(previousExpirationDate))) {
		   resetPasswordExpiration(context, username);
		   return false;
		}

        return isDateExpired(nDate, previousExpirationDate);
	}
    
	public void resetPasswordExpiration(ExecutionContext context, String username) {
		User user = getUser(context, username);
		if (user != null) {
			user.setPreviousPasswordChangeTime(new Date());
			putUser(context, user);
		}
	}

	public String getTenantId(ExecutionContext context, String userName) {
		User user = getUser(context, userName);
		return user.getTenantId();
	}

    public List getTenantUsers(ExecutionContext context, 
    		final Set tenantIds, final String name) {
    	List userList = (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
		        Criteria criteria = createTenantUsersCriteria(session, tenantIds, name);
		        return criteria.list();
			}
    	});

        List userDTOs = convertUserListToDtoList(userList);

        return userDTOs;
    }

    public List getTenantUsers(ExecutionContext context, 
    		final Set tenantIds, final String name, 
    		final int firstResult, final int maxResults) {
    	List userList = (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
		        Criteria criteria = createTenantUsersCriteria(session, tenantIds, name);
				if (firstResult >= 0) {
					criteria.setFirstResult(firstResult);
				}
				if (maxResults > 0) {
					criteria.setMaxResults(maxResults);
				}
				return criteria.list();
			}
    	});

        List userDTOs = convertUserListToDtoList(userList);

        return userDTOs;
    }

    public List getTenantRoles(ExecutionContext context, 
    		final Set tenantIds, final String name) {
    	List roleList = (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
		        Criteria criteria = createTenantRolesCriteria(session, tenantIds, name);
		        return criteria.list();
			}
    	});
        return convertRoleListToDtoList(roleList);
    }

    public List getTenantRoles(ExecutionContext context, 
    		final Set tenantIds, final String name, 
    		final int firstResult, final int maxResults) {
    	List roleList = (List) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
		        Criteria criteria = createTenantRolesCriteria(session, tenantIds, name);
				if (firstResult >= 0) {
					criteria.setFirstResult(firstResult);
				}
				if (maxResults > 0) {
					criteria.setMaxResults(maxResults);
				}
		        return criteria.list();
			}
    	});
        return convertRoleListToDtoList(roleList);
    }

    public List getTenantVisibleRoles(ExecutionContext context, Set tenantIds, String name, int firstResult, int maxResults) {
        DetachedCriteria criteria = createTenantVisibleRolesCriteria(tenantIds, name);

        List results = getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);

        List roleDTOs = convertRoleListToDtoList(results);

        return roleDTOs;
    }

    public int getTenantVisibleRolesCount(ExecutionContext context, Set tenantIds, String name) {
        DetachedCriteria criteria = createTenantVisibleRolesCriteria(tenantIds, name, false);

        criteria.setProjection(Projections.rowCount());

        List results = getHibernateTemplate().findByCriteria(criteria);

        Integer rowCount = new Integer(0) ;
        if (results != null && !results.isEmpty()) {
            rowCount = (Integer) results.get(0);
        }

        return rowCount.intValue();        
    }

    public int getTenantUsersCount(ExecutionContext context,
    		final Set tenantIds, final String name) {
    	Integer rowCount = (Integer) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
		        Criteria criteria = createTenantUsersCriteria(session, tenantIds, name, false);
		        criteria.setProjection(Projections.rowCount());
		        return criteria.uniqueResult();
			}
    	});
        return rowCount.intValue();
    }

    public int getTenantRolesCount(ExecutionContext context, 
    		final Set tenantIds, final String name) {
    	Integer rowCount = (Integer) getHibernateTemplate().execute(new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
		        Criteria criteria = createTenantRolesCriteria(session, tenantIds, name, false);
		        criteria.setProjection(Projections.rowCount());
		        return criteria.uniqueResult();
			}
    	});
        return rowCount.intValue();
    }

    private Criteria createTenantUsersCriteria(Session session, Set tenantIds, String name) {
        return  createTenantUsersCriteria(session, tenantIds, name, true);
    }

    private Criteria createTenantUsersCriteria(Session session, Set tenantIds, String name, boolean order) {
        Set internalTenantIds = null;
        if (tenantIds != null) {
            internalTenantIds = new HashSet();
            internalTenantIds.addAll(tenantIds);
            if (internalTenantIds.contains(null)) {
                internalTenantIds.remove(null);
                internalTenantIds.add(TenantService.ORGANIZATIONS);
            }
        }
        Criteria criteria = session.createCriteria(getPersistentUserClass());
    	criteria.createAlias("tenant", "tenant", Criteria.LEFT_JOIN);

        if (internalTenantIds == null) {
            criteria.add(Restrictions.eq("tenant.tenantId", TenantService.ORGANIZATIONS));
        } else {
            if (!internalTenantIds.isEmpty()) {
            	criteria.add(Restrictions.in("tenant.tenantId", internalTenantIds));
            }
        }

        if (name != null) {
            name = databaseCharactersEscapeResolver.getEscapedText(name.trim());
            if (name.length() > 0) {
//                Criterion userNameCriterion = Restrictions.ilike("username", "%" + name + "%");
//                Criterion fullNameCriterion = Restrictions.ilike("fullName", "%" + name + "%");

                Criterion userNameCriterion = new IlikeEscapeAwareExpression("username", name, MatchMode.ANYWHERE);
                Criterion fullNameCriterion = new IlikeEscapeAwareExpression("fullName", name, MatchMode.ANYWHERE);

                criteria.add(Restrictions.or(userNameCriterion, fullNameCriterion));
            }
        }

        if (order) {
            criteria.addOrder(Order.asc("username"));
            criteria.addOrder(Order.asc("tenant.tenantId"));
        }

        return criteria;
    }

    protected Criteria createTenantRolesCriteria(Session session,
    		Set tenantIds, String name){
        return createTenantRolesCriteria(session, tenantIds, name, true);
    }
    protected Criteria createTenantRolesCriteria(Session session, Set tenantIds, String name, boolean order){

        Criteria criteria = session.createCriteria(getPersistentRoleClass());
        String roleNameField = "roleName";

        addTenantCriteria(criteria, tenantIds);

        if (name != null && name.trim().length() > 0) {
			Criterion roleNameCriterion = Restrictions.ilike(roleNameField, "%"
					+ name.trim() + "%");
			criteria.add(roleNameCriterion);
		}

		if (order) {
            criteria.addOrder(Order.asc(roleNameField));
        }

        return criteria;
    }
    protected DetachedCriteria createTenantVisibleRolesCriteria(Set tenantIds, String name) {
        return createTenantVisibleRolesCriteria(tenantIds, name, true);
    }

    protected DetachedCriteria createTenantVisibleRolesCriteria(Set tenantIds, String name, boolean order) {
        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentRoleClass());
        String roleNameField = "roleName";

        addVisibleTenantCriteria(criteria, tenantIds);

        if (name != null) {
            name = databaseCharactersEscapeResolver.getEscapedText(name.trim());

            if(name.length() > 0){
//                Criterion roleNameCriterion = Restrictions.ilike(roleNameField, "%"
//                        + name.trim() + "%");
//                criteria.add(roleNameCriterion);
                criteria.add(new IlikeEscapeAwareExpression(roleNameField, name, MatchMode.ANYWHERE));
            }
		}

		if (order) {
            criteria.addOrder(Order.asc(roleNameField));
        }

        return criteria;
    }

    private List convertUserListToDtoList(List userList) {

        List userDTOs = null;
        if (userList != null) {

            userDTOs = new ArrayList(userList.size());

            Iterator it = userList.iterator();
            while(it.hasNext()) {

                RepoUser u = (RepoUser) it.next();

                User newUser = (User) u.toClient(getObjectMappingFactory());
                userDTOs.add(newUser);
            }
        }

        return userDTOs;
    }

    public List getAvailableRoles(ExecutionContext context, String roleName, Set userRoles, String userName,
                                  int firstResult, int maxResults){

        DetachedCriteria criteria = createAvailableRolesCriteria(roleName, userRoles, userName);

        List results = getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);

        List roleDTOs = convertRoleListToDtoList(results);

        return roleDTOs;
    }

    public int getAvailableRolesCount(ExecutionContext context, String roleName, Set userRoles, String userName){

        DetachedCriteria criteria = createAvailableRolesCriteria(roleName, userRoles, userName, false);
        criteria.setProjection(Projections.rowCount());

        List results = getHibernateTemplate().findByCriteria(criteria);

        Integer rowCount = new Integer(0) ;
        if (results != null && !results.isEmpty()) {
            rowCount = (Integer) results.get(0);
        }

        return rowCount.intValue();
    }

    protected DetachedCriteria createAvailableRolesCriteria(String roleName, Set userRoles, String userName){
        return createAvailableRolesCriteria(roleName, userRoles, userName, true);
    }

    protected DetachedCriteria createAvailableRolesCriteria(String roleName, Set userRoles, String userName, boolean order){

        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentRoleClass());
        String roleNameField = "roleName";
        String externallyDefinedField = "externallyDefined";

        if (userRoles != null && userRoles.size() > 0) {

            List userRoleIdList = getRoleIdList(userRoles);
            criteria.add(Restrictions.not(Restrictions.in("id", userRoleIdList)));
        }

        Criterion roleNameCriterion = Restrictions.ilike(roleNameField, "%" + roleName.trim() + "%");
        criteria.add(roleNameCriterion);

        criteria.add(Restrictions.eq(externallyDefinedField, Boolean.FALSE));

        if (order) {
            criteria.addOrder(Order.asc(roleNameField));
        }

        return criteria;
    }

    protected List getRoleIdList(Set roleNames) {
        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentRoleClass());

        if (roleNames != null && roleNames.size() > 0) {

            criteria.add(Restrictions.in("roleName", roleNames));
        } else {

            return new ArrayList();
        }

        criteria.setProjection(Projections.id());

        return getHibernateTemplate().findByCriteria(criteria);
    }

    private List<Role> convertRoleListToDtoList(List roleList) {
        List<Role> roleDTOs = null;

        if (roleList != null) {
            roleDTOs = new ArrayList<Role>(roleList.size());
            for (Object aRoleList : roleList) {
                RepoRole r = (RepoRole) aRoleList;
                Role newRole = (Role) r.toClient((ResourceFactory) getObjectMappingFactory());
                roleDTOs.add(newRole);
            }
        }

        return roleDTOs;
    }

    public List<Role> getAvailableRoles(ExecutionContext context, String userName, String text, int firstResult,
            int maxResults) {
        DetachedCriteria criteria = createAvailableRolesCriteria(context, userName, text, true);

        List results = getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);

        return convertRoleListToDtoList(results);
    }

    public int getAvailableRolesCount(ExecutionContext context, String userName, String text) {
        DetachedCriteria criteria = createAvailableRolesCriteria(context, userName, text, false);
        criteria.setProjection(Projections.rowCount());

        List results = getHibernateTemplate().findByCriteria(criteria);

        int rowCount = 0 ;
        if (results != null && !results.isEmpty()) {
            rowCount = (Integer) results.get(0);
        }

        return rowCount;
    }

    protected DetachedCriteria createAvailableRolesCriteria(ExecutionContext context, String userName, String text,
                                                            boolean order) {
        final String roleNameField = "roleName";
        final String externallyDefinedField = "externallyDefined";

        RepoUser user = getRepoUser(context, userName);

        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentRoleClass());

        List<Long> assignedRolesIds = getUserRolesIds(user);
        if (assignedRolesIds!= null && assignedRolesIds.size() > 0) {
            criteria.add(Restrictions.not(Restrictions.in("id", assignedRolesIds)));
        }

        final String roleNameLikeValue = text == null ? "" : text;
        Criterion roleNameCriterion = Restrictions.ilike(roleNameField, "%" + roleNameLikeValue + "%");
        criteria.add(roleNameCriterion);

        criteria.add(Restrictions.eq(externallyDefinedField, Boolean.FALSE));

        if (order) {
            criteria.addOrder(Order.asc(roleNameField));
        }

        return criteria;
    }

    @SuppressWarnings({"unchecked"})
    protected List<Long> getUserRolesIds(RepoUser user) {
        DetachedCriteria criteria = createAssignedRolesCriteria(null, user, null, true);
        criteria.setProjection(Projections.id());

        return (List<Long>)getHibernateTemplate().findByCriteria(criteria);
    }

    public List getAssignedRoles(ExecutionContext context, String userName, String text, int firstResult,
            int maxResults) {
        RepoUser user = getRepoUser(context, userName);

        DetachedCriteria criteria = createAssignedRolesCriteria(context, user, text, true);

        List results = getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);

        return convertRoleListToDtoList(results);
    }

    public int getAssignedRolesCount(ExecutionContext context, String userName, String text) {
        RepoUser user = getRepoUser(context, userName);

        DetachedCriteria criteria = createAssignedRolesCriteria(context, user, text, false);
        criteria.setProjection(Projections.rowCount());

        List results = getHibernateTemplate().findByCriteria(criteria);

        int rowCount = 0 ;
        if (results != null && !results.isEmpty()) {
            rowCount = (Integer) results.get(0);
        }

        return rowCount;
    }

    private DetachedCriteria createAssignedRolesCriteria(ExecutionContext context, RepoUser user, String text, boolean order) {
        final String roleNameField = "roleName";
        final String externallyDefinedField = "externallyDefined";

        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentRoleClass());

        DetachedCriteria usersCriteria = criteria.createCriteria("users");
        usersCriteria.add(Restrictions.idEq(user.getId()));

        final String roleNameLikeValue = text == null ? "" : text;
        Criterion roleNameCriterion = Restrictions.ilike(roleNameField, "%" + roleNameLikeValue + "%");
        criteria.add(roleNameCriterion);

        criteria.add(Restrictions.eq(externallyDefinedField, Boolean.FALSE));

        if (order) {
            criteria.addOrder(Order.asc(roleNameField));
        }

        return criteria;
    }

    public void updateUser(ExecutionContext context, String userName, User aUser) {
        RepoUser existingUser = getRepoUser(context, userName);

        log.debug("updateUser: " + userName + ", " + existingUser);
        if (existingUser == null) {

            log.debug("User not found");
            throw new IllegalArgumentException("Cannot find user with name : " + userName);
        }
        updatePersistentUser(aUser, existingUser);        

        addPropertiesToUserEvent(new String[] {"updateUser"}, existingUser);
        getHibernateTemplate().saveOrUpdate(existingUser);
    }

    /* (non-Javadoc)
     * @see com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService#updateRole(com.jaspersoft.jasperserver.api.common.domain.ExecutionContext, com.jaspersoft.jasperserver.api.metadata.user.domain.Role)
     */
    public void updateRole(ExecutionContext context, String roleName, Role roleDetails) throws IllegalArgumentException{
        RepoRole existingRole = getRepoRole(context, roleName);

        log.debug("updateRole: " + roleName + ", " + existingRole);
        if (existingRole == null) {

            log.debug("Role not found");
            throw new IllegalArgumentException("Cannot find role with name : " + roleName);
        }
        existingRole.copyFromClient(roleDetails, this);

        addParametersToRoleManagementAuditEvent(new String[] {"updateRole"}, existingRole, false);
        getHibernateTemplate().saveOrUpdate(existingRole);
    }

    public List getUsersWithoutRole(ExecutionContext context, String roleName, String userName,
                                    int firstResult, int maxResults) {

        DetachedCriteria criteria = createUsersWithoutRoleCriteria(roleName, userName);

        List userList = getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);

        return convertUserListToDtoList(userList);
    }

    public int getUsersCountWithoutRole(ExecutionContext context, String roleName, String userName) {

        DetachedCriteria criteria = createUsersWithoutRoleCriteria(roleName, userName, false);
        criteria.setProjection(Projections.rowCount());

        List results = getHibernateTemplate().findByCriteria(criteria);

        return getRowCountFromResult(results);
    }

    protected DetachedCriteria createUsersWithoutRoleCriteria(String roleName, String userName){
        return createUsersWithoutRoleCriteria(roleName, userName, true);
    }

    protected DetachedCriteria createUsersWithoutRoleCriteria(String roleName, String userName, boolean order){

        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentUserClass());

        DetachedCriteria usersWithRoleCriteria = createUsersWithRoleCriteria(roleName, "");
        usersWithRoleCriteria.setProjection(Projections.id());

        List usersWithRole = getHibernateTemplate().findByCriteria(usersWithRoleCriteria);

        String userNameField = "username";

//        addTenantCriteria(criteria, tenantIds);
        createSearchByUserNameCriteria(criteria, userName);

        if (usersWithRole != null && usersWithRole.size() > 0) {
            criteria.add(Restrictions.not(Restrictions.in("id", usersWithRole)));
        }

        if (order) {
            criteria.addOrder(Order.asc(userNameField));
        }

        return criteria;
    }

    private void addTenantCriteria(Criteria criteria, Set tenantIds){
        Set internalTenantIds = null;
        if (tenantIds != null) {
            internalTenantIds = new HashSet();
            internalTenantIds.addAll(tenantIds);
            if (internalTenantIds.contains(null)) {
                internalTenantIds.remove(null);
                internalTenantIds.add(TenantService.ORGANIZATIONS);
            }
        }
    	criteria.createAlias("tenant", "tenant", Criteria.LEFT_JOIN);
        if (internalTenantIds == null) {
            criteria.add(Restrictions.eq("tenant.tenantId", TenantService.ORGANIZATIONS));
        } else {
            if (!internalTenantIds.isEmpty()) {
                Criterion idInCriterion = Restrictions.in("tenant.tenantId", internalTenantIds);
                criteria.add(idInCriterion);
            }
        }
    }

    protected List getIdByTenantIdSet(Set tenantIds) {
        DetachedCriteria idCriteria =
                DetachedCriteria.forClass(getPersistentTenantClass());
        idCriteria.add(Restrictions.in("tenantId", tenantIds));
        idCriteria.setProjection(Projections.id());
        return getHibernateTemplate().findByCriteria(idCriteria);        
    }

    protected void addVisibleTenantCriteria(DetachedCriteria criteria, Set tenantIds) {
        Set internalTenantIds = null;
        if (tenantIds != null) {
            internalTenantIds = new HashSet();
            internalTenantIds.addAll(tenantIds);
            if (internalTenantIds.contains(null)) {
                internalTenantIds.remove(null);
                internalTenantIds.add(TenantService.ORGANIZATIONS);
            }
        }
        if (internalTenantIds == null) {
            RepoTenant tenant = tenantPersistenceResolver.getPersistentTenant(TenantService.ORGANIZATIONS, true);
            criteria.add(Restrictions.eq("tenant.id", tenant.getId()));
        } else {
            if (!internalTenantIds.isEmpty()) {
                Criterion idInCriterion = Restrictions.in("tenant.id", getIdByTenantIdSet(internalTenantIds));
                criteria.add(idInCriterion);
            }
        }
    }

    public List getUsersWithRole(ExecutionContext context, String roleName, String userName,
                                int firstResult, int maxResults) {

        DetachedCriteria criteria = createUsersWithRoleCriteria(roleName, userName);

        List userList = getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);

        return convertUserListToDtoList(userList);
    }

    public int getUsersCountWithRole(ExecutionContext context, String roleName, String userName) {

        DetachedCriteria criteria = createUsersWithRoleCriteria(roleName, userName, false);
        criteria.setProjection(Projections.rowCount());

        List results = getHibernateTemplate().findByCriteria(criteria);

        return getRowCountFromResult(results);
    }

    protected DetachedCriteria createUsersWithRoleCriteria(String roleName, String userName){
        return  createUsersWithRoleCriteria(roleName, userName, true);
    }

    protected DetachedCriteria createUsersWithRoleCriteria(String roleName, String userName, boolean order){

        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentUserClass());

        String userNameField = "username";
        String roleNameField = "roleName";

//        addTenantCriteria(criteria, tenantIds);
        createSearchByUserNameCriteria(criteria, userName);

        if (roleName != null && roleName.trim().length() > 0) {

            Criterion roleNameCriterion = Restrictions.eq(roleNameField, roleName.trim());

            criteria.createCriteria("roles").add(roleNameCriterion);
        }

		if (order) {
            criteria.addOrder(Order.asc(userNameField));
        }

        return criteria;
    }

    protected void createSearchByUserNameCriteria(DetachedCriteria criteria, String userName){

        if (userName != null && userName.trim().length() > 0) {

            Criterion userNameCriterion = Restrictions.ilike("username", "%" + userName.trim() + "%");
            Criterion fullNameCriterion = Restrictions.ilike("fullName", "%" + userName.trim() + "%");
            
            criteria.add(Restrictions.or(userNameCriterion, fullNameCriterion));
        }
    }

    protected int getRowCountFromResult(List results) {

        Integer rowCount = new Integer(0) ;
        if (results != null && !results.isEmpty()) {
            rowCount = (Integer) results.get(0);
        }

        return rowCount.intValue();
    }

    private void addUserParamsToUpdateRoleAuditEvent(final String actionPrefix, final List users) {
        auditContext.doInAuditContext("updateRole", new AuditContext.AuditContextCallbackWithEvent() {
            public void execute(AuditEvent auditEvent) {
                if (users != null && !users.isEmpty()) {
                    for (Object userObject: users) {
                        RepoUser user = (RepoUser)userObject;
                        auditContext.addPropertyToAuditEvent(actionPrefix + "UserId", user.getId(), auditEvent);
                    }
                }
            }
        });
    }

    public void assignUsers(ExecutionContext context, String roleName, Set userNames) {

        if (userNames != null && !userNames.isEmpty()) {

            List users = getUsersByUserNames(context, userNames);
            addUserParamsToUpdateRoleAuditEvent("added", users);
            RepoRole role = getRepoRole(context, roleName);

            for (Iterator it = users.iterator(); it.hasNext(); ) {
                RepoUser user = (RepoUser) it.next();

                user.addRole(role);
            }
        }
    }

    public void unassignUsers(ExecutionContext context, String roleName, Set userNames) {

        if (userNames != null && !userNames.isEmpty()) {

            List users = getUsersByUserNames(context, userNames);
            addUserParamsToUpdateRoleAuditEvent("removed", users);

            RepoRole role = getRepoRole(context, roleName);

            for (Iterator it = users.iterator(); it.hasNext(); ) {
                RepoUser user = (RepoUser) it.next();

                user.removeRole(role);
            }
        }

    }

    protected List getUsersByUserNames(ExecutionContext context, Set userNames) {
        DetachedCriteria criteria = DetachedCriteria.forClass(getPersistentUserClass());

        if (userNames != null && !userNames.isEmpty()) {

            criteria.add(Restrictions.in("username", userNames));
        }

        return getHibernateTemplate().findByCriteria(criteria);
    }

	public RepoUser getPersistentUser(String username) {
		return getRepoUser((ExecutionContext) null, username);
	}

	protected boolean isNullTenant(String tenantId) {
		return tenantId == null || tenantId.length() == 0 || TenantService.ORGANIZATIONS.equals(tenantId);
	}
	
	public RepoTenant getPersistentTenant(String tenantId, boolean required) {
		if (isNullTenant(tenantId)) {
            return getTenantPersistenceResolver().getPersistentTenant(TenantService.ORGANIZATIONS, true);
		}

		throw new IllegalArgumentException("This implementation does not support tenants");
	}

    public RepoTenant getPersistentTenantByAlias(String tenantAlias, boolean required) {
        if (isNullTenant(tenantAlias)) {
            return getTenantPersistenceResolver().getPersistentTenant(TenantService.ORGANIZATIONS, true);
        }

        throw new IllegalArgumentException("This implementation does not support tenants");
    }

    public TenantPersistenceResolver getTenantPersistenceResolver() {
        return tenantPersistenceResolver;
    }

    public void setTenantPersistenceResolver(
            TenantPersistenceResolver tenantPersistenceResolver) {
        this.tenantPersistenceResolver = tenantPersistenceResolver;
    }

}
