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

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import com.silverwrist.util.StringUtil;
import com.silverwrist.util.ServletMultipartHandler;
import com.silverwrist.util.ServletMultipartException;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.servlets.format.*;

public abstract class VeniceServlet extends HttpServlet
{
  /*--------------------------------------------------------------------------------
   * Static data values
   *--------------------------------------------------------------------------------
   */

  private static final String LOCATION_ATTR = "com.silverwrist.venice.servlets.internal.Location";

  private static Category logger = Category.getInstance(VeniceServlet.class.getName());

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private static final void notSupported(HttpServletRequest request, String message) throws ErrorResult
  {
    String protocol = request.getProtocol();
    if (protocol.endsWith("1.1"))
      throw new ErrorResult(HttpServletResponse.SC_METHOD_NOT_ALLOWED,message);
    else
      throw new ErrorResult(HttpServletResponse.SC_BAD_REQUEST,message);

  } // end notSupported

  private static final CommunityContext getCommunityParameter(String str, UserContext user, boolean required,
							      String on_error) throws ErrorBox
  {
    if (StringUtil.isStringEmpty(str))
    { // there's no community parameter
      if (required)
      { // no communty 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

    CommunityContext rc = null;
    try
    { // turn the string into a community ID, and thence to a CommunityContext
      int tmp_id = Integer.parseInt(str);
      rc = user.getCommunityContext(tmp_id);
      if (rc==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 #" + rc.getCommunityID());

    } // end try
    catch (NumberFormatException nfe)
    { // error in Integer.parseInt
      logger.error("Cannot convert community parameter '" + str + "'!");
      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 rc;

  } // end getCommunityParameter

  private static TopicContext getTopicParameter(String str, ConferenceContext conf, boolean required,
						String on_error) throws ErrorBox
  {
    if (StringUtil.isStringEmpty(str))
    { // there's no topic parameter
      if (required)
      { // no topic parameter - bail out now!
	logger.error("Topic parameter not specified!");
	throw new ErrorBox(null,"No topic specified.",on_error);

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

      } // end else

    } // end if

    TopicContext rc = null;
    try
    { // turn the string into a topic number, and thence to a TopicContext
      short tmp_id = Short.parseShort(str);
      rc = conf.getTopic(tmp_id);
      if (rc==null)
      { // the topic was not found!
	logger.error("ConfID #" + conf.getConfID() + " did not have topic #" + tmp_id);
	throw new ErrorBox(null,"Topic #" + tmp_id + " was not found in the '" + conf.getName()
			   + "' conference.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("found topic #" + rc.getTopicID());

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

    } // end catch
    catch (AccessError ae)
    { // these all get handled in pretty much the same way
      throw new ErrorBox("Access Error",ae.getMessage(),on_error);

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

    } // end catch

    return rc;

  } // end getTopicParameter

  private static TopicMessageContext getMessageParameter(String str, ConferenceContext conf, boolean required,
							 String on_error) throws ErrorBox
  {
    if (StringUtil.isStringEmpty(str))
    { // there's no message parameter
      if (required)
      { // no message parameter - bail out now!
	logger.error("Message parameter not specified!");
	throw new ErrorBox(null,"No message specified.",on_error);

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

      } // end else

    } // end if

    TopicMessageContext rc = null;
    try
    { // turn the string into a postid, and thence to a TopicMessageContext
      long tmp_id = Long.parseLong(str);
      rc = conf.getMessageByPostID(tmp_id);
      if (rc==null)
      { // the message was not found
	logger.error("ConfID #" + conf.getConfID() + " does not contain postid " + tmp_id);
	throw new ErrorBox(null,"The post with ID number " + tmp_id + " was not found in the '"
			   + conf.getName() + "' conference.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("found post #" + rc.getPostID());

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

    } // end catch
    catch (AccessError ae)
    { // these all get handled in pretty much the same way
      throw new ErrorBox("Access Error",ae.getMessage(),on_error);

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

    } // end catch

    return rc;

  } // end getMessageParameter

  private static TopicMessageContext getMessageParameter(String str, TopicContext topic, boolean required,
							 String on_error) throws ErrorBox
  {
    if (StringUtil.isStringEmpty(str))
    { // there's no message parameter
      if (required)
      { // no message parameter - bail out now!
	logger.error("Message parameter not specified!");
	throw new ErrorBox(null,"No message specified.",on_error);

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

      } // end else

    } // end if

    TopicMessageContext rc = null;
    try
    { // turn the string into a post number, and thence to a TopicMessageContext
      int tmp_id = Integer.parseInt(str);
      rc = topic.getMessage(tmp_id);
      if (rc==null)
      { // could not find the message
	logger.error("TopicID " + topic.getTopicID() + " does not contain message #" + tmp_id);
	throw new ErrorBox(null,"There is no message #" + tmp_id + " in the '" + topic.getName() + "' topic.",
			   on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("found post #" + rc.getPostID());

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

    } // end catch
    catch (AccessError ae)
    { // these all get handled in pretty much the same way
      throw new ErrorBox("Access Error",ae.getMessage(),on_error);

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

    } // end catch

    return rc;

  } // end getMessageParameter

  /*--------------------------------------------------------------------------------
   * Internal operations intended for use by derived classes
   *--------------------------------------------------------------------------------
   */

  protected static final boolean isImageButtonClicked(ServletRequest request, String name)
  {
    String val = request.getParameter(name + ".x");
    return (val!=null);

  } // end isImageButtonClicked

  protected static final boolean isImageButtonClicked(ServletMultipartHandler mphandler, String name)
  {
    String val = mphandler.getValue(name + ".x");
    return (val!=null);

  } // end isImageButtonClicked

  protected final void putUserContext(HttpServletRequest request, UserContext ctxt)
  {
    Variables.putUserContext(request.getSession(true),ctxt);
    
  } // end putUserContext

  protected final void clearUserContext(HttpServletRequest request)
  {
    Variables.clearUserContext(request.getSession(true));

  } // end clearUserContext

  protected final void changeMenuTop(HttpServletRequest request)
  {
    Variables.setMenuTop(getServletContext(),request.getSession(true));

  } // end changeMenuTop

  protected final void changeMenuCommunity(HttpServletRequest request, CommunityContext comm)
  {
    Variables.setMenuCommunity(request.getSession(true),comm);

  } // end changeMenuCommunity

  protected final void clearMenu(HttpServletRequest request)
  {
    Variables.clearMenu(request.getSession(true));

  } // end clearMenu

  protected final String getStandardCommandParam(ServletRequest request)
  {
    String foo = request.getParameter("cmd");
    if (foo==null)
      return "???";
    else
      return foo;

  } // end getStandardCommandParam

  protected final void setMyLocation(ServletRequest request, String loc)
  {
    request.setAttribute(LOCATION_ATTR,loc);

  } // end setMyLocation

  protected final static CommunityContext getCommunityParameter(ServletRequest request, UserContext user,
								boolean required, String on_error)
      throws ErrorBox
  {
    return getCommunityParameter(request.getParameter("sig"),user,required,on_error);

  } // end getCommunityParameter

  protected final static CommunityContext getCommunityParameter(ServletMultipartHandler mphandler,
								UserContext user, boolean required,
								String on_error) throws ErrorBox
  {
    if (mphandler.isFileParam("sig"))
      throw new ErrorBox(null,"Internal Error: community should be a normal param",on_error);
    return getCommunityParameter(mphandler.getValue("sig"),user,required,on_error);

  } // end getCommunityParameter

  protected static ConferenceContext getConferenceParameter(String str, CommunityContext comm,
							    boolean required, String on_error) throws ErrorBox
  {
    if (StringUtil.isStringEmpty(str))
    { // there's no conference parameter
      if (required)
      { // no conference parameter - bail out now!
	logger.error("Conference parameter not specified!");
	throw new ErrorBox(null,"No conference specified.",on_error);

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

      } // end else

    } // end if

    ConferenceContext rc = null;
    try
    { // turn the string into a ConfID, and thence to a ConferenceContext
      int tmp_id = Integer.parseInt(str);
      rc = comm.getConferenceContext(tmp_id);
      if (rc==null)
      { // couldn't find the conference
	logger.error("community #" + comm.getCommunityID() + " does not contain conference #" + tmp_id);
	throw new ErrorBox(null,"The conference #" + tmp_id + " could not be found in the '" + comm.getName()
			   + "' community.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("found conf #" + rc.getConfID());

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

    } // end catch
    catch (AccessError ae)
    { // these all get handled in pretty much the same way
      throw new ErrorBox("Access Error",ae.getMessage(),on_error);

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

    } // end catch

    return rc;

  } // end getConferenceParameter

  protected final static ConferenceContext getConferenceParameter(ServletRequest request,
								  CommunityContext comm, boolean required,
								  String on_error) throws ErrorBox
  {
    return getConferenceParameter(request.getParameter("conf"),comm,required,on_error);

  } // end getConferenceParameter

  protected final static ConferenceContext getConferenceParameter(ServletMultipartHandler mphandler,
								  CommunityContext comm, boolean required,
								  String on_error) throws ErrorBox
  {
    if (mphandler.isFileParam("conf"))
      throw new ErrorBox(null,"Internal Error: conference should be a normal param",on_error);
    return getConferenceParameter(mphandler.getValue("conf"),comm,required,on_error);

  } // end getConferenceParameter

  protected final static TopicContext getTopicParameter(ServletRequest request, ConferenceContext conf,
							boolean required, String on_error) throws ErrorBox
  {
    return getTopicParameter(request.getParameter("top"),conf,required,on_error);

  } // end getTopicParameter

  protected final static TopicContext getTopicParameter(ServletMultipartHandler mphandler,
							ConferenceContext conf, boolean required,
							String on_error) throws ErrorBox
  {
    if (mphandler.isFileParam("top"))
      throw new ErrorBox(null,"Internal Error: topic should be a normal param",on_error);
    return getTopicParameter(mphandler.getValue("top"),conf,required,on_error);

  } // end getTopicParameter

  protected final static TopicMessageContext getMessageParameter(ServletRequest request,
								 ConferenceContext conf, boolean required,
								 String on_error) throws ErrorBox
  {
    return getMessageParameter(request.getParameter("msg"),conf,required,on_error);

  } // end getMessageParameter

  protected final static TopicMessageContext getMessageParameter(ServletMultipartHandler mphandler,
								 ConferenceContext conf, boolean required,
								 String on_error) throws ErrorBox
  {
    if (mphandler.isFileParam("msg"))
      throw new ErrorBox(null,"Internal Error: message should be a normal param",on_error);
    return getMessageParameter(mphandler.getValue("msg"),conf,required,on_error);

  } // end getMessageParameter

  protected final static TopicMessageContext getMessageParameter(ServletRequest request, TopicContext topic,
								 boolean required, String on_error)
      throws ErrorBox
  {
    return getMessageParameter(request.getParameter("msg"),topic,required,on_error);

  } // end getMessageParameter

  protected final static TopicMessageContext getMessageParameter(ServletMultipartHandler mphandler,
								 TopicContext topic, boolean required,
								 String on_error) throws ErrorBox
  {
    if (mphandler.isFileParam("msg"))
      throw new ErrorBox(null,"Internal Error: message should be a normal param",on_error);
    return getMessageParameter(mphandler.getValue("msg"),topic,required,on_error);

  } // end getMessageParameter

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

  protected String getMyLocation(HttpServletRequest request, VeniceEngine engine, UserContext user,
				 RenderData rdat)
  {
    return (String)(request.getAttribute(LOCATION_ATTR));

  } // end getMyLocation

  protected VeniceContent doVeniceGet(HttpServletRequest request, VeniceEngine engine,
				      UserContext user, RenderData rdat)
    throws ServletException, IOException, VeniceServletResult
  {
    notSupported(request,"GET method not supported");
    return null;

  } // end doVeniceGet

  protected VeniceContent doVenicePost(HttpServletRequest request, VeniceEngine engine,
				       UserContext user, RenderData rdat)
    throws ServletException, IOException, VeniceServletResult
  {
    notSupported(request,"POST method not supported for normal form posts");
    return null;

  } // end doVenicePost

  protected VeniceContent doVenicePost(HttpServletRequest request, ServletMultipartHandler mphandler,
				       VeniceEngine engine, UserContext user, RenderData rdat)
    throws ServletException, IOException, VeniceServletResult
  {
    notSupported(request,"POST method not supported for multipart/form-data posts");
    return null;

  } // end doVenicePost

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

  public final void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    ServletContext ctxt = getServletContext();
    VeniceEngine engine = Variables.getVeniceEngine(ctxt);
    VeniceContent content = null;

    // Make all log messages for the request carry the remote address.
    NDC.push(request.getRemoteAddr());
    try
    { // get the user context
      UserContext user = Variables.getUserContext(ctxt,request,request.getSession(true));
      boolean record_uname = user.isLoggedIn();
      if (record_uname)
	NDC.push(user.getUserName());

      try
      { // and now we proceed!
	RenderData rdat = RenderConfig.createRenderData(ctxt,user,request,response);

	try
	{ // run the actual "get" in the servlet
	  content = doVeniceGet(request,engine,user,rdat);

	} // end try
	catch (VeniceServletResult res)
	{ // special VeniceServletResult catch here - figure out what result it is
	  if (res instanceof VeniceContent)
	    content = (VeniceContent)res; // this is content
	  else if (res instanceof ContentResult)
	  { // this contains content
	    ContentResult cres = (ContentResult)res;
	    content = cres.getContent();

	  } // end else if
	  else if (res instanceof ExecuteResult)
	  { // direct-execution result
	    ExecuteResult xres = (ExecuteResult)res;
	    Variables.flushCookies(request,response);
	    xres.execute(rdat);
	    return;

	  } // end else if
	  else  // unrecognized VeniceServletResult
	    content = null;

	} // end catch
	catch (RuntimeException re)
	{ // record that we caught a runtime exception in here!
	  logger.error("VeniceServlet.doGet caught " + re.getClass().getName() + " in doVeniceGet",re);
	  throw re;

	} // end catch

	Variables.flushCookies(request,response);
	if (content!=null)
	{ // display the content!
	  BaseJSPData base = new BaseJSPData(engine,user,getMyLocation(request,engine,user,rdat),content);
	  base.transfer(ctxt,rdat);

	} // end if
	else  // there is no content - display the null response
	  rdat.nullResponse();

      } // end try
      finally
      { // pop the username from the NDC if we used it
	if (record_uname)
	  NDC.pop();

      } // end finally

    } // end try
    finally
    { // pop the nested diagnostic context
      NDC.pop();

    } // end finally
    
  } // end doGet
  
  public final void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    ServletContext ctxt = getServletContext();
    VeniceEngine engine = Variables.getVeniceEngine(ctxt);
    ServletMultipartHandler mphandler = null;
    VeniceContent content = null;

    // Make all log messages for the request carry the remote address.
    NDC.push(request.getRemoteAddr());
    try
    { // get the user context
      UserContext user = Variables.getUserContext(ctxt,request,request.getSession(true));
      boolean record_uname = user.isLoggedIn();
      if (record_uname)
	NDC.push(user.getUserName());

      try
      { // and now we proceed!
	RenderData rdat = RenderConfig.createRenderData(ctxt,user,request,response);

	if (ServletMultipartHandler.canHandle(request))
	{ // if this is a multipart/form-data request, invoke our special handler code
	  try
	  { // create the multipart handler
	    mphandler = new ServletMultipartHandler(request);

	  } // end try
	  catch (ServletMultipartException e)
	  { // this is an error message we need to generate and just bail out on
	    logger.error("ServletMultipartException caught in doVenicePost!",e);
	    BaseJSPData base = new BaseJSPData(engine,user,getMyLocation(request,engine,user,rdat),
					       new ErrorBox(null,"Internal Error: " + e.getMessage(),null));
	    base.transfer(ctxt,rdat);
	    return;

	  } // end if

	} // end if

	try
	{ // call the appropriate doVenicePost method
	  if (mphandler!=null)
	    content = doVenicePost(request,mphandler,engine,user,rdat);
	  else
	    content = doVenicePost(request,engine,user,rdat);

	} // end try
	catch (VeniceServletResult res)
	{ // special VeniceServletResult catch here - figure out what result it is
	  if (res instanceof VeniceContent)
	    content = (VeniceContent)res; // this is content
	  else if (res instanceof ContentResult)
	  { // this contains content
	    ContentResult cres = (ContentResult)res;
	    content = cres.getContent();

	  } // end else if
	  else if (res instanceof ExecuteResult)
	  { // direct-execution result
	    ExecuteResult xres = (ExecuteResult)res;
	    Variables.flushCookies(request,response);
	    xres.execute(rdat);
	    return;

	  } // end else if
	  else  // unrecognized VeniceServletResult
	    content = null;

	} // end catch
	catch (RuntimeException re)
	{ // record that we caught a runtime exception in here!
	  logger.error("VeniceServlet.doPost caught " + re.getClass().getName() + " in doVenicePost",re);
	  throw re;

	} // end catch

	Variables.flushCookies(request,response);
	if (content!=null)
	{ // display the content!
	  BaseJSPData base = new BaseJSPData(engine,user,getMyLocation(request,engine,user,rdat),content);
	  base.transfer(ctxt,rdat);

	} // end if
	else  // there is no content - display the null response
	  rdat.nullResponse();

      } // end try
      finally
      { // pop the username from the NDC if we used it
	if (record_uname)
	  NDC.pop();

      } // end finally

    } // end try
    finally
    { // pop the nested diagnostic context
      NDC.pop();

    } // end finally
    
  } // end doPost

} // end class VeniceServlet
