/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.server.realm;

import org.bodington.sqldatabase.*;

import java.security.acl.*;
import org.bodington.database.*;
import org.bodington.server.*;
import org.bodington.server.realm.*;
import java.util.*;

import org.apache.log4j.Logger;

/**
 * An implementation of java.security.acl.AclEntry that uses the Bodington server for
 * data storage.  The database has a primary key for each entry, the id of the asociated
 * group and Acl and a string representing permissions.  When the permission string is
 * taken from the database it is unpacked into a Hashtable of permissions and a boolean
 * to indicate whether the permission is positive or negative.  THe format of the string
 * is [-|+]firstpermission[,nextpermission,anotherpermission]
 * 
 * @author Jon Maber
 * @version 1.0
 */

public class AclEntry 
	extends org.bodington.sqldatabase.SqlPersistentObject 
	implements java.security.acl.AclEntry
	{
    private static Logger log = Logger.getLogger(AclEntry.class);
    
	private PrimaryKey acl_entry_id;
	private PrimaryKey acl_id;
	private PrimaryKey group_id;
	
	private boolean is_negative;	
	private Hashtable permissions;

	/**
	 * Finds an aclentry indexed by its primary key.
	 * 
	 * @param key The primary key of the requested aclentry.
	 * @return The entry or null.
	 * @exception org.bodington.server.BuildingServerException Thrown if there were problems fetching the object.
	 */
	public static AclEntry findAclEntry( PrimaryKey key )
	    throws BuildingServerException
	    {
	    return (AclEntry)findPersistentObject( key, "org.bodington.server.realm.AclEntry" );
	    }
	
	/**
	 * Finds an entry by a search expression.
	 * 
	 * @param where An SQL like WHERE clause.
	 * @return The entry or null.
	 * @exception org.bodington.server.BuildingServerException Thrown if there were problems fetching the object.
	 */
	public static AclEntry findAclEntry( String where )
	    throws BuildingServerException
	    {
	    return (AclEntry)findPersistentObject( where, "org.bodington.server.realm.AclEntry" );
	    }
    
    public static AclEntry findAclEntry( PrimaryKey group_id, PrimaryKey acl_id )
        throws BuildingServerException
        {
        return (AclEntry)findAclEntry("group_id = "+ group_id.intValue()+
            " AND acl_id = "+ acl_id);
        }
	
	/**
	 * Finds a set of entries.
	 * 
	 * @param where An SQL WHERE clause.
	 * @return An Enumeration of AclEntries.
	 * @exception org.bodington.server.BuildingServerException Thrown if there were problems fetching the object.
	 */
	public static Enumeration findAclEntries( String where )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, "org.bodington.server.realm.AclEntry" );
	    }
	

	/**
	 * Initialises a table of permissions.
	 */
	public AclEntry()
		{
		permissions= new Hashtable();
        }
		
    /**
     * Returns primary key of this object.
     * 
     * @return A primary key.
     */

    public PrimaryKey getPrimaryKey()
	    {
        return getAclEntryId();
    	}

    /**
     * Calls setAclEntryId()
     * 
     * @param key A primary key from the database.
     */


    public void setPrimaryKey(PrimaryKey key)
	    {
	    setAclEntryId( key );
	    }

	/**
	 * A method for PersistentObject aspect of this class.
	 * 
	 * @param key A primary key from the database.
	 */
	
	public void setAclEntryId( PrimaryKey key )
		{
		acl_entry_id = key;
    	setUnsaved();
		}

    /**
     * A method for PersistentObject aspect of this class.
     * 
     * @return A primary key.
     */
    
    public PrimaryKey getAclEntryId()
    	{
    	return acl_entry_id;
    	}

	/**
	 * A method for PersistentObject aspect of this class.
	 * 
	 * @param key A primary key from the database.
	 */
	
	public void setAclId( PrimaryKey key )
		{
		acl_id = key;
    	setUnsaved();
		}
    
    /**
     * A method for PersistentObject aspect of this class.
     * 
     * @return A primary key.
     */
    
    public PrimaryKey getAclId()
    	{
    	return acl_id;
    	}

    /**
     * Fetches the Acl this entry belongs to.
     * 
     * @return The Acl or null.
     * @exception org.bodington.server.BuildingServerException Thrown if there were problems fetching the object.
     */
    public Acl getAcl()
        throws BuildingServerException
        {
    	return Acl.findAcl( acl_id );
        }

    /**
     * Sets the Acl this entry is associated with (actually it will
     * only store the acl_id from the passed acl).
     * 
     * @param a The acl to associate this entry with.
     */
    public void setAcl( Acl a )
        {
        setAclId( a.getAclId() );
        }
	/**
	 * A method for PersistentObject aspect of this class.
	 * 
	 * @param key A primary key from the database.
	 */
	
	public void setGroupId( PrimaryKey key )
		{
		group_id = key;
    	setUnsaved();
		}
    
    /**
     * A method for PersistentObject aspect of this class.
     * 
     * @return A primary key.
     */
    
    public PrimaryKey getGroupId()
    	{
    	return group_id;
    	}

    /**
     * A method to fetch the group associated with this AclEntry.
     * 
     * @return The group if found.
     * @exception org.bodington.server.BuildingServerException Thrown if there were problems fetching the object.
     */
    
    public Group getGroup()
    	throws BuildingServerException
    	{
    	return Group.findGroup( group_id );
    	}

    /**
     * Sets the group for this entry.  Actually it stores just the
     * group_id.
     * 
     * @param g The group to associate this entry with.
     */
    public void setGroup( Group g )
        {
        setGroupId( g.getGroupId() );
        }
	/**
	 * Doesn't actually store the coded string but unpacks it into
	 * a hashtable and a boolean.
	 * 
	 * @param coded Something like "+see,view,post" or "-create,manage".
	 * @exception org.bodington.server.BuildingServerException Thrown if there were problems with database.
	 */

	public void setPermissionCoded( String coded )
		throws BuildingServerException
		{
		is_negative=false;
		permissions=new Hashtable();
		if ( coded==null )
			return;
		String totok=coded;
		if ( coded.charAt( 0 ) == '+' )
			totok=coded.substring( 1, coded.length() );
		else
			if ( coded.charAt( 0 ) == '-' )
				{
				is_negative=true;
				totok=coded.substring( 1, coded.length()-1 );
				}
		StringTokenizer tok = new StringTokenizer( totok, "," );
		String p;
		Permission permit;
		while ( tok.hasMoreTokens() )
			{
			p=tok.nextToken();
			permit = Permission.forName( p );
			if ( permit==null )
				throw new BuildingServerException( "Invalid permission in AclEntry" );
			permissions.put( p, permit );
			}
    	setUnsaved();
		}

	/**
	 * Constructs a string to represent a list of permissions.
	 * Puts them in the order preferred by Permission class.
	 * 
	 * @return Something like "+see,view,post".
	 */
	
	public String getPermissionCoded()
		{
		StringBuffer coded = new StringBuffer( 64 );
		coded.append( is_negative?"-":"+" );
		Enumeration enumeration=Permission.permissions();
		Permission permission;
		boolean first=true;
		while ( enumeration.hasMoreElements() )
			{
			permission=(Permission)enumeration.nextElement();
			if ( permissions.containsKey( permission.toString() ) )
			    {
			    if ( !first )
				    coded.append( "," );
			    first=false;
			    coded.append( permission.toString() );
			    }
			}
		return coded.toString();
		}

    /**
     * Implementation of java.security.acl.AclEntry method.
     * Similar to getPermissionCoded() but includes the
     * group ID.
     * 
     * @return Something like "+345=see,view,post".
     */

    public String toString()
	    {
	    Permission permission;
	    StringBuffer buffer= new StringBuffer();
	    buffer.append( is_negative?"-":"+" );
	    buffer.append( group_id );
	    buffer.append( "=" );
		Enumeration enumeration=permissions.keys();
		for ( int i=0; enumeration.hasMoreElements(); i++ )
			{
			if ( i>0 )
				buffer.append( "," );
			buffer.append( (String)enumeration.nextElement() );
			}
        return buffer.toString();
    	}

    /**
     * Sets the principal that these permissions apply to.
     * 
     * @param user For this
     * implementation throws an exception is the principal is not 
     * instance of server.realm.Group
     * @return true if the principal is set, false if there was already a principal set 
     * for this entry.
     */

    public boolean setPrincipal( java.security.Principal user)
	    {
	    Group g = (Group)user;
	    group_id = g.getGroupId();
	    return true;
	    }

    /**
     * Implementation of java.security.acl.AclEntry method.
     * Makes this a negative Acl.
     */

    public void setNegativePermissions()
	    {
	    is_negative=true;
    	setUnsaved();
	    }

    /**
     * Implementation of java.security.acl.AclEntry method.
     * 
     * @param permission The permission to remove.
     * @return true if the permission is removed, false if the permission was not part of this entry's permission set.
     */

    public boolean removePermission( java.security.acl.Permission permission )
    	{
    	if ( !permissions.containsKey( permission.toString() ) )
        	return false;
        permissions.remove( permission.toString() );
    	setUnsaved();
        return true;
	    }

    /**
     * Implementation of java.security.acl.AclEntry method.
     * 
     * @return Enumeration of the current permissions.
     */

    public Enumeration permissions()
	    {
        return permissions.elements();
    	}

    /**
     * Implementation of java.security.acl.AclEntry method.
     * 
     * @return True if this is negative AclEntry.
     */

    public boolean isNegative()
	    {
        return is_negative;
	    }

    /**
     * Implementation of java.security.acl.AclEntry method.
     * 
     * @return The prinicipal this entry applies to.  Current implementation
     * will always return type com.bnodington.server.realm.Group
     */

    public java.security.Principal getPrincipal()
	    {
	    Group group;
	    try
	    	{
	    	group = getGroup();
	    	}
	    catch ( BuildingServerException bex )
	    	{
	        log.error(bex.getMessage(), bex );	       
	    	return null;
	    	}
	    
        return group;
    	}

    /**
     * Implementation of java.security.acl.AclEntry method.
     * If you want to save to cloned object back to the database
     * you probably want to call {@link #setAclEntryId(PrimaryKey)}
     * with a null.
     * @return A new AclEntry object based on this one.
     */
    public Object clone()
	    {
	    AclEntry other = new AclEntry();
	    other.acl_entry_id = acl_entry_id;
	    other.acl_id = acl_id;
	    other.group_id = group_id;
	    other.is_negative = is_negative;
	    other.permissions = (Hashtable)permissions.clone();
        return other;
	    }

    /**
     * Implementation of java.security.acl.AclEntry method.
     * 
     * @param permission The permission to test.
     * @return True if this entry includes the permission.
     */

    public boolean checkPermission( java.security.acl.Permission permission )
		{
		return permissions.containsKey( permission.toString() );
		}

    /**
     * Implementation of java.security.acl.AclEntry method.
     * 
     * @param permission The permission to add to this entry.
     * @return true if the permission was added, false if the permission was already part of this entry's permission set.
     */

    public boolean addPermission( java.security.acl.Permission permission )
	    {
	    if ( checkPermission( permission ) )
	        return false;
	    permissions.put( permission.toString(), permission );
    	setUnsaved();
	    return true;
	    }
		//{{DECLARE_CONTROLS
	//}}
}
