/*
 * 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.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.apache.log4j.xml.DOMConfigurator;
import com.silverwrist.dynamo.HttpStatusCode;
import com.silverwrist.dynamo.Verb;
import com.silverwrist.dynamo.WebConstants;
import com.silverwrist.dynamo.app.ApplicationContainer;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

/**
 * The base class for all servlets running under the Dynamo framework.  It handles servlet lifecycle issues and
 * controls the overall lifecycle of a request, providing an abstract method to be used to process a request.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 */
public abstract class ServletBase extends HttpServlet implements WebConstants
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing the servlet application substrate
   *--------------------------------------------------------------------------------
   */

  private static class ServletSubstrate implements ApplicationSubstrate
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private HashMap m_map = new HashMap();

    /*====================================================================
     * Constructor
     *====================================================================
     */

    ServletSubstrate(File base_path, ServletConfig sconf)
    {
      m_map.put(OBJ_BASE_PATH,base_path.getAbsolutePath());
      File code_path = new File(base_path,"WEB-INF");
      m_map.put(OBJ_CODE_PATH,code_path.getAbsolutePath());
      m_map.put(OBJ_CLASSES_PATH,new File(code_path,"classes").getAbsolutePath());
      m_map.put(OBJ_LIBS_PATH,new File(code_path,"lib").getAbsolutePath());
      m_map.put(OBJ_SUBSTRATE_TYPE,"Servlet");
      ServletContext ctxt = sconf.getServletContext();
      String tmp = String.valueOf(ctxt.getMajorVersion()) + "." + String.valueOf(ctxt.getMinorVersion());
      m_map.put(OBJ_SUBSTRATE_SPECVER,tmp);
      m_map.put(OBJ_APP_NAME,ctxt.getServletContextName());

    } // end constructor

    /*====================================================================
     * Implementations from interface ObjectProvider
     *====================================================================
     */

    public Object getObject(String namespace, String name)
    {
      Object rc = null;
      if (NAMESPACE.equals(namespace))
      { // look up in either our map or system properties
	String xkey = (String)(SYS_PROPERTY_MAP.get(name));
	if (xkey==null)
	  rc = m_map.get(name);
	else
	  rc = System.getProperty(xkey);

      } // end if

      if (rc==null)
	throw new NoSuchObjectException("ServletSubstrate",namespace,name);
      return rc;

    } // end getObject
    
    /*====================================================================
     * Implementations from interface ApplicationSubstrate
     *====================================================================
     */

    public void initialize() throws ConfigException
    {
      logger.info("ServletSubstrate initializing");
      if (logger.isDebugEnabled())
      { // check out critical values
	String s = (String)getObject(NAMESPACE,OBJ_TEMP_PATH);
	logger.debug("Temp path is: " + s);

      } // end if

    } // end initialize

    public void terminate()
    {
      logger.info("ServletSubstrate terminating");

    } // end terminate

  } // end class ServletSubstrate

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

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

  private static final Map SYS_PROPERTY_MAP;

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

  private LinkedList m_shutdown_list = null;   // shutdown list

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

  private final boolean supportsMethodWrapper(HttpMethod method, Application app)
  {
    if (HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method))
      return true;
    if (HttpMethod.HEAD.equals(method))
      return supportsMethod(HttpMethod.GET,app);
    return supportsMethod(method,app);

  } // end supportsMethodWrapper

  private final ServiceProvider createRequestServicesWrapper(HttpServletRequest req, HttpServletResponse resp,
							     ApplicationContainer appcon)
  {
    ServiceProvider base = createRequestServices(req,resp);
    if (resp!=null)
    { // add the URL Rewriter service
      URLRewriterImpl url_rewrite = new URLRewriterImpl(req,resp,appcon.getRewriteRuleMap());
      base = new SingletonServiceProvider("Request Services",base,URLRewriter.class,url_rewrite);

    } // end if

    return appcon.wrapServices(base);

  } // end createRequestServices

  private final ServiceProvider createOutputServicesWrapper(ApplicationContainer appcon,
							    HttpServletRequest req, HttpServletResponse resp)
  {
    ServiceProvider base = createOutputServices(req,resp);
    URLRewriterImpl url_rewrite = new URLRewriterImpl(req,resp,appcon.getRewriteRuleMap());
    return appcon.wrapOutputServices(new SingletonServiceProvider("Output Services",base,URLRewriter.class,
								  url_rewrite));

  } // end createOutputServicesWrapper

  protected static final ApplicationContainer getApplicationContainer(ServletConfig cfg)
  {
    return getApplicationContainer(cfg.getServletContext());

  } // end getApplicationContainer

  protected static final BrowserDataProvider createBrowserDataProvider(HttpServletRequest req)
  {
    return new ServletBrowserDataProvider(req);

  } // end createBrowserDataProvider

  protected static final HeaderInput createHeaderInput(HttpServletRequest req)
  {
    return new ServletHeaderInput(req);

  } // end createHeaderInput

  protected static final HeaderOutput createHeaderOutput(HttpServletRequest req)
  {
    return new ServletHeaderOutput(req);

  } // end createHeaderOutput

  protected static final HeaderOutput createHeaderOutput(HttpServletRequest req, HttpServletResponse resp)
  {
    return new ServletHeaderOutput(req,resp);

  } // end createHeaderOutput

  protected static final ChainParameterInput createChainParameterInput(ServletRequest req)
  {
    return new ServletChainParameterInput(req);

  } // end createChainParameterInput

  protected static final void renderAsRawText(HttpServletRequest req, HttpServletResponse resp, Object result)
      throws IOException
  {
    resp.setStatus(HttpServletResponse.SC_OK);
    resp.setContentType("text/plain; charset=ISO-8859-1");
    PrintWriter wr = resp.getWriter();
    wr.println(result);
    wr.flush();

  } // end renderAsRawText

  protected final ApplicationContainer getApplicationContainer()
  {
    return getApplicationContainer(getServletContext());

  } // end getApplicationContainer

  protected final synchronized void addShutdownHook(ComponentShutdown cs)
  {
    if (m_shutdown_list==null)
      m_shutdown_list = new LinkedList();
    m_shutdown_list.addFirst(cs);

  } // end addShutdownHook

  protected final MIMETypeMapper getMIMETypeMapper()
  {
    ServletContext ctxt = getServletContext();
    MIMETypeMapper rc = (MIMETypeMapper)(ctxt.getAttribute(SERVLET_MAPPER_ATTRIBUTE));
    if (rc==null)
    { // create a new ServletMIMETypeMapper and save off its reference
      rc = new ServletMIMETypeMapper(ctxt);
      ctxt.setAttribute(SERVLET_MAPPER_ATTRIBUTE,rc);

    } // end if

    return rc;

  } // end getMIMETypeMapper

  /**
   * Returns the appropriate {@link com.silverwrist.dynamo.iface.Renderer Renderer} for an object.
   *
   * @param obj The object to be rendered.
   * @return The object's appropriate <CODE>Renderer</CODE>, or <CODE>null</CODE> if none was found.
   */
  protected final Renderer getRendererForObject(Object obj)
  {
    return getApplicationContainer().getRendererForObject(obj);

  } // end getRendererForObject

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

  /**
   * Called as the last part of servlet initialization, to allow code in derived classes to initialize itself.
   * Servlet initialization parameters are accessible through the provided
   * {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider}, which also provides the whole gamut
   * of initialization services.
   *
   * @param services The <CODE>ServiceProvider</CODE> controlling access to initialization services.
   * @exception com.silverwrist.dynamo.except.DynamoException If the servlet initialization failed.
   */
  protected void initServlet(ServiceProvider services) throws DynamoException
  { // default implementation does nothing
  } // end initServlet

  protected boolean supportsMethod(HttpMethod method, Application app)
  {
    return (HttpMethod.GET.equals(method) || HttpMethod.POST.equals(method));

  } // end supportsMethod

  protected long getLastModified(Request r, Application app)
  {
    return -1;  // default value

  } // end getLastModified

  /**
   * Creates the {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} containing the "request
   * services" which may be used by whatever code processes the request.  Note that the
   * {@link com.silverwrist.dynamo.app.ApplicationContainer ApplicationContainer} may add to these services.
   *
   * @param req The <CODE>HttpServletRequest</CODE> object that contains the request the client made of the servlet.
   * @param resp The <CODE>HttpServletResponse</CODE> object that contains the response the servlet returns to the
   *             client.
   * @return The <CODE>ServiceProvider</CODE> through which the request services may be accessed.
   */
  protected ServiceProvider createRequestServices(HttpServletRequest req, HttpServletResponse resp)
  {
    // Get the SessionInfo for this HTTP session.
    HttpSession http_session = req.getSession();
    HttpSessionInfo session = (HttpSessionInfo)(http_session.getAttribute(SESSION_PARAM));
    if (session==null)
    { // create the session and bind it into the HTTP session object; also set it into the
      // request object attributes so it can call the session init notifications in the context
      // of the current request once it's been fully constructed
      session = new HttpSessionInfo(http_session,getApplicationContainer());
      http_session.setAttribute(SESSION_PARAM,session);
      req.setAttribute(REQUEST_SESSIONINIT_ATTR,session);

    } // end if

    // Create the SessionInfoProvider.  We also save it in the request attributes so we
    // can pick it up later and add it to the output services.
    DefaultSessionInfoProvider session_prov = new DefaultSessionInfoProvider(session);
    req.setAttribute(REQUEST_SESSION_PROVIDER_ATTR,session_prov);

    // Create the service provider interface and return it.
    SimpleServiceProvider services = new SimpleServiceProvider("Request Services");
    services.addService(SessionInfoProvider.class,session_prov);
    services.addService(BrowserDataProvider.class,createBrowserDataProvider(req));
    services.addService(HeaderInput.class,createHeaderInput(req));
    services.addService(HeaderOutput.class,createHeaderOutput(req));
    services.addService(CookieControl.class,new ServletCookieControl(req));
    services.addService(MIMETypeMapper.class,getMIMETypeMapper());
    services.addService(ChainParameterInput.class,createChainParameterInput(req));
    services.addService(ExternalAppAttributes.class,new ServletExternalAppAttributes(getServletContext()));
    services.addService(ExternalSessionAttributes.class,new ServletExternalSessionAttributes(http_session));
    return services;

  } // end createRequestServices

  protected Request createRequest(HttpServletRequest req, Verb verb, ServiceProvider services, Request prev)
      throws RequestParseException
  {
    if (Verb.POST.equals(verb))
    { // this may be a file upload request - if so, treat it as such
      String ctype = req.getContentType();
      if (ctype.startsWith("multipart/form-data"))
	return new FileUploadRequestImpl(req,verb,services,prev);

    } // end if

    return new NormalRequestImpl(req,verb,services,prev);

  } // end createRequest

  protected Object translateRequestParseException(RequestParseException e)
  {
    return new HttpErrorMessage(HttpStatusCode.E_BAD_REQUEST,e.getMessage());

  } // end translateRequestParseException

  protected Object translateException(Request r, Exception e)
  {
    return getApplicationContainer().translateException(r,e);

  } // end translateException

  /**
   * Creates the {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} containing the "output
   * services" which may be used by a renderer rendering an object.  Note that the
   * {@link com.silverwrist.dynamo.app.ApplicationContainer ApplicationContainer} may add to these services.
   *
   * @param req The <CODE>HttpServletRequest</CODE> object that contains the request the client made of the servlet.
   * @param resp The <CODE>HttpServletResponse</CODE> object that contains the response the servlet returns to the
   *             client.
   * @return The <CODE>ServiceProvider</CODE> through which the output services may be accessed.
   */
  protected ServiceProvider createOutputServices(HttpServletRequest req, HttpServletResponse resp)
  {
    // Retrieve the SessionInfoProvider object from the request where we left it.
    SessionInfoProvider sess_prov = (SessionInfoProvider)(req.getAttribute(REQUEST_SESSION_PROVIDER_ATTR));

    // Create the output services.
    SimpleServiceProvider services = new SimpleServiceProvider("Rendering Services");
    services.addService(BrowserDataProvider.class,createBrowserDataProvider(req));
    if (sess_prov!=null)
      services.addService(SessionInfoProvider.class,sess_prov);
    services.addService(CookieControl.class,new ServletCookieControl(req,resp));
    services.addService(HeaderOutput.class,createHeaderOutput(req,resp));
    services.addService(MIMETypeMapper.class,getMIMETypeMapper());
    return services;

  } // end createOutputServices

  /**
   * Renders a "result" object as HTML to the servlet's output.  This involves looking up a "renderer"
   * for the object, and passing the result to that renderer, along with a "render control" object instance
   *
   * @param req The <CODE>HttpServletRequest</CODE> object that contains the request the client made of the servlet.
   * @param resp The <CODE>HttpServletResponse</CODE> object that contains the response the servlet returns to the
   *             client.
   * @param services An instance of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} which
   *                 controls a set of "output services" which may be employed by a renderer.
   * @param result The object to be rendered.
   * @exception java.io.IOException If an I/O error occured while rendering the object.
   * @exception com.silverwrist.dynamo.except.RenderingException If the renderer failed in some fashion.
   */
  protected void outputResult(HttpServletRequest req, HttpServletResponse resp,
			      ServiceProvider services, Object result) throws IOException, RenderingException
  {
    if (result instanceof SelfRenderable)
    { // this object can render itself - let's do that!
      ServletSelfRenderControl control = new ServletSelfRenderControl(req,resp,
                                                                      getServletConfig().getServletContext(),services);
      ((SelfRenderable)result).render(control);
      return;
      
    } // end if

    Renderer rend = getRendererForObject(result);
    if (rend==null)
    { // the only thing we know about this is that we can get its string equivalent,
      // so send that out 
      renderAsRawText(req,resp,result);
      return;

    } // end if

    if (rend instanceof TextRenderer)
    { // render as a text object
      ServletTextRenderControl control = new ServletTextRenderControl(req,resp,services);
      ((TextRenderer)rend).render(result,control);

    } // end if
    else if (rend instanceof BinaryRenderer)
    { // render as a binary object
      ServletBinaryRenderControl control = new ServletBinaryRenderControl(req,resp,services);
      ((BinaryRenderer)rend).render(result,control);

    } // end else if
    else
    { // default to raw text
      logger.error("Unknown renderer object class: " + rend.getClass().getName());
      renderAsRawText(req,resp,result);

    } // end else
    
  } // end outputResult

  /*--------------------------------------------------------------------------------
   * Overrides from class GenericServlet
   *--------------------------------------------------------------------------------
   */

  /**
   * Called by the servlet container to indicate to a servlet that the servlet is being placed into service.
   * This implementation stores the <CODE>ServletConfig</CODE> object it receives from the servlet container
   * for later use.
   *
   * @param config The <CODE>ServletConfig</CODE> object that contains configuration information for this
   *               servlet.
   * @exception javax.servlet.ServletException If an exception occurs that interrupts the servlet's normal
   *            operation.
   */
  public final void init(ServletConfig config) throws ServletException
  {
    super.init(config);  // call this first!
    ServletContext ctxt = config.getServletContext();

    // determine the base path for the application
    File base_path = new File(ctxt.getRealPath("/"));

    ApplicationContainer appcon = null;
    synchronized (ServletBase.class)
    { // initialize logging
      if (ctxt.getAttribute(LOGGING_ATTRIBUTE)==null)
      { // logging needs to be initialized
	String logging_param = ctxt.getInitParameter(LOGGING_INIT_PARAM);
	if (logging_param!=null)
	{ // this is the name of the log file, relative to the base path
	  File logging_file = new File(base_path,logging_param);
	  if (logging_file.canRead())
	    DOMConfigurator.configure(logging_file.getAbsolutePath());

	} // end if
	// else logging does not need to be configured

	ctxt.setAttribute(LOGGING_ATTRIBUTE,Boolean.TRUE);

      } // end if

    } // end synchronized block

    NDC.push("[-servlet-init-]");
    try
    { // initialize the application
      synchronized (ServletBase.class)
      { // get the application container
	appcon = getApplicationContainer(ctxt);
	if (appcon==null)
	{ // find the application configuration file
	  String config_param = ctxt.getInitParameter(CONFIG_INIT_PARAM);
	  if (config_param==null)
	    config_param = "WEB-INF/dynamo.xml";
	  File config_file = new File(base_path,config_param);
	  if (config_file.canRead())
	  { // configuration file could be read - prepare the substrate object
	    ApplicationSubstrate substrate = new ServletSubstrate(base_path,config);
	    try
	    { // create the application container and save it as the attribute
	      appcon = new ApplicationContainer(config_file,substrate);
	      ctxt.setAttribute(APPLICATION_ATTRIBUTE,appcon);

	    } // end try
	    catch (ConfigException e)
	    { // error while configuring application container
	      logger.fatal("error initializing ApplicationContainer",e);
	      throw new ServletException("error initializing application",e);

	    } // end catch
            catch (RuntimeException e)
            { // REALLY bad error while configuring application container
              logger.fatal("Runtime error initializing ApplicationContainer",e);
              throw new ServletException("error initializing application",e);

            } // end catch

	  } // end if
	  else // the configuration file could not be read!
	    throw new ServletException("application configuration file " + config_file.getAbsolutePath()
				       + " could not be read");

	} // end if
	else // already initialized, just add a reference
	  appcon.addRef();

      } // end synchronized block

      // Create the initialization services for the servlet.
      ServiceProvider init_services = appcon.getInitServices();
      ObjectProvider objects = (ObjectProvider)(init_services.queryService(ObjectProvider.class));
      ServletInitObjectProvider new_objects = new ServletInitObjectProvider(objects,config);
      SingletonServiceProvider my_services = new SingletonServiceProvider("ServletBase",init_services,
									  ObjectProvider.class,new_objects);
      try
      { // initialize the servlet!
	initServlet(my_services);

      } // end try
      catch (DynamoException e)
      { // translate DynamoException
	logger.fatal("servlet initialization failed in " + getClass().getName(),e);
	throw new ServletException("servlet initialization failed in " + getClass().getName(),e);

      } // end catch
      catch (RuntimeException e)
      { // translate RuntimeException
	logger.fatal("servlet initialization failed in " + getClass().getName(),e);
	throw new ServletException("servlet initialization failed in " + getClass().getName(),e);

      } // end catch

    } // end try
    finally
    { // pop the NDC before we go
      NDC.pop();

    } // end finally

  } // end init

  /**
   * Called by the servlet container to indicate to a servlet that the servlet is being taken out of service.
   */
  public final void destroy()
  {
    ServletContext ctxt = getServletContext();
    ApplicationContainer appcon = getApplicationContainer(ctxt);

    NDC.push("[-servlet-destroy-]");
    try
    { // destroy this servlet
      if (this instanceof ComponentShutdown)
      { // call our own shutdown method
	if (logger.isDebugEnabled())
	  logger.debug(getClass().getName() + " servlet destroy: shutting down servlet itself");
	((ComponentShutdown)this).shutdown();

      } // end if

      // destroy components registered with the shutdown list
      if (m_shutdown_list!=null)
      { // use the shutdown list
	if (logger.isDebugEnabled())
	  logger.debug(getClass().getName() + " servlet destroy: " + m_shutdown_list.size()
		       + " object(s) to shutdown");
	while (m_shutdown_list.size()>0)
	{ // shutdown each component in turn
	  ComponentShutdown cs = (ComponentShutdown)(m_shutdown_list.removeFirst());
	  cs.shutdown();

	} // end while

	m_shutdown_list = null;

      } // end if

      if (logger.isDebugEnabled())
	logger.debug(getClass().getName() + " servlet destroy: releasing application container");
      synchronized (ServletBase.class)
      { // release the context, which may destroy it
	if ((appcon!=null) && appcon.release())  // the application container is destroyed
	  ctxt.removeAttribute(APPLICATION_ATTRIBUTE);

      } // end synchronized block

    } // end try
    finally
    { // pop the NDC before we go
      NDC.pop();

    } // end finally

    super.destroy();  // call this last

  } // end destroy

  public String getServletInfo()
  {
    return "Dynamo servlet";

  } // end getServletInfo

  /*--------------------------------------------------------------------------------
   * Overrides from class HttpServlet
   *--------------------------------------------------------------------------------
   */

  /**
   * Called from the <CODE>service</CODE> method to allow this servlet to handle a OPTIONS request. The OPTIONS
   * request determines which HTTP methods the server supports and returns an appropriate header. For example, if
   * the servlet allows GET methods, this method returns the following header:
   * <P><CODE>Allow: GET, HEAD, TRACE, OPTIONS</CODE><P>
   *
   * @param req The <CODE>HttpServletRequest</CODE> object that contains the request the client made of the servlet.
   * @param resp The <CODE>HttpServletResponse</CODE> object that contains the response the servlet returns to the
   *             client.
   * @exception javax.servlet.ServletException If the request could not be handled for any reason.
   * @exception java.io.IOException If there was an I/O error writing the response to the request.
   */
  protected final void doOptions(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException
  {
    ApplicationContainer appcon = getApplicationContainer();
    Application app = appcon.getApplication();

    StringBuffer b = null;
    Iterator it = HttpMethod.iterator();
    while (it.hasNext())
    { // check to see which methods we support!
      HttpMethod method = (HttpMethod)(it.next());
      if (supportsMethodWrapper(method,app))
      { // add to the header value we're building
	if (b==null)
	  b = new StringBuffer(method.getName());
	else
	  b.append(", ").append(method.getName());

      } // end if

    } // end while

    if (b!=null)
      resp.setHeader("Allow",b.toString());
    appcon.setServerHeader(resp);

  } // end doOptions

  protected final long getLastModified(HttpServletRequest req)
  {
    ApplicationContainer appcon = getApplicationContainer();
    Request r = null;
    try
    { // get a new request
      r = createRequest(req,Verb._LASTMOD,createRequestServicesWrapper(req,null,appcon),null);

    } // end try
    catch (RequestParseException rpe)
    { // yIkes!  request parse failed!
      logger.error("getLastModified() denied because of RequestParseException",rpe);
      return -1;

    } // end catch

    return getLastModified(r,appcon.getApplication());

  } // end getLastModified

  /**
   * The standard entry point for all HTTP requests, called by the servlet engine itself.  It creates the
   * Dynamo-specific request context and dispatches the call to the
   * {@link #process(com.silverwrist.dynamo.iface.Request,com.silverwrist.dynamo.iface.Application) process()} method.
   *
   * @param req The <CODE>HttpServletRequest</CODE> object that contains the request the client made of the servlet.
   * @param resp The <CODE>HttpServletResponse</CODE> object that contains the response the servlet returns to the
   *             client.
   * @exception javax.servlet.ServletException If the request could not be handled for any reason.
   * @exception java.io.IOException If there was an I/O error writing the response to the request.
   */
  protected final void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException
  {
    ApplicationContainer appcon = getApplicationContainer();
    Application app = appcon.getApplication();
    ResourceBundle local_strings =
        ResourceBundle.getBundle("com.silverwrist.dynamo.servlet.ServletBaseMessages");

    NDC.push(req.getRemoteAddr());
    try
    { // run the service request
      HttpMethod method = HttpMethod.getEnum(req.getMethod());
      if (method==null)
      { // the Dynamo framework does not support this method - return appropriate response
	logger.error("HTTP method " + req.getMethod() + " does not map to a HttpMethod value");
	Object[] args = new Object[1];
	args[0] = req.getMethod();
	String err = MessageFormat.format(local_strings.getString("method.notValid"),args);
	appcon.setServerHeader(resp);
	resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,err);
	return;

      } // end if

      if (!supportsMethodWrapper(method,app))
      { // this particular entry point does not support this method
	logger.error("HTTP method " + method.getName() + " is not supported by entry point "
		     + req.getRequestURI());
	Object[] args = new Object[1];
	args[0] = method.getName();
	String err = MessageFormat.format(local_strings.getString("method.notSupported"),args);
	String protocol = req.getProtocol();
	appcon.setServerHeader(resp);
	if (protocol.endsWith("1.1"))
	  resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED,err);
	else
	  resp.sendError(HttpServletResponse.SC_BAD_REQUEST,err);
	return;
      
      } // end if

      if (HttpMethod.TRACE.equals(method))
      { // TRACE method call - superclass can do this well enough, just let it ride
	appcon.setServerHeader(resp);
	super.doTrace(req,resp);
	return;

      } // end if

      if (HttpMethod.OPTIONS.equals(method))
      { // do our own OPTIONS method processing
	doOptions(req,resp);
	return;

      } // end if

      HttpServletResponse response = resp;
      NullResponse head_response = null;
      if (HttpMethod.HEAD.equals(method))
      { // set up null response and act like we're doing a GET
	if (!(response instanceof NullResponse))
        { // wrap the response in a NullResponse
	  head_response = new NullResponse(resp);
	  response = head_response;

	} // end if

	method = HttpMethod.GET;

      } // end if

      if (HttpMethod.GET.equals(method))
      { // get the Last-Modified data from the entry point
	long last_modified = getLastModified(req);
	if (last_modified!=-1)
	{ // we actually have an implementation for this object...
	  if (head_response==null)
	  { // this is an actual GET request - look at the If-Modified-Since header to
	    // see if the request should go through.
	    long tmp = last_modified / 1000;
	    last_modified = tmp * 1000;
	    long if_modified_since = req.getDateHeader(HDR_IF_MODIFIED_SINCE);
	    if (if_modified_since>=last_modified)
	    { // not modified since time in header - return a "Not Modified" status code
	      appcon.setServerHeader(response);
	      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
	      return;

	    } // end if (not modified)

	  } // end if (actual GET request)

	  // set "last modified" header information 
	  if (!(response.containsHeader(HDR_LAST_MODIFIED)))
	    response.setDateHeader(HDR_LAST_MODIFIED,last_modified);

	} // end if
	// else ignore this value, as the servlet doesn't implement it

      } // end if

      // See if this request has gone through Dynamo before, in which case we already have a Request
      // object defined.
      Object result_value = null;
      boolean do_process = true;
      Request prev_req = (Request)(req.getAttribute(REQUEST_SELF_ATTR));

      // Translate the HTTP method to a Dynamo verb.
      Verb verb = method.toVerb();
      if (verb==null)
      { // internal server error...
	logger.error("HTTP method " + method.getName() + " does not map to a Dynamo Verb");
	Object[] args = new Object[1];
	args[0] = method.getName();
	String err = MessageFormat.format(local_strings.getString("method.noVerb"),args);
	appcon.setServerHeader(resp);
	resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,err);
	return;

      } // end if

      // Create an appropriate request object.
      Request r = null;
      try
      { // create a request object
	r = createRequest(req,verb,createRequestServicesWrapper(req,response,appcon),prev_req);

	// If we have an HTTP session to create, call its session initialization functions
	// in the context of this request.
	HttpSessionInfo hsi = (HttpSessionInfo)(req.getAttribute(REQUEST_SESSIONINIT_ATTR));
	if (hsi!=null)
	{ // strip out the attribute and call the init functions
	  req.removeAttribute(REQUEST_SESSIONINIT_ATTR);
	  hsi.callSessionInit(r);

	} // end if

	req.setAttribute(REQUEST_SELF_ATTR,r);  // save off as request attribute

      } // end try
      catch (RequestParseException rpe)
      { // output a "bad request" error...
	logger.error("Request parse failed",rpe);
	result_value = translateRequestParseException(rpe);
	do_process = false;

      } // end catch

      if (do_process)
      { // Process the request.
	appcon.preprocessRequest(r);  // preprocess the request first

	try
	{  // process the request!
	  result_value = process(r,app);
	  if (result_value==null)
	    result_value = app.getNullContentError(r);

	} // end try
	catch (Exception e)
	{ // handle all exceptions in the process
	  if ((e instanceof RuntimeException) && !(e instanceof DynamoRuntimeException))
	  { // filter out RuntimeExceptions and rethrow them
	    logger.error("ServletBase caught runtime exception in service method",e);
	    throw (RuntimeException)e;

	  } // end if

	  result_value = translateException(r,e);

	} // end catch

      } // end if
      // else request error has already set the result value

      try
      { // Call through any output filters that have been configured.
	result_value = appcon.filterOutput(result_value,r);

      } // end try
      catch (RenderingException e)
      { // turn RenderingExceptions into ServletExceptions
	logger.error("RenderingException in filterOutput",e);
	throw new ServletException("error filtering output: " + e.getMessage(),e);

      } // end catch

      // Retrieve the rendering flags for this object.
      long rflags = RenderFlags.DEFAULT;
      if (result_value instanceof RenderFlags)
        rflags = ((RenderFlags)result_value).getRenderFlags();

      // Preprocess the response as needed.
      if ((rflags & RenderFlags.NO_COOKIE_FLUSH)==0)
        ServletCookieControl.flushCookies(req,response);
      if ((rflags & RenderFlags.NO_HEADER_FLUSH)==0)
      { // flush out the headers
        appcon.setServerHeader(response);
        ServletHeaderOutput.flushHeaders(req,response);

      } // end if

      try
      { // Output the result.
	outputResult(req,response,createOutputServicesWrapper(appcon,req,response),result_value);

      } // end try
      catch (RenderingException e)
      { // for now, just turn RenderingExceptions into ServletExceptions
	logger.error("RenderingException in outputResult",e);
	throw new ServletException("error rendering output: " + e.getMessage(),e);

      } // end catch

      if ((rflags & RenderFlags.NO_RESPONSE_FLUSH)==0)
      { // finalize the response
	if (head_response!=null)
	  head_response.fixupContentLength();  // fixup Content-Length
	response.flushBuffer();  // we're done with the request

      } // end if

    } // end try
    finally
    { // pop the diagnostic context before we go
      NDC.pop();

    } // end finally
    
  } // end service

  /*--------------------------------------------------------------------------------
   * Abstract operations
   *--------------------------------------------------------------------------------
   */

  /**
   * Actually processes a Dynamo request.  Requests, when processed, return objects, which
   * are "rendered" to create HTML which is output.
   *
   * @param r The {@link com.silverwrist.dynamo.iface.Request Request} to be serviced.
   * @param app A reference to the {@link com.silverwrist.dynamo.iface.Application Application} object for
   *            this instance of the framework.
   * @return An object representing the output, which will be rendered into HTML by the framework.
   * @exception java.lang.Exception An error occurred in processing the request.  This exception may be
   *            turned into a renderable object via the exception translation mechanism.
   */
  protected abstract Object process(Request r, Application app) throws Exception;

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

  /**
   * Returns the instance of the {@link com.silverwrist.dynamo.app.ApplicationContainer ApplicationContainer} for
   * this instance of the framework.
   *
   * @return The <CODE>ApplicationContainer</CODE> object.
   */
  public static final ApplicationContainer getApplicationContainer(ServletContext ctxt)
  {
    return (ApplicationContainer)(ctxt.getAttribute(APPLICATION_ATTRIBUTE));

  } // end getApplicationContainer

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // initialize the SYS_PROPERTY_MAP
    HashMap tmp = new HashMap();
    tmp.put(ApplicationSubstrate.OBJ_TEMP_PATH,"java.io.tmpdir");
    tmp.put(ApplicationSubstrate.OBJ_JAVA_VERSION,"java.version");
    tmp.put(ApplicationSubstrate.OBJ_OS_NAME,"os.name");
    tmp.put(ApplicationSubstrate.OBJ_OS_VERSION,"os.version");
    SYS_PROPERTY_MAP = Collections.unmodifiableMap(tmp);

  } // end static initializer

} // end class ServletBase
