/* Copyright (c) 2003 Oracle Corporation */
package oracle.portal.provider.v2.render.http;

import oracle.portal.provider.v2.render.http.BaseManagedRenderer;
import oracle.portal.provider.v2.render.PortletRenderRequest;
import oracle.portal.provider.v2.PortletException;
import oracle.portal.provider.v2.render.PortletRenderer;
import oracle.portal.provider.v2.render.PortletRendererUtil;

import oracle.portal.provider.v2.http.HttpCommonConstants;
import oracle.portal.log.LogManager;

import oracle.webdb.utils.SimpleStringBuffer;
import oracle.webdb.provider.v2.utils.http.ProviderRequestWrapper;
import oracle.webdb.provider.v2.utils.http.TextResponseWrapper;

import java.io.PrintWriter;
import java.io.IOException;
import java.io.CharArrayWriter;

import javax.servlet.ServletContext;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.*;

/**
 * This is the base class for renderers that use a request dispatcher to render
 * content.
 */
public abstract class AbstractResourceRenderer extends BaseManagedRenderer
{
    /**
     * Error message indicating the target resource generated an error.
     */
    private static final String TARGET_ERROR_MESSAGE =
        "Error occurred while rendering portlet - " +
        "see provider log file for details";

    // Context containing the resource. It is protected so that it may be set
    // by some subclasses only (e.g. ResourceRenderer).
    protected String mContext = null;

    // The character set to be used for decoding, overriding the default
    // behaviour, i.e, use the character set specified in the content type
    // request header or ISO-8859-1
    private String mCharSet;

    /**
     * Null Constructor. The {@link BaseManagedRenderer#preInitialize(Object)}
     * method should be called after creating an object with this constructor
     * and the {@link BaseManagedRenderer#postInitialize()} method should be
     * called after setting up page properties with the various accessor
     * methods. Normally a {@link DefaultNodeHandler} takes care of these
     * details.
     */
    public AbstractResourceRenderer()
    {
        super();
    }

    /**
     * Gets the context root of the <code>javax.servlet.ServletContext</code>
     * containing the resource. If the resource is located in the same context
     * as the current provider, this method returns
     * <code>null</code>.
     *
     * @return the context root, or <code>null</code> if the context is the
     * same as the current provider.
     */
    public String getContext()
    {
        return mContext;
    }

    /**
     * Sets the character set to be used for rendering resource content.
     *
     * Overrides the default behaviour where the character set is defined
     * in the content type request header or defaults to ISO-8859-1.
     */
    public void setCharSet(String charSet)
    {
        mCharSet = charSet;
    }

    /**
     * Gets the character set to be used for rendering resource content.
     * 
     */
    public String getCharSet()
    {
        return mCharSet;
    }
    
    /**
     * In addition to setting default http headers for the response (see {@link
     * BaseManagedRenderer}), this method ensures that the
     * <code>HttpServletResponse</code> is wrapped in the correct manner.
     *
     * @param pr a {@link
     * oracle.portal.provider.v2.render.PortletRenderRequest}.
     */
    public boolean prepareResponse(PortletRenderRequest pr)
        throws PortletException
    {
        LogManager.debug("Entering AbstractResourceRenderer.prepareResponse.");
        // Make sure we invoke the superclass first.
        if (super.prepareResponse(pr))
        {
            // Only take action if the portlet is required to render
            // non-container markup.
            if (  !pr.getRenderContext().isCollapsed() 
               || !mParent.getRenderContainer() )
            {
                // Wrap the response in a protected method so that, if need be,
                // default collapse behaviour can be overriden.
                wrapResponse(pr);
            }
            return true;
        }
        return false;
    }

    /**
     * Override this method to return a path to the resource to be rendered.
     */
    public abstract String getRequestResourcePath(PortletRenderRequest prr)
      throws PortletException;

