/*
 * 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.venice.session;

import java.util.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.RequestType;
import com.silverwrist.dynamo.db.UserManagement;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;
import com.silverwrist.venice.VeniceNamespaces;

public class VeniceSessionManager
    implements NamedObject, ComponentInitialize, ComponentShutdown, SessionInfoListener, RequestPreprocessor,
               DynamicUpdateListener, SessionInfoParams
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing "venice_session" operations in the script engine
   *--------------------------------------------------------------------------------
   */

  public class VeniceSessionOpsImpl
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

    VeniceSessionOpsImpl()
    { // do nothing
    } // end constructor

    /*====================================================================
     * External operations
     *====================================================================
     */

    public String getLoginCookieName()
    {
      return m_cookie_name;

    } // end getLoginCookieName

    public int getLoginCookieAge()
    {
      return m_cookie_maxage * 86400;  // convert to seconds

    } // end getLoginCookieAge

  } // end class VeniceSessionOpsImpl

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

  private static final String CONTROL_NAMESPACE = VeniceNamespaces.SESSION_CONTROL_NAMESPACE;

  private static final String PROP_SESSION_INIT_SCRIPT = "session.init.script";
  private static final String PROP_LOGIN_COOKIE = "login.cookie";
  private static final String PROP_LOGIN_COOKIE_MAXAGE = "login.cookie.maxage";
  private static final String PROP_PASSWORD_RECOVERY_TIME = "password.recovery.time";

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

  private String m_name;                                  // name of this object
  private DynamoUser m_anonymous;                         // anonymous user
  private ObjectProvider m_props;                         // global properties provider
  private String m_session_init_script;                   // session initialization script
  private String m_cookie_name;                           // name of login cookie
  private int m_cookie_maxage;                            // maximum age of cookie (in days)
  private int m_pass_recovery_time;                       // password recovery time (in minutes)
  private LinkedList m_shutdown_list = new LinkedList();  // shutdown list

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

  public VeniceSessionManager()
  { // do nothing
  } // end constructor

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

  private final String getInitString(String name) throws ConfigException
  {
    try
    { // call through to the property provider
      return (String)(m_props.getObject(CONTROL_NAMESPACE,name));

    } // end try
    catch (NoSuchObjectException e)
    { // the property value isn't present?  yIkes!
      ConfigException ce = new ConfigException(VeniceSessionManager.class,"SessionMessages","prop.missing",e);
      ce.setParameter(0,name);
      throw ce;

    } // end catch
    catch (ClassCastException e)
    { // not a String?  for shame!
      ConfigException ce = new ConfigException(VeniceSessionManager.class,"SessionMessages",
					       "prop.wrongType",e);
      ce.setParameter(0,name);
      throw ce;

    } // end catch

  } // end getInitString

  private final int getInitInteger(String name) throws ConfigException
  {
    try
    { // call through to the property provider
      return ((Integer)(m_props.getObject(CONTROL_NAMESPACE,name))).intValue();

    } // end try
    catch (NoSuchObjectException e)
    { // the property value isn't present?  yIkes!
      ConfigException ce = new ConfigException(VeniceSessionManager.class,"SessionMessages","prop.missing",e);
      ce.setParameter(0,name);
      throw ce;

    } // end catch
    catch (ClassCastException e)
    { // not an Integer?  for shame!
      ConfigException ce = new ConfigException(VeniceSessionManager.class,"SessionMessages",
					       "prop.wrongType",e);
      ce.setParameter(0,name);
      throw ce;

    } // end catch

  } // end getInitString

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

  public String toString()
  {
    return "the VeniceSessionManager";

  } // end toString

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentInitialize
   *--------------------------------------------------------------------------------
   */

  /**
   * Initialize the component.
   *
   * @param config_root Pointer to the section of the Dynamo XML configuration file that configures this
   *                    particular component.  This is to be considered "read-only" by the component.
   * @param services An implementation of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider}
   *                 which provides initialization services to the component.  This will include an implementation
   *                 of {@link com.silverwrist.dynamo.iface.ObjectProvider ObjectProvider} which may be used to
   *                 get information about other objects previously initialized by the application.
   * @exception com.silverwrist.dynamo.except.ConfigException If an error is encountered in the component
   *            configuration.
   */
  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    logger.info("VeniceSessionManager initializing");

    XMLLoader loader = XMLLoader.get();
    String gprops = null, users = null;
    try
    { // verify the right node name
      loader.verifyNodeName(config_root,"object");

      // get the object's name
      m_name = loader.getAttribute(config_root,"name");

      // get the name of the global properties object
      DOMElementHelper config_root_h = new DOMElementHelper(config_root);
      Element foo = loader.getSubElement(config_root_h,"global-properties");
      gprops = loader.getAttribute(foo,"object");

      // get the name of the users object
      foo = loader.getSubElement(config_root_h,"user-manager");
      users = loader.getAttribute(foo,"object");

    } // end try
    catch (XMLLoadException e)
    { // error loading XML config data
      logger.fatal("XML loader exception in VeniceSessionManager",e);
      throw new ConfigException(e);

    } // end catch

    // Get the standard properties provider.
    ServiceProvider gdm_sp =
        (ServiceProvider)(GetObjectUtils.getDynamoComponent(services,ServiceProvider.class,gprops));
    try
    { // get the "properties" service from that object
      m_props = (ObjectProvider)(gdm_sp.queryService(ObjectProvider.class,"properties"));

    } // end try
    catch (NoSuchServiceException e)
    { // this shouldn't happen, but...
      throw new ConfigException(VeniceSessionManager.class,"SessionMessages","init.noProp",e);

    } // end catch

    // Get the values of the session properties.
    m_session_init_script = getInitString(PROP_SESSION_INIT_SCRIPT);
    m_cookie_name = getInitString(PROP_LOGIN_COOKIE);
    m_cookie_maxage = getInitInteger(PROP_LOGIN_COOKIE_MAXAGE);
    m_pass_recovery_time = getInitInteger(PROP_PASSWORD_RECOVERY_TIME);

    try
    { // Get the "anonymous" user context and save a reference to it.
      UserManagement umgmt =
          (UserManagement)(GetObjectUtils.getDynamoComponent(services,UserManagement.class,users));
      m_anonymous = umgmt.getAnonymousUser();

    } // end try
    catch (DatabaseException e)
    { // translate to ConfigException
      throw new ConfigException(VeniceSessionManager.class,"SessionMessages","no.anon",e);

    } // end catch

    // Register a scripting object that exposes the session properties to scripts.
    ScriptEngineConfig sconf = (ScriptEngineConfig)(services.queryService(ScriptEngineConfig.class));
    m_shutdown_list.addFirst(sconf.addDeclaredObject("venice_session",new VeniceSessionOpsImpl(),
						     VeniceSessionOpsImpl.class));

    // Register our "cookie" authenticator and password recovery authenticator.
    AuthenticatorRegistration authreg =
        (AuthenticatorRegistration)(services.queryService(AuthenticatorRegistration.class));
    m_shutdown_list.addFirst(authreg.registerAuthenticator(CONTROL_NAMESPACE,"cookie",
							   new CookieAuthenticator()));
    m_shutdown_list.addFirst(authreg.registerAuthenticator(CONTROL_NAMESPACE,"password.recovery",
							   new PasswordRecoveryAuthenticator(this)));

    // Register this object to listen to session info events and property update events.
    EventListenerRegistration reg =
        (EventListenerRegistration)(services.queryService(EventListenerRegistration.class));
    m_shutdown_list.addFirst(reg.registerSessionInfoListener(this));
    m_shutdown_list.addFirst(reg.registerDynamicUpdateListener(GlobalPropertyUpdateEvent.class,this));

    // Register this object to preprocess requests.
    RequestPreprocessorRegistration rpreg =
        (RequestPreprocessorRegistration)(services.queryService(RequestPreprocessorRegistration.class));
    m_shutdown_list.addFirst(rpreg.registerRequestPreprocessor(this));

    logger.info("VeniceSessionManager initialized");

  } // end initialize

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

  public void shutdown()
  {
    logger.info("Shutting down VeniceSessionManager");
    while (m_shutdown_list.size()>0)
    { // shut down everything on the shutdown list
      ComponentShutdown cs = (ComponentShutdown)(m_shutdown_list.removeFirst());
      cs.shutdown();

    } // end while

    m_props = null;
    m_anonymous = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface SessionInfoListener
   *--------------------------------------------------------------------------------
   */

  public void sessionInitialized(SessionInfoEvent event)
  {
    SessionInfo session = event.getSession();

    // Add the "anonymous" user context to this session.
    session.setObject(NAMESPACE,ATTR_USER,m_anonymous);

    // Add the default stylesheet selector to the session.
    session.setObject(NAMESPACE,ATTR_STYLESHEET_SELECTOR,"normal");

    try
    { // Execute the session-initialization script.
      Request r = event.getRequest();
      ScriptExecute exec = (ScriptExecute)(r.queryService(ScriptExecute.class));
      exec.executeScript(r,m_session_init_script);

    } // end try
    catch (ScriptingException e)
    { // just log this exception
      logger.error("Session initialization script failed",e);

    } // end catch

  } // end sessionInitialized

  public void sessionExiting(SessionInfoEvent event)
  { // don't use this notification
  } // end sessionExiting

  /*--------------------------------------------------------------------------------
   * Implementations from interface RequestPreprocessor
   *--------------------------------------------------------------------------------
   */

  public void preprocessRequest(Request r)
  {
    if (RequestType.HTTP.equals(r.getType()))
    { // only need to worry about any of this in HTTP requests
      if (logger.isDebugEnabled())
	logger.debug("VeniceSessionManager preprocessing request: " + r);

      RequestHelper rh = new RequestHelper(r);
      SessionInfo session = rh.getSession();
      try
      { // look for the stylesheet selector
	session.getObject(NAMESPACE,ATTR_STYLESHEET_SELECTOR);

      } // end try
      catch (NoSuchObjectException e)
      { // TEMP: until we can sniff the browser
	session.setObject(NAMESPACE,ATTR_STYLESHEET_SELECTOR,"normal");

      } // end catch

      try
      { // see if the "location" has been set yet
	r.getObject(REQ_NAMESPACE,RATTR_LOCATION);

      } // end r
      catch (NoSuchObjectException e)
      { // Create the initial "location" request data.
	StringBuffer loc_buf = new StringBuffer(r.getRequestPath());
	if (loc_buf.charAt(0)=='/')
	  loc_buf.delete(0,1);
	String tmp = r.getExtraPath();
	if (tmp!=null)
	  loc_buf.append(tmp);
	tmp = r.getQueryString();
	if (tmp!=null)
	  loc_buf.append('?').append(tmp);
	if (logger.isDebugEnabled())
	  logger.debug("Setting location: " + loc_buf.toString());
	r.setObject(REQ_NAMESPACE,RATTR_LOCATION,loc_buf.toString());

      } // end catch

      try
      { // see if "display login" has already been set
	r.getObject(REQ_NAMESPACE,RATTR_DISPLAY_LOGIN);

      } // end try
      catch (NoSuchObjectException e)
      { // Set the "display login" flag.
	r.setObject(REQ_NAMESPACE,RATTR_DISPLAY_LOGIN,Boolean.TRUE);

      } // end catch

    } // end if

  } // end preprocessRequest

  /*--------------------------------------------------------------------------------
   * Implementations from interface DynamicUpdateListener
   *--------------------------------------------------------------------------------
   */

  public void updateReceived(DynamicUpdateEvent evt)
  {
    GlobalPropertyUpdateEvent event = (GlobalPropertyUpdateEvent)evt;
    if (CONTROL_NAMESPACE.equals(event.getPropertyNamespace()))
    { // see if this is one of the properties we monitor
      try
      { // reuse the "initialization" getters here
	if (PROP_SESSION_INIT_SCRIPT.equals(event.getPropertyName()))
	{ // process new initialization script
	  String s = getInitString(PROP_SESSION_INIT_SCRIPT);
	  m_session_init_script = s;

	} // end if
	else if (PROP_LOGIN_COOKIE.equals(event.getPropertyName()))
	{ // process new cookie name
	  String s = getInitString(PROP_LOGIN_COOKIE);
	  m_cookie_name = s;

	} // end else if
	else if (PROP_LOGIN_COOKIE_MAXAGE.equals(event.getPropertyName()))
	{ // process new cookie maximum age
	  int x = getInitInteger(PROP_LOGIN_COOKIE_MAXAGE);
	  m_cookie_maxage = x;

	} // end else if
	else if (PROP_PASSWORD_RECOVERY_TIME.equals(event.getPropertyName()))
	{ // process new password recovery time
	  int x = getInitInteger(PROP_PASSWORD_RECOVERY_TIME);
	  m_pass_recovery_time = x;
	  
	} // end else if

      } // end try
      catch (ConfigException ce)
      { // property get failed - don't change the value
      } // end catch

    } // end if

  } // end updateReceived

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

  long getPasswordRecoveryTime()
  {
    return (long)(m_pass_recovery_time * 60000L);

  } // end getPasswordRecoveryTime

} // end class VeniceSessionManager
