/* ======================================================================
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.io.File;
import java.lang.ref.SoftReference;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.bodington.database.IndexKey;
import org.bodington.database.PrimaryKey;
import org.bodington.pool.ObjectPoolException;
import org.bodington.server.BuildingContext;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingSessionImpl;
import org.bodington.server.realm.Acl;
import org.bodington.server.realm.Permission;
import org.bodington.server.realm.User;
import org.bodington.server.realm.Group;
import org.bodington.server.realm.Zone;
import org.bodington.servlet.FacilityList;
import org.bodington.servlet.facilities.Facility;
import org.bodington.text.BigString;
import org.bodington.text.Metadatum;

/**
 * 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
 * @author Antony Corfield
 * @version 1.0
 * @see ResourceTree
 * @see FacilityList
 */
public class Resource extends org.bodington.sqldatabase.SqlPersistentObject
    {
    
    
    private static Logger log = Logger.getLogger(Resource.class);
    /**
     * Time in ms that we keep permission data for.
     */
    private static int permissionCacheTimeout = 5000;
    
    /**
     * Constant to represent an unknown resource.
     */
	public static final int RESOURCE_UNKNOWN = 0;
    /**
     * Constant to represent a site.
     */
    public static final int RESOURCE_SITE = 1;
    /**
     * Constant to represent a building.
     */
    public static final int RESOURCE_BUILDING = 2;
    /**
     * Constant to represent a floor.
     */
    public static final int RESOURCE_FLOOR = 3;
    /**
     * Constant to represent a suite.
     */
    public static final int RESOURCE_SUITE = 4;
    /**
     * Constant to represent room.
     */
    public static final int RESOURCE_ROOM = 5;
    /**
     * Constant to represent an empty room.
     * @deprecated No longer used. Preserved for backwards compatibility.
     */
    public static final int RESOURCE_EMPTY_ROOM = 6;
    /**
     * Constant to represent a document.
     */
    public static final int RESOURCE_DOCUMENT = 7;
    /**
     * Constant to represent a tool.
     * @deprecated No longer used. Preserved for backwards compatibility.
     */
    public static final int RESOURCE_TOOL = 8;
    /**
     * Constant to represent a recycler.
     */
    public static final int RESOURCE_RECYCLER = 9;
    /**
     * Constant to represent a heading.
     * @see HeadingResource
     */
    public static final int RESOURCE_HEADING = 10;
    /**
     * Constant to represent a directory.
     */
    public static final int RESOURCE_DIRECTORY = 11;
    /**
     * Constant to represent a query.
     */
    public static final int RESOURCE_QUERY = 12;
    /**
     * Constant to represent a questionnaire.
     * @see org.bodington.assessment.Questionnaire
     */
    public static final int RESOURCE_QUESTIONNAIRE = 13;
    /**
     * Constant to represent a group container.
     */
    public static final int RESOURCE_GROUPCONTAIN = 14;
    /**
     * Constant to represent a CBL string.
     */
    public static final int RESOURCE_CBLSTRING = 15;
    /**
     * Constant to represent a text questionnaire.
     * @see org.bodington.assessment.TextQPaper
     */
    public static final int RESOURCE_TEXTQ = 16;
    /**
     * Constant to represent a password.
     */
    public static final int RESOURCE_PASSWORD = 17;
    /**
     * Constant to represent a search resource.
     */
    public static final int RESOURCE_SEARCH = 18;
    /**
     * Constant to represent a notification.
     */
    public static final int RESOURCE_NOTIFICATION = 19;
    /**
     * Constant to represent a log book.
     * @see org.bodington.logbook.server.LogBook
     */
    public static final int RESOURCE_LOGBOOK = 20;
    /**
     * Constant to represent a permission.
     */
    public static final int RESOURCE_PERMISSION = 21;
    /**
     * Constant to represent an employee.
     */
    public static final int RESOURCE_EMPLOYEE = 22;
    /**
     * Constant to represent a messaging room.
     * @see org.bodington.messaging.MessagingRoom
     */
    public static final int RESOURCE_MESSAGINGROOM = 23;
    /**
     * Constant to represent an MCQ test.
     * @see org.bodington.assessment.McqPaper
     */
    public static final int RESOURCE_MCQ = 24;
    /**
     * Constant to represent a communication room.
     */
    public static final int RESOURCE_COMMUNICATION = 25;
    /**
     * Constant to represent a pigeon hole.
     * @see org.bodington.assessment.PigeonHole
     */
    public static final int RESOURCE_PIGEONHOLE = 26;
    /**
     * Constant to represent an alias editor.
     * @see org.bodington.server.realm.AliasEditor
     */
    public static final int RESOURCE_ALIAS = 27;
    /**
     * Constant to represent a workshop suite.
     */
    public static final int RESOURCE_WORKSHOPSUITE = 28;
    /**
     * Constant to represent a media document.
     */
    public static final int RESOURCE_MEDIADOCUMENT = 29;
    /**
     * Constant to represent a personal page.
     */
    public static final int RESOURCE_PERSONALPAGE = 30;
    /**
     * Constant to represent a user.
     */
    public static final int RESOURCE_USER = 31;
    
    /**
     * Constant to represent an external link.
     * @see ExternalLink
     */
    public static final int RESOURCE_LINK		= 101;

	/** 
	 * Constant to represent a sentient link. <p>
	 * <i>(WebLearn modification: 14/05/2004 Colin Tatham)</i>
     * @see SentientLink        
	 */
    /* TODO:
     *  - Create superclass/interface for all types of links?
     */
	public static final int RESOURCE_SENTIENTLINK		= 102;

	/**
	 * Constant to represent a text block. <p>
	 * <i>(WebLearn addition : 12/2004 Colin Tatham)</i>
     * @see TextBlockResource
	 */
    public static final int RESOURCE_TEXTBLOCK		= 103;
	/**
	 * Constant to represent a news feed. <p>
	 * <i>(WebLearn addition : 12/2004 Colin Tatham)</i>
     * @see NewsFeedResource
	 */
    public static final int RESOURCE_NEWSFEED		= 104;
	/**
	 * Constant to represent a blog. <p>
	 * <i>(WebLearn addition : 12/2004 Colin Tatham)</i>
     * @see BlogResource
	 */
    public static final int RESOURCE_BLOG			= 105;
    /**
     * Constant to represent a quick link.<p>
     * <i>(WebLearn addition [21/01/05] Alexis O'Connor)</i>
     * @see QuickLink
     */
    public static final int RESOURCE_QUICKLINK = 106;
    /**
     * Constant to represent an easybuilder resource.<p>
     * <i>(WebLearn addition [21/01/05] Alexis O'Connor)</i>
     */
    public static final int RESOURCE_EASYBUILDER = 107;
	/**
	 * Constant to represent an image block. <p>
	 * <i>(WebLearn addition : 04/05/2005 Colin Tatham)</i>
     * @see ImageBlockResource
	 */
    public static final int RESOURCE_IMAGEBLOCK		= 108;

    /**
     * Constant for a Users container.
     * @see HomeContainerResource
     */
    public static final int RESOURCE_HOME_CONTAINER = 109;
    
    /**
     * Constant for a Users home space.
     * @see HomeResource
     */
    public static final int RESOURCE_HOME = 110;
    
    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;
    
    /**
     * Instance variable for an open timestamp.
     */
    private java.sql.Timestamp open_timestamp;
    
    /**
     * Instance variable for a close timestamp.
     */
    private java.sql.Timestamp close_timestamp;

    // 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 File gen_root_folder=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;

    private ResourceParentIndexKey index[] = new ResourceParentIndexKey[1];
    
    
	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" );
	    }
	
    /**
     * Find a resource that is a child of the current one. The advantage of this
     * method over the existing ones is that you don't have to load all of the
     * children of the parent.
     */
    public static Resource findResource( PrimaryKey parent, String child )
        throws BuildingServerException
        {
        return (Resource)findPersistentObject(new ResourceParentIndexKey(parent, child),
            "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()
        {
        index[0] = new ResourceParentIndexKey();
		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();
        }
    
    /**
     * Create a resource with the required data.
     * @param name The name of the resource.
     * @param title The title of the resource.
     * @param description The desctiption of the resource.
     * @param introduction The introduction to the resource.
     * @throws BuildingServerException If there was a problem creating the resource.
     * This may well because of a failure to create one of the BigString objects.
     */
    public Resource(String name, String title, String description, String introduction) throws BuildingServerException
    {
    		this();
    		setName(name);
    		setTitle(title);
    		setDescription(description);
    		setIntroduction(introduction);
    }


    /**
     * Get the session class that will be useful for this resource.
     * Most subclasses should override this method.
     * @return A Class that performs all the operations. The returned
     * class should have a empty constructor and implement BuildingSession.
     * @see org.bodington.server.BuildingSession
     */
	public Class sessionClass()
		{
		return BuildingSessionImpl.class;
		}

    public IndexKey[] getIndexKeys()
    {
        return index;
    }
    
    public boolean matchesKey(IndexKey index)
    {
        return this.index[0].equals(index);
    }

    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;
        index[0].setParent(key);
        setUnsaved();
        }

    /**
     * Gets the parent resource to this one.
     * @return The parent resource or <code>null</code> if this resource doesn't
     * have a parent.
     */
    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 );
		}


    /**
     * Get the Facility number that should handle this resource.
     * This returned value can be used to lookup the {@link Facility} that
     * should handle this resource through the {@link FacilityList}. 
     * @return The number of the handling Facility.
     */
	public int getHttpFacilityNo()
		{
		return http_facility_no;
		}
    
    /**
     * Set the Facility number.
     * @see #getHttpFacilityNo()
     * @param f The facility number.
     */
	public void setHttpFacilityNo( int f )
		{
		if ( f==http_facility_no ) return;
		http_facility_no=f;
        resourceType = mapHttpFacilityNoToResourceType(f);
    	setUnsaved();
		}

	public Integer getHttpUIStyle()
		{
		return http_ui_style;
		}
    
    /**
     * Set the User Interface Style that should be used when looking for
     * templates for this resource.
     * @param i The interface 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;
        index[0].setChild(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 );
		}


   /**
     * Gets the timestamp for when this resource will be available.
     * @return the timestamp for when this resource will be available.
     */
    public java.sql.Timestamp getOpenTimestamp()
    {
        return open_timestamp;
    }

    /**
     * Sets the timestamp for when this resource will be available.
     * @param t the timestamp for when this resource will be available.
     */
    public void setOpenTimestamp( java.sql.Timestamp t )
    {
        open_timestamp = t;
        setUnsaved();
    }

    /**
     * Get the date for when this resource will be available.
     * @return the date for when this resource will be available.
     */
    public java.util.Date getOpenDate()
    {
        if ( open_timestamp == null ) return null;
        return new java.util.Date( open_timestamp.getTime() );
    }

    /**
     * Get the timestamp for when this resource will no longer be available.
     * @return the timestamp for when this resource will no longer be available.
     */
    public java.sql.Timestamp getCloseTimestamp()
    {
        return close_timestamp;
    }

    /**
     * Set the timestamp for when this resource will no longer be available.
     * @param t the timestamp for when this resource will no longer be
     *        available.
     */
    public void setCloseTimestamp( java.sql.Timestamp t )
    {
        close_timestamp = t;
        setUnsaved();
    }

    /**
     * Get the date for when this resource will no longer be available.
     * @return the date for when this resource will no longer be available.
     */
    public java.util.Date getCloseDate()
    {
        if ( close_timestamp == null ) return null;
        return new java.util.Date( close_timestamp.getTime() );
    }

    /**
     * Indicates whether or not this resource is available. A resource is always
     * available if neither the open or close timestamps have been specified.
     * Otherwise, the current time is checked to see if it comes before any
     * specified close timestamp and / or equal to, or after any specified open
     * timestamp.
     * @return <code>true</code> if this resource is available, otherwise
     *         <code>false</code>.
     * @see #getOpenTimestamp()
     * @see #getCloseTimestamp()
     */
    public boolean isAvailable()
    {
        if ( open_timestamp == null && close_timestamp == null ) 
            return true;
        if ( open_timestamp == null )
            return (System.currentTimeMillis() < getCloseTimestamp().getTime());
        if ( close_timestamp == null )
            return (System.currentTimeMillis() >= getOpenTimestamp().getTime());

        return (System.currentTimeMillis() < getCloseTimestamp().getTime() 
            && System.currentTimeMillis() >= getOpenTimestamp().getTime());
    }

	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 );
		}

	private int resourceType = RESOURCE_UNKNOWN; 

	/**
     * Get the type of this resource. This should match one of the constants in
     * this class beginning with <code>RESOURCE_ </code>.
     * If a resource subclasses this class then is should only override this
     * method if the resource has only one resource type. An example of a
     * a subclass that doesn't override this method is
     * {@link org.bodington.server.realm.AliasEditor}
     * as it has multiple resource types.
     * @return the type of this resource.
     */
	public int getResourceType()
		{
		return resourceType;
		}
    
    
    /**
     * Map a facility number to a resource type.
     * @param httpFacilityNo the facility number to map.
     * @return a value corresponding to a resource type constant.
     * @see #getResourceType()
     * @see #getHttpFacilityNo()
     * @see org.bodington.servlet.facilities.Facility
     */
    private int mapHttpFacilityNoToResourceType(int httpFacilityNo)
    {
        /*
         * NOTES: this method assumes that there is a clean correspondance
         * between facilities and resources, which there isn't necessarily(!).
         * Resources that do not have their own subclass typically up until now
         * do not actually know their type. However, examining the current
         * object interactions suggested that the the httpFacilityNo property is
         * set, so this enables us to discover our type from there. This method
         * is clearly entirely dependant on the facility numbers not changing!!
         * Future directions might be to establish a resources type at
         * instantiation time (constructor) and / or to harmonize this class and
         * Facility.
         */
        switch (httpFacilityNo)
        {
            case 28:
                return RESOURCE_DIRECTORY;
            case 102:
                return RESOURCE_BLOG;
            case 2:
                return RESOURCE_QUERY;
            case 56:
                return RESOURCE_QUICKLINK;
            case 3:
                return RESOURCE_FLOOR;
            case 12:
                return RESOURCE_QUESTIONNAIRE;
            case 27:
                return RESOURCE_GROUPCONTAIN;
            case 23:
                return RESOURCE_BUILDING;
            case 101:
                return RESOURCE_NEWSFEED;
            case 17:
                return RESOURCE_CBLSTRING;
            case 16:
                return RESOURCE_TEXTQ;
            case 8:
                return RESOURCE_PASSWORD;
            case 33:
                return RESOURCE_SEARCH;
            case 15:
                return RESOURCE_NOTIFICATION;
            case 35:
                return RESOURCE_LOGBOOK;
            case 6:
                return RESOURCE_PERMISSION;
            case 5:
                return RESOURCE_ROOM;
            case 4:
                return RESOURCE_SUITE;
            case 9:
                return RESOURCE_EMPLOYEE;
            case 103:
                return RESOURCE_TEXTBLOCK;
            case 29:
                return RESOURCE_MESSAGINGROOM;
            case 11:
                return RESOURCE_MCQ;
            case 30:
                return RESOURCE_RECYCLER;
            case 10:
                return RESOURCE_COMMUNICATION;
            case 13:
                return RESOURCE_PIGEONHOLE;
            case 25:
                return RESOURCE_ALIAS;
            case 22:
                return RESOURCE_LINK;
            case 100:
                return RESOURCE_EASYBUILDER;
            case 14:
                return RESOURCE_WORKSHOPSUITE;
            case 99:
                return RESOURCE_SENTIENTLINK;
            case 21:
                return RESOURCE_MEDIADOCUMENT;
            case 24:
                return RESOURCE_SITE;
            case 20:
                return RESOURCE_PERSONALPAGE;
            case 7:
                return RESOURCE_DOCUMENT;
            case 26:
                return RESOURCE_USER;
            case 31:
                return RESOURCE_HEADING;
            case 105:
                return RESOURCE_HOME_CONTAINER;
            case 1: // 'default' facility.
            default:
                return RESOURCE_UNKNOWN;
        }
    }

    
    /**
     * Checks to see if this resource should only be available to sysadmins.
     * This shouldn't be here.
     * @param resourceType The type of the resource.
     * @return True if only a sysadmin can create this resource.
     */
    public static boolean isSysadminResource(int resourceType)
    {
    		switch (resourceType) 
    		{
    			case RESOURCE_ALIAS:
    			case RESOURCE_USER:
    			case RESOURCE_DIRECTORY:
                case RESOURCE_GROUPCONTAIN:
    			case RESOURCE_PASSWORD:
    			case RESOURCE_NOTIFICATION:
    			case RESOURCE_SEARCH:
    			case RESOURCE_HOME_CONTAINER:
    				return true;
    		}
    		return false;
    }
    
    /**
     * Gets the shortname for the resource constant. As an example you get
     * something like "site", "building" or "suite". This is NOT used for looking up
     * the template folder that should be used, that comes from the Facility.
     * @param type The type of resource. 
     * @return A short string for this resource.
     * @see Facility#facilityname
     */
    public static String getShortName(int type)
    {
        switch (type)
        {
            case RESOURCE_DIRECTORY:  
                return "directory";
            case RESOURCE_BLOG: 
                return "blog";
            case RESOURCE_QUERY: 
                return "query";
            case RESOURCE_QUICKLINK: 
                return "quicklink";
            case RESOURCE_FLOOR:
                return "floor";
            case RESOURCE_QUESTIONNAIRE:
                return "questionnaire";
            case RESOURCE_GROUPCONTAIN:
                return "groupcontain";
            case RESOURCE_BUILDING:
                return "building";
            case RESOURCE_NEWSFEED:
                return "newsfeed";
            case RESOURCE_CBLSTRING:
                return "cblstring";
            case RESOURCE_TEXTQ:
                return "textq";
            case RESOURCE_PASSWORD:
                return "password";
            case RESOURCE_SEARCH:
                return "search";
            case RESOURCE_NOTIFICATION:
                return "notification";
            case RESOURCE_LOGBOOK:
                return "logbook";
            case RESOURCE_PERMISSION:
                return "permission";
            case RESOURCE_ROOM:
                return "room";
            case RESOURCE_SUITE:
                return "suite";
            case RESOURCE_EMPLOYEE:
                return "employee";
            case RESOURCE_TEXTBLOCK:
                return "textblock";
            case RESOURCE_IMAGEBLOCK:
                return "imageblock";
            case RESOURCE_MESSAGINGROOM:
                return "messagingroom";
            case RESOURCE_MCQ:
                return "mcq";
            case RESOURCE_RECYCLER:
                return "recycler";
            case RESOURCE_COMMUNICATION:
                return "communication";
            case RESOURCE_PIGEONHOLE:
                return "pigeon";
            case RESOURCE_ALIAS:
                return "alias";
            case RESOURCE_LINK:
                return "link";
            case RESOURCE_EASYBUILDER:
                return "easybuilder";
            case RESOURCE_WORKSHOPSUITE:
                return "workshopsuite";
            case RESOURCE_SENTIENTLINK:
                return "sentientlink";
            case RESOURCE_MEDIADOCUMENT:
                return "mediadocument";
            case RESOURCE_SITE:
                return "site";
            case RESOURCE_PERSONALPAGE:
                return "personalpage";
            case RESOURCE_DOCUMENT:
                return "document";
            case RESOURCE_USER:
                return "usercreation";
            case RESOURCE_HEADING:
                return "heading";
            case RESOURCE_HOME_CONTAINER:
                return "homecontainer";
            case RESOURCE_UNKNOWN:
            default:
                return "unknown";
        }
    }

	protected 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 enumeration = Resource.findResourceIds( "parent_resource_id = " + getResourceId(), "left_index" );
			
			child_resource_ids  = new Vector();
			while ( enumeration.hasMoreElements() )
				{
				child_resource_ids.addElement( enumeration.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
        {
        if (child_resource_ids != null)
        {
        // 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
        {
        if (child_resource_ids != null)
        {
        child_resource_ids.removeElement( key );
        }
        }
    
    /**
     * Find the children of this resource. 
     * @return An enumeration containing the Resources that are the children.
     * This should never return <code>null</code>.
     */
    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 Resource findChild(String name)
        throws BuildingServerException
        {
            return Resource.findResource(getPrimaryKey(), name);
        }

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

        ResourceTreeManager.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();
	    }

    /**
     * Get the full path of this resource from the root.
     * The root resouce name is ignored. For the root resource "/", and for
     * resources further down the tree somehing like "/admin/ground/"
     * @return The full path to the resource. The name always starts and ends
     * with a slash.
     */
	public String getFullName()
        throws BuildingServerException
		{
		Resource current;
		Enumeration enumeration = findAncestors();
		StringBuffer name = new StringBuffer();
		name.append( "/" );
		//skip root if there is one.
		if ( enumeration.hasMoreElements() )
			enumeration.nextElement();
		while ( enumeration.hasMoreElements() )
			{
			current = (Resource)enumeration.nextElement();
			name.append( current.getName() );
			name.append( "/" );
			}
		return name.toString();
		}
		
	public boolean isInside( Resource other )
        throws BuildingServerException
		{
		return ResourceTreeManager.getInstance().isInside( this, other );
		}
		
    public org.bodington.server.realm.Acl getAcl()
	    throws BuildingServerException
        {
        if ( getAclId() != null )
            return Acl.findAcl( getAclId() );
        return null;
		}
		
	


    /**
     * Checks the permission against recently check permissions. This method
     * uses the Resource class to cache recently checked permissions. It currently
     * only caches user lookups.
     * @param principal The principal to check.
     * @param permission The permission requested.
     * @return
     *               <ul>
     *               <li>-1 We don't have any cached permissions/error.</li>
     *               <li>0 Access denied.</li>
     *               <li>1 Access granted.</li>
     *               </ul>
     */
	private int checkRecentPermission( Principal principal, java.security.acl.Permission permission )
		{

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

		if (principal instanceof User)
		{
		    User user = (User) principal;
		    
		    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]) < permissionCacheTimeout						)
		            
		        {
		            return (this.recent_result[i])?1:0;
		        }
		    }
		    
		}
        
		return -1;
		}

	/**
	 * Store recently checked permission in the Resource class.
	 * If any checked permissions have expired, replace one of them, otherwise
	 * if we have space store it in a new spot.
	 * @param principal The principal who was check.
	 * @param permission The permission which was requested.
	 * @param result If the request was allowed.
	 */
	private void storeRecentPermission( Principal principal, java.security.acl.Permission permission, boolean result )
		{

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

		if (principal instanceof User)
		{
		    User user = (User) principal;
		    
		    int i;
		    for ( i=0; i<recent_list_size && this.recent_user_ids[i]!=null; i++ )
		    {
		        if ( (now - this.last_checked_properly[i]) > permissionCacheTimeout )
		            
		        {
		            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;
		    }
		    
		}

		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.
	 * @see #checkPermission(Principal, java.security.acl.Permission)
	 */
	public boolean checkPermission( java.security.acl.Permission permission )
	    {
        User user = (User)BuildingContext.getContext().getUser();
	    return checkPermission( user, permission );
	    }
	    
    /**
     * Checks a specific access right.
     * Each resource caches the last use permissions for a short ammount
	 * of time (default 5 seconds).
     * 
     * @param principal The user to check the permission against.
     * @param permission The permission to check.
     * @return True if the current user has the specified permission with this
     * resource.
     * @see #checkRecentPermission(Principal, java.security.acl.Permission)
     * @see #storeRecentPermission(Principal, java.security.acl.Permission, boolean)
     */
    public boolean checkPermission( Principal principal, java.security.acl.Permission permission )
	{
	if ( principal == null )
	    {
	    Exception e = new Exception( "Attempt to check permission on null principal." );
	    log.error( e.getMessage(), e );
	    return false;
	    }

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

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

	return p;
	}
	    
	/**
	 * Checks permission fot the supplied principal.
	 * @param principal The Principal to check the permissions against.
	 * @param permission The permission to check for.
     * @param allowed The current state of the permission check.
	 * @return True if the principal has the specified permission with this resource.
	 */
	private boolean checkPermission( Principal principal, 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( principal, permission, allowed );
				}
    	    
    	    // now run through local acl
	        acl = getAcl();
	        if ( acl != null )
	            allowed = acl.checkPermission( principal, 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( principal, 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.UPLOAD ) ||
        			  permission.equals( Permission.SEE )       )	)
        		{
        		allowed = checkPermission( principal, Permission.ADMINISTER, allowed );
        		if ( !allowed )
        			allowed = checkPermission( principal, Permission.SYSADMIN, allowed );
        		}
                // <TimedResources> available and have permission?
                else if ( allowed && !isAvailable() 
                    && !( permission.equals(Permission.SYSADMIN) 
                            || permission.equals(Permission.ADMINISTER) 
                            || permission.equals(Permission.MANAGE)))
                {
                    allowed = false;
                    allowed = checkPermission( principal, Permission.MANAGE, allowed );
                    if ( !allowed )
                        allowed = checkPermission( principal, Permission.ADMINISTER,
                            allowed );
                    if ( !allowed )
                        allowed = checkPermission( principal, Permission.SYSADMIN,
                            allowed );
                } 
	        }
	    catch ( Exception ex )
	        {
	        log.error( ex.getMessage(), ex );
	        }
	        
        return allowed;
	    }
	
	    
	/**
     * Get all the groups who have the specified access to this resource.
     * (Includes groups with specified access in ancestor resources if inherited 
     * access control is in place.)
     * <br />This method returns a map whose form is:
     * <p>
     * <code>Map &lt;{@link Group#getPrimaryKey()}, {@link Group}&gt;</code>
     * </p>
     * Compare with {@link Acl#everyGroupWhoCan(java.security.acl.Permission)}
	 * @param permission the required access permission.
	 * @param include_admin whether or not to include admin users.
	 * @return all the groups who have the specified access to this resource.
     * @throws BuildingServerException
	 */
	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 )
            {
            log.error( ex.getMessage(), ex );
            }

        return groups;
        }
    
    /**
     * Get all the special groups who have the specified access to this resource.
     * (Includes special groups with specified access in ancestor resources if inherited 
     * access control is in place.)
     * <br />This method returns a map whose form is:
     * <p>
     * <code>Map &lt;{@link Group#getPrimaryKey()}, {@link Group}&gt;</code>
     * </p>
     * Compare with {@link #everyGroupWhoCan(java.security.acl.Permission, boolean)}
     * <br />Compare with {@link Acl#everyGroupWhoCan(java.security.acl.Permission)}
     * @param permission the required access permission.
     * @param include_admin whether or not to include admin users.
     * @return all the groups who have the specified access to this resource.
     * @throws BuildingServerException
     */
	public Hashtable everySpecialGroupWhoCan( Permission permission, boolean include_admin )
	throws BuildingServerException
	{
        Hashtable groups = everyGroupWhoCan( permission, include_admin );
        
        for ( Iterator iter = groups.entrySet().iterator(); iter.hasNext(); )
        {
            Group group = (Group)((Map.Entry)iter.next()).getValue();
            if ( !group.isSpecialGroup() )
                iter.remove();
        }
        
        return groups;
	}

	/**
     * Get all the users who have the specified access to this resource.
     * (Includes users with specified access in ancestor resources if inherited 
     * access control is in place. Doesn't enumerate members of special groups.)
     * <br />This method returns an enumeration whose elements are instances of
     * {@link User}
     * <br />Compare with {@link Acl#everyoneWhoCan(java.security.acl.Permission)}
     * @param permission the access permission of interest.
     * @param include_admin whether or not you wish to include admin users.
     * @return all the users who have the specified access to this resource.
     * @throws BuildingServerException
     */
	public synchronized Enumeration everyoneWhoCan(
        java.security.acl.Permission permission, boolean include_admin )
        throws BuildingServerException
        {
        
        Hashtable groupsWhoCan = everyGroupWhoCan( permission, include_admin );
        Vector usersWhoCan = new Vector();
        Group group;

        // amendment - if there are no groups don't bother looking for members!
        if ( groupsWhoCan.isEmpty() )
            return usersWhoCan.elements();

        StringBuffer where = new StringBuffer();
        where.append( "user_id IN (SELECT user_id FROM members " );
        where.append( "WHERE group_id IN ( " );
        
        boolean first = true;
        Enumeration groups = groupsWhoCan.elements();
        for ( int i = 0; groups.hasMoreElements(); i++ )
        {
            group = (Group)groups.nextElement();
            if ( group.isSpecialGroup() )
                continue;
            
            if ( !first )
                where.append( ", " );
            where.append( group.getGroupId() );
            first = false;
        }
        
        where.append( " ) )" );
        
        if ( !first ) //i.e there will be at least one Group ID in the SQL
        {
            // execute non-special group SQL:
            Enumeration users = User.findUsers( where.toString(), "surname" );
            while ( users.hasMoreElements() )
                usersWhoCan.addElement( users.nextElement() );
        }
        return usersWhoCan.elements();
        }
		
    /**
     * Get the root folder in which we store folders for generated files.
     * @return The folder file.
     */
    public static File getWebPublishFolderRoot()
    {
        File root = new File(BuildingContext
            .getProperty("webpublish.filestore"));
        return root;
    }
    
	public File getWebPublishFolder()
		throws BuildingServerException
		{
		if ( root_folder!=null )
			return root_folder;

		root_folder = new File(getWebPublishFolderRoot(), 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;
		}
	    
    /**
     * Get the root folder in which we store folders for generated files.
     * @return The folder file.
     */
    public static File getGeneratedFileFolderRoot()
    {
        File root = new File(BuildingContext
            .getProperty("filegeneration.filestore"));
        return root;
    }
    
	public File getGeneratedFileFolder()
		throws BuildingServerException
		{
		if ( gen_root_folder!=null )
			return gen_root_folder;
			
		gen_root_folder = new File(getGeneratedFileFolderRoot(),
            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 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 String getMenuItemDisplay(Resource resource, String smliconurl)
        {
          /** @todo add general display info here */
          return "";
        }

    
    
    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 Metadatum getMetadatum( String name)
        throws BuildingServerException
    {
        Resource current = this;
        Metadatum m;
        do
        {
            m = Metadatum.findMetadatumByIdAndName( current.getResourceId(), name );
            if ( m!= null )
                return m;
            if ( !current.isPropertyInheritable( name ) )
                return null;
            current = current.getParent();
        }
        while ( current != null );
        
        return null;
    }
    

    public String getProperty( String name )
        throws BuildingServerException
    {
        Metadatum m = getMetadatum(name);
        return (m == null)?null:m.getValue();
    }
    
    public boolean isPropertyInheritable( String name )
        throws BuildingServerException
    {
        return true;
    }
    
    
    
    /**
     * Time in ms we can use a cached permission check for.
     * @return Returns the permissionCacheTimeout.
     */
    public static int getPermissionCacheTimeout()
    {
        return permissionCacheTimeout;
    }
    
    /**
     * Sets how long (max) we can cache permission checks for.
     * @param cacheTimeout The permissionCacheTimeout to set. If 0 cache is not used.
     */
    public static void setPermissionCacheTimeout(int cacheTimeout)
    {
        Resource.permissionCacheTimeout = cacheTimeout;
    }
    
    /**
     * Attempt to remove this Resource. It also removes the associated 
     * ACL with this resource.
     * @throws BuildingServerException
     */
    public void remove() throws BuildingServerException
    {
        delete();
        Acl acl = getAcl();
        acl.delete();
        acl.remove();   
    }
    
    }
    
