/* ======================================================================
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.resources;

import java.util.logging.*;



import org.bodington.server.realm.*;
import org.bodington.sqldatabase.*;
import org.bodington.database.*;
import org.bodington.pool.*;
import org.bodington.text.BigString;
import org.bodington.server.BuildingSession;
import org.bodington.server.BuildingSessionImpl;
import org.bodington.server.BuildingContext;
import org.bodington.server.BuildingServerException;
import org.bodington.text.Metadatum;

import java.util.*;
import java.io.File;
import java.lang.ref.*;

/**
 * An instance of this class represents a resource that will be available
 * for users to access.  This is due for lots of development work.
 * Resources are arranged in a tree structure so there are fields to 
 * allow suitable indexing and each resource has an associated access
 * control list.  In this set of packages resources don't really do anything
 * but in the future a resource could represent a MCQ test paper, a web
 * document, a discussion room etc. etc.
 * There are "find" methods, property set/get methods, methods for adding
 * removing child resources and a checkPermission method.
 * The left and right index refer to database fields that are useful for
 * tree operations - the visitation model.  See chapter 26 of SQL for
 * smarties, Joe Celko.
 * 
 * @author Jon Maber
 * @version 1.0
 * @see ResourceTree
 */
public class Resource extends org.bodington.sqldatabase.SqlPersistentObject
    {
	public static final int RESOURCE_UNKNOWN		= 0;
	public static final int RESOURCE_SITE      	= 1;
	public static final int RESOURCE_BUILDING  	= 2;
	public static final int RESOURCE_FLOOR			= 3;
	public static final int RESOURCE_SUITE			= 4;
	public static final int RESOURCE_ROOM			= 5;
	public static final int RESOURCE_EMPTY_ROOM	= 6;
	public static final int RESOURCE_DOCUMENT		= 7;
	public static final int RESOURCE_TOOL			= 8;
	public static final int RESOURCE_RECYCLER		= 9;
	public static final int RESOURCE_HEADING		= 10;


	private static Hashtable pin_down_table = new Hashtable();
	
    
    //persistent properties
    private PrimaryKey resource_id;
    private PrimaryKey parent_resource_id;
    private PrimaryKey zone_id;
    private PrimaryKey acl_id;
    
    //private int left_index;
    //private int right_index;
    
    private int http_facility_no;
    private Integer http_ui_style;
    private String name;

	private PrimaryKey        title_big_string_id;
	private PrimaryKey introduction_big_string_id;
	private PrimaryKey  description_big_string_id;

    private int flags;

    //references that have to be set up.
    
    // list of ids for child resources
    // a null vector indicates that they haven't 
    // been loaded
    private Vector child_resource_ids=null;
    

    private File root_folder=null;
    private String root_address=null;

    private File gen_root_folder=null;
    private String gen_root_address=null;
	
	
	private static final int recent_list_size=3;
	private PrimaryKey[] recent_user_ids;
	private Permission[] recent_permissions;
	private boolean[] recent_result;
	private long[] last_checked_properly;
	private long most_recent;


        private long properties_last_modified;
        

	private SoftReference ref_menu;

    
    
	public static Resource findResource( PrimaryKey key )
	    throws BuildingServerException
	    {
	    Resource thing = (Resource)findPersistentObject( key, "org.bodington.server.resources.Resource" );
	    if ( thing!=null )
	    	pin_down_table.put( thing.getResourceId(), thing );
	    return thing;
	    }
	
	public static Resource findResource( String where )
	    throws BuildingServerException
	    {
	    return (Resource)findPersistentObject( where, "org.bodington.server.resources.Resource" );
	    }
	
	public static Enumeration findResources( String where, String order )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, order, "org.bodington.server.resources.Resource" );
	    }
	
	public static Enumeration findResourceIds( String where, String order )
	    throws BuildingServerException
	    {
	    return findPrimaryKeys( where, order, "org.bodington.server.resources.Resource" );
	    }
	

        
    public Resource()
        {
		recent_user_ids = new PrimaryKey[recent_list_size];
		recent_permissions = new Permission[recent_list_size];
		recent_result = new boolean[recent_list_size];
		last_checked_properly = new long[recent_list_size];
		most_recent = System.currentTimeMillis();
                properties_last_modified = System.currentTimeMillis();
        }


	// return a class that will 'front end' the resource
	// for a user.  If there is none return null.
	public Class sessionClass()
		{
		//return dummy_session_instance.getClass();
		//return NavigationSessionImpl.class;
		return BuildingSessionImpl.class;
		}

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

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

    public PrimaryKey getResourceId()
        {
        return resource_id;
        }

    public void setResourceId( PrimaryKey key )
        {
        resource_id = key;
        setUnsaved();
        }

    public PrimaryKey getParentResourceId()
        {
        return parent_resource_id;
        }

    public void setParentResourceId( PrimaryKey key )
        {
        parent_resource_id = key;
        setUnsaved();
        }

    public Resource getParent()
	    throws BuildingServerException
        {
        if ( getParentResourceId()==null )
            return null;
        return findResource( getParentResourceId() );
        }

    public PrimaryKey getAclId()
        {
        return acl_id;
        }

    public void setAclId( PrimaryKey key )
        {
        acl_id = key;
        setUnsaved();
        }


    /**
     * Sets property.
     * 
     * @param key The new zone id.
     */
    public void setZoneId( PrimaryKey key )
	    {
	    zone_id = key;
	    setUnsaved();
	    }

    /**
     * Sets property.
     * 
     * @return The zone id.
     */
    public PrimaryKey getZoneId()
	    {
        return zone_id;
	    }

	/**
	 * Sets zone id from given zone.
	 * 
	 * @param z The new zone.
	 */
	public void setZone( Zone z )
		{
		if ( z == null )
			setZoneId( null );
		else
			setZoneId( z.getZoneId() );
		}

	/**
	 * Finds the ZOne object associated with this ALias.
	 * 
	 * @return A Zone or null.
	 * @exception org.bodington.server.BuildingServerException If there is a database problem.
	 */
	public Zone getZone()
		throws BuildingServerException
		{
		if ( zone_id == null )
			return null;
		return Zone.findZone( zone_id );
		}

	/**
	 * Finds the ZOne object associated with this resource or container resource.
	 * 
	 * @return A Zone or null.
	 * @exception org.bodington.server.BuildingServerException If there is a database problem.
	 */
	public PrimaryKey getEffectiveZoneId()
		throws BuildingServerException
		{
		if ( zone_id != null )
			return zone_id;
			
		Resource parent = getParent();
		if ( parent == null )
			return null;
			
		return  parent.getEffectiveZoneId();
		}

    
	/**
	 * Finds the ZOne object associated with this ALias.
	 * 
	 * @return A Zone or null.
	 * @exception org.bodington.server.BuildingServerException If there is a database problem.
	 */
	public Zone getEffectiveZone()
		throws BuildingServerException
		{
		PrimaryKey key = getEffectiveZoneId();
		if ( key == null )
			return null;
		return Zone.findZone( key );
		}


	public int getHttpFacilityNo()
		{
		return http_facility_no;
		}
	public void setHttpFacilityNo( int f )
		{
		if ( f==http_facility_no ) return;
		http_facility_no=f;
    	setUnsaved();
		}

	public Integer getHttpUIStyle()
		{
		return http_ui_style;
		}
	public void setHttpUIStyle( Integer i )
		{
		if ( i ==null && http_ui_style==null )
			return;
		if ( i!= null && http_ui_style!=null && i.equals( http_ui_style ) ) return;
		http_ui_style=i;
    	setUnsaved();
		}


	public Integer getImplicitHttpUIStyle()
		throws BuildingServerException
		{
		// if this resource doesn't have a set style,
		// find most recent ancestor that has specified style
		Integer s;
		Resource parent=this;
		
		s = getHttpUIStyle();
		while ( s==null )
			{
			parent = parent.getParent();
			if ( parent==null )
				return s;
			s = parent.getHttpUIStyle();
			}
			
		return s;
		}


	public String getName()
		{
		return name;
		}
	public void setName( String x )
		{
		name=x;
    	setUnsaved();
		}


    public PrimaryKey getTitleBigStringId()
        {
        return title_big_string_id;
        }

    public void setTitleBigStringId( PrimaryKey key )
        {
        title_big_string_id = key;
        setUnsaved();
        }

	public BigString getTitleBigString()
	    throws BuildingServerException
        {
        if ( getTitleBigStringId() != null )
            return BigString.findBigString( getTitleBigStringId() );
        return null;
		}
		
	public void setTitleBigString( BigString b )
		{
		setTitleBigStringId( b.getBigStringId() );
		}
		
	public String getTitle()
	    throws BuildingServerException
		{
		BigString big = getTitleBigString();
		if ( big == null )
			return null;
		return big.getString();
		}

	public void setTitle( String t )
	    throws BuildingServerException
		{
		boolean fresh=false;
		BigString big = getTitleBigString();
		if ( big==null )
			{
			fresh=true;
			big = new BigString();
			}
		big.setString( t );
		big.save();
		if ( fresh )
			setTitleBigString( big );
		}


    public PrimaryKey getIntroductionBigStringId()
        {
        return introduction_big_string_id;
        }

    public void setIntroductionBigStringId( PrimaryKey key )
        {
        introduction_big_string_id = key;
        setUnsaved();
        }

	public BigString getIntroductionBigString()
	    throws BuildingServerException
        {
        if ( getIntroductionBigStringId() != null )
            return BigString.findBigString( getIntroductionBigStringId() );
        return null;
		}
		
	public void setIntroductionBigString( BigString b )
		{
		setIntroductionBigStringId( b.getBigStringId() );
		}
		
	public String getIntroduction()
	    throws BuildingServerException
		{
		BigString big = getIntroductionBigString();
		if ( big == null )
			return null;
		return big.getString();
		}

	public void setIntroduction( String t )
	    throws BuildingServerException
		{
		boolean fresh=false;
		BigString big = getIntroductionBigString();
		if ( big==null )
			{
			fresh=true;
			big = new BigString();
			}
		big.setString( t );
		big.save();
		if ( fresh )
			setIntroductionBigString( big );
		}


    public PrimaryKey getDescriptionBigStringId()
        {
        return description_big_string_id;
        }

    public void setDescriptionBigStringId( PrimaryKey key )
        {
        description_big_string_id = key;
        setUnsaved();
        }

	public BigString getDescriptionBigString()
	    throws BuildingServerException
        {
        if ( getDescriptionBigStringId() != null )
            return BigString.findBigString( getDescriptionBigStringId() );
        return null;
		}
		
	public void setDescriptionBigString( BigString b )
		{
		setDescriptionBigStringId( b.getBigStringId() );
		}
		
	public String getDescription()
	    throws BuildingServerException
		{
		BigString big = getDescriptionBigString();
		if ( big == null )
			return null;
		return big.getString();
		}

	public void setDescription( String t )
	    throws BuildingServerException
		{
		boolean fresh=false;
		BigString big = getDescriptionBigString();
		if ( big==null )
			{
			fresh=true;
			big = new BigString();
			}
		big.setString( t );
		big.save();
		if ( fresh )
			setDescriptionBigString( big );
		}


	public static final int FLAG_USE_PARENT_ACL  = 1;
	public static final int FLAG_HAS_CUSTOM_MENU = 2;
	
	public int getFlags()
		{
		return flags;
		}
	public void setFlags( int f )
		{
		if ( f==flags ) return;
		flags=f;
    	setUnsaved();
		}
	public boolean getUseParentAcl()
		{
		return (flags & FLAG_USE_PARENT_ACL)!=0;
		}
	public void setUseParentAcl( boolean b )
		{
		if ( b != getUseParentAcl() )
			setFlags( flags ^ FLAG_USE_PARENT_ACL );
		}

	public boolean getHasCustomMenu()
		{
		return (flags & FLAG_HAS_CUSTOM_MENU)!=0;
		}
	public void setHasCustomMenu( boolean b )
		{
		if ( b != getHasCustomMenu() )
			setFlags( flags ^ FLAG_HAS_CUSTOM_MENU );
		}



	public int getResourceType()
		{
		return RESOURCE_UNKNOWN;
		}


	public void ensureChildrenLoaded()
	    throws BuildingServerException
		{
		synchronized ( this )
			{
			if ( child_resource_ids != null )
				return;
			}
			
		// In case getting a database connection is going to take some time
		// make sure it is done outside of synchronized code.
		try
			{
			BuildingContext.getContext().getConnection();
			}
		catch ( ObjectPoolException opex )
			{
			throw new BuildingServerException( opex );
			}
			
		synchronized ( this )
			{
			if ( child_resource_ids != null )
				return;

			Enumeration enum = Resource.findResourceIds( "parent_resource_id = " + getResourceId(), "left_index" );
			
			child_resource_ids  = new Vector();
			while ( enum.hasMoreElements() )
				{
				child_resource_ids.addElement( enum.nextElement() );
				}
			}
		}

	Vector getChildIds()
	    throws BuildingServerException
		{
		ensureChildrenLoaded();
		return child_resource_ids;
		}


	void setChildIds( Vector siblings )
		{
		child_resource_ids = siblings;
		}
		
    /**
     * Adds a child.  The list of children is not stored when the resource is saved
     * because it can be rebuilt from the parent_resource_id of the children or from
     * the left/right indexes.
     * 
     * @param key The primary key of the child resource.
     */
    public void addChildResourceId( PrimaryKey key )
	    throws BuildingServerException
        {
        ensureChildrenLoaded();
        // there was a bug in the Installer class that meant
        // resources could include two references to the same
        // child.  This makes sure only one reference can exist.
        if ( !child_resource_ids.contains( key ) )
            child_resource_ids.addElement( key );
        }
    
    public void removeChildResourceId( PrimaryKey key )
	    throws BuildingServerException
        {
        ensureChildrenLoaded();
        
        child_resource_ids.removeElement( key );
        }
    
    public Enumeration findChildren()
	    throws BuildingServerException
        {
        ensureChildrenLoaded();
        
        Resource r;
        Vector children = new Vector();
        for ( int i=0; i<child_resource_ids.size(); i++ )
            {
            r = findResource( (PrimaryKey)child_resource_ids.elementAt( i ) );
            children.addElement( r );
            }
        return children.elements();
        }

    public void sortChildren( Vector keys )
	    throws BuildingServerException
        {
        ensureChildrenLoaded();

        ResourceTree.getInstance().sortResources( this, keys );
        }

	public Enumeration findAncestors()
	    throws BuildingServerException
	    {
	    Vector branch = new Vector();
	    Resource current = this;
	    do
	    	{
	    	branch.insertElementAt( current, 0 );
	    	current = current.getParent();
	    	}
		while ( current!=null );
		return branch.elements();
	    }

	public String getFullName()
        throws BuildingServerException
		{
		Resource current;
		Enumeration enum = findAncestors();
		StringBuffer name = new StringBuffer();
		name.append( "/" );
		//skip root if there is one.
		if ( enum.hasMoreElements() )
			enum.nextElement();
		while ( enum.hasMoreElements() )
			{
			current = (Resource)enum.nextElement();
			name.append( current.getName() );
			name.append( "/" );
			}
		return name.toString();
		}
		
	public boolean isInside( Resource other )
        throws BuildingServerException
		{
		return ResourceTree.getInstance().isInside( this, other );
		}
		
    public org.bodington.server.realm.Acl getAcl()
	    throws BuildingServerException
        {
        if ( getAclId() != null )
            return Acl.findAcl( getAclId() );
        return null;
		}
		
	


	private int checkRecentPermission( User user, java.security.acl.Permission permission )
		{
		//Logger.getLogger( "org.bodington" ).fine( "checkRecentPermission A" );

		long now = System.currentTimeMillis();
		if ( (now - most_recent) >= 5000 )
			return -1;
			
		
		if ( !(permission instanceof org.bodington.server.realm.Permission) )
			return -1;

		//Logger.getLogger( "org.bodington" ).fine( "checkRecentPermission B" );
		
		for ( int i=0; i<recent_list_size && this.recent_user_ids[i]!=null; i++ )
			{
			if ( user.getUserId().equals( this.recent_user_ids[i] )	&&
				 permission.equals( this.recent_permissions[i] )			&&
				 (now - this.last_checked_properly[i]) < 5000						)

				{
				//Logger.getLogger( "org.bodington" ).fine( "returning " + ((this.recent_result[i])?1:0) );
				return (this.recent_result[i])?1:0;
				}
			}
		//Logger.getLogger( "org.bodington" ).fine( "returning don't know" );
		return -1;
		}

	private void storeRecentPermission( User user, java.security.acl.Permission permission, boolean result )
		{
		//Logger.getLogger( "org.bodington" ).fine( "storeRecentPermission A" );

		long now = System.currentTimeMillis();
		most_recent=now;
		
		if ( !(permission instanceof org.bodington.server.realm.Permission) )
			return;
		
		//Logger.getLogger( "org.bodington" ).fine( "storeRecentPermission B" );

		//overwrite an out of date entry if there is one
		int i;
		for ( i=0; i<recent_list_size && this.recent_user_ids[i]!=null; i++ )
			{
			if ( (now - this.last_checked_properly[i]) >= 5000 )

				{
				this.recent_user_ids[i] = user.getUserId();
				this.recent_permissions[i] = (Permission)permission;
				this.recent_result[i] = result;
				this.last_checked_properly[i] = now;
				return;
				}
			}
			
		//otherwise store in null entry
		if ( i<recent_list_size && this.recent_user_ids[i]==null )
			{
			this.recent_user_ids[i] = user.getUserId();
			this.recent_permissions[i] = (Permission)permission;
			this.recent_result[i] = result;
			this.last_checked_properly[i] = now;
			return;
			}

		//Logger.getLogger( "org.bodington" ).fine( "not stored" );
		//or don't store it at all - all the current entries are being used
		return;
		}


	/**
	 * Checks a specific access right for the user in this thread's
	 * BuildingContext.  (Determined by this resource's acl.)
	 * 
	 * @param permission
	 * @return True if the current user has the specified permission with this
	 * resource.
	 */
	public boolean checkPermission( java.security.acl.Permission permission )
	    {
        User user = (User)BuildingContext.getContext().getUser();
	    return checkPermission( user, permission );
	    }
	    
    /**
     * Checks a specific access right for the user in this thread's
     * BuildingContext.  (Determined by this resource's acl.)
     * 
     * @param permission
     * @return True if the current user has the specified permission with this
     * resource.
     */
    public boolean checkPermission( User user, java.security.acl.Permission permission )
	{
	if ( user == null )
	    {
	    Exception e = new Exception( "Attempt to check permission on null user." );
	    Logger.getLogger( "org.bodington" ).logp( 
		Level.SEVERE, 
		"Resource", 
		"checkPermission", 
		e.getMessage(), 
		e );
	    return false;
	    }

	boolean p;
	int pr = checkRecentPermission( user, permission );
	if ( pr>=0 )
	return ( pr!=0 );

	p = checkPermission( user, permission, false );
	storeRecentPermission( user, permission, p );

	return p;
	}
	    
	/**
	 * Checks a specific access right for the user in this thread's
	 * BuildingContext.  (Determined by this resource's acl.)
	 * 
	 * @param permission
	 * @return True if the current user has the specified permission with this
	 * resource.
	 */
	private boolean checkPermission( User user, java.security.acl.Permission permission, boolean allowed )
	    {
	    Acl acl;
	    
	    try
	        {
			Resource parent = getParent();
			
			//check parent first so local acl can override it.
			if ( parent != null )
				{
				// if the resource is set to  check acl of parent do so now
				if ( getUseParentAcl() )
					allowed = parent.checkPermission( user, permission, allowed );
				}
    	    
    	    // now run through local acl
	        acl = getAcl();
	        if ( acl != null )
	            allowed = acl.checkPermission( user, permission, allowed );

	        // administration and sysadmin access is added now overiding local acl
	        // if the user has this access to an ancestor.
			if (	!allowed && 
					parent != null && 
						(permission.equals( Permission.ADMINISTER ) || permission.equals( Permission.SYSADMIN )      ) )
					allowed = parent.checkPermission( user, permission, allowed );


			// if still denied and we are checking manage view or see
			// grant access to people with administer or sysadmin access.
	        if ( !allowed &&
        			( permission.equals( Permission.MANAGE ) || 
        			  permission.equals( Permission.EDIT )   || 
        			  permission.equals( Permission.VIEW )   || 
        			  permission.equals( Permission.SEE )       )	)
        		{
        		allowed = checkPermission( user, Permission.ADMINISTER, allowed );
        		if ( !allowed )
        			allowed = checkPermission( user, Permission.SYSADMIN, allowed );
        		}


			
	        }
	    catch ( Exception ex )
	        {
		Logger.getLogger( "org.bodington" ).logp( 
		    Level.SEVERE, 
		    "Resource", 
		    "checkPermission", 
		    ex.getMessage(), 
		    ex );
	        }
	        
        return allowed;
	    }
	
	    
	/**
	 * Get table of groups who have specified permission
	 * 
	 * @param permission The access right to check.
	 * @return An enumeration of objects of type org.bodington.server.realm.Principal.
	 */
	public synchronized Hashtable everyGroupWhoCan( java.security.acl.Permission permission, boolean include_admin )
		throws BuildingServerException
		{
		Hashtable groups = new Hashtable();
		Hashtable acl_groups;
	    Acl acl;
	    
	    try
	        {
			Resource parent = getParent();
			
			//check parent first so local acl can override it.
			if ( parent != null )
				{
				// if the resource is set to  check acl of parent do so now
				if ( getUseParentAcl() )
					{
					acl_groups = parent.everyGroupWhoCan( permission, false );
					groups.putAll( acl_groups );
					}
				}
    	    
    	    // now run through local acl
	        acl = getAcl();
	        if ( acl != null )
	        	{
	            acl_groups = acl.everyGroupWhoCan( permission );
				groups.putAll( acl_groups );
	            }


			// administration and sysadmin access is added now overiding local acl
	        // if the group has this access to an ancestor.
			if ( parent != null && 
						(permission.equals( Permission.ADMINISTER ) || permission.equals( Permission.SYSADMIN )      ) )
				{
				acl_groups = parent.everyGroupWhoCan( permission, false );
				groups.putAll( acl_groups );
				}


			// if we are checking manage, view or see
			// add groups with administer or sysadmin access.
	        if ( include_admin &&
	        		( permission.equals( Permission.MANAGE ) || 
        			  permission.equals( Permission.EDIT )   || 
        			  permission.equals( Permission.VIEW )   || 
        			  permission.equals( Permission.SEE )       )	)
        		{
				acl_groups = parent.everyGroupWhoCan( Permission.ADMINISTER, false );
				groups.putAll( acl_groups );
				acl_groups = parent.everyGroupWhoCan( Permission.SYSADMIN, false );
				groups.putAll( acl_groups );
        		}
			
	        }
	    catch ( Exception ex )
	        {
		Logger.getLogger( "org.bodington" ).logp( 
		    Level.SEVERE, 
		    "Resource", 
		    "everyGroupWhoCan", 
		    ex.getMessage(), 
		    ex );
	        }
	        
        return groups;
		}


	
	/**
	 * Get table of groups who have specified permission
	 * 
	 * @param permission The access right to check.
	 * @return An enumeration of objects of type org.bodington.server.realm.Principal.
	 */
	public synchronized Enumeration everyoneWhoCan( java.security.acl.Permission permission, boolean include_admin )
		throws BuildingServerException
		{
		Hashtable groups = everyGroupWhoCan( permission, include_admin );
		Vector principals = new Vector();
		org.bodington.server.realm.Group entrygroup;

		//amendment - if there are no groups don't bother looking for members!
		if ( groups.isEmpty() )
			return  principals.elements();
			
		StringBuffer where = new StringBuffer();
		where.append( "user_id IN (SELECT user_id FROM members WHERE group_id IN ( " );
		Enumeration enum = groups.elements();
		for ( int i=0; enum.hasMoreElements(); i++ )
			{
			entrygroup = (org.bodington.server.realm.Group)enum.nextElement();
			principals.addElement( entrygroup );
			if ( i>0 )
				where.append( ", " );
			where.append( entrygroup.getGroupId().toString() );
			}
		where.append( " ) )" );
			
		enum = User.findUsers( where.toString(), "surname" );
		while ( enum.hasMoreElements() )
			principals.addElement( enum.nextElement() );
		
		return principals.elements();
		}
		
		
		
	
	public File getWebPublishFolder()
		throws BuildingServerException
		{
		if ( root_folder!=null )
			return root_folder;
			
		String store = BuildingContext.getContext().getProperty( "webpublish.filestore" );
		root_folder = new File( store, getResourceId().toString() );
		if ( root_folder.exists() )
			{
			if ( !root_folder.isDirectory() )
				throw new BuildingServerException( "File exists in place of web publishing folder for this resource." );
			}
		else
			root_folder.mkdir();
			
		return root_folder;
		}
	    
	public String getWebPublishAddress()
		throws BuildingServerException
		{
		if ( root_address!=null )
			return root_address;
			
		String store = BuildingContext.getContext().getProperty( "webpublish.webaddress" );
		root_address = store + getResourceId().toString() + "/";
		return root_address;
		}
	    
	public File getGeneratedFileFolder()
		throws BuildingServerException
		{
		if ( gen_root_folder!=null )
			return gen_root_folder;
			
		String store = BuildingContext.getContext().getProperty( "filegeneration.filestore" );
		gen_root_folder = new File( store, getResourceId().toString() );
		if ( gen_root_folder.exists() )
			{
			if ( !gen_root_folder.isDirectory() )
				throw new BuildingServerException( "File exists in place of web publishing folder for this resource." );
			}
		else
			gen_root_folder.mkdir();
			
		return gen_root_folder;
		}
	    
	public String getGeneratedFileAddress()
		throws BuildingServerException
		{
		if ( gen_root_address!=null )
			return gen_root_address;
			
		String store = BuildingContext.getContext().getProperty( "filegeneration.webaddress" );
		gen_root_address = store + getResourceId().toString() + "/";
		return gen_root_address;
		}
	    
	    
	    
	public void checkUpload( String pathname, String mime_type )
		throws BuildingServerException
		{
		// do nothing - approve all uploads
		
		//throw new BuildingServerException( "This resource doesn't support uploading of files." );
		}
	public void checkFolder( String pathname )
		throws BuildingServerException
		{
		// do nothing - approve all new folders
		
		//throw new BuildingServerException( "This resource doesn't support uploading of files." );
		}

    
    
    public Menu getMenu()
   	{
   	if ( ref_menu == null )
   		return null;
   		
   	Menu menu = (Menu)ref_menu.get();

   	if ( menu == null )
   		ref_menu = null;
   		
   	return menu;
   	}
   	
   public void setMenu( Menu menu )
   	{
   	if ( menu == null )
   		{
			ref_menu = null;
   		return;
   		}
   		
   	ref_menu = new SoftReference( menu );
   	}

   
    public long getPropertiesLastModifiedTime()
        throws BuildingServerException
    {
        // when this specific resource was modified but...
        long when = properties_last_modified;
        Resource current = getParent();
        while ( current != null )
        {
            // in effect this resource was modified when any
            // ancestor was modified
            if ( current.properties_last_modified > when )
                when = current.properties_last_modified;
            current = current.getParent();
        }
        return when;
    }

    
    
    public void unspecifyProperty( String name )
        throws BuildingServerException
    {
        Metadatum m = Metadatum.findMetadatumByIdAndName( resource_id, name );
        if ( m != null )
            m.delete();
        properties_last_modified = System.currentTimeMillis();
    }
    
    public void specifyProperty( String name, String value )
        throws BuildingServerException
    {
        if ( value == null )
            throw new BuildingServerException( "Null properties not supported." );
        Metadatum m = Metadatum.findMetadatumByIdAndName( resource_id, name );
        if ( m == null )
        {
            m=new Metadatum();
            m.setObjectId( resource_id );
            m.setName( name );
        }
        m.setValue( value );
        m.save();
        properties_last_modified = System.currentTimeMillis();
    }
    
    public String getSpecifiedProperty( String name )
        throws BuildingServerException
    {
        Metadatum m = Metadatum.findMetadatumByIdAndName( resource_id, name );
        if ( m != null )
            return m.getValue();
        return null;
    }
    
    public String getProperty( String name )
        throws BuildingServerException
    {
        Resource current = this;
        Metadatum m;
        do
        {
            m = Metadatum.findMetadatumByIdAndName( current.getResourceId(), name );
            if ( m!= null )
                return m.getValue();
            if ( !current.isPropertyInheritable( name ) )
                return null;
            current = current.getParent();
        }
        while ( current != null );
        
        return null;
    }
    
    public boolean isPropertyInheritable( String name )
        throws BuildingServerException
    {
        return true;
    }
    
    
    
    }
    
