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

import java.awt.Dimension;
import java.io.*;
import java.lang.ref.*;
import java.sql.Blob;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.config.*;
import com.silverwrist.venice.ui.dlg.Dialog;
import com.silverwrist.venice.ui.helpers.*;
import com.silverwrist.venice.ui.menus.CommunityMenu;
import com.silverwrist.venice.ui.menus.Menu;
import com.silverwrist.venice.ui.menus.MenuComponent;
import com.silverwrist.venice.ui.script.*;
import com.silverwrist.venice.util.XMLLoader;

public class RequestImpl implements RequestInput
{
  /*--------------------------------------------------------------------------------
   * Internal class which implements the output.
   *--------------------------------------------------------------------------------
   */

  class OutputImpl implements RequestOutput
  {
    private Writer wr = null;

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

    public Object queryService(Class klass)
    {
      if (klass==HTMLRendering.class)
	return RequestImpl.this.queryService(klass);
      throw new NoSuchServiceException("RequestOutput",klass);

    } // end queryService

    public Object queryService(Class klass, String serviceid)
    {
      return this.queryService(klass);

    } // end queryService

    public Writer getWriter() throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      return wr;

    } // end getWriter

    public void write(String s) throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      wr.write(s);

    } // end write

    public void writeStackTrace(Throwable t) throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      PrintWriter tmp;
      if (wr instanceof PrintWriter)
	tmp = (PrintWriter)wr;
      else
	tmp = new PrintWriter(wr);
      t.printStackTrace(tmp);
      if (!(wr instanceof PrintWriter))
	tmp.flush();

    } // end writeStackTrace
    
    public void flush() throws IOException
    {
      if (wr!=null)
	wr.flush();
      response.flushBuffer();

    } // end flush

    public void output(Content c) throws IOException, ServletException
    {
      if (c instanceof ContentDirect)
      { // render the content object here
	ContentDirect cd = (ContentDirect)c;
	if (wr!=null)
	{ // attempt to flush the writer
	  try
	  { // flush the output
	    wr.flush();

	  } // end try
	  catch (IOException e)
          { // do nothing
	  } // end catch

	} // end if

	response.flushBuffer();   // flush the previous stuff to disk
	cd.render(this);
	response.flushBuffer();   // flush the included page properly
	return;

      } // end if

      if (c instanceof ContentJSP)
      { // include the JSP page here
	ContentJSP cj = (ContentJSP)c;
	RequestDispatcher disp = ctxt.getRequestDispatcher(config.getFormatJSPPath(cj.getJSPName()));

	// Configure the request attributes.
	request.setAttribute(REQUEST_INPUT,(RequestInput)(RequestImpl.this));
	request.setAttribute(REQUEST_OUTPUT,(RequestOutput)this);
	Object stack = request.getAttribute(REQUEST_CONTENT);
	request.setAttribute(REQUEST_CONTENT,c);

	// Configure the writer, if it's present.
	if (wr!=null)
	{ // attempt to flush the writer
	  try
	  { // flush the output
	    wr.flush();

	  } // end try
	  catch (IOException e)
          { // do nothing
	  } // end catch

	} // end if

	Writer wr_stack = wr;
	wr = null;

	cj.initialize(RequestImpl.this);
	try
	{ // Include the content now!
	  response.flushBuffer();   // flush the previous stuff to disk
	  disp.include(request,response);  // include the JSP
	  if (wr!=null)
	    wr.flush();
	  response.flushBuffer();   // flush the included page properly

	} // end try
	finally
	{ // Clean up the stacked objects.
	  cj.terminate(RequestImpl.this);
	  wr = wr_stack;
	  if (stack==null)
	    request.removeAttribute(REQUEST_CONTENT);
	  else
	    request.setAttribute(REQUEST_CONTENT,stack);

	} // end finally

	return;

      } // end if

      // if nothing else...
      this.write(c.toString());

    } // end output

    public void output(Writer out, Content c) throws IOException, ServletException
    {
      if (c instanceof ContentDirect)
      { // do a ContentDirect render, but replace the internal Writer with the specified one
	// before we do so
	ContentDirect cd = (ContentDirect)c;
	if (wr!=null)
	{ // attempt to flush the writer
	  try
	  { // flush the output
	    wr.flush();

	  } // end try
	  catch (IOException e)
	  { // do nothing
	  } // end catch

	} // end if

	Writer wr_stack = wr;
	wr = out;
	try
	{ // do the actual output
	  cd.render(this);
	  wr.flush();

	} // end try
	finally
	{ // restore the stacked stuff before we go
	  wr = wr_stack;

	} // end finally

	return;

      } // end if

      this.output(c);  // fall back in ContentJSP case

    } // end output

    public void writeFrameHead(Writer out, Content c) throws IOException
    {
      HTMLRendering html = (HTMLRendering)(RequestImpl.this.queryService(HTMLRendering.class));

      out.write("<HEAD>\n<TITLE>" + config.getPageTitle(c.getPageTitle(this)) + "</TITLE>\n"
		+ config.getBaseFontTag() + "\n");
      if (config.usingStyleSheet())
	out.write("<LINK REL=\"stylesheet\" HREF=\"" + html.formatURL("stylesheet",html.SERVLET)
		  + "\" TYPE=\"text/css\">\n");
      String tmp = config.getPageIconTags();
      if (tmp!=null)
	out.write(tmp);
      if (!(config.useSmartTags()))
	out.write("<META NAME=\"MSSmartTagsPreventParsing\" CONTENT=\"TRUE\">\n");
      out.write("</HEAD>\n");

    } // end writeFrameHead
    
    public void writeFrameHead(Content c) throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      this.writeFrameHead(wr,c);

    } // end writeFrameHead

    public void writeSiteImageTag(Writer out) throws IOException
    {
      HTMLRendering html = (HTMLRendering)(RequestImpl.this.queryService(HTMLRendering.class));

      String href = config.getSiteLogoLink();
      if (href!=null)
	out.write("<A HREF=\"" + html.formatURL(href,config.getSiteLogoLinkType()) + "\">");
      out.write(config.getSiteLogoImageTag());
      if (href!=null)
	out.write("</A>");

    } // end writeSiteImageTag

    public void writeSiteImageTag() throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      this.writeSiteImageTag(wr);

    } // end writeSiteImageTag

    public void writeVeniceLogo(Writer out) throws IOException
    {
      out.write(config.getVeniceLogoTag());

    } // end writeVeniceLogo

    public void writeVeniceLogo() throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      this.writeVeniceLogo(wr);

    } // end writeVeniceLogo

    public void writeContentHeader(Writer out, String primary, String secondary) throws IOException
    {
      out.write(config.getContentHeader(primary,secondary));

    } // end writeContentHeader

    public void writeContentHeader(String primary, String secondary) throws IOException
    {
      if (wr==null)
	wr = response.getWriter();
      wr.write(config.getContentHeader(primary,secondary));

    } // end writeContentHeader

  } // end class OutputImpl

  /*--------------------------------------------------------------------------------
   * Internal class which implements the execution output.
   *--------------------------------------------------------------------------------
   */

  class ExecImpl implements RequestExec
  {
    ExecImpl()
    { // do nothing
    } // end constructor

    public void error(int code) throws IOException
    {
      flushCookies();
      if (logger.isDebugEnabled())
	logger.debug("ExecImpl.error(): code " + code);
      response.sendError(code);

    } // end sendError

    public void error(int code, String message) throws IOException
    {
      flushCookies();
      if (logger.isDebugEnabled())
	logger.debug("ExecImpl.error(): code " + code + ", msg: " + message);
      if (message==null)
	response.sendError(code);
      else
	response.sendError(code,message);

    } // end sendError

    public void redirect(String where, int type) throws IOException
    {
      flushCookies();
      String fmt_url;
      if (type==ABSOLUTE)
        fmt_url = where;
      else if (type==SERVLET)
        fmt_url = response.encodeRedirectURL(request.getContextPath() + "/" + where);
      else if (type==FRAME)
        fmt_url = response.encodeRedirectURL(request.getContextPath() + "/frame/" + where);
      else
	throw new IndexOutOfBoundsException("invalid format type index for redirect");

      if (logger.isDebugEnabled())
	logger.debug("ExecImpl.redirect(): going to \"" + fmt_url + "\"");
      response.sendRedirect(fmt_url);

    } // end redirect

    public void noContent()
    {
      flushCookies();
      response.setStatus(HttpServletResponse.SC_NO_CONTENT);

    } // end noContent

    public void sendBinary(String type, String filename, int length, InputStream data) throws IOException
    {
      flushCookies();
      if (logger.isDebugEnabled())
        logger.debug("ExecImpl.sendBinary(): MIME " + type + ", filename \"" + filename
                     + "\", length " + length);
      response.setContentType(type);
      response.setContentLength(length);
      addDynamicHeaders();
      if (filename!=null)  // make sure we pass the filename along, too
	response.setHeader("Content-Disposition","attachment; filename=\"" + filename + "\";");

      // Copy the contents of the "data" stream to the output.
      IOUtil.copy(data,response.getOutputStream());
      response.flushBuffer();

    } // end sendBinary

    public void sendBinary(String type, int length, InputStream data) throws IOException
    {
      this.sendBinary(type,null,length,data);

    } // end sendBinary

    public void sendBinary(String type, String filename, Blob data) throws IOException, SQLException
    {
      this.sendBinary(type,filename,(int)(data.length()),data.getBinaryStream());

    } // end sendBinary

    public void sendBinary(String type, Blob data) throws IOException, SQLException
    {
      this.sendBinary(type,null,(int)(data.length()),data.getBinaryStream());

    } // end sendBinary

    public void sendBinary(String type, String filename, byte[] data) throws IOException
    {
      this.sendBinary(type,filename,data.length,new ByteArrayInputStream(data));

    } // end sendBinary

    public void sendBinary(String type, byte[] data) throws IOException
    {
      this.sendBinary(type,null,data.length,new ByteArrayInputStream(data));

    } // end sendBinary

    public void sendText(String type, String data) throws IOException
    {
      flushCookies();
      if (logger.isDebugEnabled())
	logger.debug("ExecImpl.sendText(): MIME " + type + ", length " + data.length());
      response.setContentType(type);
      response.setContentLength(data.length());
      addDynamicHeaders();
      Writer wr = response.getWriter();
      wr.write(data);
      wr.flush();
      response.flushBuffer();

    } // end sendText

    public void sendText(String type, char[] data) throws IOException
    {
      flushCookies();
      if (logger.isDebugEnabled())
	logger.debug("ExecImpl.sendText(): MIME " + type + ", length " + data.length);
      response.setContentType(type);
      response.setContentLength(data.length);
      addDynamicHeaders();
      Writer wr = response.getWriter();
      wr.write(data);
      wr.flush();
      response.flushBuffer();

    } // end sendText

  } // end class ExecImpl

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

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

  private static final String APP_ATTRIBUTE_STEM = "com.silverwrist.venice.ui.variables.";

  public static final String REQUEST_CONTENT = "com.silverwrist.venice.ui.Content";
  public static final String REQUEST_INPUT = "com.silverwrist.venice.ui.RequestInput";
  public static final String REQUEST_OUTPUT = "com.silverwrist.venice.ui.RequestOutput";
  public static final String REQUEST_ATTRIBUTE_STEM = "com.silverwrist.venice.ui.variables.";

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

  private ServletContext ctxt;                           // the servlet context
  private HttpServletRequest request;                    // the servlet request data
  private HttpServletResponse response;                  // the servlet response data
  private VeniceUISession session;                       // the current HTTP session
  private VeniceEngine engine;                           // the Venice engine context
  private RootConfig config;                             // the UI configuration data
  private ServletMultipartHandler mphandler = null;      // if this is a multipart/form-data POST
  private boolean end_response = true;                   // do we need to properly end the response?
  private Locale my_locale;                              // user's current locale
  private TimeZone my_timezone;                          // user's current time zone
  private String location;                               // current servlet location
  private boolean show_login = true;                     // do we show the login links?
  private ScriptManager script_mgr;                      // the script manager to use for executing script
  private CommunityContext community = null;             // the current community
  private LinkedList auto_cleanup = null;                // auto-cleanup callbacks
  private Document request_doc = null;                   // request parsed as an XML document
  private SessionControlImpl session_control = null;     // session control interface
  private CookieControlImpl cookie_control = null;       // cookie control interface
  private HTMLRenderingImpl html_rendering = null;       // HTML rendering interface
  private ScriptSupportImpl script_support = null;       // script support interface

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

  RequestImpl(ServletContext ctxt, HttpServletRequest request, HttpServletResponse response,
	      VeniceEngine engine, RootConfig config, VeniceUISessionFactory factory)
      throws ServletException, ValidationException
  {
    // Set up the basic variables.
    this.ctxt = ctxt;
    this.request = request;
    this.response = response;
    this.engine = engine;
    this.config = config;

    if (request.getMethod().equals("POST"))
    { // POSTs can have special handling for the request data
      if (request.getContentType().startsWith("text/xml"))
      { // this is some sort of XML-based request - parse it into a DOM tree
	XMLLoader loader = XMLLoader.get();
	try
	{ // load the document
	  request_doc = loader.loadPostData(request.getInputStream()); // may throw ValidationException

	} // end try
	catch (IOException ioe)
	{ // IO exception...sigh
	  logger.error("IO error loading request document",ioe);
	  throw new ServletException("IO error loading request document: " + ioe.toString(),ioe);

	} // end catch

      } // end if
      else if (ServletMultipartHandler.canHandle(request))
      { // if this is a multipart/form-data POST, create the handler object
	try
	{ // use the multipart handler and get something
	  mphandler = new ServletMultipartHandler(request);

	} // end try
	catch (ServletMultipartException smpe)
	{ // unable to parse POST data for some reason
	  logger.error("Multipart data acquisition failed: " + smpe.getMessage(),smpe);
	  throw new ValidationException("invalid multipart request: " + smpe.getMessage(),smpe);

	} // end catch

      } // end else if

    } // end if

    // Create the UI session.
    session = factory.createSession(ctxt,request,response,engine,config);
    session.preprocess(this);

    if (!(session instanceof NullSession))
    { // read the user's preferred locale
      try
      { // get the user default locale
	my_locale = session.getUser().getLocale();

      } // end try
      catch (DataException de)
      { // locale problems...
	my_locale = Locale.getDefault();

      } // end catch

      // read the user's preferred time zone
      try
      { // get the user default timezone
	my_timezone = session.getUser().getTimeZone();

      } // end try
      catch (DataException de)
      { // time zone problems...
	my_timezone = TimeZone.getDefault();

      } // end catch

    } // end if
    else
    { // just default locale/timezone
      my_locale = Locale.getDefault();
      my_timezone = TimeZone.getDefault();

    } // end else

    // Come up with a "first guess" at the location.
    StringBuffer loc_buf = new StringBuffer(request.getServletPath());
    if (loc_buf.charAt(0)=='/')
      loc_buf.delete(0,1);
    String tmp = request.getPathInfo();
    if (tmp!=null)
      loc_buf.append(tmp);
    tmp = request.getQueryString();
    if (tmp!=null)
      loc_buf.append('?').append(tmp);
    location = loc_buf.toString();

    // Retrieve a scripting engine.
    try
    { // get the script engine
      ScriptManagerContainer cnr = ScriptManagerContainer.get(ctxt);
      script_mgr = cnr.getScriptManager();

      // push in the variables
      script_mgr.pushContext();
      script_mgr.register("request",(RequestInput)this);
      script_mgr.register("engine",engine);

    } // end try
    catch (ScriptingException e)
    { // unable to load the scripting engine for some reason
      logger.error("Scripting engine get failed: " + e.getMessage(),e);
      throw new ServletException("Unable to retrieve scripting engine: " + e.getMessage(),e);

    } // end catch

  } // end constructor

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

  private final void flushCookies()
  {
    if (cookie_control!=null)
      cookie_control.flushCookies(response);

  } // end flushCookies

  private final void addDynamicHeaders()
  {
    // these should prevent browsers and proxy servers from caching a dynamically-generated Venice page
    response.addHeader("Pragma","No-cache");
    response.addHeader("Cache-Control","no-cache");
    response.addDateHeader("Expires",1);

  } // end addDynamicHeaders

  private final void updateMenus(int selector)
  {
    MenuComponent oldmenu = (MenuComponent)(getSessionAttribute(LEFT_MENU_SESSION_ATTR));
    MenuComponent newmenu = null;
    boolean change = false;

    switch (selector)
    {
      case Content.MENU_SELECTOR_TOP:
	if ((oldmenu!=null) && (oldmenu instanceof Menu))
	{ // check to see if the "top" menu is still in place
	  Menu mu = (Menu)oldmenu;
	  change = !(mu.getIdentifier().equals("top"));

	} // end if
	else  // force change
	  change = true;
	if (change)
	  newmenu = config.getMenu("top");
	break;

      case Content.MENU_SELECTOR_COMMUNITY:
	if (community!=null)
	{ // this only makes sense if the community is properly set
	  if ((oldmenu!=null) && (oldmenu instanceof CommunityMenu))
	  { // get the community menu and see if the CID has changed
	    CommunityMenu cm = (CommunityMenu)(oldmenu);
	    change = (cm.getID()!=community.getCommunityID());

	  } // end if
	  else  // force change
	    change = true;
	  if (change)
	    newmenu = config.getCommunityMenu(community);

	} // end if
	else if (oldmenu==null)  // set a reasonable default
	  newmenu = config.getMenu("top");
	break;

      default:
	if (oldmenu==null)  // only set the menu if we don't have it already
	  newmenu = config.getMenu("top");
	break;

    } // end switch

    if (newmenu!=null)
      setSessionAttribute(LEFT_MENU_SESSION_ATTR,newmenu);

  } // end updateMenus

  private final void updateMenus(Content c)
  {
    updateMenus(c.getMenuSelector());

  } // end updateMenus

  /*--------------------------------------------------------------------------------
   * Operations called by other objects in this package
   *--------------------------------------------------------------------------------
   */

  final VeniceUISession getSession()
  {
    return session;

  } // end getSession

  final void setSession(VeniceUISession s)
  {
    session = s;

  } // end setSession

  final void endSession()
  {
    session.invalidate();
    session = null;

  } // end endSession

  final boolean sessionBound()
  {
    return ((session!=null) && !(session instanceof NullSession));

  } // end sessionBound

  final String encodeServletPath(String servlet)
  {
    return response.encodeURL(request.getContextPath() + "/" + servlet);

  } // end encodeServletPath

  final Locale getLocale()
  {
    return my_locale;

  } // end getLocale

  final TimeZone getTimeZone()
  {
    return my_timezone;

  } // end getTimeZone

  /**
   * Expands the pathname of the servlet into a fully-specified HTTP URL.  For instance,
   * the servlet name &quot;foobar&quot; might be expanded into
   * &quot;http://venice.example.org/venice/foobar&quot;.
   *
   * @param spath Servlet path to be expanded.
   * @return The fully-expanded servlet path.
   */
  final String expandServletPath(String spath)
  {
    StringBuffer buf = new StringBuffer("http://");
    buf.append(request.getServerName());
    if (request.getServerPort()!=80)
      buf.append(':').append(request.getServerPort());
    buf.append(request.getContextPath()).append('/').append(spath);
    return buf.toString();

  } // end expandServletPath

  /*--------------------------------------------------------------------------------
   * Implementations from interface ServiceProvider
   *--------------------------------------------------------------------------------
   */

  /**
   * Queries this object for a specified service.
   *
   * @param klass The class of the object that should be returned as a service.
   * @return A service object.  The service object is guaranteed to be of the class
   *         specified by <CODE>klass</CODE>; that is, if <CODE>queryService(klass)</CODE>
   *         yields some object <CODE>x</CODE>, then the expression <CODE>klass.isInstance(x)</CODE>
   *         is true.
   * @exception com.silverwrist.venice.except.NoSuchServiceException If no service is available in
   *            the specified class.
   */
  public Object queryService(Class klass)
  {
    if (klass==ScriptManager.class)
      return script_mgr;

    if (klass==SessionControl.class)
    { // create session control and return it
      if (session_control==null)
	session_control = new SessionControlImpl(this);
      return session_control;

    } // end if

    if (klass==CookieControl.class)
    { // create cookie control and return it
      if (cookie_control==null)
	cookie_control = new CookieControlImpl(this);
      return cookie_control;

    } // end if

    if (klass==HTMLRendering.class)
    { // create HTML rendering object and return it
      if (html_rendering==null)
	html_rendering = new HTMLRenderingImpl(this,config);
      return html_rendering;

    } // end if

    if (klass==ScriptSupport.class)
    { // create script support object and return it
      if (script_support==null)
	script_support = new ScriptSupportImpl(this,config);
      return script_support;

    } // end if

    throw new NoSuchServiceException("RequestInput",klass);

  } // end queryService

  /**
   * Queries this object for a specified service.
   *
   * @param klass The class of the object that should be returned as a service.
   * @param serviceid ID for the service to be requested, to further discriminate between requests.
   * @return A service object.  The service object is guaranteed to be of the class
   *         specified by <CODE>klass</CODE>; that is, if <CODE>queryService(klass)</CODE>
   *         yields some object <CODE>x</CODE>, then the expression <CODE>klass.isInstance(x)</CODE>
   *         is true.
   * @exception com.silverwrist.venice.except.NoSuchServiceException If no service is available in
   *            the specified class.
   */
  public Object queryService(Class klass, String serviceid)
  {
    if ((klass==Document.class) && (serviceid!=null) && serviceid.equalsIgnoreCase("requestDocument"))
      return request_doc;

    return this.queryService(klass);

  } // end queryService

  /*--------------------------------------------------------------------------------
   * Implementations from interface RequestInput
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns a <CODE>String</CODE> containing the real path for a given virtual path. For example,
   * the virtual path &quot;/index.html&quot; has a real path of whatever file on the server's filesystem
   * would be served by a request for &quot;/index.html&quot;.<P>
   * This method returns <CODE>null</CODE> if the virtual path cannot be translated to a real path for
   * any reason.
   *
   * @param s A <CODE>String</CODE> specifying a virtual path.
   * @return A <CODE>String</CODE> specifying the real path, or <CODE>null</CODE> if the translation cannot
   *         be performed.
   */
  public String mapPath(String s)
  {
    return ctxt.getRealPath(s);

  } // end mapPath

  /**
   * Returns the portion of the request URI that indicates the context of the request, i.e. the servlet
   * context under which Venice is installed. The context path always comes first in a request URI. The
   * path starts with a &quot;/&quot; character but does not end with a &quot;/&quot; character.
   *
   * @return A <CODE>String</CODE> specifying the portion of the request URI that indicates the context
   * of the request.
   */
  public String getContextPath()
  {
    return request.getContextPath();

  } // end getContextPath

  /**
   * Returns the part of this request's URI that calls the servlet. This includes either the servlet name
   * or a path to the servlet, but does not include any extra path information or a query string.
   *
   * @return A <CODE>String</CODE> containing the name or path of the servlet being called, as specified
   *         in the request URI.
   */
  public String getServletPath()
  {
    return request.getServletPath();

  } // end getServletPath

  /**
   * Returns any extra path information associated with the URI the client sent when it made this request.
   * The extra path information follows the servlet path but precedes the query string. This method
   * returns <CODE>null</CODE> if there was no extra path information.  In Venice, the path information is
   * used to specify parameters in certain servlets.
   *
   * @return A <CODE>String</CODE> specifying extra path information that comes after the servlet path
   *         but before the query string in the request URI; or <CODE>null</CODE> if the URI does not
   *         have any extra path information.
   */
  public String getPathInfo()
  {
    return request.getPathInfo();

  } // end getPathInfo

  /**
   * Returns the query string that is contained in the request URI after the path. This method returns
   * <CODE>null</CODE> if the URI does not have a query string.
   *
   * @return A <CODE>String</CODE> containing the query string or <CODE>null</CODE> if the URI contains
   *         no query string.
   */
  public String getQueryString()
  {
    return request.getQueryString();

  } // end getQueryString

  /**
   * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
   *
   * @return A <CODE>String</CODE> specifying the name of the method with which this request was made.
   */
  public String getVerb()
  {
    return request.getMethod();

  } // end getVerb

  /**
   * Returns the Internet Protocol (IP) address of the client that sent the request.
   *
   * @return A <CODE>String</CODE> containing the IP address of the client that sent the request.
   */
  public String getSourceAddress()
  {
    return request.getRemoteAddr();

  } // end getSourceAddress

  /**
   * Returns <CODE>true</CODE> if the request parameter with the specified name is defined,
   * <CODE>false</CODE> if not.  For standard Venice requests, parameters are contained in the
   * query string or posted form data.
   *
   * @param name Parameter name to be checked.
   * @return See above.
   */
  public boolean hasParameter(String name)
  {
    if (request_doc!=null)
      return false;
    if (mphandler!=null)
      return mphandler.hasParameter(name);
    else
      return (request.getParameter(name)!=null);

  } // end hasParameter

  /**
   * Returns the value of a request parameter as a <CODE>String</CODE>, or <CODE>null</CODE> if the
   * parameter does not exist. For standard Venice requests, parameters are contained in the query
   * string or posted form data.<P>
   * You should only use this method when you are sure the parameter has only one value. If the parameter
   * might have more than one value, use {@link #getParameterValues(java.lang.String)}.<P>
   * If you use this method with a multivalued parameter, the value returned is equal to the first value
   * in the array returned by <CODE>getParameterValues</CODE>.
   *
   * @param name A <CODE>String</CODE> specifying the name of the parameter.
   * @return A <CODE>String</CODE> representing the single value of the parameter.
   * @see #getParameterValues(java.lang.String)
   */
  public String getParameter(String name)
  {
    if (request_doc!=null)
      return null;
    if (mphandler!=null)
      return mphandler.getValue(name);
    else
      return request.getParameter(name);

  } // end getParameter

  /**
   * Returns the value of a request parameter as a <CODE>int</CODE>, or a specified default value if
   * the parameter does not exist. For standard Venice requests, parameters are contained in the query
   * string or posted form data.<P>
   * If you use this method with a multivalued parameter, the value returned is equal to the
   * <CODE>int</CODE> equivalent of the first value in the array returned by <CODE>getParameterValues</CODE>.
   *
   * @param name A <CODE>String</CODE> specifying the name of the parameter.
   * @param default_value The default value to use for the parameter if the specified parameter does not exist.
   * @return An <CODE>int</CODE> representing the single value of the parameter.
   */
  public int getParameterInt(String name, int default_value)
  {
    String s = this.getParameter(name);
    if (s==null)
      return default_value;

    try
    { // turn the parameter into an integer
      return Integer.parseInt(s);

    } // end try
    catch (NumberFormatException nfe)
    { // on conversion error, just return the default
      return default_value;

    } // end catch

  } // end getParameterInt

  /**
   * Returns the value of a request parameter as a <CODE>short</CODE>, or a specified default value if
   * the parameter does not exist. For standard Venice requests, parameters are contained in the query
   * string or posted form data.<P>
   * If you use this method with a multivalued parameter, the value returned is equal to the
   * <CODE>short</CODE> equivalent of the first value in the array returned by <CODE>getParameterValues</CODE>.
   *
   * @param name A <CODE>String</CODE> specifying the name of the parameter.
   * @param default_value The default value to use for the parameter if the specified parameter does not exist.
   * @return A <CODE>short</CODE> representing the single value of the parameter.
   */
  public short getParameterShort(String name, short default_value)
  {
    String s = this.getParameter(name);
    if (s==null)
      return default_value;

    try
    { // turn the parameter into a short
      return Short.parseShort(s);

    } // end try
    catch (NumberFormatException nfe)
    { // on conversion error, just return the default
      return default_value;

    } // end catch

  } // end getParameterShort

  /**
   * Returns the value of a request parameter as a <CODE>long</CODE>, or a specified default value if
   * the parameter does not exist. For standard Venice requests, parameters are contained in the query
   * string or posted form data.<P>
   * If you use this method with a multivalued parameter, the value returned is equal to the
   * <CODE>long</CODE> equivalent of the first value in the array returned by <CODE>getParameterValues</CODE>.
   *
   * @param name A <CODE>String</CODE> specifying the name of the parameter.
   * @param default_value The default value to use for the parameter if the specified parameter does not exist.
   * @return A <CODE>long</CODE> representing the single value of the parameter.
   */
  public long getParameterLong(String name, long default_value)
  {
    String s = this.getParameter(name);
    if (s==null)
      return default_value;

    try
    { // turn the parameter into a long
      return Long.parseLong(s);

    } // end try
    catch (NumberFormatException nfe)
    { // on conversion error, just return the default
      return default_value;

    } // end catch

  } // end getParameterLong

  /**
   * Returns an <CODE>Enumeration</CODE> of <CODE>String</CODE> objects containing the names of the
   * parameters contained in this request. If the request has no parameters, the method returns an empty
   * <CODE>Enumeration.</CODE>
   *
   * @return An <CODE>Enumeration</CODE> of <CODE>String</CODE> objects, each <CODE>String</CODE>
   *         containing the name of a request parameter, or an empty <CODE>Enumeration</CODE> if the
   *         request has no parameters.
   */
  public Enumeration getParameterNames()
  {
    if (request_doc!=null)
      return Collections.enumeration(Collections.EMPTY_LIST);
    if (mphandler!=null)
      return mphandler.getNames();
    else
      return request.getParameterNames();

  } // end getParameterNames

  /**
   * Returns an array of <CODE>String</CODE> objects containing all of the values the given request
   * parameter has, or <CODE>null</CODE> if the parameter does not exist.<P>
   * If the parameter has a single value, the array has a length of 1.
   *
   * @param name A <CODE>String</CODE> containing the name of the parameter whose value is requested.
   * @return An array of <CODE>String</CODE> objects containing the parameter's values.
   * @see #getParameter(java.lang.String)
   */
  public String[] getParameterValues(String name)
  {
    if (request_doc!=null)
      return new String[0];
    if (mphandler!=null)
      return mphandler.getParamValues(name);
    else
      return request.getParameterValues(name);

  } // end getParameterValues

  /**
   * Returns <CODE>true</CODE> if the specified parameter is a file parameter, <CODE>false</CODE> if
   * not.  If the parameter does not exist, the return value is <CODE>false</CODE>.
   *
   * @param name A <CODE>String</CODE> containing the name of the parameter to test.
   * @return See above.
   */
  public boolean isFileParam(String name)
  {
    if (request_doc!=null)
      return false;
    if (mphandler!=null)
      return mphandler.isFileParam(name);
    else
      return false;

  } // end isFileParam

  /**
   * Returns the MIME type of the specified parameter.  If the type cannot be determined, the return
   * value is <CODE>null</CODE>.<P>
   * N.B.: For non-file parameters, or all parameters to a request that is not a file upload, the MIME
   * type is &quot;text/plain&quot;.
   *
   * @param name A <CODE>String</CODE> containing the name of the parameter to test.
   * @return See above.
   */
  public String getParameterType(String name)
  {
    if (request_doc!=null)
      return null;
    if (mphandler!=null)
      return mphandler.getContentType(name);
    else if (request.getParameter(name)!=null)
      return "text/plain";
    else
      return null;

  } // end getParameterType

  /**
   * Returns the size in bytes of the specified parameter.  If the size cannot be determined, the return
   * value is -1.<P>
   * N.B.: For non-file parameters, or all parameters to a request that is not a file upload, the size
   * is the number of bytes occupied by the parameter, expressed in UTF-8 encoding.
   *
   * @param name A <CODE>String</CODE> containing the name of the parameter to test.
   * @return See above.
   */
  public int getParameterSize(String name)
  {
    if (request_doc!=null)
      return -1;
    if (mphandler!=null)
      return mphandler.getContentSize(name);
    else
    { // just fake a return value
      String blort = request.getParameter(name);
      if (blort==null)
	return -1;
      try
      { // convert to bytes, return length
	return blort.getBytes("UTF-8").length;

      } // end try
      catch (UnsupportedEncodingException e)
      { // this sucks...this shouldn't happen
	return -1;

      } // end catch

    } // end else

  } // end getParameterSize

  /**
   * Returns an <CODE>InputStream</CODE> reading from the data of the named file parameter.  If the
   * named parameter does not exist or is not a file parameter, returns <CODE>null;
   *
   * @param name A <CODE>String</CODE> containing the name of the parameter whose value is requested.
   * @return See above.
   * @exception com.silverwrist.util.ServletMultipartException If there is a problem retrieving
   *            the parameter data stream.
   */
  public InputStream getParameterDataStream(String name) throws ServletMultipartException
  {
    if (mphandler!=null)
      return mphandler.getFileContentStream(name);
    else  // ServletMultipartHandler will return null if parameter is not a file parameter
      return null;

  } // end getParameterDataStream

  /**
   * Returns <CODE>true</CODE> if the parameter set reflects the clicking of an image button with a
   * specified name, <CODE>false</CODE> if not.
   *
   * @param name A <CODE>String</CODE> containing the name of the image button to test.
   * @return See above.
   */
  public boolean isImageButtonClicked(String name)
  {
    return this.hasParameter(name + ".x");

  } // end isImageButtonClicked

  /**
   * Returns the application-level attribute with the given name, or <CODE>null</CODE> if there is no
   * attribute by that name.
   *
   * @param name A <CODE>String</CODE> specifying the name of the attribute.
   * @return An <CODE>Object</CODE> containing the value of the attribute, or <CODE>null</CODE> if no
   *         attribute exists matching the given name.
   */
  public Object getAppAttribute(String name)
  {
    return getAppAttribute(ctxt,name);

  } // end getAppAttribute

  /**
   * Sets the application-level attribute with the given name.
   *
   * @param name A <CODE>String</CODE> specifying the name of the attribute.
   * @param o The object to be bound as the value of that attribute, or <CODE>null</CODE> if the binding
   *          is to be removed.
   */
  public void setAppAttribute(String name, Object o)
  {
    setAppAttribute(ctxt,name,o);

  } // end setAppAttribute

  /**
   * Returns the session-level attribute with the given name, or <CODE>null</CODE> if there is no
   * attribute by that name.
   *
   * @param name A <CODE>String</CODE> specifying the name of the attribute.
   * @return An <CODE>Object</CODE> containing the value of the attribute, or <CODE>null</CODE> if no
   *         attribute exists matching the given name.
   */
  public Object getSessionAttribute(String name)
  {
    return session.getAttribute(name);

  } // end getSessionAttribute

  /**
   * Sets the session-level attribute with the given name.
   *
   * @param name A <CODE>String</CODE> specifying the name of the attribute.
   * @param o The object to be bound as the value of that attribute, or <CODE>null</CODE> if the binding
   *          is to be removed.
   */
  public void setSessionAttribute(String name, Object o)
  {
    if (o==null)
      session.removeAttribute(name);
    else
      session.setAttribute(name,o);

  } // end setSessionAttribute

  /**
   * Returns the request-level attribute with the given name, or <CODE>null</CODE> if there is no
   * attribute by that name.
   *
   * @param name A <CODE>String</CODE> specifying the name of the attribute.
   * @return An <CODE>Object</CODE> containing the value of the attribute, or <CODE>null</CODE> if no
   *         attribute exists matching the given name.
   */
  public Object getRequestAttribute(String name)
  {
    return getRequestAttribute(request,name);

  } // end getRequestAttribute

  /**
   * Sets the request-level attribute with the given name.
   *
   * @param name A <CODE>String</CODE> specifying the name of the attribute.
   * @param o The object to be bound as the value of that attribute, or <CODE>null</CODE> if the binding
   *          is to be removed.
   */
  public void setRequestAttribute(String name, Object o)
  {
    setRequestAttribute(request,name,o);

  } // end setRequestAttribute

  /**
   * Returns the instance of the Venice engine associated with the application.
   *
   * @return See above.
   */
  public VeniceEngine getEngine()
  {
    return engine;

  } // end getEngine

  /**
   * Returns the instance of the Venice user object associated with the session.
   *
   * @return See above.
   */
  public UserContext getUser()
  {
    return session.getUser();

  } // end getUser

  /**
   * Returns the current servlet location.
   *
   * @return The current servlet location.
   */
  public String getLocation()
  {
    return location;

  } // end getLocation

  /**
   * Sets the &quot;current&quot; servlet location that is displayed.  This is used, for instance,
   * as the context to return to after a login.
   *
   * @param str The new location to be set.
   */
  public void setLocation(String str)
  {
    location = str;

  } // end setLocation

  /**
   * Returns <CODE>true</CODE> if the &quot;Log In&quot; link is to be displayed on the outer frame,
   * <CODE>false</CODE> if not.
   *
   * @return See above.
   */
  public boolean getDisplayLogin()
  {
    return show_login;

  } // end getDisplayLogin

  public void setDisplayLogin(boolean val)
  {
    show_login = val;

  } // end setDisplayLogin

  public MenuComponent getMenu(String name)
  {
    return config.getMenu(name);

  } // end getMenu

  public MenuComponent getMenu(String name, Map vars)
  {
    return config.getMenu(name,vars);

  } // end getMenu

  public Dialog getDialog(String name)
  {
    return config.getDialog(name);

  } // end getDialog

  public Content[] getSideBoxes() throws AccessError, DataException
  {
    UserContext user = session.getUser();
    SideBoxManager sbmgr = config.getSideBoxManager();
    List descrs = user.getSideBoxList();
    Content[] rc = new Content[descrs.size()];
    for (int i=0; i<descrs.size(); i++)
    { // get all the sideboxes created
      SideBoxDescriptor d = (SideBoxDescriptor)(descrs.get(i));
      rc[i] = sbmgr.createSideBox(d.getID(),engine,user);

    } // end for

    return rc;

  } // end getSideBoxes

  public CommunityContext getCommunity()
  {
    return community;

  } // end getCommunity

  public CommunityContext getCommunity(boolean required, String on_error) throws ErrorBox
  {
    if (community!=null)
      return community;

    String parm = this.getParameter("cc");   // try the new parameter first
    if (StringUtil.isStringEmpty(parm))
      parm = this.getParameter("sig");       // try the old parameter
    if (StringUtil.isStringEmpty(parm))
    { // the parameter is not specified...
      if (required)
      { // no community parameter - bail out now!
	logger.error("community parameter not specified!");
	throw new ErrorBox(null,"No community specified.",on_error);

      } // end if
      else
      { // a null CommunityContext is permitted
	logger.debug("no community specified");
	return null;

      } // end else

    } // end if

    try
    { // turn the string into a community ID, and thence to a CommunityContext
      int tmp_id = Integer.parseInt(parm);
      community = session.getUser().getCommunityContext(tmp_id);
      if (community==null)
      { // trap any null results (may not be possible with communities, but you never know)
	logger.error("Community #" + tmp_id + " was not found!");
	throw new ErrorBox(null,"The specified community (#" + tmp_id + ") was not found in the database.",
			   on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("found community #" + community.getCommunityID());

    } // end try
    catch (NumberFormatException nfe)
    { // error in Integer.parseInt
      logger.error("Cannot convert community parameter '" + parm + "'!");
      throw new ErrorBox(null,"Invalid community parameter.",on_error);

    } // end catch
    catch (DataException de)
    { // error looking up the community
      throw new ErrorBox("Database Error","Database error finding community: " + de.getMessage(),on_error);

    } // end catch

    return community;

  } // end getCommunity

  public String getDefaultServletAddress(CommunityContext comm)
  {
    return config.getDefaultServletAddress(this,comm);

  } // end getDefaultServletAddress

  public void registerCleanup(AutoCleanup ac)
  {
    if (auto_cleanup==null)
      auto_cleanup = new LinkedList();
    auto_cleanup.addFirst(ac);

  } // end registerCleanup

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

  public final static Object getAppAttribute(ServletContext ctxt, String name)
  {
    return ctxt.getAttribute(APP_ATTRIBUTE_STEM + name);

  } // end getAppAttribute

  public final static void setAppAttribute(ServletContext ctxt, String name, Object o)
  {
    if (o==null)
      ctxt.removeAttribute(APP_ATTRIBUTE_STEM + name);
    else
      ctxt.setAttribute(APP_ATTRIBUTE_STEM + name,o);

  } // end setAppAttribute

  public final static Object getRequestAttribute(ServletRequest request, String name)
  {
    return request.getAttribute(REQUEST_ATTRIBUTE_STEM + name);

  } // end getRequestAttribute

  public final static void setRequestAttribute(ServletRequest request, String name, Object o)
  {
    if (o==null)
      request.removeAttribute(REQUEST_ATTRIBUTE_STEM + name);
    else
      request.setAttribute(REQUEST_ATTRIBUTE_STEM + name,o);

  } // end setRequestAttribute

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

  void output(Content content) throws ServletException, IOException
  {
    if (content.needFrame())
    { // get the frame JSP and pass it along
      RequestDispatcher disp = ctxt.getRequestDispatcher(config.getFrameJSPName());
      request.setAttribute(REQUEST_INPUT,(RequestInput)this);
      request.setAttribute(REQUEST_OUTPUT,(RequestOutput)(new OutputImpl()));
      request.setAttribute(REQUEST_CONTENT,content);
      updateMenus(content);
      addDynamicHeaders();
      flushCookies();
      try
      { // forward it!
	disp.forward(request,response);

      } // end try
      finally
      { // make sure we end the response properly
	end_response = false;

      } // end finally

    } // end if
    else if (content instanceof ContentJSP)
    { // transfer straight to the top-level JSP
      ContentJSP cj = (ContentJSP)content;
      RequestDispatcher disp = ctxt.getRequestDispatcher(config.getFormatJSPPath(cj.getJSPName()));
      request.setAttribute(REQUEST_INPUT,(RequestInput)this);
      request.setAttribute(REQUEST_OUTPUT,(RequestOutput)(new OutputImpl()));
      request.setAttribute(REQUEST_CONTENT,content);
      updateMenus(content);
      addDynamicHeaders();
      flushCookies();
      cj.initialize(this);
      try
      { // forward the response
	disp.forward(request,response);

      } // end try
      finally
      { // make sure we terminate, even on error
	cj.terminate(this);
	end_response = false;

      } // end finally

    } // end else if
    else
    { // use OutputImpl to handle the rest
      OutputImpl oi = new OutputImpl();
      response.setContentType("text/html");
      updateMenus(content);
      addDynamicHeaders();
      flushCookies();
      oi.output(content);

    } // end else

  } // end output

  void outputRaw(String data) throws ServletException, IOException
  {
    OutputImpl oi = new OutputImpl();
    response.setContentType("text/html");
    updateMenus(Content.MENU_SELECTOR_NOCHANGE);
    addDynamicHeaders();
    flushCookies();
    oi.write(data);
    oi.flush();

  } // end data

  void execute(ContentExecute content) throws IOException
  {
    try
    { // perform the execute
      content.execute(new ExecImpl());

    } // end try
    finally
    { // still set the flag on error
      end_response = false;

    } // end finally

  } // end execute

  void end()
  {
    if (end_response)
    { // we can touch the response object when we end...
      try
      { // flush the response buffer
	response.flushBuffer();

      } // end try
      catch (IOException e)
      { // just print a warning message
	logger.warn("IOException flushing buffer in end()",e);

      } // end catch

    } // end if

    // perform automatic cleanups
    if (auto_cleanup!=null)
      while (auto_cleanup.size()>0)
      { // pop items off and clean them up
	AutoCleanup ac = (AutoCleanup)(auto_cleanup.removeFirst());
	ac.cleanup();

      } // end while and if

    // blow a bunch of our object fields to hell (this might make garbage collection easier)
    session_control = null;
    cookie_control = null;
    html_rendering = null;
    script_support = null;
    auto_cleanup = null;
    community = null;
    my_locale = null;
    my_timezone = null;
    mphandler = null;
    request = null;
    response = null;
    session = null;

    // release the script manager
    script_mgr.popContext();
    ScriptManagerContainer cnr = ScriptManagerContainer.get(ctxt);
    cnr.releaseScriptManager(script_mgr);
    script_mgr = null;

  } // end end

} // end class RequestImpl

class SessionControlImpl implements SessionControl
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private RequestImpl req;                    // request we're tied to
  private LinkedList session_stack = null;    // session stack

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

  SessionControlImpl(RequestImpl req)
  {
    this.req = req;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Implementations from interface SessionControl
   *--------------------------------------------------------------------------------
   */

  public void endSession()
  {
    req.endSession();

  } // end endSession

  public void pushSession(VeniceUISession sess)
  {
    if (session_stack==null)
      session_stack = new LinkedList();
    session_stack.addFirst(req.getSession());
    req.setSession(sess);

  } // end pushSession

  public void popSession()
  {
    if ((session_stack!=null) && (session_stack.size()>0))
      req.setSession((VeniceUISession)(session_stack.removeFirst()));

  } // end popSession

  public void replaceUser(UserContext new_user)
  {
    req.getSesson().setUser(new_user);

  } // end replaceUser

} // end class SessionControlImpl

class CookieControlImpl implements CookieControl
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private RequestImpl req;                    // request we're tied to
  private ArrayList new_cookies = null;       // cookies to be added to the response

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

  CookieControlImpl(RequestImpl req)
  {
    this.req = req;

  } // end constructor

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

  private final void putCookie(Cookie cookie)
  {
    if (new_cookies==null)
      new_cookies = new ArrayList();
    new_cookies.add(cookie);

  } // end putCookie

  /*--------------------------------------------------------------------------------
   * Implementations from interface CookieControl
   *--------------------------------------------------------------------------------
   */

  public void savePersistentCookie(String name, String value, int max_age)
  {
    Cookie c = new Cookie(name,value);
    c.setMaxAge(Math.max(max_age,1));
    c.setPath(req.getContextPath());
    putCookie(c);
    
  } // end savePersistentCookie

  public void saveTemporaryCookie(String name, String value)
  {
    Cookie c = new Cookie(name,value);
    c.setMaxAge(-1);
    c.setPath(req.getContextPath());
    putCookie(c);
    
  } // end saveTemporaryCookie

  public void deleteCookie(String name)
  {
    Cookie c = new Cookie(name,"");
    c.setMaxAge(0);
    c.setPath(req.getContextPath());
    putCookie(c);

  } // end deleteCookie

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

  final void flushCookies(HttpServletResponse response)
  {
    if (new_cookies==null)
      return;  // no-op

    // loop over the cookies and add them to the response
    Iterator it = new_cookies.iterator();
    while (it.hasNext())
    { // add cookies to the response
      Cookie c = (Cookie)(it.next());
      response.addCookie(c);

    } // end while

    new_cookies = null;

  } // end flushCookies

} // end class CookieControlImpl

