// Copyright (c) 2001 Oracle Corporation
package oracle.portal.provider.v1;

import java.util.*;
import java.io.*;
import java.lang.reflect.*;

import oracle.webdb.utils.*;
import oracle.portal.provider.v1.*;
import oracle.portal.provider.v1.http.*;
import oracle.portal.utils.xml.v1.*;
import oracle.portal.utils.v1.SimpleParameterMap;

/**
 * <code>RenderManager</code> implements a model whereby users can <i>plug
 * in</i> individual renderers which handle a request for a specific render
 * mode in a specific content type. Any class that implements the {@link
 * ManagedRenderer} interface can be used with <code>RenderManager</code>.
 * Consult the JPDK documentation for information about the various renderers
 * supplied with the JPDK.<p>
 *
 * The individual <code>ManagedRenderer</code>s controlled by a
 * <code>RenderManager</code> are registered by calling the
 * <code>set&ltModeName&gtPage(Object)</code> methods (e.g. {@link
 * PortletPageLocator#setShowPage(Object)}), which associate a renderer with a
 * specific render mode. If you are using {@link DefaultProvider}, these calls
 * are made automatically when you include the appropriate tags in your
 * provider XML registry file. For example:
 *
 * <code>
 * <pre>
 * &lt;showPage class="oracle.portal.provider.v1.http.JspRenderer">
 *    &lt;contentType>text/html&lt;/contentType>
 *    &lt;name>hello.jsp&lt;/name>
 *    &lt;appPath>/helloworld&lt;/appPath>
 *    &lt;appRoot>/htdocs/helloworld&lt;/appRoot>
 * &lt;/showPage></pre>
 * </code>
 *
 * Multiple <code>ManagedRenderer</code>s can be registered for a single render
 * mode, provided they all handle different content types (i.e. their {@link
 * ManagedRenderer#getContentType()} methods return different values). When
 * locating the appropriate <code>ManagedRenderer</code> to service a
 * particular render request, <code>RenderManager</code> will look for the
 * first <code>ManagedRenderer</code> registered for the requested render mode
 * whose content type matches the 'preferred content type' of the request. This
 * allows a <code>RenderManager</code> to be configured so that it supports
 * 'standard' browsers (e.g. with a <code>"text/html"</code> content type) as
 * well as mobile devices (e.g. with a <code>"text/vnd.oracle.mobilexml"</code> content
 * type).<p>
 *
 * <code>RenderManager</code> also supports registration of <i>default
 * renderers</i>, controlled by {@link
 * PortletPageLocator#setDefaultPage(Object)} or the
 * <code>&lt;defaultPage></code> tag. If unable to locate a
 * <code>ManagedRenderer</code> for the requested render mode and content type
 * and a default renderer has been registered with the appropriate content
 * type, <code>RenderManager</code> will pass the request on to that
 * renderer.<p>
 *
 * For those <code>ManagedRenderer</code>s subclassed from the {@link Page}
 * abstract class (i.e. {@link JspRenderer} and {@link FileRenderer},
 * <code>RenderManager</code> supports a set of defaults for the individual
 * properties of these renderers. This allows a <code>RenderManager</code>'s
 * <code>Pages</code>s to be configured by specifying only the properties of
 * each which differ from the defaults defined at <code>RenderManager</code>
 * level. These 'defaultable properties' include:<ul>
 *
 * <li><code>appPath</code>
 * <li><code>appRoot</code>
 * <li><code>pageName</code>
 * <li><code>charSet</code>
 * <li><code>pageParameterName</code></ul>
 *
 * Consult the {@link Page} javadoc for more information.<p>
 *
 * If <i>all</i> of a <code>Page</code>'s defaultable properties conform to the
 * defaults set on the <code>RenderManager</code>, then a special 'shorthand'
 * style of XML declaration can be used to configure an instance, where a page
 * name is included directly inside a 'page' tag, e.g.
 * <code>&lt;showPage>my.jsp&lt;/showPage></code>. Support for these
 * 'shorthand' declarations are provided by the
 * <code>set&ltModeName&gtPage(String)</code> methods (e.g. {@link
 * PortletPageLocator#setShowPage(String)}). If the page name is suffixed by
 * ".jsp" or ".sqljsp", then it is assumed that the <code>JspRenderer</code> is
 * to be used; otherwise, it is assumed that the <code>FileRenderer</code>
 * should be used. Consult the supplied documentation for more detailed
 * instructions and examples.<p>
 *
 * By default, <code>RenderManager</code> will automatically render portlet
 * containers for you. To disable the automatic rendering of the container call
 * <code>setRenderContainer(false)</code>. Alternatively, you can set this in
 * the provider XML registry:
 * <code>&ltrenderContainer&gtfalse&lt/renderContainer&gt</code><p>
 *
 * When renderering a Portlet's container, <code>RenderManager</code> will
 * render the user's customized title (label) if the Portlet supports
 * personalization and if the personalization data class implements {@link
 * PersonalizationObject} or is subclassed from {@link BaseCustomization}.
 */
