package com.andypemberton.jboss.portal.identity.ldap;

import java.util.HashSet;
import java.util.Set;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;

import org.jboss.portal.identity.IdentityException;
import org.jboss.portal.identity.Role;
import org.jboss.portal.identity.User;
import org.jboss.portal.identity.ldap.LDAPStaticRoleMembershipModuleImpl;
import org.jboss.portal.identity.ldap.LDAPUserImpl;
import org.jboss.portal.identity.ldap.helper.LDAPTools;

/**
 * Add recursive, nested role searches to JBoss LDAPStaticRoleMembershipModuleImpl
 * @author Andy Pemberton
 * @see {LDAPStaticRoleMembershipModuleImpl}
 *
 */
public class LDAPNestedStaticRoleMembershipModuleImpl extends LDAPStaticRoleMembershipModuleImpl {

	/**
	 * Override getRoles from LDAPStaticRoleMembershipModuleImpl to include recursion.
	 */
	@Override
	public Set getRoles(User user) throws IdentityException {
		if (user == null) { throw new IllegalArgumentException("User cannot be null"); }
		LDAPUserImpl ldapUser = null;
		if (user instanceof LDAPUserImpl) {
			ldapUser = (LDAPUserImpl) user;
		} else {
			throw new IllegalArgumentException("UserMembershipModuleImpl supports only LDAPUserImpl objects");
		}
		Set roles = new HashSet();
		try {
			if (ldapUser.getUserName() == null) { throw new IdentityException("Role name canot be null"); }

			//get all attributes on user record
			Attributes attrs = getConnectionContext().createInitialContext().getAttributes(ldapUser.getDn());
			if (attrs == null) { throw new IdentityException("Cannot find User with DN: " + ldapUser.getDn()); }
			//get the memberOf attribute(s) from the user attribute set
			Attribute memberOfAttribute = attrs.get(getMemberAttributeID());
			if (memberOfAttribute == null) { return roles; }

			NamingEnumeration values = memberOfAttribute.getAll();
			while (values.hasMoreElements()) {
				addRoles(values.nextElement().toString(), roles, 0);
			}
		} catch (NamingException e) {
			throw new IdentityException("Resolving User Roles failed.", e);
		}
		return roles;
	}

	/**
	 * Add the given role and optionally recurse the roles nested within the given role; 
	 * based on settings from ldap_identity-config.xml.
	 * @param roleName
	 * @param roles
	 */
	public void addRoles(String roleName, Set roles, Integer roleNestedLevel) throws IdentityException {
		try {
			addRole(roleName, roles);
			if (getRoleRecursion() != 0) { //if roleRecursion is enabled
				if (getRoleRecursion() == -1 || (roleNestedLevel + 1) <= getRoleRecursion()) { //if within roleRecursion limit
					Attributes roleAttrs = getConnectionContext().createInitialContext().getAttributes(roleName);
					Attribute memberAttribute = roleAttrs.get(getMemberAttributeID());
					if (memberAttribute != null) {
						NamingEnumeration nestedRoles = memberAttribute.getAll();
						while (nestedRoles.hasMoreElements()) {
							addRoles(nestedRoles.nextElement().toString(), roles, (roleNestedLevel + 1));
						}
					}
				}	
			}
		} catch (IdentityException ie) {
			throw new IdentityException(ie);
		} catch (NamingException e) {
			throw new IdentityException("Resolving User Roles failed.", e);
		}

	}

	/**
	 * Add the role represented by the given String name to the given Set of roles.
	 * @param roleName
	 * @param roles
	 * @throws IdentityException
	 */
	public void addRole(String roleName, Set roles) throws IdentityException {
		Role role = null;
		if (!isUidAttributeIsDN()) {
			roleName = LDAPTools.stripDnToName(roleName);
			role = getRoleModule().findRoleByName(roleName);
		} else {
			role = getRoleModule().findRoleByDN(roleName);
		}
		roles.add(role);
	}

	/** 
	 * Get the configured number of levels to recurse roles from ldap_identity-config.xml.
	 * 0 (default) to disable.
	 * -1 for infinite.
	 * Any positive integer for set number of role levels to recurse.
	 * @return
	 * @throws IdentityException
	 */
	protected Integer getRoleRecursion() throws IdentityException {
		String roleRecursion = getIdentityConfiguration().getValue("roleRecursion");
		try {
			return Integer.valueOf(roleRecursion);
		} catch (Exception e) {
    		return 0;
		}
		return 0;
	}

}

