/*
 * 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 javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.Logger;
import com.silverwrist.dynamo.HttpStatusCode;
import com.silverwrist.dynamo.WebConstants;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

class ServletSelfRenderControl extends BaseDelegatingServiceProvider implements SelfRenderControl
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing text and binary rendering
   *--------------------------------------------------------------------------------
   */

  private class InternalRender implements TextRenderControl, BinaryRenderControl
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private boolean m_binary;
    private PrintWriter m_wr = null;
    private OutputStream m_stm = null;

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

    InternalRender(boolean binary)
    {
      m_binary = binary;

    } // end constructor

    /*====================================================================
     * 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.dynamo.except.NoSuchServiceException If no service is available in
     *            the specified class.
     */
    public Object queryService(Class klass)
    {
      return ServletSelfRenderControl.this.queryService(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.dynamo.except.NoSuchServiceException If no service is available in
     *            the specified class.
     */
    public Object queryService(Class klass, String serviceid)
    {
      return ServletSelfRenderControl.this.queryService(klass,serviceid);

    } // end queryService

    /*====================================================================
     * Implementations from interface RenderControl
     *====================================================================
     */

    public void setContentType(String type) throws RenderingException
    {
      if (m_locked)
	throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadyType");
      else
	m_resp.setContentType(type);

    } // end setContentType

    public void setContentLength(int length) throws RenderingException
    {
      if (m_locked)
	throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadyLength");
      else
	m_resp.setContentLength(length);

    } // end setContentLength

    /*====================================================================
     * Implementations from interface TextRenderControl
     *====================================================================
     */

    public PrintWriter getWriter() throws IOException, RenderingException
    {
      if (m_wr==null)
      { // get the writer
	if (m_binary)
	  throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages",
				       "selfRender.alreadyBinary");
	m_wr = m_resp.getWriter();

      } // end if

      return m_wr;

    } // end getWriter

    public void renderSubObject(Object obj) throws IOException, RenderingException
    {
      if (obj instanceof SelfRenderable)
      { // this object can render itself - let's do that!
	SubObjectSelfRenderControl control = new SubObjectSelfRenderControl(this);
	((SelfRenderable)obj).render(control);
	return;

      } // end if

      QueryRenderer qrend = (QueryRenderer)(ServletSelfRenderControl.this.queryService(QueryRenderer.class));
      Renderer rend = qrend.getRendererForObject(obj);
      if (rend==null)
      { // the only thing we know about this is that we can get its string equivalent,
	// so send that out 
	PrintWriter wr = this.getWriter();
	wr.println(obj.toString());
	return;

      } // end if

      if (rend instanceof TextRenderer)
      { // render subobject here
	SubObjectTextRenderControl control = new SubObjectTextRenderControl(this);
	((TextRenderer)rend).render(obj,control);

      } // end if
      else if (rend instanceof BinaryRenderer)
	// cannot render binary objects as a subobject to text objects!
	throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","binaryInsideText");
      else
      { // default to raw text
	logger.error("Unknown renderer object class: " + rend.getClass().getName());
	PrintWriter wr = this.getWriter();
	wr.println(obj.toString());

      } // end else

    } // end renderSubObject

    /*====================================================================
     * Implementations from interface BinaryRenderControl
     *====================================================================
     */

    public OutputStream getStream() throws IOException, RenderingException
    {
      if (m_stm==null)
      { // get the output stream
	if (!m_binary)
	  throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages",
				       "selfRender.alreadyText");
	m_stm = m_resp.getOutputStream();

      } // end if

      return m_stm;

    } // end getStream

    /*====================================================================
     * External operations
     *====================================================================
     */

    boolean isBinary()
    {
      return m_binary;

    } // end isBinary

  } // end class InternalRender

  /*--------------------------------------------------------------------------------
   * Internal class implementing chain execution.
   *--------------------------------------------------------------------------------
   */

  private class Chainer implements ChainForward
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

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

    /*====================================================================
     * Implementations from interface ChainForward
     *====================================================================
     */

    public void setChainParameter(String name, Object value)
    {
      if (!(name.startsWith(WebConstants.REQUEST_PREFIX)))
	m_req.setAttribute(name,value);

    } // end setChainParameter

    public void chainToPath(String path) throws IOException, RenderingException
    {
      try
      { // get the right dispatcher and forward there
	RequestDispatcher disp = m_req.getRequestDispatcher(path);
	disp.forward(m_req,m_resp);

      } // end try
      catch (ServletException e)
      { // wrap the exception in a RenderingException
	RenderingException re = new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages",
						       "selfRender.forwardPath",e);
	re.setParameter(0,path);
        re.setParameter(1,e.getMessage());
        throw re;

      } // end catch

    } // end chainToPath

    public void chainToNamedServlet(String servlet_name) throws IOException, RenderingException
    {
      try
      { // get the right dispatcher and forward there
        RequestDispatcher disp = m_ctxt.getNamedDispatcher(servlet_name);
        disp.forward(m_req,m_resp);

      } // end try
      catch (ServletException e)
      { // wrap the exception in a RenderingException
	RenderingException re = new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages",
						       "selfRender.forwardServlet",e);
	re.setParameter(0,servlet_name);
        re.setParameter(1,e.getMessage());
        throw re;

      } // end catch

    } // end chainToNamedServlet

  } // end class Chainer

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

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

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

  private HttpServletRequest m_req;
  private HttpServletResponse m_resp;
  private ServletContext m_ctxt;
  private boolean m_locked = false;
  private InternalRender m_render = null;
  private Chainer m_chainer = null;

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

  ServletSelfRenderControl(HttpServletRequest req, HttpServletResponse resp, ServletContext ctxt,
			   ServiceProvider services)
  {
    super("ServletSelfRenderControl",services);
    m_req = req;
    m_resp = resp;
    m_ctxt = ctxt;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Overrides from class BaseDelegatingServiceProvider
   *--------------------------------------------------------------------------------
   */

  /**
   * 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.dynamo.except.NoSuchServiceException If no service is available in
   *            the specified class.
   */
  public Object queryService(Class klass)
  {
    if (klass==ChainForward.class)
    { // return the chainer object
      if (m_chainer==null)
        m_chainer = new Chainer();
      return m_chainer;

    } // end if

    return super.queryService(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.dynamo.except.NoSuchServiceException If no service is available in
   *            the specified class.
   */
  public Object queryService(Class klass, String serviceid)
  {
    if (klass==ChainForward.class)
    { // return the chainer object
      if (m_chainer==null)
        m_chainer = new Chainer();
      return m_chainer;

    } // end if

    return super.queryService(klass,serviceid);

  } // end queryService

  /*--------------------------------------------------------------------------------
   * Implementations from interface SelfRenderControl
   *--------------------------------------------------------------------------------
   */

  public synchronized void status(HttpStatusCode code) throws RenderingException
  {
    if (m_chainer!=null)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.areForwarding");
    if (!m_locked)
      m_resp.setStatus(code.getValue());
    else
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadyStatus");

  } // end status

  public synchronized void redirect(String location) throws IOException, RenderingException
  {
    if (m_chainer!=null)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.areForwarding");
    if (m_locked)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadyRedirect");
    m_resp.sendRedirect(location);
    m_locked = true;

  } // end redirect

  public synchronized void error(HttpStatusCode code) throws IOException, RenderingException
  {
    if (m_chainer!=null)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.areForwarding");
    if (m_locked)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadySetErr");
    m_resp.sendError(code.getValue());
    m_locked = true;

  } // end error

  public synchronized void error(HttpStatusCode code, String message) throws IOException, RenderingException
  {
    if (m_chainer!=null)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.areForwarding");
    if (m_locked)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadySetErr");
    m_resp.sendError(code.getValue(),message);
    m_locked = true;

  } // end error

  public TextRenderControl getTextRender() throws RenderingException
  {
    if (m_chainer!=null)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.areForwarding");
    if (m_render==null)
      m_render = new InternalRender(false);
    else if (m_render.isBinary())
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadyBinary");
    return (TextRenderControl)m_render;

  } // end getTextRender

  public BinaryRenderControl getBinaryRender() throws RenderingException
  {
    if (m_chainer!=null)
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.areForwarding");
    if (m_render==null)
      m_render = new InternalRender(true);
    else if (!(m_render.isBinary()))
      throw new RenderingException(ServletSelfRenderControl.class,"ServletBaseMessages","selfRender.alreadyText");
    return (BinaryRenderControl)m_render;

  } // end getBinaryRender

} // end class ServletSelfRenderControl
