/*
 * Created on 2004-7-15
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
package org.huihoo.willow.session;

import java.beans.PropertyChangeSupport;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;

import org.huihoo.willow.util.StringManager;

import workflow.services.WorkflowSession;
import workflow.wfmc.pm.Participant;

/**
 * @author reic
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public class WillowSession implements WorkflowSession
{

	// ----------------------------------------------------------- Constructors

	/**
	 * Construct a new Session associated with the specified SessionManager.
	 *
	 * @param manager The manager with which this Session is associated
	 */
	public WillowSession(SessionManager manager)
	{

		super();
		this.manager = manager;
		this.debug = manager.getDebug();

	}

	// ----------------------------------------------------- Instance Variables

	/**
	 * The dummy attribute value serialized when a NotSerializableException is
	 * encountered in <code>writeObject()</code>.
	 */
	protected static final String NOT_SERIALIZED = "___NOT_SERIALIZABLE_EXCEPTION___";

	/**
	 * The collection of user data attributes associated with this Session.
	 */
	protected HashMap attributes = new HashMap();

	/**
	 * The authentication type used to authenticate our cached Participant,
	 * if any.  NOTE:  This value is not included in the serialized
	 * version of this object.
	 */
	protected transient String authType = null;

	/**
	 * The <code>java.lang.Method</code> for the
	 * <code>fireContainerEvent()</code> method of the
	 * <code>org.apache.catalina.core.StandardContext</code> method,
	 * if our Context implementation is of this class.  This value is
	 * computed dynamically the first time it is needed, or after
	 * a session reload (since it is declared transient).
	 */
	protected transient Method containerEventMethod = null;

	/**
	 * The method signature for the <code>fireContainerEvent</code> method.
	 */
	protected static final Class containerEventTypes[] = { String.class, Object.class };

	/**
	 * The time this session was created, in milliseconds since midnight,
	 * January 1, 1970 GMT.
	 */
	protected long creationTime = 0L;

	/**
	 * The debugging detail level for this component.  NOTE:  This value
	 * is not included in the serialized version of this object.
	 */
	protected transient int debug = 0;

	/**
	 * We are currently processing a session expiration, so bypass
	 * certain IllegalStateException tests.  NOTE:  This value is not
	 * included in the serialized version of this object.
	 */
	protected transient boolean expiring = false;

	/**
	 * The session identifier of this Session.
	 */
	protected String id = null;

	/**
	 * Descriptive information describing this Session implementation.
	 */
	protected static final String info = "WillowSession/1.0";

	/**
	 * The last accessed time for this Session.
	 */
	protected long lastAccessedTime = creationTime;

	/**
	 * The session event listeners for this Session.
	 */
	protected transient ArrayList listeners = new ArrayList();

	/**
	 * The SessionManager with which this Session is associated.
	 */
	protected SessionManager manager = null;

	/**
	 * The maximum time interval (in seconds) between client requests before
	 * the servlet container may invalidate this session.  A negative time
	 * indicates that the session should never time out.
	 */
	protected int maxInactiveInterval = -1;

	/**
	 * Flag indicating whether this session is new or not.
	 */
	protected boolean isNew = false;

	/**
	 * Flag indicating whether this session is valid or not.
	 */
	protected boolean isValid = false;

	/**
	 * Internal notes associated with this session by Catalina components
	 * and event listeners.  <b>IMPLEMENTATION NOTE:</b> This object is
	 * <em>not</em> saved and restored across session serializations!
	 */
	protected transient HashMap notes = new HashMap();

	/**
	 * The authenticated Participant associated with this session, if any.
	 * <b>IMPLEMENTATION NOTE:</b>  This object is <i>not</i> saved and
	 * restored across session serializations!
	 */
	protected transient Participant workflowUser = null;

	/**
	 * The string manager for this package.
	 */
	protected static StringManager sm = StringManager.getManager(Constants.PACKAGE);

	/**
	 * The property change support for this component.  NOTE:  This value
	 * is not included in the serialized version of this object.
	 */
	protected transient PropertyChangeSupport support = new PropertyChangeSupport(this);

	/**
	 * The current accessed time for this session.
	 */
	protected long thisAccessedTime = creationTime;

	/**
	 * Set the creation time for this session.  This method is called by the
	 * SessionManager when an existing Session instance is reused.
	 *
	 * @param time The new creation time
	 */
	public void setCreationTime(long time)
	{

		this.creationTime = time;
		this.lastAccessedTime = time;
		this.thisAccessedTime = time;

	}

	/**
	 * Return the session identifier for this session.
	 */
	public String getId()
	{

		return (this.id);

	}

	/**
	 * Set the session identifier for this session.
	 *
	 * @param id The new session identifier
	 */
	public void setId(String id)
	{

		if ((this.id != null) && (manager != null))
		{
			manager.remove(this);
		}

		this.id = id;

		if (manager != null)
		{
			manager.add(this);
		}
	}

	/**
	 * Return descriptive information about this Session implementation and
	 * the corresponding version number, in the format
	 * <code>&lt;description&gt;/&lt;version&gt;</code>.
	 */
	public String getInfo()
	{

		return (this.info);

	}

	/**
	 * Return the last time the client sent a request associated with this
	 * session, as the number of milliseconds since midnight, January 1, 1970
	 * GMT.  Actions that your application takes, such as getting or setting
	 * a value associated with the session, do not affect the access time.
	 */
	public long getLastAccessedTime()
	{
		if (!isValid())
		{
			throw new IllegalStateException(sm.getString("willowSession.getLastAccessedTime.ise"));
		}
		return (this.lastAccessedTime);

	}

	/**
	 * Return the SessionManager within which this Session is valid.
	 */
	public SessionManager getSessionManager()
	{

		return (this.manager);

	}

	/**
	 * Set the SessionManager within which this Session is valid.
	 *
	 * @param manager The new SessionManager
	 */
	public void setSessionManager(SessionManager manager)
	{

		this.manager = manager;

	}

	/**
	 * Return the maximum time interval, in seconds, between client requests
	 * before the servlet container will invalidate the session.  A negative
	 * time indicates that the session should never time out.
	 */
	public int getMaxInactiveInterval()
	{

		return (this.maxInactiveInterval);

	}

	/**
	 * Set the maximum time interval, in seconds, between client requests
	 * before the servlet container will invalidate the session.  A negative
	 * time indicates that the session should never time out.
	 *
	 * @param interval The new maximum interval
	 */
	public void setMaxInactiveInterval(int interval)
	{

		this.maxInactiveInterval = interval;
		if (isValid && interval == 0)
		{
			expire();
		}

	}

	/**
	 * Set the <code>isNew</code> flag for this session.
	 *
	 * @param isNew The new value for the <code>isNew</code> flag
	 */
	public void setNew(boolean isNew)
	{

		this.isNew = isNew;

	}

	/**
	 * Return the authenticated Participant that is associated with this Session.
	 * This provides an <code>Authenticator</code> with a means to cache a
	 * previously authenticated Participant, and avoid potentially expensive
	 * <code>Realm.authenticate()</code> calls on every request.  If there
	 * is no current associated Participant, return <code>null</code>.
	 */
	public Participant getParticipant()
	{

		return (this.workflowUser);

	}

	/**
	 * Set the authenticated Participant that is associated with this Session.
	 *
	 * @param workflowUser The new Participant, or <code>null</code> if none
	 */
	public void setParticipant(Participant workflowUser)
	{

		Participant oldWorkflowUser = this.workflowUser;
		this.workflowUser = workflowUser;
		support.firePropertyChange("participant", oldWorkflowUser, this.workflowUser);

	}

	/**
	 * Return the <code>WiilowSession</code> for which this object
	 * is the facade.
	 */
	public WorkflowSession getSession()
	{

		return this;

	}

	/**
	 * Return the <code>isValid</code> flag for this session.
	 */
	public boolean isValid()
	{

		if (this.expiring)
		{
			return true;
		}

		if (!this.isValid)
		{
			return false;
		}

		if (maxInactiveInterval >= 0)
		{		
			long timeNow = System.currentTimeMillis();
			int timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);
			if (timeIdle >= maxInactiveInterval)
			{
				System.err.println("expired : "+this);
				expire();
			}
		}

		return (this.isValid);
	}

	/**
	 * Set the <code>isValid</code> flag for this session.
	 *
	 * @param isValid The new value for the <code>isValid</code> flag
	 */
	public void setValid(boolean isValid)
	{

		this.isValid = isValid;
	}

	// ------------------------------------------------- Session Public Methods

	/**
	 * Update the accessed time information for this session.  This method
	 * should be called by the context when a request comes in for a particular
	 * session, even if the application does not reference it.
	 */
	public void access()
	{

		this.isNew = false;
		this.lastAccessedTime = this.thisAccessedTime;
		this.thisAccessedTime = System.currentTimeMillis();

		evaluateIfValid();
	}

	/**
	 * Perform the internal processing required to invalidate this session,
	 * without triggering an exception if the session has already expired.
	 */
	public void expire()
	{

		// Mark this session as "being expired" if needed
		if (expiring)
			return;

		synchronized (this)
		{

			if (manager == null)
				return;

			expiring = true;

			setValid(false);

			// Remove this session from our manager's active sessions
			if (manager != null)
			{
				manager.remove(this);
			}

			// We have completed expire of this session
			expiring = false;

		}


	}

	/**
	 * Release all object references, and initialize instance variables, in
	 * preparation for reuse of this object.
	 */
	public void recycle()
	{

		// Reset the instance variables associated with this Session
		attributes.clear();
		creationTime = 0L;
		expiring = false;
		id = null;
		lastAccessedTime = 0L;
		maxInactiveInterval = -1;
		notes.clear();
		setParticipant(null);
		isNew = false;
		isValid = false;
		manager = null;

	}

	/**
	 * Return a string representation of this object.
	 */
	public String toString()
	{

		StringBuffer sb = new StringBuffer();
		sb.append("WillowSession[");
		sb.append(id);
		sb.append("]");
		return (sb.toString());

	}

	// ------------------------------------------------- WillowSession Properties

	/**
	 * Return the time when this session was created, in milliseconds since
	 * midnight, January 1, 1970 GMT.
	 *
	 * @exception IllegalStateException if this method is called on an
	 *  invalidated session
	 */
	public long getCreationTime()
	{

		if (!isValid())
			throw new IllegalStateException(sm.getString("willowSession.getCreationTime.ise"));

		return (this.creationTime);

	}

	/**
	 * Invalidates this session and unbinds any objects bound to it.
	 *
	 * @exception IllegalStateException if this method is called on
	 *  an invalidated session
	 */
	public void invalidate()
	{

		if (!isValid())
			throw new IllegalStateException(sm.getString("willowSession.invalidate.ise"));

		// Cause this session to expire
		expire();

	}

	/**
	 * Return <code>true</code> if the client does not yet know about the
	 * session, or if the client chooses not to join the session.  For
	 * example, if the server used only cookie-based sessions, and the client
	 * has disabled the use of cookies, then a session would be new on each
	 * request.
	 *
	 * @exception IllegalStateException if this method is called on an
	 *  invalidated session
	 */
	public boolean isNew()
	{

		if (!isValid())
			throw new IllegalStateException(sm.getString("willowSession.isNew.ise"));

		return (this.isNew);

	}

	protected void evaluateIfValid()
	{
		/*
		* If this session has expired or is in the process of expiring or
		* will never expire, return
		*/
		if (!this.isValid || expiring || maxInactiveInterval < 0)
			return;

		if (!isValid())
		{
			try
			{
				expire();
			}
			catch (Throwable t)
			{
				log(sm.getString("willowSession.expireException"), t);
			}
		}

	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * Log a message on the Logger associated with our SessionManager (if any).
	 *
	 * @param message Message to be logged
	 */
	protected void log(String message)
	{

		if (manager != null)
		{
			 manager.log(message);
		}
		else
		{
			System.out.println("WillowSession: " + message);
		}

	}

	/**
	 * Log a message on the Logger associated with our SessionManager (if any).
	 *
	 * @param message Message to be logged
	 * @param throwable Associated exception
	 */
	protected void log(String message, Throwable throwable)
	{

		if ((manager != null) && (manager instanceof SessionManager))
		{
			((SessionManager) manager).log(message, throwable);
		}
		else
		{
			System.out.println("WillowSession: " + message);
			throwable.printStackTrace(System.out);
		}

	}

}