package org.apache.jackrabbit.j2ee.accessmanager;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.AccessDeniedException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import javax.jcr.security.Privilege;

import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.gcube.common.homelibary.model.acl.AccessRights;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A simple utility class providing utilities with respect to
 * access control over repositories.
 */
public class AccessControlUtil {

	// the name of the accessor method for the AccessControlManager
    private static final String METHOD_GET_ACCESS_CONTROL_MANAGER = "getAccessControlManager";
    // the name of the accessor method for the UserManager
    private static final String METHOD_GET_USER_MANAGER = "getUserManager";
    // the name of the accessor method for the PrincipalManager
    private static final String METHOD_GET_PRINCIPAL_MANAGER = "getPrincipalManager";
    // the name of the JackrabbitAccessControlList method getPath
    private static final String METHOD_JACKRABBIT_ACL_GET_PATH = "getPath";
    // the name of the JackrabbitAccessControlList method
    private static final String METHOD_JACKRABBIT_ACL_IS_EMPTY = "isEmpty";
    // the name of the JackrabbitAccessControlList method
    private static final String METHOD_JACKRABBIT_ACL_SIZE = "size";
    // the name of the JackrabbitAccessControlList method
    private static final String METHOD_JACKRABBIT_ACL_ADD_ENTRY = "addEntry";
    // the name of the JackrabbitAccessControlEntry method
    private static final String METHOD_JACKRABBIT_ACE_IS_ALLOW = "isAllow";

    private static final Logger log = LoggerFactory.getLogger(AccessControlUtil.class);

    // ---------- SessionImpl methods -----------------------------------------------------

	/**
     * Returns the <code>AccessControlManager</code> for the given
     * <code>session</code>. If the session does not have a
     * <code>getAccessControlManager</code> method, a
     * <code>UnsupportedRepositoryOperationException</code> is thrown. Otherwise
     * the <code>AccessControlManager</code> is returned or if the call fails,
     * the respective exception is thrown.
     *
     * @param session The JCR Session whose <code>AccessControlManager</code> is
     *            to be returned. If the session is a pooled session, the
     *            session underlying the pooled session is actually used.
     * @return The <code>AccessControlManager</code> of the session
     * @throws UnsupportedRepositoryOperationException If the session has no
     *             <code>getAccessControlManager</code> method or the exception
     *             thrown by the method.
     * @throws RepositoryException Forwarded from the
     *             <code>getAccessControlManager</code> method call.
     */
	public static AccessControlManager getAccessControlManager(Session session)
											throws UnsupportedRepositoryOperationException, RepositoryException {
        return safeInvokeRepoMethod(session, METHOD_GET_ACCESS_CONTROL_MANAGER, AccessControlManager.class);
	}

	// ---------- JackrabbitSession methods -----------------------------------------------

