/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.servlet;

import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.Logger;
import com.silverwrist.dynamo.RequestType;
import com.silverwrist.dynamo.WebConstants;
import com.silverwrist.dynamo.app.ApplicationContainer;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

/**
 * An implementation of {@link com.silverwrist.dynamo.iface.SessionInfo SessionInfo} that is used in
 * conjunction with the servlet container's <CODE>HttpSession</CODE>.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 */
class HttpSessionInfo implements SessionInfo, ComponentShutdown, HttpSessionBindingListener
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Logger logger = Logger.getLogger(HttpSessionInfo.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private HttpSession m_session;            // backreference to the HTTP session
  private String m_id;                      // the session ID
  private ApplicationContainer m_appcon;    // backreference to the application container
  private MemoryObjectStore m_store;        // where we keep all session attributes

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  /**
   * Constructs a new <CODE>HttpSessionInfo</CODE> instance.
   *
   * @param session The <CODE>HttpSession<CODE> associated with this session.
   * @param appcon The application's {@link com.silverwrist.dynamo.app.ApplicationContainer ApplicationContainer}.
   */
  HttpSessionInfo(HttpSession session, ApplicationContainer appcon)
  {
    m_session = session;
    m_id = session.getId();
    m_appcon = appcon;
    m_store = new MemoryObjectStore("Session Store " + m_id);

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Shuts down the session.  It sends out session-exiting notifications, unbinds all variables from the
   * session, and erases the contents.
   */
  private final void destroy()
  {
    logger.info("Calling sessionExiting notifications on session " + m_id);

    // Send the end-session notifications.
    SessionInfoListener[] listeners = m_appcon.getSessionListeners();
    if (listeners.length>0)
    { // call each listener in turn
      SessionEventRequest req = new SessionEventRequest(m_appcon.wrapServices(null),this,false);
      SessionInfoEvent evt = new SessionInfoEvent(req,this);
      for (int i=0; i<listeners.length; i++)
	listeners[i].sessionExiting(evt);

    } // end if

    // Tell all the values in the object store they're being kicked out.
    m_store.visitAllContents(new ObjectVisitor()
      {
	public void visit(String namespace, String name, Object value)
	{
	  if (value instanceof SessionValueBindListener)
	    ((SessionValueBindListener)value).valueRemoved(new SessionValueBindEvent(HttpSessionInfo.this,
										     namespace,name));

	} // end visit

      }); // end ObjectVisitor object

    // Detach all data.
    m_store.clear();
    m_store = null;
    m_session = null;
    m_appcon = null;

  } // end destroy

  /*--------------------------------------------------------------------------------
   * Overrides from class Object
   *--------------------------------------------------------------------------------
   */

  /**
   * Indicates whether some other object is "equal to" this one.
   *
   * @param o The reference object with which to compare.
   * @return <CODE>true</CODE> if this object is the same as the <CODE>o</CODE> argument; <CODE>false</CODE> otherwise.
   */
  public boolean equals(Object o)
  {
    if ((o==null) || !(o instanceof HttpSessionInfo))
      return false;
    SessionInfo other = (SessionInfo)o;
    return m_id.equals(other.getID());

  } // end equals

  /**
   * Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those
   * provided by <CODE>java.util.Hashtable</CODE>.
   *
   * @return A hash code value for this object.
   */
  public int hashCode()
  {
    return m_id.hashCode();

  } // end hashCode

  /**
   * Returns a string representation of the object. In general, the <CODE>toString</CODE> method returns a string
   * that "textually represents" this object. 
   *
   * @return A string representation of the object.
   */
  public String toString()
  {
    return "{HttpSessionInfo id=" + m_id + "}";

  } // end toString

  /*--------------------------------------------------------------------------------
   * Implementations from interface ObjectProvider
   *--------------------------------------------------------------------------------
   */

  /**
   * Retrieves an object from this session's <CODE>ObjectProvider</CODE>.
   *
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be retrieved.
   * @return The object reference specified.
   */
  public Object getObject(String namespace, String name)
  {
    if (namespace==null)
      throw new NullPointerException("HttpSessionInfo.getObject(namespace)");
    if (name==null)
      throw new NullPointerException("HttpSessionInfo.getObject(name)");

    return m_store.getObject(namespace,name);

  } // end getObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface ObjectStore
   *--------------------------------------------------------------------------------
   */

  /**
   * Sets an object into this session's <CODE>ObjectStore</CODE>.  If the object implements
   * {@link com.silverwrist.dynamo.event.SessionValueBindListener SessionValueBindListener}, its
   * {@link com.silverwrist.dynamo.event.SessionValueBindListener#valueAdded(com.silverwrist.dynamo.event.SessionValueBindEvent) valueAdded()}
   * method will be called.  If there was an object previously stored in this <CODE>ObjectStore</CODE> with the given
   * namespace and name, and it implements <CODE>SessionValueBindListener</CODE>, its
   * {@link com.silverwrist.dynamo.event.SessionValueBindListener#valueReplaced(com.silverwrist.dynamo.event.SessionValueBindEvent) valueReplaced()}
   * method will be called.
   *
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be set.
   * @param value The object to set into the <CODE>ObjectStore</CODE>.
   * @return The previous object that was set into the <CODE>ObjectStore</CODE> under this namespace and name, or
   *         <CODE>null</CODE> if there was no such object.
   */
  public Object setObject(String namespace, String name, Object value)
  {
    if (namespace==null)
      throw new NullPointerException("HttpSessionInfo.setObject(namespace)");
    if (name==null)
      throw new NullPointerException("HttpSessionInfo.setObject(name)");
    if (value==null)
      throw new NullPointerException("HttpSessionInfo.setObject(value)");

    Object old_val = m_store.setObject(namespace,name,value);
    if (   (value instanceof SessionValueBindListener)
	|| ((old_val!=null) && (old_val instanceof SessionValueBindListener)))
    { // fire events indicating an object is being replaced and/or added
      SessionValueBindEvent evt = new SessionValueBindEvent(this,namespace,name,value);
      if ((old_val!=null) && (old_val instanceof SessionValueBindListener))
	((SessionValueBindListener)old_val).valueReplaced(evt);
      if (value instanceof SessionValueBindListener)
	((SessionValueBindListener)value).valueAdded(evt);

    } // end if

    return old_val;

  } // end setObject

  /**
   * Removes an object from this session's <CODE>ObjectStore</CODE>.  If the object implements
   * {@link com.silverwrist.dynamo.event.SessionValueBindListener SessionValueBindListener}, its
   * {@link com.silverwrist.dynamo.event.SessionValueBindListener#valueRemoved(com.silverwrist.dynamo.event.SessionValueBindEvent) valueRemoved()}
   * method will be called.
   *
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be removed.
   * @return The previous object that was set into the <CODE>ObjectStore</CODE> under this namespace and name, or
   *         <CODE>null</CODE> if there was no such object.
   */
  public Object removeObject(String namespace, String name)
  {
    if (namespace==null)
      throw new NullPointerException("HttpSessionInfo.removeObject(namespace)");
    if (name==null)
      throw new NullPointerException("HttpSessionInfo.removeObject(name)");

    Object old_val = m_store.removeObject(namespace,name);
    if ((old_val!=null) && (old_val instanceof SessionValueBindListener))
      ((SessionValueBindListener)old_val).valueRemoved(new SessionValueBindEvent(this,namespace,name));
    return old_val;

  } // end removeObject

  /**
   * Returns a collection of all object namespaces that have been set into this session's <CODE>ObjectStore</CODE>.
   *
   * @return A <CODE>java.util.Collection</CODE> containing <CODE>String</CODE> objects specifying all the
   *         object namespaces.
   */
  public Collection getNamespaces()
  {
    return m_store.getNamespaces();

  } // end getNamespaces

  /**
   * Returns a collection of all object names that have been set into this session's <CODE>ObjectStore</CODE>
   * under a given namespace.
   *
   * @param namespace The namespace to look for names under.
   * @return A <CODE>java.util.Collection</CODE> containing <CODE>String</CODE> objects specifying all the
   *         object names for this namespace.
   */
  public Collection getNamesForNamespace(String namespace)
  {
    return m_store.getNamesForNamespace(namespace);

  } // end getNamesForNamespace

  /*--------------------------------------------------------------------------------
   * Implementations from interface SessionInfo
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns a value indicating the type of this session.  This will usually be the same as the type of the
   * {@link com.silverwrist.dynamo.iface.Request Request} objects that work with this session object.
   *
   * @return The type of this request.
   * @see com.silverwrist.dynamo.RequestType
   * @see com.silverwrist.dynamo.iface.Request#getType()
   */
  public RequestType getSessionType()
  {
    return RequestType.HTTP;

  } // end getSessionType

  /**
   * Returns the unique identifier for this session.
   *
   * @return The unique identifier for this session.
   */
  public String getID()
  {
    return m_id;

  } // end getID

  /**
   * Returns the time at which this session was created.  This is expressed in the same units as
   * <CODE>System.currentTimeMillis()</CODE>.
   *
   * @return The time at which the session was created.
   */
  public long getCreationTime()
  {
    return m_session.getCreationTime();

  } // end getCreationTime

  /**
   * Returns the time at which this session was last accessed.  This is expressed in the same units as
   * <CODE>System.currentTimeMillis()</CODE>.
   *
   * @return The time at which the session was last accessed.
   */
  public long getLastAccessedTime()
  {
    return m_session.getLastAccessedTime();

  } // end getLastAccessedTime

  /**
   * Specifies the time, in seconds, between client requests before the framework will invalidate this
   * session. A negative value indicates the session should never timeout.
   *
   * @param interval An integer specifying the number of seconds.
   */
  public void setMaxInactiveInterval(int interval)
  {
    m_session.setMaxInactiveInterval(interval);

  } // end setMaxInactiveInterval

  /**
   * Returns the maximum time interval, in seconds, that the framework will keep this session open between
   * client accesses. After this interval, the framework will invalidate the session. The maximum time
   * interval can be set with the {@link #setMaxInactiveInterval(int) setMaxInactiveInterval} method. A negative
   * value indicates the session should never timeout.
   *
   * @return An integer specifying the number of seconds this session remains open between client requests.
   */
  public int getMaxInactiveInterval()
  {
    return m_session.getMaxInactiveInterval();

  } // end getMaxInactiveInterval

  /**
   * Causes the session to be invalidated, and any objects bound to it to be unbound.  Also invalidates
   * any underlying session attributes in the enclosing environment (such as the servlet container), if
   * present.
   */
  public void invalidate()
  {
    m_session.invalidate();

  } // end invalidate

  /**
   * Causes the session to be invalidated, and any objects bound to it to be unbound.  If there are any
   * underlying session attributes in the enclosing environment (such as the servlet container), they are
   * unaffected.
   */
  public void detach()
  {
    m_session.removeAttribute(WebConstants.SESSION_PARAM);

  } // end detach

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentShutdown
   *--------------------------------------------------------------------------------
   */

  /**
   * Shuts down the session.  It sends out session-exiting notifications, unbinds all variables from the
   * session, and erases the contents.
   */
  public void shutdown()
  {
    destroy();

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface HttpSessionBindingListener
   *--------------------------------------------------------------------------------
   */

  /**
   * Notifies the object that it is being bound to an <CODE>HttpSession</CODE> and identifies the session.
   *
   * @param event The event that identifies the session.
   */
  public void valueBound(HttpSessionBindingEvent event)
  {
    m_appcon.addSessionLink(this);

  } // end valueBound

  /**
   * Notifies the object that it is being unbound from an <CODE>HttpSession</CODE> and identifies the session.
   *
   * @param event The event that identifies the session.
   */
  public void valueUnbound(HttpSessionBindingEvent event)
  {
    m_appcon.removeSessionLink(this);
    destroy();

  } // end valueUnbound

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Sends out all session-initialized notifications.  This is done as a separate operation because of the need
   * to provide a {@link com.silverwrist.dynamo.iface.Request Request} context to send the event out in.
   *
   * @param r The <CODE>Request</CODE> to provide context for the events.
   */
  void callSessionInit(Request r)
  {
    logger.info("Calling sessionInitialized notifications on session " + m_id);

    // Send the start-session notifications.
    SessionInfoListener[] listeners = m_appcon.getSessionListeners();
    if (listeners.length>0)
    { // call each listener in turn
      SessionEventRequest my_req = new SessionEventRequest(this,true,r);
      SessionInfoEvent evt = new SessionInfoEvent(my_req,this);
      for (int i=0; i<listeners.length; i++)
	listeners[i].sessionInitialized(evt);

    } // end if

  } // end callSessionInit

} // end class HttpSessionInfo
