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

import org.apache.log4j.Logger;

import org.bodington.database.*;
import org.bodington.pool.*;
import org.bodington.server.realm.*;
import org.bodington.server.resources.*;
import org.bodington.xml.*;

import java.security.Principal;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.Hashtable;
import java.sql.*;

/**
 * Application code that uses BuildingServer can retrieve a freshly
 * initalised BuildingContext at the start of a series of transactions.
 * The context object need not be passed as a parameter to methods that
 * implement the various transactions because the context object can
 * be retreived at any time using a static method of BuildingContext.
 * BuildingContext objects are keyed against the threads of execution
 * that use them.
 * <P>
 * The Building Context object provides access to the Database and
 * will maintain information about transactions.
 * 
 * @author Jon Maber
 * @version 1
 */

public class BuildingContext
    {

    private static Logger log = Logger.getLogger(BuildingContext.class);
    /**
     * Instances will only be constructed by BuildingServer.
     */
    private static int serial_number=0;
    private static Hashtable user_count = new Hashtable();
    
    private static String tempdirname = null;
    private static File tempdir = null;
    
    private int id;
    private Thread thread;
    private int state;

    private Object pool_owner;
    private Principal user;
    private PrimaryKey resource_id;
    private PrimaryKey job_result_id;
    
    public static final int STATE_UNASSIGNED     =  0;
    public static final int STATE_AUTHENTICATING =  4;
    public static final int STATE_PROCESSING     =  8;
    public static final int STATE_COMPLETED      = 12;
    
    private Database database;
    
    private Connection sql_con;
    
    
    private StringBuffer transaction_trace;
    private static int next_trace_code=1000000;
    private volatile int trace_code=-1;
    private long trace_time=0;
    
    
    BuildingContext( )
    	{
    	init();
    	}

    public void init()
	{
        id=serial_number++;
        thread = Thread.currentThread();
        sql_con=null;
        state = STATE_UNASSIGNED;
        setPoolOwner( null );
        setUser( null );
        resource_id=null;
        job_result_id=null;
	}


    void clear()
	{
	thread=null;
	sql_con=null;
        state = STATE_UNASSIGNED;
        setPoolOwner( null );
        setUser( null );
        resource_id=null;
        job_result_id=null;
	}


    /**
     * Find out what state the context object is in.
     *
     * @return  The state value of this context object.
     */
    	
    public int getState()
	    {
        return state;
    	}


    /**
     * Classes in this package can set the state (i.e. BulidingServer).
     *
     * @param s The new state value.
     */
    	
    void setState( int s )
	    {
        state=s;
    	}



    /**
     * Called only by BuildingServer.
     */
    	
    void close()
    	{
    	setUser( null );
    	setState( STATE_UNASSIGNED );
    	}

	/**
	 * Gets a unique (within the VM) id number.
	 * 
	 * @return The ID number of this context.
	 */
    	
	public int getId()
		{
		return id;
		}

	/**
	 * Used by BuildingServer to get a reference to the Thread that this
	 * context is currently serving.
	 */

    
	Thread getThread()
		{
		return thread;
		}


	/**
	 * Every context object references a database.
	 * 
	 * @return The Database implmentation for the BuildingServer.
	 */

	public Database getDatabase()
		{
		return database;
		}

	/**
	 * Used by BuildingServer to set the database.
	 * 
	 * @param db The Database instance that this context should work with.
	 */
	void setDatabase( Database  db )
		{
		database = db;
		}

	public void setPoolOwner( Object p )
		{
		pool_owner = p;
		}
	public Object getPoolOwner()
		{
		return pool_owner;
		}
		
	/**
	 * Context can reference the user of this thread.
	 * 
	 * @return The Principal implementation representing the user.
	 */

	public Principal getUser()
		{
		return user;
		}

	public static int getUserContextUsage( Principal u )
		{
		if ( u== null )
			return 0;
		Integer n = (Integer)user_count.get( u );
		if ( n==null )
			return 0;
		return n.intValue();
		}


	/**
	 * Used by BuildingServer to set the current user.
	 * maintains a count of the number of contexts assigned
	 * to the user.
	 * 
	 * @param u The Principal instance that is using this thread.
	 */
	public void setUser( Principal u )
		{
		Integer n;
		synchronized ( user_count )
			{
			if ( user!=null )
				{
				n = (Integer)user_count.get( user );
				if ( n.intValue() <= 1 )
					user_count.remove( user );
				else
					user_count.put( user, new Integer( n.intValue()-1 ) );
				}
			user = u;
			if ( user!=null )
				{
				n = (Integer)user_count.get( user );
				if ( n == null )
					user_count.put( user, new Integer( 1 ) );
				else
					user_count.put( user, new Integer( n.intValue()+1 ) );
				}
			}
		}

	public Resource getResource()
	    throws BuildingServerException
		{
		if ( resource_id==null ) return null;
		return Resource.findResource( resource_id );
		}

	/**
	 * Used to record which resource is in use for this context.
	 * 
	 * @param r The resource.
	 */
	public void setResource( Resource r )
		{
		if ( r==null )
		    {
		    resource_id=null;
		    return;
		    }
		resource_id = r.getPrimaryKey();
		}


	/**
	 * Context holds reference to a JobResult if it is run from the JobScheduler.
	 * 
	 * @return The resource object.
	 */

	public JobResult getJobResult()
	    throws BuildingServerException
		{
		if ( job_result_id==null ) return null;
		return JobResult.findJobResult( job_result_id );
		}

	/**
	 * Context holds reference to a JobResult if it is run from the JobScheduler.
	 * 
	 * @param j The JobResult.
	 */
	public void setJobResult( JobResult j )
		{
		if ( j==null )
		    {
		    job_result_id=null;
		    return;
		    }
		job_result_id = j.getPrimaryKey();
		}


	/**
	 * Context can get ACL from current resource.
	 * 
	 * @return The resource object.
	 */

	public org.bodington.server.realm.Acl getAcl()
	    throws BuildingServerException
		{
		if ( resource_id==null ) return null;
		Resource r=Resource.findResource( resource_id );
		if ( r == null ) return null;
		return r.getAcl();
		}

	public boolean checkPermission( java.security.acl.Permission permission )
		{
		try
		    {
			if ( resource_id==null )
				{
				throw new Exception( "Attempt to check permission on null resource id." );
				}
			Resource r=Resource.findResource( resource_id );
			if ( r == null )
				{
				throw new Exception( "Attempt to check permission on null resource." );
				}
		    return r.checkPermission( permission );
		    }
		catch ( Exception ex )
		    {
		    log.error(ex.getMessage(), ex );
		    return false;
		    }
		}

	public boolean checkPermission( String p )
		{
		Permission permit = Permission.forName( p );
		if ( permit==null )
			{
			try
			    {
			    throw new Exception( "Attempt to check unknown permission " + p );
			    }
			catch ( Exception ex )
			    {
			    log.error( ex.getMessage(), ex );			
			    return false;
			    }
			}
		return checkPermission( permit );
		}



	/**
	 * Gets the database connection for this context.
	 * 
	 * @return The Connection for this context or null if the database implementation
	 * doesn't use Connections.
	 */



	public Connection peekConnection()
		{
		return sql_con;
		}
	/**
	 * Gets the database connection for this context.
	 * 
	 * @return The Connection for this context or null if the database implementation
	 * doesn't use Connections.
	 */



	public Connection getConnection()
		throws ObjectPoolException
		{
		if ( sql_con==null )
			database.initContext();
				
		return sql_con;
		}

	/**
	 * Used by the database implmentation to set the current Connection.
	 * 
	 * @param con The Connection object to set.
	 */
	public void setConnection( Connection con)
		{
		sql_con = con;
		}


	public void freeConnection()
		{
		if ( database!=null )
			database.freeContext();
		}

	/**
	 * The context can reference the BuildingServer instance.
	 * 
	 * @return The BuildingServer instance.
	 */



	public BuildingServer getServer()
		{
		return BuildingServer.getInstance();
		}


	/**
	 * The context can provide th server's xml repoitory
	 * 
	 * @return The BuildingServer instance.
	 */

	public XMLRepository getXMLRepository()
		{
        BuildingServer server=BuildingServer.getInstance();
      if ( server == null )
      	return null;
		return server.getXMLRepository();
		}


	/**
	 * Gets BuildingServer properties.
	 * 
	 * @param key The name of the property to fetch.
	 * @return The value of the property or null if it wasn't found.
	 */

	public static String getProperty( String key )
		{
        BuildingServer server=BuildingServer.getInstance();
      if ( server == null )
      	return null;
		return server.getProperty( key );
		}

	/**
	 * Gets BuildingServer properties.
	 * 
	 * @param key The name of the property to fetch.
	 * @return The value of the property or null if it wasn't found.
	 */

	public static String getProperty( String key, String def )
		{
        BuildingServer server=BuildingServer.getInstance();
        if ( server == null )
      	return null;
		return server.getProperty( key, def );
		}
    
    /**
     * Get the integer value of a property. If the value doens't exist or
     * isn't parsable then return the deault.
     * @param key The key to lookup in the properties.
     * @param def The default value to return.
     * @param warn If set to true then unless we get a good value we will log it.
     * @return The integer value of the value or default.
     */
    public static int getProperty( String key, int def, boolean warn )
        {
        BuildingServer server = BuildingServer.getInstance();
        if (server == null)
        {
            if (warn)
                log.warn("BuildingServer Down. Key: "+ key+ " Default: "+ def);
            return def;
        }
        String value = server.getProperty(key);
        if (value == null)
        {
            if (warn)
                log.warn("Property Undefined. Key: "+ key+ " Default: "+def);
            return def;
        }
        try
            {
            return Integer.parseInt(value);
            }
        catch (NumberFormatException nfe)
            {
            if (warn)
                log.warn("Property Malformed. Key: "+ key+ " Value: "+ value + " Default: "+ def);
            return def;
            }
        }

	public Properties getProperties()
		{
        BuildingServer server=BuildingServer.getInstance();
        if ( server == null )
      	return null;
		return server.getProperties();
		}

		
    public static void resetTempDirectory()
	{
	tempdir=null;
	tempdirname = null;
	}
		
    public static File getTempDirectory()
	throws IOException
	{
	if ( tempdirname == null )
	    {
	    tempdirname = getProperty( "buildingserver.temp.path" );
	    if ( tempdirname==null || tempdirname.trim().length() == 0 )
		{
		tempdirname = "";
		tempdir = null;
		}
	    else
		{
		tempdir = new File( tempdirname );
		if ( !tempdir.exists() || !tempdir.isDirectory() )
		    tempdir = null;
		}
	    }
	
	return tempdir;
	}
	
    public static File createTempFile( String prefix, String suffix )
	throws IOException
	{
	return File.createTempFile( prefix, suffix, getTempDirectory() );
	}
		
    /**
     * This static method is called by a client just before running
     * custom authentication code.
     * 
     * @return A context is initialised and returned.
     */

    
    public static BuildingContext startContextForAuthentication()
        {
        BuildingServer server=BuildingServer.getInstance();
        if ( server == null )
      	return null;
        return server.startContextForAuthentication();
        }

    /**
     * This static method is called by a client at the start of a series of
     * transactions.
     * 
     * @return A context is initialised and returned.
     */

    
    public static BuildingContext startContext()
        {
        BuildingServer server=BuildingServer.getInstance();
        if ( server == null )
      	return null;
      	
        BuildingContext c = server.startContext();
        if ( c!=null )
        	{
        	if ( c.transaction_trace == null )
        		c.transaction_trace = new StringBuffer( 1024 );
        	else
        		c.transaction_trace.setLength( 0 );
        	c.trace_code = next_trace_code++;
        	c.trace_time = System.currentTimeMillis();
        	}
        return c;
        }


    /**
     * This static method is called by the client to fetch the
     * context that was initalised when this thread last called
     * startContext().
     * 
     * @return The context for the current thread.
     */
    
    public static BuildingContext getContext()
        {
        BuildingServer server=BuildingServer.getInstance();
        if ( server == null )
      	return null;
        return server.getContext();
        }

    /**
     * Called at the end of a series of transactions to indicate that
     * the context is no longer needed and can be recycled.
     */
    
    public static void endContext()
        {
        BuildingServer server=BuildingServer.getInstance();
        if ( server == null )
      	return;
        server.endContext();
        }
        
        
       
    /**
     * Add a message to the trace for this context. This allows a set
     * messages to be added to a log message without any worries
     * about threading as each thread has a different context.
     * @param t The message to add to the trace log.
     * @deprecated Use Log4J which allows context information. 
     */
    public static void trace( String t )
    	{
    	BuildingContext c = getContext();
        if ( c.transaction_trace == null )
        	c.transaction_trace = new StringBuffer( 1024 );
        c.transaction_trace.append( t );
        c.transaction_trace.append( "\r\n" );
    	}
    	
    /**
     * Writes the current set of trace messages to the log.
     * @see #dumpThisTrace()
     * @deprecated Use Log4J which allows context information. 
     */
    public static void dumpTrace()
    	{
    	BuildingContext c = getContext();
    	c.dumpThisTrace();
		}
		
    /**
     * Writes the current set of trace messages to the log.
     *@deprecated Use Log4J which allows context information. 
     */
    public void dumpThisTrace()
   	{
	StringBuffer buffer = new StringBuffer();
	buffer.append( "BuildingContext trace code = " );
	buffer.append( Integer.toString( trace_code ) );
	buffer.append( "Trace time = " );
	buffer.append( (new java.util.Date( trace_time )).toString() );
	buffer.append( "resource_id = " );
	if ( resource_id == null )
		buffer.append( "null" );
	else 			
		buffer.append( resource_id.toString() );
	if ( transaction_trace != null )
		for ( int i=0; i<transaction_trace.length(); i++ )
			buffer.append( transaction_trace.charAt( i ) );
	log.warn( buffer.toString() );
    	}
    	
    /**
     * Gets the ID of this context. This should be unique while the system
     * remains running.
     * @return The number of this context.
     * @deprecated Use Log4J which allows context information. 
     */
    public static int getTraceCode()
    	{
    	BuildingContext c = getContext();
    	return c.trace_code;
    	}

    /**
     * The time this context was started.
     * @return The time in seconds since epoch when this context was started.
     * @deprecated Use Log4J which allows context information. 
     */
    public static long getTraceTime()
    	{
    	BuildingContext c = getContext();
    	return c.trace_time;
    	}
    }