class HTMLRenderingImpl implements HTMLRendering
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private RequestImpl req;                 // request we're attached to
  private RootConfig config;               // configuration data
  private DateFormat display_date = null;  // format to use for displaying dates
  private DateFormat activity_time = null; // format to use for activity string times

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

  HTMLRenderingImpl(RequestImpl req, RootConfig config)
  {
    this.req = req;
    this.config = config;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Implementations from interface HTMLRendering
   *--------------------------------------------------------------------------------
   */

  public String getColor(int selector)
  {
    return config.getColor(selector);

  } // end getColor

  public String getColor(String name)
  {
    return config.getColor(name);

  } // end getColor

  public boolean useHTMLComments()
  {
    return config.useHTMLComments();

  } // end useHTMLComments

  public String formatURL(String url, int type)
  {
    if (type==ABSOLUTE)
      return url;
    else if (type==SERVLET)
      return req.encodeServletPath(url);
    else if (type==FRAME)
      return req.encodeServletPath("frame/" + url);
    else
      throw new IndexOutOfBoundsException("invalid format type index for formatURL");

  } // end formatURL

  public String formatDate(Date date)
  {
    synchronized (this)
    { // create the display date formatter if we don't yet have it
      if (display_date==null)
      { // create the display date formatter
	display_date = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.MEDIUM,req.getLocale());
	display_date.setTimeZone(req.getTimeZone());

      } // end if

    } // end synchronized block

    return display_date.format(date);

  } // end formatDate

  public String getFontTag(int colorsel, int size)
  {
    return config.getFontTag(colorsel,size);

  } // end getFontTag

  public String getFontTag(String color, int size)
  {
    return config.getFontTag(color,size);

  } // end getFontTag

  public String getFontTag(int colorsel, String size)
  {
    return config.getFontTag(colorsel,size);

  } // end getFontTag

  public String getFontTag(String color, String size)
  {
    return config.getFontTag(color,size);

  } // end getFontTag

  public int convertLinkType(String str)
  {
    return config.convertLinkType(str);

  } // end convertLinkType

  public String getStockMessage(String key)
  {
    return config.getStockMessage(key);

  } // end getStockMessage

  public String getStockMessage(String key, Map vars)
  {
    return config.getStockMessage(key,vars);

  } // end getStockMessage

  public String getStaticPath(String s)
  {
    return config.getStaticPath(s);

  } // end getStaticPath

  public String getExternalStaticPath(String s)
  {
    return config.getExternalStaticPath(s);

  } // end getExternalStaticPath

  public String getImagePath(String s)
  {
    return config.getImagePath(s);

  } // end getImagePath

  public String getButtonVisual(String id)
  {
    return config.getButtonVisual(id);

  } // end getButtonVisual

  public String getButtonInput(String id)
  {
    return config.getButtonInput(id);

  } // end getButtonInput

  public String getUserPhotoTag(String url)
  {
    return getUserPhotoTag(url,req.getEngine().getUserPhotoSize());

  } // end getUserPhotoTag

  public String getUserPhotoTag(String url, Dimension size)
  {
    StringBuffer buf = new StringBuffer("<IMG SRC=\"");
    if (StringUtil.isStringEmpty(url))
      buf.append(config.getBlankPhoto());
    else
      buf.append(url);
    buf.append("\" ALT=\"\" ALIGN=LEFT BORDER=0 WIDTH=").append(size.width).append(" HEIGHT=");
    buf.append(size.height).append(">");
    return buf.toString();

  } // end getUserPhotoTag

  public String getActivityString(Date date)
  {
    if (date==null)
      return "Never";  // safeguard

    // Set up the two calendars we'll use for comparison.
    Calendar c_last = new GregorianCalendar(req.getTimeZone(),req.getLocale());
    c_last.setTime(date);
    Calendar c_now = new GregorianCalendar(req.getTimeZone(),req.getLocale());

    // Determine the exact number of days in the delta.
    int delta_days = 0;
    while (   (c_last.get(Calendar.YEAR)!=c_now.get(Calendar.YEAR))
	   || (c_last.get(Calendar.DAY_OF_YEAR)!=c_now.get(Calendar.DAY_OF_YEAR)))
    { // advance until we're pointing at the same year and the same day of the year
      delta_days++;
      c_last.add(Calendar.DAY_OF_YEAR,1);

    } // end while

    // For today or yesterday, we want to paste the time in as well.
    String s = null;
    if (delta_days<=1)
    { // set up to convert the time into a string
      if (activity_time==null)
      { // get the "activity" time formatter
	activity_time = DateFormat.getTimeInstance(DateFormat.MEDIUM,req.getLocale());
	activity_time.setTimeZone(req.getTimeZone());

      } // end if

      s = activity_time.format(date);

    } // end if

    // Return the actual activity string.
    if (delta_days==0)
      return "Today, " + s;
    else if (delta_days==1)
      return "Yesterday, " + s;
    else
      return String.valueOf(delta_days) + " days ago";

  } // end getActivityString

  public String getCommunityLogoTag(String url)
  {
    StringBuffer buf = new StringBuffer("<IMG SRC=\"");
    if (StringUtil.isStringEmpty(url))
      buf.append(this.getImagePath("sig_other.jpg"));
    else
      buf.append(url);
    Dimension sz = req.getEngine().getCommunityLogoSize();
    buf.append("\" ALT=\"\" BORDER=0 WIDTH=").append(sz.width).append(" HEIGHT=").append(sz.height);
    buf.append(">");
    return buf.toString();

  } // end getCommunityLogoTag

  public String expandServletPath(String spath)
  {
    return req.expandServletPath(spath);

  } // end expandServletPath

} // end class HTMLRenderingImpl