public class RenderManager extends PortletPageLocator
    implements PortletRenderer, InitializableXMLObject 
{
    private String mEditFormParameter = null;
    private boolean mRenderContainer = true;
    private boolean mRenderCustomize = true;


    /**
     *  Null Constructor
     */
    public RenderManager()
    {
    }
    
    /**
     *  Renders the requested mode in the requested content type with an
     *  appropriate renderer. If no renderer with the appropriate content type
     *  has been declared for this mode, rendering will fall back to the
     *  default renderer. The renderer is asked to prepare its response, its
     *  portlet header is drawn, the renderer is asked to render its body and
     *  finally the portlet footer is drawn.
     *
     *  @param pr a PortletRenderRequest
     *  @exception PortletException if a ManagedRenderer for the requested
     *  render mode and content type could not be located or an error occurs
     *  while rendering
     *  @exception PortletNotFoundException if the portlet instance referred to
     *  by the request does not exist
     *  @exception AccessControlException if the user is not authorized to
     *  access the portlet instance referred to by the request
     */
    public void render(PortletRenderRequest pr) 
        throws PortletException, PortletNotFoundException, AccessControlException
    {
        // Check whether this user can render this instance
        PortletSecurityManager securityManager = pr.getSecurityManager();
        if (securityManager != null &&
            !securityManager.hasAccess(pr.getPortletReference(), pr.getUser()))
        {
            throw new AccessControlException();
        }

        // If so then go ahead
        ManagedRenderer renderer = getManagedRenderer(pr);

        // Let the renderer prepare its response and check whether we need to
        // render anything else (perhaps a cached version of the page is to be
        // used)
        if(renderer.prepareResponse(pr))
        {
            try
            {
                // If we are allowed to continue, first test whether we should
                // be handling data submitted to the renderer
                String editAction = pr.getParameter(getEditFormParameter());
                // If the edit form parameter is null, then we can render as
                // normal
                if(editAction == null)
                {
                    if(mRenderContainer)
                    {
                        renderPortletHeader(pr);
                    }
                    renderer.renderBody(pr);
                    if(mRenderContainer)
                    {
                        renderPortletFooter(pr);
                    }
                }
                else
                {
                    renderer.renderBody(pr);
                    // Otherwise, we have to set a redirect, depending on the
                    // value of the parameter, and assume renderBody will
                    // submit the edit data to a PortletPersonalizationManager
                    handleEditAction(pr, editAction);
                }
            }
            catch (IOException ioe)
            {
                throw new PortletException(ioe);
            }
        }
    }

    /**
     * Sets the hidden parameter used on customize forms to hold the form's
     * action. If none is specified in the provider registry, the portlet's
     * instance name is used.
     *
     * @param param the parameter name.
     */
    public void setEditFormParameter(String param)
    {
        mEditFormParameter = param;
    }

    /**
     * Gets the String to be used as the hidden action parameter on customize
     * forms
     */
    public String getEditFormParameter()
    {
        return mEditFormParameter;
    }

    /**
     * Controls whether <code>RenderManager</code> renders the portlet's
     * container.
     *
     * @param value Boolean flag to tell <code>RenderManager</code> to either
     * render the portlet's container (<code>true</code>) or not
     * (<code>false</code>). <code>true</code> by default.
     */
    public void setRenderContainer(boolean value)
    {
        mRenderContainer = value;
    }

    /**
     * Controls whether <code>RenderManager</code> renders the portlet's
     * container.
     *
     * @param value String representation of boolean value, indicating whether
     * <code>RenderManager</code> should render the portlet's container
     * (<code>"true"</code>) or not (<code>"false"</code>). <code>"true"</code>
     * by default.
     */
    public void setRenderContainer(String value)
    {
        setRenderContainer(XMLUtil.getBooleanValue(value));
    }

    /**
     * Gets the boolean flag used by <code>RenderManager</code> to decide
     * whether or not it renders the portlet's container.
     */
    public boolean getRenderContainer()
    {
        return mRenderContainer;
    }

    /**
     * Controls whether <code>RenderManager</code> renders the portlet's
     * customize container.
     *
     * @param value Boolean flag to tell <code>RenderManager</code> to either
     * render the portlet's customize container (<code>true</code>) or not
     * (<code>false</code>). <code>true</code> by default.
     */
    public void setRenderCustomize(boolean value)
    {
        mRenderCustomize = value;
    }

    /**
     * Controls whether <code>RenderManager</code> renders the portlet's
     * customize container.
     *
     * @param value String representation of boolean value, indicating whether
     * <code>RenderManager</code> should render the portlet's customize
     * container (<code>"true"</code>) or not (<code>"false"</code>).
     * <code>"true"</code> by default.
     */
    public void setRenderCustomize(String value)
    {
        setRenderCustomize(XMLUtil.getBooleanValue(value));
    }

    /**
     * Gets the boolean flag used by <code>RenderManager</code> to decide
     * whether or not it renders the portlet's customize container.
     */
    public boolean getRenderCustomize()
    {
        return mRenderCustomize;
    }

    /**
     * Performs the steps required to initialize this
     * <code>RenderManager</code> after it has been constructed. Currently,
     * this method sets the default edit form action parameter. This will be
     * overridden if the user calls <code>setEditFormParameter()</code> on
     * <code>RenderManager</code>, e.g. by including an information tag
     * <code>&lt;editFormParameter></code> in the provider registry.
     *
     * @param o the object that will become this object's parent
     * @exception NodeHandlerException if an error occurs while initializing
     * this object
     */
    public void preInitialize(Object o) throws NodeHandlerException
    {
        if(!(o instanceof Portlet))
        {
            throw new NodeHandlerException("Parent of a PortletRenderer " + 
                                           "must be a Portlet");
        }
        Portlet p = (Portlet)o;
        SimpleStringBuffer param = new SimpleStringBuffer("JPDK_");
        String toAdd = Long.toString(p.getId());
        param.append(toAdd);
        mEditFormParameter = param.toString();
    }

    /**
     * Performs the extra steps required to initialize and validate a
     * <code>RenderManager</code> once its properties have been set with the
     * appropriate accessor methods.
     * @exception NodeHandlerException if not all properties have been set
     * correctly
     */
    public void postInitialize() throws NodeHandlerException
    {
    }

    /**
     * Render the header for the portlet.
     *
     * The default implementation of this method uses the PortletContainerRenderer
     * to render the portlet header.
     */
    protected void renderPortletHeader(PortletRenderRequest pr)
        throws PortletException, AccessControlException, IOException
    {
        // initialize the parameters for rendering the container
        SimpleParameterMap map = new SimpleParameterMap();

        map.putParameterValue(PortletContainerRenderer.PORTLET_TITLE,
                              PortletRendererUtil.getTitle(pr));
        map.putParameterValue(PortletContainerRenderer.RENDER_CUSTOMIZE,
                              new Boolean(getRenderCustomize()));
        map.putParameterValue(PortletContainerRenderer.EDIT_FORM_NAME,
                              PortletRendererUtil.CUSTOMIZE_FORM_NAME);
        map.putParameterValue(PortletContainerRenderer.ACTION_PARAM_NAME,
                              getEditFormParameter());

        // get the container renderer to use
        PortletContainerRenderer pcr = null;
        Portlet p = pr.getPortlet();

        if (p instanceof PortletXtra3)
        {
            PortletXtra3 p3 = (PortletXtra3)p;
            pcr = p3.getContainerRenderer();
        }

        if (pcr == null)
        {
            // Portlet cannot provide a PortletContainerRenderer so use the default
            pcr = new DefaultContainerRenderer();
        }

        // render the portlet Header
        pcr.renderPortletHeader(pr, map);
    }

    protected void renderPortletFooter(PortletRenderRequest pr)
        throws PortletException, AccessControlException, IOException
    {
        // initialize the parameters for rendering the container
        // Send the parameters for both the header and the footer because
        // we do not know where they might be used by the renderer. (links
        // might be rendered as part of the footer...)
        SimpleParameterMap map = new SimpleParameterMap();

        map.putParameterValue(PortletContainerRenderer.PORTLET_TITLE,
                              PortletRendererUtil.getTitle(pr));
        map.putParameterValue(PortletContainerRenderer.RENDER_CUSTOMIZE,
                              new Boolean(getRenderCustomize()));
        map.putParameterValue(PortletContainerRenderer.EDIT_FORM_NAME,
                              PortletRendererUtil.CUSTOMIZE_FORM_NAME);
        map.putParameterValue(PortletContainerRenderer.ACTION_PARAM_NAME,
                              getEditFormParameter());

        // get the container renderer to use
        PortletContainerRenderer pcr = null;
        Portlet p = pr.getPortlet();

        if (p instanceof PortletXtra3)
        {
            PortletXtra3 p3 = (PortletXtra3)p;
            pcr = p3.getContainerRenderer();
        }

        if (pcr == null)
        {
            // Portlet cannot provide a PortletContainerRenderer so use the default
            pcr = new DefaultContainerRenderer();
        }

        // render the portlet Footer
        pcr.renderPortletFooter(pr, map);
    }


    protected void handleEditAction(PortletRenderRequest pr, String action)
        throws IOException
    {
        if (action.equalsIgnoreCase("OK"))
        {
            HttpPortletRendererUtil.sendRedirect(pr, pr.getBackURL());
        } 
        else if (action.equalsIgnoreCase("APPLY")) 
        {
            HttpPortletRendererUtil.sendRedirect(pr, pr.getPageURL());
        }
    }
}
