/* ======================================================================
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 java.util.Enumeration;
import java.util.Hashtable;

import org.apache.log4j.Logger;
import org.bodington.database.PrimaryKey;
import org.bodington.server.realm.PassPhrase;
import org.bodington.server.realm.User;
import org.bodington.server.resources.Resource;


//  This class is due to be really simplified - the client will get
//  the navigation session just once per session so this class doesn't
//  need to reference it.  In the future resource sessions ought to
//  be doled out by the navigation session.

public class BuildingSessionManagerImpl 
	implements org.bodington.server.BuildingSessionManager
	{
    
    private static Logger log = Logger.getLogger(BuildingSessionManagerImpl.class);
    
	//sessions hashed against user_id_resource_id or
	// user_idclass name
	private static Hashtable session_table= new Hashtable();
	private static long last_checked = System.currentTimeMillis();
	
	
	
	public BuildingSessionManagerImpl()
		{
		super();
		}
	
        public static void reset()
        {
            log.info("Reset");
            session_table= new Hashtable();
        }
	
	public static void dumpSessionTable()
		{
		synchronized ( session_table )
			{
			Enumeration enumeration = session_table.keys();
			String key;
			BuildingSession session;
			log.debug( "Current sessions: " );
			while ( enumeration.hasMoreElements() )
				{
				key = (String)enumeration.nextElement();
				session = (BuildingSession)session_table.get( key );
				log.debug( key );
				log.debug( "\t" );
				log.debug( session.getClass().getName() );
				try
					{
					if ( session.getResourceId() != null );
						{
						log.debug( "\t" );
						log.debug( session.getResourceId().toString() );
						}
					}
				catch ( Exception ex )
					{
					}
				}
			}
		}
	
	public static BuildingSession getSession( Resource resource )
		throws BuildingServerException
		{
		if ( resource == null || resource.getResourceId() == null )
			throw new BuildingServerException( "Session requested for unspecified resource." );
		return getSession( resource, null );
		}
	
        // this method now always returns a new session.  The client is responsible for
        // keeping a reference to it for the duration of the session.
	public static NavigationSession getSession( Class session_class )
		throws BuildingServerException
		{
		if ( session_class == null )
			throw new BuildingServerException( "Session of unknown type requested." );
		if ( !NavigationSession.class.isAssignableFrom( session_class ) )
			throw new BuildingServerException( "Requested session must be used with a specific resource." );
                try
                {
                    return (NavigationSession)session_class.newInstance();
                }
                catch ( java.lang.InstantiationException iex )
                        {
                        log.error( iex.getMessage(), iex );
                        throw new BuildingServerException( "Technical error starting session: " + iex );
                        }
                catch ( java.lang.IllegalAccessException iaex )
                        { 
                        log.error( iaex.getMessage(), iaex );
                        throw new BuildingServerException( "Technical error starting session: " + iaex );
                        }
		}
		
	private static String sessionCode( Resource r, Class session_class, User u )
		{
		// if a specific class is requested hash against user+class name
		if ( session_class!=null )
			return u.getPrimaryKey().toString() + session_class.toString();
			
		// we can be certain that a resource has been specified at this point
		// but what type of session does that imply? 
		
		Class c = r.sessionClass();
		// if resources of this type have a session and all share one session 
		// per user hash against user + RESOURCE class name
		if ( c!=null && ReusableResourceSession.class.isAssignableFrom( c ) )
			return u.getPrimaryKey().toString() + r.getClass().toString();
		
		// each resource of this type must have its own session so
		// hash against user + "_" + resource.
		return u.getPrimaryKey().toString() + "_" + r.getPrimaryKey().toString();
		}

	private static BuildingSession getSession( Resource resource, Class session_class )
		throws BuildingServerException
		{
		String code;
		BuildingSessionImpl session, othersession;
		synchronized ( session_table )
			{
			//clear out old sessions before dealing with current request
			    
			long now = System.currentTimeMillis();
			last_checked = now;
			for ( Enumeration enumeration = session_table.keys(); enumeration.hasMoreElements(); )
				{
				code = (String)enumeration.nextElement();
				othersession = (BuildingSessionImpl)session_table.get( code );
				//unused for 2 minutes...
				if ( (now - othersession.last_used) > (2*60*1000) )
					session_table.remove( code );
				}


            //now ready to fetch or create requested session
			BuildingContext context = BuildingContext.getContext();
			User user = (User)context.getUser();
			if ( user == null )
				throw new BuildingServerException( "Can't start session without user." );
			
			code = sessionCode( resource, session_class, user );
			session = (BuildingSessionImpl)session_table.get( code );
			if ( session!=null )
				{
				session.last_used = now;
				}
				
		    // if session wasn't found in the table one must be created
			if ( session == null )
				{
				try
					{
					if ( resource != null )
						{
						Class c = resource.sessionClass();
						if ( c!=null )
							session = (BuildingSessionImpl)c.newInstance();
						}
					else
						{
						session = (BuildingSessionImpl)session_class.newInstance();
						}
					}
				catch ( java.lang.InstantiationException iex )
					{
					log.error(iex.getMessage(),iex );
					throw new BuildingServerException( "Technical error starting session: " + iex );
					}
				catch ( java.lang.IllegalAccessException iaex )
					{
					log.error( iaex.getMessage(), iaex );
					throw new BuildingServerException( "Technical error starting session: " + iaex );
					}
				
				if ( session!=null )
					{
                        session.setUserId(user.getUserId());
                        if ( resource!=null )
                        	session.setResource( resource.getResourceId() );
					code = sessionCode( resource, session_class, user );
					session_table.put( code, session );
					}
				}
			}
			
		return session;
		}    	
	}
	
