/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

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

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
	//}}
}