    /**
     * Render the specified page's contents from the required resource. This
     * resource may be any that the servlet container can return a valid
     * <code>javax.servlet.RequestDispatcher</code> for. Examples include JSPs,
     * servlets and static HTML files.
     *
     * @param pr a {@link oracle.portal.provider.v2.render.PortletRenderRequest}.
     */
    public void renderBody(PortletRenderRequest pr) throws PortletException
    {
        // Buffer for use when creating log/exception messages, saves doing
        // string concats.
        SimpleStringBuffer msg = new SimpleStringBuffer(200);

        // Get the ServletContext for this request.
        ServletContext context =
            (ServletContext)pr.getAttribute(HttpCommonConstants.SERVLET_CONTEXT);

        // Get the request and response objects for this request.
        ProviderRequestWrapper wrappedRequest =
            (ProviderRequestWrapper)pr.getAttribute(HttpCommonConstants.SERVLET_REQUEST);
        TextResponseWrapper wrappedResponse =
            (TextResponseWrapper)pr.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);

        // Retrieve the current content of the wrapped response, for example
        // portlet container markup.
        String initialContent = null;
        try
        {
            initialContent = wrappedResponse.getData();
        }
        catch (IOException ioExp)
        {
            LogManager.severe("AbstractResourceRenderer.renderBody - received " +
                              "IOException getting initial content", ioExp);
            throw new PortletException(ioExp);
        }

        // Ensure the destination resource will believe that the response is
        // empty.
        wrappedResponse.resetBuffer();

        // Ensure our reference is to the required context.
        if (getContext() != null)
        {
            context = context.getContext(getContext());
            if (context == null)
            {
                msg.append("Unable to retrieve context: ").append(getContext());
                throw new PortletException(msg.toString());
            }
        }

        // Get the RequestDispatcher for this request. This will be defined in 
        // a subclass of AbstractResourceRenderer. Currently the two subclasses
        // are ResourceRenderer, where the resource path (e.g. a jsp)is fixed in 
        // the provider definition, and StrutsRenderer where the resource path
        // is based on a Struts action in the request.
        String requestResourcePath = getRequestResourcePath(pr);
        RequestDispatcher dispatcher =
            context.getRequestDispatcher(requestResourcePath);

        // If the RequestDispatcher is null, raise an Exception.
        if (dispatcher == null)
        {
            msg.append("Unable to obtain a RequestDispatcher for the "
                       + "resource: ").append(requestResourcePath);
            throw new PortletException(msg.toString());
        }

        if (LogManager.isDebugEnabled())
        {
            SimpleStringBuffer dbgMsg = new SimpleStringBuffer(100);
            dbgMsg.append("Attempting to forward request, context: ")
                  .append(getContext())
                  .append(", resource path: ")
                  .append(requestResourcePath);
            LogManager.debug(dbgMsg.toString());
        }

        try
        {
            // Forward to the destination resource, passing our request and
            // response wrappers.
            dispatcher.forward(wrappedRequest, wrappedResponse);

            // get the status code from the wrapped response and set it on the
            // real response.
            int statusCode = wrappedResponse.getStatusCode();
            msg.append("Servlet/JSP returned status: ").append(statusCode);
            LogManager.debug(msg.toString());
            
            // The PortletRenderRequest no longer needs to know about an
            // overridden response. Hence we reset its state which enables us
            // to access the original response again.
            ((ServletPortletRenderRequest)pr).resetResponse();

            // Now get a writer from the real response to which we can write
            // the dispatchee's content.
            HttpServletResponse realResponse = (HttpServletResponse)
                pr.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);
            PrintWriter out = realResponse.getWriter();

