/*
 * 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.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.apache.log4j.xml.DOMConfigurator;
import com.silverwrist.util.StringUtil;
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.helpers.ErrorBox;
import com.silverwrist.venice.ui.helpers.ThrowableContent;
import com.silverwrist.venice.ui.script.ScriptingException;
import com.silverwrist.venice.ui.script.ScriptManagerContainer;

public abstract class BaseServlet extends HttpServlet
{
  /*--------------------------------------------------------------------------------
   * Internal session factory object 
   *--------------------------------------------------------------------------------
   */

  static class HttpSessionFactory implements VeniceUISessionFactory
  {
    private static final String ATTR_NAME = VeniceUISession.class.getName();

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

    public VeniceUISession createSession(ServletContext ctxt, HttpServletRequest request,
					 HttpServletResponse response, VeniceEngine engine,
					 RootConfig config) throws ServletException
    {
      HttpSession session = request.getSession(true);
      VeniceUISession rc = (VeniceUISession)(session.getAttribute(ATTR_NAME));
      if (rc==null)
      { // return a new session
	rc = new HttpVeniceUISession(request,session,engine);
	session.setAttribute(ATTR_NAME,rc);

      } // end if

      return rc;

    } // end createSession

  } // end class HttpSessionFactory

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

  private static final String LOGGING_ATTRIBUTE = "com.silverwrist.venice.util.LoggingStarted";
  private static final String ENGINE_ATTRIBUTE = "com.silverwrist.venice.core.Engine";
  private static final String UICONFIG_ATTRIBUTE = "com.silverwrist.venice.ui.Config";

  private static final String LOGGING_INIT_PARAM = "logging.config";
  private static final String ENGINE_INIT_PARAM = "venice.config";
  private static final String UICONFIG_INIT_PARAM = "ui.config";

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

  private static VeniceUISessionFactory factory = new HttpSessionFactory();

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

  private Object translateException(RequestInput req, Exception e)
  {
    // try the servlet first
    Object rc = translateServletException(req,e);
    if (rc!=null)
      return rc;

    // otherwise, go it alone
    logger.info("BaseServlet.translateException translating exception",e);
    if (e instanceof ScriptingException)
      return new ErrorBox("Scripting Error",e,null);

    return new ErrorBox("Unhandled Exception Detected!",e.toString(),e,null);

  } // end translateException

  private void commonProcess(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException
  {
    // log remote address with all log messages
    NDC.push(request.getRemoteAddr());

    try
    { // create the actual request implementation
      ServletContext ctxt = getServletContext();
      RequestImpl the_request = null;
      try
      { // create the request object
        the_request = new RequestImpl(ctxt,request,response,getVeniceEngine(ctxt),
				      getRootConfig(ctxt),getSessionFactory());

      } // end try
      catch (ValidationException ve)
      { // error in formatting of the request - bug out
	response.sendError(HttpServletResponse.SC_BAD_REQUEST,ve.toString());
	return;

      } // end catch

      String default_location = the_request.getLocation();

      // log the username with all log messages, if the user is logged in
      boolean record_user = false;
      if (the_request.sessionBound())
      { // has the session been bound?
	try
	{ // find out whether they're logged in
	  record_user = the_request.getUser().isLoggedIn();

	} // end try
	catch (IllegalStateException e)
	{ // if not, just forget about it
	  record_user = false;

	} // end catch

      } // end if

      if (record_user)
	NDC.push(the_request.getUser().getUserName());

      try
      { // run the actual operation!
	Object my_output;   // the output we get from the user
	
	try
	{ // process this request!
	  my_output = process(the_request);
	  if (my_output==null) // this shouldn't happen
	    my_output = new ErrorBox("Internal Error!",
                                     "No content returned for request: " + the_request.getVerb() + ": /"
				     + default_location,null);

	} // end try
	catch (ThrowableContent tc)
	{ // special escape for ThrowableContent
	  my_output = tc;

	} // end catch
	catch (Exception e)
	{ // if an exception is thrown, translate it
	  if (e instanceof RuntimeException)
	  { // filter out RuntimeExceptions and rethrow them
	    logger.error("BaseServlet caught runtime exception" + e.getClass().getName()
			 + " in commonProcess",e);
	    throw (RuntimeException)e;

	  } // end if

	  my_output = translateException(the_request,e);

	} // end catch

	if (logger.isDebugEnabled())
	  logger.debug("Outputting an object of type " + my_output.getClass().getName());

	// if the output object needs automatic cleanup, register it
	if (my_output instanceof AutoCleanup)
	  the_request.registerCleanup((AutoCleanup)my_output);

	try
	{ // Output the resulting object.
	  if (my_output instanceof ContentExecute)
	    the_request.execute((ContentExecute)my_output);
	  else if (my_output instanceof Content)
	    the_request.output((Content)my_output);
	  else
	    the_request.outputRaw(my_output.toString());

	} // end try
	finally
	{ // end the request
	  the_request.end(); 

	} // end finally

      } // end try
      finally
      { // make sure and pop the diagnostic context, if it was pushed
	if (record_user)
	  NDC.pop();

      } // end finally

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

    } // end finally

  } // end commonProcess

  /*--------------------------------------------------------------------------------
   * Abstract operations which MUST be overridden!
   *--------------------------------------------------------------------------------
   */

  public abstract Object process(RequestInput req) throws Exception, ThrowableContent;

  /*--------------------------------------------------------------------------------
   * Overrideable operations
   *--------------------------------------------------------------------------------
   */

  protected void init(ServletConfig config, ServletContext ctxt, VeniceEngine engine, RootConfig root,
		      String root_file_path)
  { // default does nothing
  } // end init

  protected VeniceUISessionFactory getSessionFactory()
  {
    return factory;

  } // end getSessionFactory

  protected Object translateServletException(RequestInput ri, Exception e)
  {
    return null;  // does nothing by default

  } // end translateServletException

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

  protected final VeniceEngine getVeniceEngine(ServletContext ctxt)
  {
    return (VeniceEngine)(ctxt.getAttribute(ENGINE_ATTRIBUTE));

  } // end getVeniceEngine

  protected final VeniceEngine getVeniceEngine()
  {
    return this.getVeniceEngine(getServletContext());

  } // end getVeniceEngine

  protected final RootConfig getRootConfig(ServletContext ctxt)
  {
    return (RootConfig)(ctxt.getAttribute(UICONFIG_ATTRIBUTE));

  } // end getRootConfig

  protected final RootConfig getRootConfig()
  {
    return this.getRootConfig(getServletContext());

  } // end getRootConfig

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

  public void init(ServletConfig config) throws ServletException
  {
    super.init(config);
    ServletContext ctxt = config.getServletContext();
    String root_file_path = ctxt.getRealPath("/");
    if (!(root_file_path.endsWith("/")))
      root_file_path += "/";

    synchronized (BaseServlet.class)
    { // initialize the logging
      if (ctxt.getAttribute(LOGGING_ATTRIBUTE)==null)
      { // get logging config file and initialize
	String log_cfg = ctxt.getInitParameter(LOGGING_INIT_PARAM);
	if (!(log_cfg.startsWith("/")))
	  log_cfg = root_file_path + log_cfg;

	DOMConfigurator.configure(log_cfg);
	logger.info("Initialized logging with config file: " + log_cfg);

	ctxt.setAttribute(LOGGING_ATTRIBUTE,Boolean.TRUE);

      } // end if

    } // end synchronized block

    NDC.push("[-servlet-init-]");

    VeniceEngine engine = null;
    RootConfig rootconf = null;
    try
    { // check and initialize the Venice engine
      synchronized (BaseServlet.class)
      { // initialize the engine
	engine = (VeniceEngine)(ctxt.getAttribute(ENGINE_ATTRIBUTE));
	if (engine==null)
	{ // initialize the engine
	  String engine_cfg = ctxt.getInitParameter(ENGINE_INIT_PARAM);
	  if (!(engine_cfg.startsWith("/")))
	    engine_cfg = root_file_path + engine_cfg;
	  logger.info("Initializing Venice engine using config file: " + engine_cfg);

	  try
	  { // extract the configuration file name and create the engine
	    engine = Startup.createEngine(engine_cfg,root_file_path);
	    ctxt.setAttribute(ENGINE_ATTRIBUTE,engine);

	  } // end try
	  catch (ConfigException e)
	  { // configuration failed!  post an error message
	    logger.fatal("Engine configuration failed: " + e.getMessage(),e);
	    throw new ServletException("Venice engine configuration failed: " + e.getMessage(),e);

	  } // end catch
	  catch (DataException e2)
	  { // configuration failed!  post an error message
	    logger.fatal("Engine data load failed: " + e2.getMessage(),e2);
	    throw new ServletException("Venice engine data load failed: " + e2.getMessage(),e2);

	  } // end catch

	} // end if

	// check and initialize the root configuration for the UI
	rootconf = (RootConfig)(ctxt.getAttribute(UICONFIG_ATTRIBUTE));
	if (rootconf==null)
	{ // load the root configuration
	  String ui_cfg = ctxt.getInitParameter(UICONFIG_INIT_PARAM);
	  if (!(ui_cfg.startsWith("/")))
	    ui_cfg = root_file_path + ui_cfg;
	  logger.info("Initializing Venice user interface using config file: " + ui_cfg);

	  try
	  { // create a root configuration
	    rootconf = new RootConfig(ui_cfg,root_file_path);
	    ctxt.setAttribute(UICONFIG_ATTRIBUTE,rootconf);

	  } // end try
	  catch (ConfigException e)
	  { // user interface configuration failed...
	    logger.fatal("User interface configuration failed: " + e.getMessage(),e);
	    throw new ServletException("Venice user interface configuration failed: " + e.getMessage(),e);

	  } // end catch
	  
	} // end if

	ScriptManagerContainer.initialize(rootconf,ctxt);  // make sure container initialized

      } // end synchronized block

      // now do per-servlet initialization
      init(config,ctxt,engine,rootconf,root_file_path);

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

    } // end finally

  } // end init

  public void destroy()
  {
    super.destroy();

  } // end destroy

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

  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  {
    commonProcess(req,resp);

  } // end doGet

  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  {
    commonProcess(req,resp);

  } // end doPost

} // end class BaseServlet
