/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.security;

import java.security.Principal;
import java.security.acl.*;
import java.util.*;
import org.apache.commons.collections.*;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.db.UserProxyManagement;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

class AclObject implements DynamoAcl
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private AclOperations m_ops;                   // ACL operations object
  private NamespaceCache m_ns_cache;             // namespace cache object
  private UserProxyManagement m_proxy;           // for generating user and group proxies
  private AceCache m_ace_cache;                  // cache for ACEs
  private int m_aclid;                           // ID of this ACL
  private String m_aclname;                      // name of this ACL

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  AclObject(AclOperations ops, NamespaceCache ns_cache, UserProxyManagement proxy, AceCache ace_cache,
	    Map data)
  {
    m_ops = ops;
    m_ns_cache = ns_cache;
    m_proxy = proxy;
    m_ace_cache = ace_cache;
    m_aclid = ((Integer)(data.get(SRMOperations.HMKEY_ACLID))).intValue();
    m_aclname = ((String)(data.get(SRMOperations.HMKEY_ACLNAME)));

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private static final int getAceID(AclEntry entry)
  {
    if (entry instanceof DynamoAce)
      return ((DynamoAce)entry).getAceID();
    throw new IllegalArgumentException("expected a DynamoAce as argument");

  } // end getAceID

  private final PropertyKey getPKey(Permission perm) throws DatabaseException
  {
    if (perm instanceof PermObject)
      return ((PermObject)perm).getPKey();
    if (perm instanceof DynamoPermission)
    { // convert to a PropertyKey
      DynamoPermission dp = (DynamoPermission)perm;
      return new PropertyKey(m_ns_cache.namespaceNameToId(dp.getNamespace()),dp.getName());

    } // end if

    throw new IllegalArgumentException("expected DynamoPermission as argument");

  } // end getPKey

  /*--------------------------------------------------------------------------------
   * Implementations from interface Owner
   *--------------------------------------------------------------------------------
   */

  public boolean addOwner(Principal caller, Principal owner) throws NotOwnerException
  {
    try
    { // call down to the database
      return m_ops.addOwner(m_aclid,new PrincipalID(caller),new PrincipalID(owner));

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end addOwner

  public boolean deleteOwner(Principal caller, Principal owner) throws NotOwnerException, LastOwnerException
  {
    try
    { // call down to the database
      return m_ops.deleteOwner(m_aclid,new PrincipalID(caller),new PrincipalID(owner));

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end deleteOwner

  public boolean isOwner(Principal owner)
  {
    try
    { // call down to the database
      return m_ops.testOwner(m_aclid,new PrincipalID(owner));

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end isOwner

  /*--------------------------------------------------------------------------------
   * Implementations from interface Acl
   *--------------------------------------------------------------------------------
   */

  public void setName(Principal caller, String name) throws NotOwnerException
  {
    try
    { // call down to the database
      m_ops.setAclName(m_aclid,new PrincipalID(caller),name);
      m_aclname = name;

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end setName

  public String getName()
  {
    return m_aclname;

  } // end getName

  public boolean addEntry(Principal caller, AclEntry entry) throws NotOwnerException
  {
    try
    { // call down to the database
      return m_ops.insertAce(m_aclid,new PrincipalID(caller),getAceID(entry),-1);

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end addEntry

  public boolean removeEntry(Principal caller, AclEntry entry) throws NotOwnerException
  {
    try
    { // call down to the database
      return m_ops.removeAce(m_aclid,new PrincipalID(caller),getAceID(entry));

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end removeEntry

  public Enumeration getPermissions(Principal user)
  {
    PrincipalID prid = new PrincipalID(user);
    Collection rc = null;
    try
    { // is this for a user or a group?
      if (prid.isGroup())
      { // compute permissions only for the group itself
	// (everything that appears in positive set but not in negative set)
	Set positive = m_ops.getGroupPermissionSetForGroup(m_aclid,prid.getID(),false);
	Set negative = m_ops.getGroupPermissionSetForGroup(m_aclid,prid.getID(),true);
	rc = CollectionUtils.subtract(positive,negative);

      } // end if
      else
      { // compute permissions for the user and for all groups of which the user is a member
	Set pos_group = m_ops.getGroupPermissionSetForUser(m_aclid,prid.getID(),false);
	Set neg_group = m_ops.getGroupPermissionSetForUser(m_aclid,prid.getID(),true);
	Set pos_user = m_ops.getUserPermissionSet(m_aclid,prid.getID(),false);
	Set neg_user = m_ops.getUserPermissionSet(m_aclid,prid.getID(),true);
	// "Real" groups have been normalized by removing all elements present in the corresponding
	// "other" group as well
	Collection real_pos_group = CollectionUtils.subtract(pos_group,neg_group);
	Collection real_neg_group = CollectionUtils.subtract(neg_group,pos_group);
	Collection real_pos_user = CollectionUtils.subtract(pos_user,neg_user);
	Collection real_neg_user = CollectionUtils.subtract(neg_user,pos_user);
	// "grant_group" = all permissions granted by the groups and not explicitly denied by user
	Collection grant_group = CollectionUtils.subtract(real_pos_group,real_neg_user);
	// "deny_group" = all permissions denied by the groups and not explicitly granted by user
	Collection deny_group = CollectionUtils.subtract(real_neg_group,real_pos_user);
	// "grant" - all grants from user plus all surviving grants from groups
	Collection grant = CollectionUtils.union(real_pos_user,grant_group);
	// "deny" - all denies from user plus all surviving denies from groups
	Collection deny = CollectionUtils.union(real_neg_user,deny_group);
	rc = CollectionUtils.subtract(grant,deny);

      } // end else

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

    // Compute the proper return value.
    if (rc.isEmpty())
      return Collections.enumeration(Collections.EMPTY_LIST);
    ArrayList real_rc = new ArrayList(rc.size());
    Iterator it = rc.iterator();
    while (it.hasNext())
    { // transform PropertyKey values into PermObjects
      PropertyKey pk = (PropertyKey)(it.next());
      real_rc.add(new PermObject(pk,m_ns_cache));

    } // end while

    return Collections.enumeration(real_rc);

  } // end getPermissions

  public Enumeration entries()
  {
    try
    { // get the IDs of all the ACEs
      int[] ids = m_ops.getAceIDs(m_aclid);

      // now get the ACEs themselves
      ArrayList rc = new ArrayList(ids.length);
      for (int i=0; i<ids.length; i++)
	rc.add(m_ace_cache.getAce(ids[i]));
      return Collections.enumeration(rc);

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end entries

  public boolean checkPermission(Principal principal, Permission permission)
  {
    PrincipalID prid = new PrincipalID(principal);
    try
    { // test user or group permissions, depending
      if (prid.isGroup())
	return m_ops.testGroupPermission(m_aclid,prid.getID(),getPKey(permission));
      else
	return m_ops.testUserPermission(m_aclid,prid.getID(),getPKey(permission));

    } // end try
    catch (DatabaseException e)
    { // translate this into a SecurityRuntimeException
      throw new SecurityRuntimeException(e);

    } // end catch

  } // end checkPermission

  public String toString()
  {
    return "DynamoAcl[" + m_aclid + "]: " + m_aclname;

  } // end toString

  /*--------------------------------------------------------------------------------
   * Implementations from interface DynamoAcl
   *--------------------------------------------------------------------------------
   */

  public int getAclID()
  {
    return m_aclid;

  } // end getAclID

  public int getAceCount() throws DatabaseException
  {
    return m_ops.getAceCount(m_aclid);

  } // end getAceCount

  public DynamoAce getAce(Principal caller, int index) throws DatabaseException, NotOwnerException
  {
    if (index<0)
      throw new IndexOutOfBoundsException("ACE index less than 0");

    // get the ACE ID from the database and then get it from the ACE cache
    return m_ace_cache.getAce(m_ops.getAceID(m_aclid,new PrincipalID(caller),index));

  } // end getAce

  public boolean insertEntry(Principal caller, AclEntry entry, int at_index)
      throws DatabaseException, NotOwnerException
  {
    // call down to the database
    return m_ops.insertAce(m_aclid,new PrincipalID(caller),getAceID(entry),at_index);

  } // end insertEntry

  public boolean testPermission(DynamoUser user, String perm_namespace, String perm_name)
      throws DatabaseException
  {
    // call down to the database
    return m_ops.testUserPermission(m_aclid,user.getUID(),
				    new PropertyKey(m_ns_cache.namespaceNameToId(perm_namespace),perm_name));

  } // end testPermission

} // end class AclObject
