/* ======================================================================
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 org.apache.log4j.Logger;

import java.io.*;
import java.sql.Timestamp;
import java.util.*;
import java.net.URLDecoder;
import java.net.URLEncoder;

import org.bodington.server.BuildingServerException;
import org.bodington.sqldatabase.*;
import org.bodington.database.*;
import org.bodington.server.realm.User;
import org.bodington.server.BuildingContext;
import org.bodington.text.BigString;
import org.bodington.util.VisitationObject;

/**
 * Class used for managing Uploaded Files.
 * @author Jon Maber
 * @author buckett
 */
public class UploadedFile
    extends org.bodington.sqldatabase.SqlPersistentObject 
    implements VisitationObject, Serializable, Comparable
    {
    
    private static Logger log = Logger.getLogger(UploadedFile.class);

    /**
     * Constant to represent a file.
     */
    public final static int FILE = 0;
    /**
     * Constant to represent a folder.
     */
    public final static int FOLDER = 1;
    
    UploadedFileSummary summary;
    String real_name;
    String[] name_in_resource;
    
    File file;

    private UploadedFileIndexKey[] ikeys;

    private static String encoding = null;

    public static String getURLCharEncoding()
	{
	if ( encoding != null )
	    return encoding;
	encoding = BuildingContext.getProperty( "webpublish.url.charencoding", "bodington_underscore" );
	return encoding;
	}

    
    public static String nameToURL( String name )
	{
	try
	{
	return org.bodington.util.URLEncoder.encode( name, getURLCharEncoding() );
	}
	catch ( UnsupportedEncodingException e )
	{
	    log.error( e.getMessage(), e );
	    return null;
	}
	}
	
    public static String URLToName( String url )
	{
	 try
	 {
	return org.bodington.util.URLDecoder.decode( url, getURLCharEncoding() );
	}
	catch ( UnsupportedEncodingException e )
	{
	    log.error(e.getMessage(), e );
	    return null;
	}
    }
		
	public static String[] parsePath( String path )
		{
		if ( path==null )
			path="/";
		StringTokenizer tokenizer = new StringTokenizer( path, "/" );
		int n = tokenizer.countTokens();
		String[] list = new String[n];
		for ( int i=0; i<n; i++ )
		    list[i] = tokenizer.nextToken();
		return list;
		}
		
		
		
	public static UploadedFile findUploadedFileByPath( PrimaryKey resource_id, String path )
		throws BuildingServerException
		{
		return findUploadedFileByPath( resource_id, parsePath( path ) );
		}
	
	public static Enumeration findUploadedFileAncestors( PrimaryKey resource_id, String path )
		throws BuildingServerException
		{
		return findUploadedFileAncestors( resource_id, parsePath( path ) );
		}
	
	public static Enumeration findUploadedFileDescendents( PrimaryKey resource_id, String path, boolean omit_deleted )
		throws BuildingServerException
		{
		return findUploadedFileDescendents( resource_id, parsePath( path ), omit_deleted );
		}
	
	public static UploadedFile findUploadedFileByParentAndName( PrimaryKey resource_id, PrimaryKey parent_uf_id, String name )
		throws BuildingServerException
		{
		UploadedFileIndexKey ikey = new UploadedFileIndexKey();
		ikey.setResourceId( resource_id );
		ikey.setParentUploadedFileId( parent_uf_id );
		ikey.setName( name );
		return (UploadedFile)findPersistentObject( ikey, "org.bodington.server.resources.UploadedFile" );
		}
		
	private static Vector findPath( PrimaryKey resource_id, String[] path, boolean only_leaf )
		throws BuildingServerException
		{
		Vector list = new Vector();
		
		UploadedFileIndexKey ikey = new UploadedFileIndexKey();
		ikey.setResourceId( resource_id );
		ikey.setParentUploadedFileId( null );
		ikey.setName( "" );
		
		UploadedFile current = (UploadedFile)findPersistentObject( ikey, "org.bodington.server.resources.UploadedFile" );
		if ( current == null )
		    return null;
		
		if ( !only_leaf )
		    list.addElement( current );
		
		for ( int i=0; i< path.length; i++ )
		    {
		    ikey = new UploadedFileIndexKey();
		    ikey.setResourceId( resource_id );
		    ikey.setParentUploadedFileId( current.getUploadedFileId() );
		    ikey.setName( path[i] );
		    current = (UploadedFile)findPersistentObject( ikey, "org.bodington.server.resources.UploadedFile" );
		    if ( current == null )
			return null;
		    if ( !only_leaf )
			list.addElement( current );
		    }
		
		if ( only_leaf )
		    list.addElement( current );
		
		return list;
		}
		
	public static UploadedFile findUploadedFileByPath( PrimaryKey resource_id, String[] path )
		throws BuildingServerException
		{
		Vector list = findPath( resource_id, path, true );
		if ( list == null || list.size() == 0 )
		    return null;
		return (UploadedFile)list.elementAt( 0 );
		}
		
	public static Enumeration findUploadedFileAncestors(PrimaryKey resource_id, UploadedFile uploadedFile)
		throws BuildingServerException
		{
		Vector found = new Vector();
		PrimaryKey parent_id = uploadedFile.getPrimaryKey();

		while ( parent_id != null )
		    {
		    UploadedFile uf = findUploadedFile( parent_id );
		    if ( uf == null || ! uf.getResourceId().equals(resource_id) )
		        return null;
		    found.insertElementAt( uf, 0 );
		    parent_id = uf.getParentUploadedFileId();
		    }
		    
		return found.elements();
		}
		
	public static Enumeration findUploadedFileAncestors( PrimaryKey resource_id, String[] path )
		throws BuildingServerException
		{
		//break down path first
		Vector found = findPath( resource_id, path, false );
		if ( found == null )
		    return new Vector().elements();
		
		return found.elements();
		}

	public static Enumeration findUploadedFileDescendents( PrimaryKey resource_id, String[] path, boolean omit_deleted )
		throws BuildingServerException
		{

		UploadedFile uf = findUploadedFileByPath( resource_id, path );
        return findUploadedFileDescendents(resource_id, uf, omit_deleted);
        }
        
    public static Enumeration findUploadedFileDescendents(
        PrimaryKey resource_id, UploadedFile uf, boolean omit_deleted)
        throws BuildingServerException
    {
		if ( uf == null )
			return (new Vector()).elements();
		
		StringBuffer where = new StringBuffer( 100 );
		
		where.append( "resource_id = " );
		where.append( resource_id );
		// This clause is modified to avoid arithmetic operators not
		// supported by some databases - i.e. Oracle lacks the modulo
		// operator.
		if ( omit_deleted )
			{
			where.append( " AND FLOOR((flags + " );
			where.append( UploadedFileSummary.FLAG_DELETED );
			where.append( ") / " );
			where.append( UploadedFileSummary.FLAG_DELETED*2 );
			where.append( ") = FLOOR(flags / " );
			where.append( UploadedFileSummary.FLAG_DELETED*2 );
			where.append( ")" );
			}
		where.append( " AND left_index BETWEEN " );
		where.append( uf.getLeftIndex() );
		where.append( " AND " );
		where.append( uf.getRightIndex() );
		return findUploadedFiles( where.toString(), "left_index" );
		}
		
	public static UploadedFile findUploadedFile( PrimaryKey key )
		throws BuildingServerException
		{
		return (UploadedFile)findPersistentObject( key, "org.bodington.server.resources.UploadedFile" );
		}
	public static UploadedFile findUploadedFile( String where )
		throws BuildingServerException
		{
		return (UploadedFile)findPersistentObject( where, "org.bodington.server.resources.UploadedFile" );
		}
	public static Enumeration findUploadedFiles( String where )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, "org.bodington.server.resources.UploadedFile" );
	    }
	public static Enumeration findUploadedFiles( String where, String order )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, order, "org.bodington.server.resources.UploadedFile" );
	    }
	
	
	public UploadedFile()
		{
		ikeys = new UploadedFileIndexKey[1];
		ikeys[0] = new UploadedFileIndexKey();
		
		summary = new UploadedFileSummary();
		file = null;
		real_name = null;
		name_in_resource = new String[4];
		}
    
    /**
     * Create an UploadedFile object that is almost ready to use.
     * @param type The type of uploaded file.
     * @see #FILE
     * @see #FOLDER
     */
    public UploadedFile(int type)
    {
        this();
        switch (type)
        {
            case FILE:
                setFlags(0);
                setFolder( false );
                break;
            case FOLDER:
                setFlags( 0 );
                setFolder( true );
                setMimeType( "folder" );
                setSize( 0L );
                setBytesUploaded( 0L );
                break;
            default:
                throw new IllegalArgumentException("Type should be a file or folder");
        }
        
        Timestamp now = new Timestamp(System.currentTimeMillis());
        setCreatedTime( now );
        setUpdatedTime( now );
        setDeleted( false );
    }
	
    public PrimaryKey getPrimaryKey()
		{
        return getUploadedFileId();
        }

    public void setPrimaryKey(PrimaryKey key)
    	{
    	setUploadedFileId( key );
    	}
    	
    public PrimaryKey getUploadedFileId()
		{
        return summary.getUploadedFileId();
        }

    public void setUploadedFileId(PrimaryKey key)
    	{
    	summary.setUploadedFileId( key );
    	setUnsaved();
    	}
    	

    public PrimaryKey getResourceId()
		{
        return summary.getResourceId();
        }

    public void setResourceId(PrimaryKey key)
    	{
	ikeys[0].setResourceId( key );
    	summary.setResourceId( key );
    	setUnsaved();
    	}
    	

    public PrimaryKey getParentUploadedFileId()
		{
        return summary.getParentUploadedFileId();
        }

    public void setParentUploadedFileId(PrimaryKey key)
    	{
	ikeys[0].setParentUploadedFileId( key );
    	summary.setParentUploadedFileId( key );
    	setUnsaved();
    	}
    	

    public void setCreateUserId( PrimaryKey key )
	    {
	    summary.setCreateUserId( key );
    	setUnsaved();
	    }
    public PrimaryKey getCreateUserId()
	    {
        return summary.getCreateUserId();
	    }


    	
    public void setUpdateUserId( PrimaryKey key )
	    {
	    summary.setUpdateUserId( key );
    	setUnsaved();
	    }
    public PrimaryKey getUpdateUserId()
	    {
        return summary.getUpdateUserId();
	    }


    	
	public java.sql.Timestamp getCreatedTime()
		{
		return summary.getCreatedTime();
		}
	public void setCreatedTime( java.sql.Timestamp t )
		{
		summary.setCreatedTime( t );
    	setUnsaved();
		}


	public java.sql.Timestamp getUpdatedTime()
		{
		return summary.getUpdatedTime();
		}
	public void setUpdatedTime( java.sql.Timestamp t )
		{
		summary.setUpdatedTime( t );
    	setUnsaved();
		}


   	public int getFlags()
		{
		return summary.getFlags();
		}
	public void setFlags( int f )
		{
		if ( f==summary.getFlags() )
			return;
		summary.setFlags( f );
    	setUnsaved();
		}
	public boolean isDeleted()
		{
		return summary.isDeleted();
		}
	public void setDeleted( boolean b )
		{
		if ( b == summary.isDeleted() )
			return;
		summary.setFlags( summary.getFlags() ^ UploadedFileSummary.FLAG_DELETED );
    	setUnsaved();
		}
	public boolean isFolder()
		{
		return summary.isFolder();
		}
	public void setFolder( boolean b )
		{
		if ( b == summary.isFolder() )
			return;
		summary.setFlags( summary.getFlags() ^ UploadedFileSummary.FLAG_FOLDER );
    	setUnsaved();
		}
	
	public int getLowSize()
		{
		return (int)(getSize() & 0x00000000ffffffffL);
		}
	public void setLowSize( int s )
		{
		setSize( (getSize() & 0xffffffff00000000L) | ((long)s & 0x00000000ffffffffL) );
		}
	public int getHighSize()
		{
		return (int)(getSize() / 0x100000000L);
		}
	public void setHighSize( int s )
		{
		setSize( (getSize() & 0x00000000ffffffffL) | ((long)s << 32) );
		}

	public long getSize()
		{
		return summary.getSize();
		}

	public void setSize( long l )
		{
		if ( summary.getSize() == l )
			return;
		summary.setSize( l );
    	setUnsaved();
		}

	public int getLowBytesUploaded()
		{
		return (int)(getBytesUploaded() & 0x00000000ffffffffL);
		}
	public void setLowBytesUploaded( int s )
		{
		setBytesUploaded( (getBytesUploaded() & 0xffffffff00000000L) | ((long)s & 0x00000000ffffffffL) );
		}
	public int getHighBytesUploaded()
		{
		return (int)(getBytesUploaded() / 0x100000000L);
		}
	public void setHighBytesUploaded( int s )
		{
		setBytesUploaded( (getBytesUploaded() & 0x00000000ffffffffL) | ((long)s << 32) );
		}

	public long getBytesUploaded()
		{
		return summary.getBytesUploaded();
		}

	public void setBytesUploaded( long l )
		{
		if ( summary.getBytesUploaded() == l )
			return;
		summary.setBytesUploaded( l );
    	setUnsaved();
		}

	public boolean isComplete()
		{
		return summary.isComplete();
		}


    public void setName( String n )
	    throws BuildingServerException
	    {
	    // Accomadates Oracle, which insists on changing "" to null in
	    // the database.
	    if ( n==null )
	   	n="";

	    ikeys[0].setName( n );

	    summary.setName( n );
	    real_name = null;
	    name_in_resource = new String[4];
	    file=null;
	    try
	        {
	        summary.setUrl( nameToURL( n ) );
	        }
	    catch ( Exception ex )
	        {
	        throw new BuildingServerException( "Problem setting name of uploaded file. " + ex.toString() );
	        }
	    setUnsaved();
	    }

    public String getName()
	    {
        return summary.getName();
	    }


    public void setUrl( String n )
        throws BuildingServerException
	    {
	    // do nothing !!
	    // no longer interested in the url in the database
	    }

    public String getUrl()
	    {
	    return summary.getUrl();
	    }

	    
    public void setMimeType( String n )
	    {
	    summary.setMimeType( n );
    	setUnsaved();
	    }

    public String getMimeType()
	    {
        return summary.getMimeType();
	    }



   	public int getLeftIndex()
		{
		return summary.getLeftIndex();
		}
	public void setLeftIndex( int l )
		{
		if ( l==summary.getLeftIndex() ) return;
		summary.setLeftIndex( l );
    	setUnsaved();
		}

	public int getRightIndex()
		{
		return summary.getRightIndex();
		}
	public void setRightIndex( int r )
		{
		if ( r==summary.getRightIndex() ) return;
		summary.setRightIndex( r );
    	setUnsaved();
		}


    public String getRealName()
	{
	if ( real_name != null )
	    return real_name;
	    
	char c;
	StringBuffer fs_name = new StringBuffer();
	fs_name.append( Integer.toHexString( getUploadedFileId().intValue() ) );
	fs_name.append( "_" );
	
	boolean in_valid=true;
	String name = getName();
	for ( int i=0; i < name.length(); i++ )
	    {
	    c = name.charAt( i );
	    if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' )
		{
		fs_name.append( c );
		in_valid = true;
		continue;
		}
	    if ( in_valid )
		{
		in_valid = false;
		fs_name.append( "_" );
		}
	    }
	
	real_name = fs_name.toString();
	return real_name;
	}
		

    private String getNameInResource( String separator, boolean real, boolean url )
	throws BuildingServerException
	{
	StringBuffer fs_name;
	Enumeration enumeration;
	UploadedFile uf;

	if ( getParentUploadedFileId() == null )
	    return "";
	
	int item;
	if ( url )
	    {
	    if ( real )
		item = 0;
	    else
		item = 1;
	    }
	else
	    {
	    if ( real )
		item = 2;
	    else
		item = 3;
	    }
	
	if ( name_in_resource[item] != null )
	    return name_in_resource[item];
	
	enumeration=findUploadedFileAncestors(getResourceId(), this);

	fs_name = new StringBuffer();
	//skip root
	if ( enumeration.hasMoreElements() )
	    enumeration.nextElement();
	while ( enumeration.hasMoreElements() )
	    {
	    uf = (UploadedFile)enumeration.nextElement();
	    if ( url )
		{
		if ( real )
		    fs_name.append( nameToURL( uf.getRealName() ) );
		else
		    fs_name.append( nameToURL( uf.getName() ) );
		}
	    else
		{
		if ( real )
		    fs_name.append( uf.getRealName() );
		else
		    fs_name.append( uf.getName() );
		}
		
	    if ( enumeration.hasMoreElements() )
		fs_name.append( separator );
	    }
	
	name_in_resource[item] = fs_name.toString();
	
	return name_in_resource[item];
	}

    public String getNameInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", false, false );
	}
	
    public String getRealNameInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", true, false );
	}
	
    public String getURLInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", false, true );
	}
	
    public String getRealURLInResource()
        throws BuildingServerException
	{
	return getNameInResource( "/", true, true );
	}
	
    public File getFile()
        throws BuildingServerException
	{
	if ( file!=null ) return file;

	Resource resource;
	PrimaryKey rid;
	File root;

	rid = getResourceId();
	if ( rid == null )
	    return null;
	resource = Resource.findResource( rid );
	if ( resource==null )
	    return null;

	root = resource.getWebPublishFolder();

	file = new File( root, getNameInResource( File.separator, true, false ) );

	return file;
	}
		

    public File getFileAsPerVersion2_0( File root )
    throws BuildingServerException
    {
	return getFileAsPerOldVersion( root, "bodington_escape" );
    }
    
    public File getFileAsPerVersion1_X( File root )
    throws BuildingServerException
    {
	return getFileAsPerOldVersion( root, "ISO-8859-1" );
    }
    
    private File getFileAsPerOldVersion( File root, String encoding )
    throws BuildingServerException
    {
        try
        {
        // this method is only used to upgrade an old installation
        // it depends on url field which is no longer used
        // root will refer to a folder inside the old installation
        File old_file;

        Resource resource;
        PrimaryKey rid;
        StringBuffer fs_name;
        Enumeration enumeration;
        UploadedFile uf;

        rid = getResourceId();
        if ( rid == null )
            return null;
        resource = Resource.findResource( rid );
        if ( resource==null )
            return null;

	File res_folder = new File( root, resource.getResourceId().toString() );

        enumeration=findUploadedFileAncestors(getResourceId(), this);

        fs_name = new StringBuffer();
        fs_name.append( File.separator );
        //skip root
        if ( enumeration.hasMoreElements() )
        enumeration.nextElement();
        while ( enumeration.hasMoreElements() )
        {
            uf = (UploadedFile)enumeration.nextElement();
            fs_name.append( org.bodington.util.URLEncoder.encode( uf.getName(), encoding ) );
            if ( enumeration.hasMoreElements() )
            fs_name.append( File.separator );
        }
        //if ( fs_name.length()==0 )
        //	return null;

        old_file = new File( res_folder, fs_name.toString() );

        return old_file;
        }
        catch ( UnsupportedEncodingException e )
        {
            throw new BuildingServerException( "Unsupported encoding." );
        }
    }
		
		
	public UploadedFileSummary getSummary()	
		{
		return summary;
		}

	public void save()
		throws BuildingServerException
		{
		if ( this.isUnsaved() )
			super.save();
		}


	public int compareTo( Object o )
		{
		if ( o==null )
			return 1;
		if ( !(o instanceof UploadedFile) )
			return 1;
			
		UploadedFile other = (UploadedFile)o;
		
		if ( isDeleted() )
			{
			if ( !other.isDeleted() )
				return 1;
			}

		if ( other.isDeleted() )
			return -1;
		
		if ( getName() == null )
			{
			if ( other.getName() == null )
				return 0;
			else
				return 1;
			}
			
		if ( other.getName() == null )
			return -1;
			
		return getName().compareTo( other.getName() );
		}

	public IndexKey[] getIndexKeys()
	    {
	    if ( ikeys[0].getName() == null || 
		  ikeys[0].getResourceId() == null )
	        return null;

	    return ikeys;
	    }
	    
	public boolean matchesKey( IndexKey testikey )
	    {
	    return testikey.equals( ikeys[0] );
	    }
	
	}
	