	/**
	 * Returns the <code>UserManager</code> for the given
     * <code>session</code>. If the session does not have a
     * <code>getUserManager</code> method, a
     * <code>UnsupportedRepositoryOperationException</code> is thrown. Otherwise
     * the <code>UserManager</code> is returned or if the call fails,
     * the respective exception is thrown.
	 *
	 * @param session  The JCR Session whose <code>UserManager</code> is
     *            to be returned. If the session is not a <code>JackrabbitSession</code>
     *            uses reflection to retrive the manager from the repository.
	 * @return The <code>UserManager</code> of the session.
	 * @throws AccessDeniedException If this session is not allowed
	 * 			  to access user data.
	 * @throws UnsupportedRepositoryOperationException If the session has no
     *            <code>getUserManager</code> method or the exception
     *            thrown by the method.
	 * @throws RepositoryException Forwarded from the
     *             <code>getUserManager</code> method call.
	 */
	public static UserManager getUserManager(Session session)
										throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
		JackrabbitSession jackrabbitSession = getJackrabbitSession(session);
		if(jackrabbitSession != null) {
			return jackrabbitSession.getUserManager();
		} else {
			return safeInvokeRepoMethod(session, METHOD_GET_USER_MANAGER, UserManager.class);
		}
	}

	/**
	 * Returns the <code>PrincipalManager</code> for the given
     * <code>session</code>. If the session does not have a
     * <code>PrincipalManager</code> method, a
     * <code>UnsupportedRepositoryOperationException</code> is thrown. Otherwise
     * the <code>PrincipalManager</code> is returned or if the call fails,
     * the respective exception is thrown.
	 *
	 * @param session  The JCR Session whose <code>PrincipalManager</code> is
     *            to be returned. If the session is not a <code>JackrabbitSession</code>
     *            uses reflection to retrive the manager from the repository.
	 * @return The <code>PrincipalManager</code> of the session.
	 * @throws AccessDeniedException
	 * @throws UnsupportedRepositoryOperationException If the session has no
	 * 				<code>PrincipalManager</code> method or the exception
     *             	thrown by the method.
	 * @throws RepositoryException Forwarded from the
     *             <code>PrincipalManager</code> method call.
	 */
	public static PrincipalManager getPrincipalManager(Session session)
										throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
		JackrabbitSession jackrabbitSession = getJackrabbitSession(session);
		if(jackrabbitSession != null) {
			return jackrabbitSession.getPrincipalManager();
		} else {
			return safeInvokeRepoMethod(session, METHOD_GET_PRINCIPAL_MANAGER, PrincipalManager.class);
		}
	}

	// ---------- AccessControlList methods -----------------------------------------------

	/**
	 * Returns the path of the node <code>AccessControlList</code> acl
	 * has been created for.
	 */
	public static String getPath(AccessControlList acl) throws RepositoryException {
			return safeInvokeRepoMethod(acl, METHOD_JACKRABBIT_ACL_GET_PATH, String.class);
	}

	/**
	 * Returns <code>true</code> if <code>AccessControlList</code> acl
	 * does not yet define any entries.
	 */
    public static boolean isEmpty(AccessControlList acl) throws RepositoryException {
		return safeInvokeRepoMethod(acl, METHOD_JACKRABBIT_ACL_IS_EMPTY, Boolean.class);
    }

    /**
     * Returns the number of acl entries or 0 if the acl is empty.
     */
    public static int size(AccessControlList acl) throws RepositoryException {
		return safeInvokeRepoMethod(acl, METHOD_JACKRABBIT_ACL_SIZE, Integer.class);
    }

    /**
     * Same as {@link #addEntry(AccessControlList, Principal, Privilege[], boolean, Map)} using
     * some implementation specific restrictions.
     */
	public static boolean addEntry(AccessControlList acl, Principal principal, Privilege privileges[], boolean isAllow)
        							throws AccessControlException, RepositoryException {
    	Object[] args = new Object[] {principal, privileges, isAllow};
    	@SuppressWarnings("rawtypes")
        Class[] types = new Class[] {Principal.class, Privilege[].class, boolean.class};
		return safeInvokeRepoMethod(acl, METHOD_JACKRABBIT_ACL_ADD_ENTRY, Boolean.class, args, types);
    }

    /**
     * Adds an access control entry to the acl consisting of the specified
     * <code>principal</code>, the specified <code>privileges</code>, the
     * <code>isAllow</code> flag and an optional map containing additional
     * restrictions.
     * <p/>
     * This method returns <code>true</code> if this policy was modified,
     * <code>false</code> otherwise.
     */
	public static boolean addEntry(AccessControlList acl, Principal principal, Privilege privileges[], boolean isAllow, @SuppressWarnings("rawtypes") Map restrictions)
    															throws UnsupportedRepositoryOperationException, RepositoryException {
    	Object[] args = new Object[] {principal, privileges, isAllow, restrictions};
    	@SuppressWarnings("rawtypes")
        Class[] types = new Class[] {Principal.class, Privilege[].class, boolean.class, Map.class};
		return safeInvokeRepoMethod(acl, METHOD_JACKRABBIT_ACL_ADD_ENTRY, Boolean.class, args, types);
    }

    /**
     * Replaces existing access control entries in the ACL for the specified
     * <code>principal</code> and <code>resourcePath</code>. Any existing granted
     * or denied privileges which do not conflict with the specified privileges
     * are maintained. Where conflicts exist, existing privileges are dropped.
     * The end result will be at most two ACEs for the principal: one for grants
     * and one for denies. Aggregate privileges are disaggregated before checking
     * for conflicts.
     * @param session
     * @param resourcePath
     * @param principal
     * @param grantedPrivilegeNames
     * @param deniedPrivilegeNames
     * @param removedPrivilegeNames privileges which, if they exist, should be
     * removed for this principal and resource
     * @throws RepositoryException
     * @deprecated use @link {@link #replaceAccessControlEntry(Session, String, Principal, String[], String[], String[], String)} instead.
     */
    @Deprecated
    public static void replaceAccessControlEntry(Session session, String resourcePath, Principal principal,
			String[] grantedPrivilegeNames, String[] deniedPrivilegeNames, String[] removedPrivilegeNames)
    		throws RepositoryException {
    	replaceAccessControlEntry(session,
    			resourcePath,
    			principal,
    			grantedPrivilegeNames,
    			deniedPrivilegeNames,
    			removedPrivilegeNames,
    			null);
    }

    /**
     * Replaces existing access control entries in the ACL for the specified
     * <code>principal</code> and <code>resourcePath</code>. Any existing granted
     * or denied privileges which do not conflict with the specified privileges
     * are maintained. Where conflicts exist, existing privileges are dropped.
     * The end result will be at most two ACEs for the principal: one for grants
     * and one for denies. Aggregate privileges are disaggregated before checking
     * for conflicts.
     * @param session
     * @param resourcePath
     * @param principal
     * @param grantedPrivilegeNames
     * @param deniedPrivilegeNames
     * @param removedPrivilegeNames privileges which, if they exist, should be
     * removed for this principal and resource
     * @param order where the access control entry should go in the list.
     *         Value should be one of these:
     *         <table>
     *          <tr><td>null</td><td>If the ACE for the principal doesn't exist add at the end, otherwise leave the ACE at it's current position.</td></tr>
     * 			<tr><td>first</td><td>Place the target ACE as the first amongst its siblings</td></tr>
	 *			<tr><td>last</td><td>Place the target ACE as the last amongst its siblings</td></tr>
	 * 			<tr><td>before xyz</td><td>Place the target ACE immediately before the sibling whose name is xyz</td></tr>
	 * 			<tr><td>after xyz</td><td>Place the target ACE immediately after the sibling whose name is xyz</td></tr>
	 * 			<tr><td>numeric</td><td>Place the target ACE at the specified numeric index</td></tr>
	 *         </table>
     * @throws RepositoryException
     */
    public static void replaceAccessControlEntry(Session session, String resourcePath, Principal principal,
    			String[] grantedPrivilegeNames, String[] deniedPrivilegeNames, String[] removedPrivilegeNames,
    			String order)
        		throws RepositoryException {
    	AccessControlManager accessControlManager = getAccessControlManager(session);
    	Set<String> specifiedPrivilegeNames = new HashSet<String>();
    	Set<String> newGrantedPrivilegeNames = disaggregateToPrivilegeNames(accessControlManager, grantedPrivilegeNames, specifiedPrivilegeNames);
    	Set<String> newDeniedPrivilegeNames = disaggregateToPrivilegeNames(accessControlManager, deniedPrivilegeNames, specifiedPrivilegeNames);
    	disaggregateToPrivilegeNames(accessControlManager, removedPrivilegeNames, specifiedPrivilegeNames);

    	// Get or create the ACL for the node.
    	AccessControlList acl = null;
    	AccessControlPolicy[] policies = accessControlManager.getPolicies(resourcePath);
    	for (AccessControlPolicy policy : policies) {
    		if (policy instanceof AccessControlList) {
    			acl = (AccessControlList) policy;
    			break;
    		}
    	}
    	if (acl == null) {
    		AccessControlPolicyIterator applicablePolicies = accessControlManager.getApplicablePolicies(resourcePath);
    		while (applicablePolicies.hasNext()) {
    			AccessControlPolicy policy = applicablePolicies.nextAccessControlPolicy();
    			if (policy instanceof AccessControlList) {
    				acl = (AccessControlList) policy;
    				break;
    			}
    		}
    	}
    	if (acl == null) {
    		throw new RepositoryException("Could not obtain ACL for resource " + resourcePath);
    	}
    	// Used only for logging.
    	Set<Privilege> oldGrants = null;
    	Set<Privilege> oldDenies = null;
    	if (log.isDebugEnabled()) {
    		oldGrants = new HashSet<Privilege>();
    		oldDenies = new HashSet<Privilege>();
    	}

    	// Combine all existing ACEs for the target principal.
    	AccessControlEntry[] accessControlEntries = acl.getAccessControlEntries();
    	for (int i=0; i < accessControlEntries.length; i++) {
    		AccessControlEntry ace = accessControlEntries[i];
    		if (principal.equals(ace.getPrincipal())) {
    			if (log.isDebugEnabled()) {
    				log.debug("Found Existing ACE for principal {} on resource {}", new Object[] {principal.getName(), resourcePath});
    			}
    			if (order == null || order.length() == 0) {
    				//order not specified, so keep track of the original ACE position.
    				order = String.valueOf(i);
    			}

    			boolean isAllow = isAllow(ace);
    			Privilege[] privileges = ace.getPrivileges();
    			if (log.isDebugEnabled()) {
    				if (isAllow) {
    					oldGrants.addAll(Arrays.asList(privileges));
    				} else {
    					oldDenies.addAll(Arrays.asList(privileges));
    				}
    			}
    			for (Privilege privilege : privileges) {
    				Set<String> maintainedPrivileges = disaggregateToPrivilegeNames(privilege);
    				// If there is any overlap with the newly specified privileges, then
    				// break the existing privilege down; otherwise, maintain as is.
    				if (!maintainedPrivileges.removeAll(specifiedPrivilegeNames)) {
    					// No conflicts, so preserve the original.
    					maintainedPrivileges.clear();
    					maintainedPrivileges.add(privilege.getName());
    				}
    				if (!maintainedPrivileges.isEmpty()) {
    					if (isAllow) {
    						newGrantedPrivilegeNames.addAll(maintainedPrivileges);
    					} else {
    						newDeniedPrivilegeNames.addAll(maintainedPrivileges);
    					}
    				}
    			}
    			// Remove the old ACE.
    			acl.removeAccessControlEntry(ace);
    		}
    	}

    	//add a fresh ACE with the granted privileges
    	List<Privilege> grantedPrivilegeList = new ArrayList<Privilege>();
    	for (String name : newGrantedPrivilegeNames) {
    		Privilege privilege = accessControlManager.privilegeFromName(name);
    		grantedPrivilegeList.add(privilege);
    	}
    	if (grantedPrivilegeList.size() > 0) {
    		acl.addAccessControlEntry(principal, grantedPrivilegeList.toArray(new Privilege[grantedPrivilegeList.size()]));
    	}

   		//add a fresh ACE with the denied privileges
   		List<Privilege> deniedPrivilegeList = new ArrayList<Privilege>();
   		for (String name : newDeniedPrivilegeNames) {
   			Privilege privilege = accessControlManager.privilegeFromName(name);
   			deniedPrivilegeList.add(privilege);
   		}
   		if (deniedPrivilegeList.size() > 0) {
   			addEntry(acl, principal, deniedPrivilegeList.toArray(new Privilege[deniedPrivilegeList.size()]), false);
   		}


   		//order the ACL
   		reorderAccessControlEntries(acl, principal, order);

    	accessControlManager.setPolicy(resourcePath, acl);
    	if (log.isDebugEnabled()) {
    		List<String> oldGrantedNames = new ArrayList<String>(oldGrants.size());
    		for (Privilege privilege : oldGrants) {
    			oldGrantedNames.add(privilege.getName());
    		}
    		List<String> oldDeniedNames = new ArrayList<String>(oldDenies.size());
    		for (Privilege privilege : oldDenies) {
    			oldDeniedNames.add(privilege.getName());
    		}
    		log.debug("Updated ACE for principalName {} for resource {} from grants {}, denies {} to grants {}, denies {}", new Object [] {
    				principal.getName(), resourcePath, oldGrantedNames, oldDeniedNames, newGrantedPrivilegeNames, newDeniedPrivilegeNames
    			});
    	}
	}

    // ---------- AccessControlEntry methods -----------------------------------------------

    /**
     * Returns true if the AccessControlEntry represents 'allowed' rights or false
     * it it represents 'denied' rights.
     */
    public static boolean isAllow(AccessControlEntry ace) throws RepositoryException {
		return safeInvokeRepoMethod(ace, METHOD_JACKRABBIT_ACE_IS_ALLOW, Boolean.class);
    }

    // ---------- internal -----------------------------------------------------

    /**
     * Use reflection to invoke a repository method.
     */
    @SuppressWarnings("unchecked")
	private static <T> T safeInvokeRepoMethod(Object target, String methodName, Class<T> returnType, Object[] args, @SuppressWarnings("rawtypes") Class[] argsTypes)
    													throws UnsupportedRepositoryOperationException, RepositoryException {
    	try {
    		Method m = target.getClass().getMethod(methodName, argsTypes);
    		if (!m.isAccessible()) {
    			m.setAccessible(true);
    		}
			return (T) m.invoke(target, args);
    	} catch (InvocationTargetException ite) {
            // wraps the exception thrown by the method
            Throwable t = ite.getCause();
            if (t instanceof UnsupportedRepositoryOperationException) {
                throw (UnsupportedRepositoryOperationException) t;
            } else if (t instanceof AccessDeniedException) {
                throw (AccessDeniedException) t;
            } else if (t instanceof AccessControlException) {
                throw (AccessControlException) t;
            } else if (t instanceof RepositoryException) {
                throw (RepositoryException) t;
            } else if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else if (t instanceof Error) {
                throw (Error) t;
            } else {
                throw new RepositoryException(methodName, t);
            }
        } catch (Throwable t) {
            // any other problem is just encapsulated
            throw new RepositoryException(methodName, t);
        }
	}

    private static <T> T safeInvokeRepoMethod(Object target, String methodName, Class<T> returnType, Object... args)
																		throws UnsupportedRepositoryOperationException, RepositoryException {
    	return safeInvokeRepoMethod(target, methodName, returnType, args, new Class[0]);
    }

    /**
     * Unwrap the jackrabbit session.
     */
	private static JackrabbitSession getJackrabbitSession(Session session) {
		if (session instanceof JackrabbitSession)
			return (JackrabbitSession) session;
		else
			return null;
	}

	/**
	 * Helper routine to transform an input array of privilege names into a set in
	 * a null-safe way while also adding its disaggregated privileges to an input set.
	 */
	private static Set<String> disaggregateToPrivilegeNames(AccessControlManager accessControlManager,
			String[] privilegeNames, Set<String> disaggregatedPrivilegeNames)
      throws RepositoryException {
		Set<String> originalPrivilegeNames = new HashSet<String>();
		if (privilegeNames != null) {
			for (String privilegeName : privilegeNames) {
				originalPrivilegeNames.add(privilegeName);
				Privilege privilege = accessControlManager.privilegeFromName(privilegeName);
				disaggregatedPrivilegeNames.addAll(disaggregateToPrivilegeNames(privilege));
			}
		}
		return originalPrivilegeNames;
	}

	/**
	 * Transform an aggregated privilege into a set of disaggregated privilege
	 * names. If the privilege is not an aggregate, the set will contain the
	 * original name.
	 */
	private static Set<String> disaggregateToPrivilegeNames(Privilege privilege) {
		Set<String> disaggregatedPrivilegeNames = new HashSet<String>();
		if (privilege.isAggregate()) {
			Privilege[] privileges = privilege.getAggregatePrivileges();
			for (Privilege disaggregate : privileges) {
				if (disaggregate.isAggregate()) {
					continue; //nested aggregate, so skip it since the privileges are already included.
				}
				disaggregatedPrivilegeNames.add(disaggregate.getName());
			}
		} else {
			disaggregatedPrivilegeNames.add(privilege.getName());
		}
		return disaggregatedPrivilegeNames;
	}

	/**
	 * Move the ACE(s) for the specified principal to the position specified by the 'order'
	 * parameter.
	 *
	 * @param acl the acl of the node containing the ACE to position
	 * @param principal the user or group of the ACE to position
     * @param order where the access control entry should go in the list.
     *         Value should be one of these:
     *         <table>
     * 			<tr><td>first</td><td>Place the target ACE as the first amongst its siblings</td></tr>
	 *			<tr><td>last</td><td>Place the target ACE as the last amongst its siblings</td></tr>
	 * 			<tr><td>before xyz</td><td>Place the target ACE immediately before the sibling whose name is xyz</td></tr>
	 * 			<tr><td>after xyz</td><td>Place the target ACE immediately after the sibling whose name is xyz</td></tr>
	 * 			<tr><td>numeric</td><td>Place the target ACE at the specified index</td></tr>
	 *         </table>
	 * @throws RepositoryException
	 * @throws UnsupportedRepositoryOperationException
	 * @throws AccessControlException
	 */
	private static void reorderAccessControlEntries(AccessControlList acl,
														Principal principal,
														String order)
							throws RepositoryException {
		if (order == null || order.length() == 0) {
			return; //nothing to do
		}
		if (acl instanceof JackrabbitAccessControlList) {
			JackrabbitAccessControlList jacl = (JackrabbitAccessControlList)acl;

			AccessControlEntry[] accessControlEntries = jacl.getAccessControlEntries();
			if (accessControlEntries.length <= 1) {
				return; //only one ACE, so nothing to reorder.
			}

			AccessControlEntry beforeEntry = null;
			if ("first".equals(order)) {
				beforeEntry = accessControlEntries[0];
			} else if ("last".equals(order)) {
				beforeEntry = null;
			} else if (order.startsWith("before ")) {
				String beforePrincipalName = order.substring(7);

				//find the index of the ACE of the 'before' principal
				for (int i=0; i < accessControlEntries.length; i++) {
					if (beforePrincipalName.equals(accessControlEntries[i].getPrincipal().getName())) {
						//found it!
						beforeEntry = accessControlEntries[i];
						break;
					}
				}

				if (beforeEntry == null) {
					//didn't find an ACE that matched the 'before' principal
					throw new IllegalArgumentException("No ACE was found for the specified principal: " + beforePrincipalName);
				}
			} else if (order.startsWith("after ")) {
				String afterPrincipalName = order.substring(6);

				//find the index of the ACE of the 'after' principal
				for (int i = accessControlEntries.length - 1; i >= 0; i--) {
					if (afterPrincipalName.equals(accessControlEntries[i].getPrincipal().getName())) {
						//found it!

						// the 'before' ACE is the next one after the 'after' ACE
						if (i >= accessControlEntries.length - 1) {
							//the after is the last one in the list
							beforeEntry = null;
						} else {
							beforeEntry = accessControlEntries[i + 1];
						}
						break;
					}
				}

				if (beforeEntry == null) {
					//didn't find an ACE that matched the 'after' principal
					throw new IllegalArgumentException("No ACE was found for the specified principal: " + afterPrincipalName);
				}
			} else {
				try {
					int index = Integer.parseInt(order);
					if (index > accessControlEntries.length) {
						//invalid index
						throw new IndexOutOfBoundsException("Index value is too large: " + index);
					}

					if (index == 0) {
						beforeEntry = accessControlEntries[0];
					} else {
						//the index value is the index of the principal.  A principal may have more
						// than one ACEs (deny + grant), so we need to compensate.
						Set<Principal> processedPrincipals = new HashSet<Principal>();
						for (int i = 0; i < accessControlEntries.length; i++) {
							Principal principal2 = accessControlEntries[i].getPrincipal();
							if (processedPrincipals.size() == index &&
									!processedPrincipals.contains(principal2)) {
								//we are now at the correct position in the list
								beforeEntry = accessControlEntries[i];
								break;
							}

							processedPrincipals.add(principal2);
						}
					}
				} catch (NumberFormatException nfe) {
					//not a number.
					throw new IllegalArgumentException("Illegal value for the order parameter: " + order);
				}
			}

			//now loop through the entries to move the affected ACEs to the specified
			// position.
			for (int i = accessControlEntries.length - 1; i >= 0; i--) {
				AccessControlEntry ace = accessControlEntries[i];
				if (principal.equals(ace.getPrincipal())) {
					//this ACE is for the specified principal.
					jacl.orderBefore(ace, beforeEntry);
				}
			}
		} else {
			throw new IllegalArgumentException("The acl must be an instance of JackrabbitAccessControlList");
		}
	}
	
	
	private static AccessControlEntry[] getEffettiveAccessControlEntries(Session session, String absPath) throws RepositoryException {
		AccessControlManager accessControlManager = AccessControlUtil.getAccessControlManager(session);
		AccessControlPolicy[] policies = accessControlManager.getEffectivePolicies(absPath);
		for (AccessControlPolicy accessControlPolicy : policies) {
			if (accessControlPolicy instanceof AccessControlList) {
				AccessControlEntry[] accessControlEntries = ((AccessControlList)accessControlPolicy).getAccessControlEntries();
				return accessControlEntries;
			}
		}
		return new AccessControlEntry[0];
	}
	
	
	private static AccessControlEntry[] getDeclaredAccessControlEntries(Session session, String absPath) throws RepositoryException {
		AccessControlManager accessControlManager = AccessControlUtil.getAccessControlManager(session);
		AccessControlPolicy[] policies = accessControlManager.getPolicies(absPath);
		for (AccessControlPolicy accessControlPolicy : policies) {
			if (accessControlPolicy instanceof AccessControlList) {
				AccessControlEntry[] accessControlEntries = ((AccessControlList)accessControlPolicy).getAccessControlEntries();
				return accessControlEntries;
			}
		}
		return new AccessControlEntry[0];
	}
	
	
	public static Map<String, AccessRights> getACL(String absPath, Session session) throws Exception {

		Map<String, AccessRights> accessMap = new HashMap<String, AccessRights>();
		AccessControlEntry[] entries = getDeclaredAccessControlEntries(session, absPath);
		if (entries != null) {
			for (AccessControlEntry ace : entries) {
				Principal principal = ace.getPrincipal();
				AccessRights accessPrivileges = accessMap.get(principal.getName());

				if (accessPrivileges == null) {
					accessPrivileges = new AccessRights();
					accessMap.put(principal.getName(), accessPrivileges);
				}
				boolean allow = AccessControlUtil.isAllow(ace);
				if (allow) {
					Privilege[] privileges = ace.getPrivileges();
					for (Privilege privilege : privileges)
						accessPrivileges.getGranted().add(privilege.getName());
				} else {
					Privilege[] privileges = ace.getPrivileges();
					for (Privilege privilege : privileges)
						accessPrivileges.getDenied().add(privilege.getName());
				}
			}
		}
		return accessMap;


	}
	
	
	public static Map<String, AccessRights> getEACL(String absPath, Session session) throws Exception {

		Map<String, AccessRights> accessMap = new HashMap<String, AccessRights>();
		AccessControlEntry[] entries = getEffettiveAccessControlEntries(session, absPath);
		if (entries != null) {
			for (AccessControlEntry ace : entries) {
				Principal principal = ace.getPrincipal();
				AccessRights accessPrivileges = accessMap.get(principal.getName());

				if (accessPrivileges == null) {
					accessPrivileges = new AccessRights();
					accessMap.put(principal.getName(), accessPrivileges);
				}
				boolean allow = AccessControlUtil.isAllow(ace);
				if (allow) {
					Privilege[] privileges = ace.getPrivileges();
					for (Privilege privilege : privileges)
						accessPrivileges.getGranted().add(privilege.getName());
				} else {
					Privilege[] privileges = ace.getPrivileges();
					for (Privilege privilege : privileges)
						accessPrivileges.getDenied().add(privilege.getName());
				}
			}
		}
		return accessMap;
	}
	
	
	
	
}