            if (statusCode >= HttpServletResponse.SC_BAD_REQUEST)
            {
                LogManager.debug("Processing error content from Servlet/JSP");
                
                realResponse.setStatus(HttpServletResponse.SC_OK);

                String contentType = getContentType().toLowerCase();
                if (contentType.equals(PortletRenderer.TYPE_HTML))
                {
                    renderHTMLErrorMessage(pr, out);
                }
                else if (contentType.equals(PortletRenderer.TYPE_MOBILE))
                {
                    renderMobileErrorMessage(out);
                }

                // log the content generated by the JSP or Servlet
                CharArrayWriter charWriter = new CharArrayWriter(1000);
                PrintWriter printWriter = new PrintWriter(charWriter);
                // Get the contents of the wrapped response, overriding the
                // character set for decoding the response if necessary.
                // This covers the case where the resource renderer is used
                // to render html files containing utf encoding. 
                printWriter.print(wrappedResponse.getData(getCharSet()));
                
                msg.setLength(0);
                msg.append("AbstractResourceRenderer.renderBody - Resource \"");
                msg.append(requestResourcePath);
                msg.append("\" returned HTTP Status: ");
                msg.append(statusCode);
                msg.append(". Error message: ");
                msg.append(wrappedResponse.getErrorMessage());
                msg.append(". Content returned follows....");
                
                LogManager.severe(msg.toString());
                LogManager.severe(charWriter.toString());
            }
            else
            {
                LogManager.debug("Processing successful Servlet/JSP content");
                realResponse.setStatus(statusCode);    
            
                // Write the contents of the wrapped response to the original
                // response ensuring portlet header markup is included.
                if (initialContent != null)
                {
                    out.print(initialContent);
                }
                out.print(wrappedResponse.getData(getCharSet()));
            }
        }
        catch (IOException ioe)
        {
            LogManager.severe("AbstractResourceRenderer.renderBody - received IOException",
                              ioe);
            throw new PortletException(ioe);
        }
        catch (ServletException se)
        {
            Throwable t = se.getRootCause();
            msg.setLength(0);
            msg.append("AbstractResourceRenderer.renderBody - recieved " +
                       "ServletException. Root cause is");
            LogManager.severe(msg.toString(), t);
            throw new PortletException(t);
        }
        LogManager.debug("Leaving AbstractResourceRenderer.renderBody.");        
    }


    /**
     * Wraps the response in preparation for a portlet body rendition.
     *
     * @param pr a {@link
     * oracle.portal.provider.v2.render.PortletRenderRequest}
     */
    protected void wrapResponse(PortletRenderRequest pr)
    {
        // Wrap the original response. Ensuring the portlet request knows that
        // we have overriden it means that any attempt to set the content-type
        // and encoding by PortletContainerRenderers will be picked up
        // correctly by the wrapper.
        HttpServletResponse response = (HttpServletResponse)
            pr.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);
        TextResponseWrapper wrappedResponse =
            new TextResponseWrapper(response);
        ((ServletPortletRenderRequest)pr).setResponse(wrappedResponse);
    }

    private void renderMobileErrorMessage(PrintWriter out)
    {
        // Discard the data written to the wrapped response as it is likely a
        // HTML error message. Generate something sensible for mobile xml.
        out.println("<SimpleText>");
        out.print("  <SimpleTextItem>");
        out.print(TARGET_ERROR_MESSAGE);
        out.println("</SimpleTextItem>");
        out.println("</SimpleText>");
    }

    private void renderHTMLErrorMessage(PortletRenderRequest pr,
                                        PrintWriter out)
        throws IOException
    {
        // discard the data written to the wrapped response because it
        // may be a fully formed HTML page (OJSP generates HTML error
        // pages) that will break the portal page if displayed on
        // Netscape.

        boolean renderErrorContainer = !mParent.getRenderContainer();

        // render a portlet header/footer around the error message if 
        // the portlet would normally
        // do this itself.
        if (renderErrorContainer)
        {
            PortletRendererUtil.renderPortletHeader(pr, out, "Error");
        }
        
        out.println("<TABLE>");
        out.println("<TR>");
        out.println("<TD ALIGN=\"RIGHT\">");
        out.println("<FONT color=\"#FF0000\" face=\"arial,helvetica\">");
        out.println("<B>");

        // FIXME - use locale to translate ERROR: when translations are available
        out.println("Error:");

        out.println("</B>");
        out.println("&nbsp");
        out.println("</FONT>");
        out.println("</TD>");

        out.println("<TD ALIGN=\"LEFT\">");
        out.println("<FONT face=\"arial,helvetica\">");
        out.println(TARGET_ERROR_MESSAGE);
        out.println("</FONT>");
        out.println("</TD>");
        out.println("</TR>");
        out.println("</TABLE>");
        
        if (renderErrorContainer)
        {
            PortletRendererUtil.renderPortletFooter(pr, out);
        }
    }
 
}
