/*
 * 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 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.ui.rpc;

import java.util.*;
import javax.servlet.*;
import org.apache.log4j.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.config.RootConfig;
import com.silverwrist.venice.ui.servlet.RequestImpl;

class RpcSessionBroker
{
  /*--------------------------------------------------------------------------------
   * Internal class for sweeping the sessions
   *--------------------------------------------------------------------------------
   */

  class SessionSweeper extends TimerTask
  {
    private LinkedList workspace = new LinkedList();

    SessionSweeper()
    {
      super();

    } // end constructor

    public void run()
    {
      synchronized (RpcSessionBroker.this)
      { // seek and destroy all expired sessions
	Iterator it = sessions.values().iterator();
	while (it.hasNext())
	{ // find all the tasks that are due to be invalidated
	  RpcVeniceUISession s = (RpcVeniceUISession)(it.next());
	  if (s.canUnloadNow())
	    workspace.addLast(s);

	} // end while

	while (workspace.size()>0)
	{ // invalidate all sessions that have not been accessed recently
	  RpcVeniceUISession s = (RpcVeniceUISession)(workspace.removeFirst());
	  if (logger.isDebugEnabled())
	    logger.debug("session sweeper removing session with ID " + s.getID());
	  s.invalidate();

	} // end while

      } // end synchronized block

    } // end run

  } // end class SessionSweeper

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

  private static Category logger = Category.getInstance(RpcSessionBroker.class);

  private static final String ATTRIBUTE = "ui.rpc.SessionBroker";

  private static final long INTERVAL = 1000;

  private static final String PREFIX = "rpcsess:";
  private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
  private static final int ID_LENGTH = 40;

  private static Random rng = new Random();  // random number generator

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

  private HashMap sessions = new HashMap();  // the actual sessions
  private VeniceEngine engine;               // the Venice engine
  private int timeout_value;                 // the timeout value
  private Timer timer = new Timer(true);     // the interval timer used to schedule sweeps

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

  private RpcSessionBroker(VeniceEngine engine, RootConfig root)
  {
    this.engine = engine;
    this.timeout_value = root.getRpcSessionTimeout();
    timer.scheduleAtFixedRate(new SessionSweeper(),INTERVAL,INTERVAL);

  } // end constructor

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

  private static final String generateID()
  {
    StringBuffer buf = new StringBuffer(PREFIX);
    for (int i=0; i<ID_LENGTH; i++)
      buf.append(ALPHABET.charAt(rng.nextInt(ALPHABET.length())));
    return buf.toString();

  } // end generateID

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

  final int getTimeout()
  {
    return timeout_value;

  } // end getTimeout

  final VeniceEngine getEngine()
  {
    return engine;

  } // end getEngine

  final synchronized VeniceUISession getSession(String id)
  {
    if (logger.isDebugEnabled())
      logger.debug("looking up session with ID " + id + " - current session count is " + sessions.size());
    RpcVeniceUISession rc = (RpcVeniceUISession)(sessions.get(id));
    if (rc!=null)
      rc.touch();
    return rc;

  } // end getSession

  final synchronized VeniceUISession createSession(String remote_address) throws DataException
  {
    String new_id = generateID();
    while (sessions.containsKey(new_id))
      new_id = generateID();
    RpcVeniceUISession rc = new RpcVeniceUISession(new_id,remote_address,this);
    sessions.put(new_id,rc);
    if (logger.isDebugEnabled())
      logger.debug("created new session with ID " + new_id + " - session count now " + sessions.size());
    return rc;

  } // end createSession

  final synchronized void detachSession(String id)
  {
    if (logger.isDebugEnabled())
      logger.debug("removing session with ID " + id + " - current session count is " + sessions.size());
    sessions.remove(id);
    if (logger.isDebugEnabled())
      logger.debug("session count now " + sessions.size());

  } // end detachSession

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

  public static RpcSessionBroker get(ServletContext ctxt)
  {
    return (RpcSessionBroker)(RequestImpl.getAppAttribute(ctxt,ATTRIBUTE));

  } // end get

  public static RpcSessionBroker get(RequestInput ri)
  {
    return (RpcSessionBroker)(ri.getAppAttribute(ATTRIBUTE));

  } // end get

  public static synchronized void init(ServletContext ctxt, VeniceEngine engine, RootConfig root)
  {
    if (RequestImpl.getAppAttribute(ctxt,ATTRIBUTE)==null)
      RequestImpl.setAppAttribute(ctxt,ATTRIBUTE,new RpcSessionBroker(engine,root));

  } // end init

} // end class RpcSessionBroker