class ScriptSupportImpl implements ScriptSupport
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private RequestImpl req;                 // request we're attached to
  private RootConfig config;               // configuration data

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

  ScriptSupportImpl(RequestImpl req, RootConfig config)
  {
    this.req = req;
    this.config = config;

  } // end constructor

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

  private static final String createLoggerName(String prefix, String raw_name)
  {
    // Strip all extensions from the end of the file.
    int limit = raw_name.lastIndexOf('/');
    int x = raw_name.lastIndexOf('.');
    while (x>limit)
    { // remove all extensions from the end
      raw_name = raw_name.substring(0,x);
      x = raw_name.lastIndexOf('.');

    } // end while

    // convert the name to a logger name
    StringBuffer rc = new StringBuffer(prefix);
    for (x=0; x<raw_name.length(); x++)
    { // process all the characters
      char c = raw_name.charAt(x);
      if (c=='/')
	rc.append('.');
      else if (c=='.')
	rc.append('_');
      else
	rc.append(c);

    } // end for

    return rc.toString();

  } // end createLoggerName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ScriptSupport
   *--------------------------------------------------------------------------------
   */

  public String getScriptName(String raw_name)
  {
    return config.getScriptPath(raw_name);

  } // end getScriptName

  public String getScriptName(boolean strip_ext)
  {
    String script = req.getServletPath();
    if (script.charAt(0)=='/')
      script = script.substring(1);

    if (strip_ext)
    { // strip off the extension of the path...
      int limit = script.lastIndexOf('/');
      int x = script.lastIndexOf('.');
      if (x>limit)
	script = script.substring(0,x);

    } // end if

    return config.getScriptPath(script);

  } // end getScriptName

  public String getScriptLoggerName(String raw_name)
  {
    return createLoggerName("SCRIPT.",raw_name);

  } // end getScriptLoggerName

  public String getScriptLoggerName()
  {
    String script = req.getServletPath();
    if (script.charAt(0)=='/')
      script = script.substring(1);

    return this.getScriptLoggerName(script);

  } // end getScriptLoggerName

  public String getRPCScriptName(String raw_name)
  {
    return config.getRPCScriptPath(raw_name);

  } // end getScriptName

  public String getRPCScriptLoggerName(String raw_name)
  {
    return createLoggerName("RPCSCRIPT.",raw_name);

  } // end getScriptLoggerName

} // end class ScriptSupportImpl
