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

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

import oracle.webdb.utils.*;
import oracle.portal.provider.v2.*;
import oracle.portal.provider.v2.render.http.*;
import oracle.portal.provider.v2.render.PortletRendererUtil;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.security.PortletSecurityManager;
import oracle.portal.provider.v2.cache.invalidation.*;
import oracle.portal.provider.v2.http.HttpCommonConstants;
import oracle.portal.utils.xml.v2.*;
import oracle.portal.log.LogManager;

/**
 * <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&lt;ModeName&gt;Page(Object)</code> methods (e.g. {@link
 * RenderManager#setShowPage(Object)}), which associate a renderer with a
 * specific render mode. If you are using {@link
 * oracle.portal.provider.v2.http.DefaultProviderLoader}, 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.v2.render.http.ResourceRenderer">
 *    &lt;resourcePath>/my/resource/path&lt;/resourcePath>
 * &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
 * RenderManager#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>
 *
 * <code>RenderManager</code> supports 'short-hand' XML declarations where a
 * <code>resourcePath</code> is included directly inside a 'page' tag,
 * e.g. <code>&lt;showPage>/my/resource/path&lt;/showPage></code>. Support for
 * these 'short-hand' declarations are provided by the
 * <code>set&lt;ModeName&gt;Page(String)</code> methods (e.g. {@link
 * RenderManager#setShowPage(String)}). These declarations result in a new
 * {@link oracle.portal.provider.v2.render.http.ResourceRenderer} being
 * initialized with the supplied <code>resourcePath</code>. 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>&lt;renderContainer&gt;false&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.
 */
public class RenderManager extends PortletRenderer
    implements InitializableXMLObject, Validateable
{
    private static final int DEFAULT_PAGE_INDEX = 0;
    private static final String[] DEFAULT_TYPES = {PortletRenderer.TYPE_HTML};

    private String mCharSet = PortletRenderer.DEFAULT_CHARACTER_ENCODING;
    private String mContentType = PortletRenderer.TYPE_HTML;
    private int mPageExpires;
    private String mParamName;
    private boolean mRenderContainer = true;
    private boolean mRenderCustomize = true;
    private boolean mAutoRedirect = false;
    private boolean mAutoInvalidate = false;
    private PortletDefinition mPortletDefinition = null;

    // vector of ManagedRenderer vectors.  This is a vector of vectors
    // because each show mode may have multiple renderers - one for
    // each content type supported by the portlet
    private Vector[] mManagedRendererVectors =
        new Vector[PortletRenderer.TOTAL_MODES + 1];

    /**
     *  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.
     *
     *  <p>Note: If the JPDK is not rendering the portlet container, then it is
     *  the responsibility of the portlet developer to correctly handle the
     *  various state in which the portlet can be rendered (eg collapsed, 
     *  not collapsed)
     *
     *  @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 AccessControlException if the user is not authorized to
     *  access the portlet instance referred to by the request
     */
    public void render(PortletRenderRequest pr)
        throws PortletException, AccessControlException
    {
        PortletInstance pi = pr.getPortletInstance();

        // Log performance stats
        long start = System.currentTimeMillis();
        LogManager.perf("Beginning rendering of portlet: " + 
                        pi.getInstanceName());

        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
            {
                RenderContext rc = pr.getRenderContext();
                boolean isCollapsed = rc.isCollapsed();
                
                // If we are allowed to continue, first test whether we should
                // be handling data submitted to the renderer
                String editAction = pr.getParameter(pi.getActionParameterName());
                
                // If the edit form parameter is null, then we can render as
                // normal
                if (editAction == null)
                {
                    // render the portlet
                    if(mRenderContainer)
                    {
                        renderPortletHeader(pr);
                        if (!isCollapsed)
                        {
                            // don't render the portlet body if the portlet
                            // should be collapsed - just render the container
                            renderer.renderBody(pr);
                        }
                        renderPortletFooter(pr);
                    }
                    else
                    {
                        // just call render body.  The portlet must render
                        // the portlet container and manage the collapse
                        // state of the portlet
                        renderer.renderBody(pr);
                    }
                }
                else
                {
                    // If autoInvalidate is turned on, invalidate webcache
                    // content for this portlet/user.
                    try
                    {
                        autoInvalidate(pr, editAction);
                    }
                    catch (InvalidationException ie)
                    {
                        throw new PortletException(ie);
                    }

                    // Assume that renderBody will extract the new
                    // customization values and submit to the persistent store
                    // via the API.
                    renderer.renderBody(pr);

                    // If autoRedirect is turned on, redirect back to the
                    // appropriate page (i.e. the back url).
                    handleEditAction(pr, editAction);
                }
            }
            catch (IOException ioe)
            {
                throw new PortletException(ioe);
            }
        }
        
        long end = System.currentTimeMillis();
        LogManager.perf("Completed rendering portlet: " + 
                        pi.getInstanceName() +
                        " in " + (end - start) + "ms");                         
    }

    /**
     * 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;
    }

    /**
     * Controls whether <code>RenderManager</code> automatically issues a 
     * redirect after the portlet has processed the customization form
     *
     * @param value Boolean flag to tell <code>RenderManager</code> to either
     * automatically issue a redirect (<code>true</code>) or not
     * (<code>false</code>). <code>false</code> by default.
     */
    public void setAutoRedirect(boolean value)
    {
        mAutoRedirect = value;
    }

    /**
     * Controls whether <code>RenderManager</code> automatically issues a 
     * redirect after the portlet has processed the customization form
     *
     * @param value String representation of boolean value, indicating whether
     * <code>RenderManager</code> should automatically issue a redirect
     * (<code>"true"</code>) or not (<code>"false"</code>).
     * <code>"false"</code> by default.
     */
    public void setAutoRedirect(String value)
    {
        setAutoRedirect(XMLUtil.getBooleanValue(value));
    }

    /**
     * Gets the boolean flag used to determine if <code>RenderManager</code>
     * should automatically issue a redirect after the portlet has 
     * processed the customization form
     * 
     * @return true if RenderManager should issue the redirect. Otherwise false.
     */
    public boolean getAutoRedirect()
    {
        return mAutoRedirect;
    }

    /**
     * Sets the flag indicating whether automatic invalidation of webcache
     * content for the portlet upon customization should be performed.
     *
     * @param value <code>true</code> if <code>RenderManager</code> is to
     * attempt automatic invalidation, <code>false</code> otherwise.
     */
    public void setAutoInvalidate(boolean value)
    {
        mAutoInvalidate = value;
    }

    /**
     * Sets the flag indicating whether automatic invalidation of webcache
     * content for the portlet upon customization should be performed.
     *
     * @param value <code>&quot;true&quot;</code> if <code>RenderManager</code>
     * is to attempt automatic invalidation, <code>&quot;false&quot;</code>
     * otherwise.
     */
    public void setAutoInvalidate(String value)
    {
        setAutoInvalidate(XMLUtil.getBooleanValue(value));
    }

    /**
     * Returns the flag indicating whether or not <code>RenderManager</code>
     * will attempt automatic invalidation of web cache content for this
     * portlet upon customization.
     */
    public boolean getAutoInvalidate()
    {
        return mAutoInvalidate;
    }

    /**
     * Performs the steps required to initialize this
     * <code>RenderManager</code> after it has been constructed.
     *
     * @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 PortletDefinition))
        {
            throw new NodeHandlerException("Parent of a PortletRenderer " +
                                           "must be a PortletDefinition");
        }
        mPortletDefinition = (PortletDefinition)o;
    }

    /**
     * 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
    {
    }

    /**
     *  Sets the MIME type to be used as the default content type for all
     *  {@link BaseManagedRenderer}s. This parameter will be inherited by any
     *  of this renderer's <code>BaseManagedRenderer</code>s for which the
     *  corresponding parameter has not been directly specified.

     *  @param contentType MIME type to be used as the default content type for
     *  {@link BaseManagedRenderer}s.
     */
    public void setContentType(String contentType)
    {
        mContentType = contentType;
    }

    /**
     *  Gets the MIME type to be used as the default content type for all
     *  {@link BaseManagedRenderer}s.
     *
     *  @return the MIME type to be used as the default content type for all
     *  {@link BaseManagedRenderer}s.
     */
    public String getContentType()
    {
        return mContentType;
    }

    /**
     *  Sets the default expiration time (in minutes) of a page produced by a
     *  {@link BaseManagedRenderer}. This parameter will be inherited by any of
     *  this renderer's <code>BaseManagedRenderer</code>s for which the
     *  corresponding parameter has not been directly specified.
     *
     *  @param expires the default integer number of minutes that the Portal
     *  should cache the output of a {@link BaseManagedRenderer}. If zero,
     *  indicates that the output should not be cached by default.
     */
    public void setPageExpires(int expires)
    {
        mPageExpires = expires;
    }

    /**
     *  Sets the default expiration time (in minutes) of a page produced by a
     *  {@link BaseManagedRenderer}. This parameter will be inherited by any of
     *  this renderer's <code>BaseManagedRenderer</code>s for which the
     *  corresponding parameter has not been directly specified.
     *
     *  @param expires String representation of the default integer number of
     *  minutes that the Portal should cache the output of a {@link
     *  BaseManagedRenderer}. If zero, indicates that the output should not be
     *  cached by default.
     */
    public void setPageExpires(String expires)
    {
        try
        {
            setPageExpires(Integer.parseInt(expires));
        }
        catch (NumberFormatException nfe)
        {
            setPageExpires(0);
            // no place to log right now -- so just eat this
        }
    }

    /**
     *  Gets the default expiration time (in minutes) of a page produced by a
     *  {@link BaseManagedRenderer}.
     *
     *  @return default expiration time in minutes for output from a {@link
     *  BaseManagedRenderer}. If non-zero, indicates the number of minutes the
     *  page should be cached by the Portal.
     */
    public int getPageExpires()
    {
        return mPageExpires;
    }

    /**
     *  Sets the Default IANA character encoding to be used. This parameter
     *  will be inherited by any of this renderer's {@link ManagedRenderer}s
     *  for which the corresponding parameter has not been directly specified.
     *
     *  @param charSet default IANA character encoding.
     */
    public void setCharSet(String charSet)
    {
        mCharSet = charSet;
    }

    /**
     *  Gets the Default IANA character encoding to be used for pages rendered
     *  by <code>ManagedRenderer</code>s.
     *
     *  @return default IANA character encoding.
     */
    public String getCharSet()
    {
        return mCharSet;
    }

    /**
     *  Sets the default name of the request parameter that is used to override
     *  the name of the page used by a <code>ManagedRenderer</code>. Used to
     *  communicate the "next" page in a portlet that uses multiple pages to
     *  render itself. This parameter will be inherited by any of this
     *  renderer's <code>Page</code>s for which the corresponding parameter has
     *  not been directly specified.
     *
     *  @param name the default name of the request parameter that is used to
     *  override the page name.
     */
    public void setPageParameterName(String name)
    {
        mParamName = name;
    }

    /**
     *  Gets the default name of the request parameter that is used to override
     *  the name of the page used by a <code>ManagedRenderer</code>.
     *
     *  @return the default name of the request parameter that is used to
     *  override the page name.
     */
    public String getPageParameterName()
    {
        return mParamName;
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Show</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setShowPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setShowPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's
     * "show" mode.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setShowPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setShowPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_SHOW, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "show" mode with
     * the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "show" mode with
     * the given content type or <code>null</code> if no such renderer exists.
     */
    public ManagedRenderer getShowPage(String type)
    {
        return getEntry(PortletRenderer.MODE_SHOW, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Show Details</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setShowDetailsPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setShowDetailsPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "showDetails"
     * mode. If not set, the "show" renderer is used by default.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setShowDetailsPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setShowDetailsPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_SHOW_DETAILS, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "showDetails" mode
     * with the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "showDetails"
     * mode with the given content type or <code>null</code> if no such
     * renderer exists.
     */
    public ManagedRenderer getShowDetailsPage(String type)
    {
        return getEntry(PortletRenderer.MODE_SHOW_DETAILS, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Edit</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setEditPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setEditPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "edit" mode. If
     * not set, the "editDefault" renderer is used by default.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setEditPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setEditPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_EDIT, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "edit" mode with
     * the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "edit" mode with
     * the given content type or <code>null</code> if no such renderer exists.
     */
    public ManagedRenderer getEditPage(String type)
    {
        return getEntry(PortletRenderer.MODE_EDIT, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Edit Defaults</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setEditDefaultsPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setEditDefaultsPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "editDefault"
     * mode. If not set, the "edit" renderer is used by default.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setEditDefaultsPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setEditDefaultsPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_EDIT_DEFAULTS, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "editDefault" mode
     * with the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "editDefault"
     * mode with the given content type or <code>null</code> if no such
     * renderer exists.
     */
    public ManagedRenderer getEditDefaultsPage(String type)
    {
        return getEntry(PortletRenderer.MODE_EDIT_DEFAULTS, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Preview</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setPreviewPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setPreviewPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "preview" mode.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setPreviewPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setPreviewPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_PREVIEW, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "preview" mode with
     * the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "preview" mode
     * with the given content type or <code>null</code> if no such renderer
     * exists.
     */
    public ManagedRenderer getPreviewPage(String type)
    {
        return getEntry(PortletRenderer.MODE_PREVIEW, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>About</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setAboutPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setAboutPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "about" mode.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setAboutPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setAboutPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_ABOUT, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "about" mode with
     * the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "about" mode
     * with the given content type or <code>null</code> if no such renderer
     * exists.
     */
    public ManagedRenderer getAboutPage(String type)
    {
        return getEntry(PortletRenderer.MODE_ABOUT, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Help</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setHelpPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setHelpPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "help" mode.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setHelpPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setHelpPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_HELP, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "help" mode with
     * the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "help" mode with
     * the given content type or <code>null</code> if no such renderer exists.
     */
    public ManagedRenderer getHelpPage(String type)
    {
        return getEntry(PortletRenderer.MODE_HELP, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Link</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setLinkPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setLinkPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's "link" mode.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setLinkPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setLinkPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(PortletRenderer.MODE_LINK, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's "link" mode with
     * the given content type.
     *
     * @return the <code>ManagedRenderer</code> that renders this portlet's "link" mode with
     * the given content type or <code>null</code> if no such renderer exists.
     */
    public ManagedRenderer getLinkPage(String type)
    {
        return getEntry(PortletRenderer.MODE_LINK, type);
    }

    /**
     * Registers a {@link oracle.portal.provider.v2.render.http.ResourceRenderer} that
     * renders this portlet's <b>Link</b> mode.
     *
     * @param path <code>ServletContext</code> relative path to the rendering
     * resource.
     * @exception PortletException if an error occurs while initializing a
     * {@link oracle.portal.provider.v2.render.http.ResourceRenderer}.
     */
    public void setDefaultPage(String path) throws PortletException
    {
        ResourceRenderer r = new ResourceRenderer(this, path);
        setDefaultPage(r);
    }

    /**
     * Registers a <code>ManagedRenderer</code> that renders this portlet's 'default page'.
     * This renderer is used when no <code>ManagedRenderer</code> has been specified for the
     * specific mode of the render request.
     *
     * @param renderer a {@link ManagedRenderer} object.
     * @exception PortletException if the given object is not a valid {@link
     * ManagedRenderer} object.
     */
    public void setDefaultPage(Object renderer) throws PortletException
    {
        if(!(renderer instanceof ManagedRenderer))
        {
            throw new PortletException("setDefaultPage requires a ManagedRenderer object");
        }
        ManagedRenderer mr = (ManagedRenderer)renderer;
        putEntry(DEFAULT_PAGE_INDEX, mr);
    }

    /**
     * Gets the <code>ManagedRenderer</code> that renders this portlet's 'default' page with
     * the given content type. This renderer is used when no <code>ManagedRenderer</code>
     * has been specified for the specific mode of the render request.
     *
     * @return the <code>ManagedRenderer</code> that renders this 'default' page with the
     * given content type or <code>null</code> if no such renderer exists.
     */
    public ManagedRenderer getDefaultPage(String type)
    {
        return getEntry(DEFAULT_PAGE_INDEX, type);
    }

    /**
     *  Returns the {@link ManagedRenderer} object to be used to render this
     *  request. The method looks for a <code>ManagedRenderer</code> registered
     *  for the appropriate mode whose content type matches the preferred
     *  content type of the render request. If no such
     *  <code>ManagedRenderer</code> exists, the method returns the
     *  <code>ManagedRenderer</code> registered as the 'default' for that
     *  content type, if one exists.
     *
     *  @param pr the current render request.
     *  @return a {@link ManagedRenderer} to be used to render this request.
     *  @exception PortletException if no suitable <code>ManagedRenderer</code>
     *  has been registered.
     */
    public ManagedRenderer getManagedRenderer(PortletRenderRequest pr)
        throws PortletException
    {
        // If we have a non-null pageParameter value, assume the value is the
        // path to a resource in the same servlet context and construct a new
        // ResourceRenderer. Otherwise, get the right ManagedRenderer for this
        // render mode.
        String paramValue = getPageParameterValue(pr);
        if (paramValue != null)
        {
            return new ResourceRenderer(this, paramValue);
        }

        String[] t = pr.getAcceptContentTypes();
        if (t == null) t = DEFAULT_TYPES;
        return getManagedRenderer(pr.getMode(), t);
    }

    /**
     * Validates the internal state of this <code>RenderManager</code>
     * instance. Errors found are reported using the supplied {@link
     * oracle.portal.provider.v2.utils.ValidationError} instance.
     *
     * @param context The context of the calling instance.
     * @param errors The current list of errors.
     */
    public void validate(String context, ValidationError errors)
    {
        String ctx = ValidationError.newContext(context, "RenderManager",
                                                null);
        SimpleStringBuffer buff = new SimpleStringBuffer(100);

        // Ensure that for each supported mime type, we have a suitable
        // ManagedRenderer
        String[] types = mPortletDefinition.getAcceptContentTypes();
        String[] t = new String[1];
        for (int i = 0; i < types.length; i++)
        {
            t[0] = types[i];
            try
            {
                getManagedRenderer(PortletRenderer.MODE_SHOW, t);
            }
            catch (PortletException pe)
            {
                buff.append("No ManagedRenderer or default ManagedRenderer");
                buff.append(" for show mode with content type ").append(t[0]);
                errors.addError(ctx, buff.toString(), null);
            }
        }

        // Validate ManagedRenderers
        Vector renderers = null;
        Iterator it = null;
        ManagedRenderer mr = null;
        for (int j = 0; j < PortletRenderer.TOTAL_MODES + 1; j++)
        {
            renderers = mManagedRendererVectors[j];
            if (renderers != null)
            {
                it = renderers.iterator();
                String modeName = "default";
                while (it.hasNext())
                {
                    mr = (ManagedRenderer)it.next();
                    if (mr instanceof Validateable)
                    {
                        // Since ManagedRenderers do not have access to useful
                        // context information, add it here.
                        buff.setLength(0);
                        buff.append(ctx).append(' ');
                        if (j > 0)
                        {
                            modeName = PortletRendererUtil.getModeName(j);
                        }
                        buff.append(modeName);
                        buff.append('(').append(mr.getContentType());
                        buff.append(')');
                        ((Validateable)mr).validate(buff.toString(), errors);
                    }
                }
            }
        }
    }

    /**
     * Render the header for the portlet.
     *
     * The default implementation of this method uses an object of type 
     *  {@link oracle.portal.provider.v2.render.PortletContainerRenderer}
     *  to render the portlet header.
     *
     * @param pr the current render request
     */
    protected void renderPortletHeader(PortletRenderRequest pr)
        throws PortletException, AccessControlException, IOException
    {
        LogManager.debug("Entering RenderManager.renderPortletHeader");

        // initialize the parameters for rendering the container
        HashMap map = new HashMap(10);

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

        // get the container renderer to use
        PortletDefinition pd = pr.getPortletInstance().getPortletDefinition();
        PortletContainerRenderer pcr = pd.getContainerRenderer();

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

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

        LogManager.debug("Leaving RenderManager.renderPortletHeader"); 
    }

    protected void renderPortletFooter(PortletRenderRequest pr)
        throws PortletException, AccessControlException, IOException
    {
        LogManager.debug("Entering RenderManager.renderPortletFooter");

        // 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...)
        HashMap map = new HashMap(10);

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

        // get the container renderer to use
        PortletDefinition pd = pr.getPortletInstance().getPortletDefinition();
        PortletContainerRenderer pcr = pd.getContainerRenderer();

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

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

        LogManager.debug("Leaving RenderManager.renderPortletFooter"); 
    }

    /**
     * Automatically invalidates webcached content for this portlet after a
     * customization event.
     *
     * @param pr the PortletRenderRequest
     * @param action the edit action
     * @exception InvalidationException if the invalidation request fails for
     * any reason.
     */
    protected void autoInvalidate(PortletRenderRequest pr, String action)
        throws InvalidationException
    {
        if (getAutoInvalidate() && action != null)
        {
            LogManager.debug("Entering RenderManager.autoInvalidate");
            // Only attempt invalidation if invalidation caching is enabled and
            // the appropriate customization event has been submitted.
            ProviderContext c = pr.getProviderInstance().getProviderContext();
            if (!c.isInvalidationCachingEnabled())
            {
                LogManager.debug("Invalidation caching not enabled");
                LogManager.debug("Leaving RenderManager.autoInvalidate");
                return;
            }
            if (!(action.equalsIgnoreCase("OK") ||
                  action.equalsIgnoreCase("APPLY")))
            {
                LogManager.debug("Not a submit action");
                LogManager.debug("Leaving RenderManager.autoInvalidate");
                return;
            }
            // Get hold of an invalidation context.
            ServletInvalidationContext sic =
                ServletInvalidationContextFactory.getServletInvalidationContext();
            // Set the appropriate properties on the context.
            sic.setProviderContext("");
            sic.setProviderServletPath((String)
                pr.getAttribute(HttpCommonConstants.ORIGINAL_PROVIDER_PATH));
            sic.setURLCharSet(pr.getURLCharSet());
            // Use the provider and portlet as basic cache keys.
            sic.setPortletReference(pr.getPortletReference());

            // Send the invalidation request.
            LogManager.debug("Sending invalidation request");
            sic.invalidate();
            LogManager.debug("Leaving RenderManager.autoInvalidate");
        }
    }

    protected void handleEditAction(PortletRenderRequest pr, String action)
        throws IOException
    {
        if (getAutoRedirect())
        {
            LogManager.debug("RenderManager.handleEditAction - auto-redirecting");
            RenderContext rc = pr.getRenderContext();
            if (action.equalsIgnoreCase("OK"))
            {
                LogManager.debug("RenderManager.handleEditAction: " +
                                 " re-directing to BackURL");
                HttpPortletRendererUtil.sendRedirect(pr, rc.getBackURL());
            }
            else if (action.equalsIgnoreCase("APPLY"))
            {
                LogManager.debug("RenderManager.handleEditAction: " +
                                 " re-directing to pageURL");
                HttpPortletRendererUtil.sendRedirect(pr, rc.getPageURL());
            }
        }
    }

    protected String getPageParameterValue(PortletRenderRequest pr)
    {
        String value = null;
        String pageParam = getPageParameterName();
        if (pageParam != null)
        {
            // first try with fully qualified name
            value = pr.getQualifiedParameter(pageParam);
            if (value == null)
            {
                // else try the regular parameter name
                value = pr.getParameter(pageParam);
            }
        }
        return value;
    }

    private String getActionParameterName(PortletRenderRequest pr)
    {
        return pr.getPortletInstance().getActionParameterName();
    }

    private void putEntry(int renderMode, ManagedRenderer mr)
    {
        if(renderMode >= 0 &&
           renderMode <= PortletRenderer.TOTAL_MODES)
        {
            Vector rendererVector = mManagedRendererVectors[renderMode];
            if( rendererVector == null)
            {
                rendererVector = new Vector();
                mManagedRendererVectors[renderMode] = rendererVector;
            }
            rendererVector.addElement(mr);
        }
    }

    private ManagedRenderer getEntry(int renderMode, String contentType)
    {
        if(renderMode >= 0 &&
           renderMode <= PortletRenderer.TOTAL_MODES)
        {
            Vector rendererVector = mManagedRendererVectors[renderMode];
            if( rendererVector != null)
            {
                Enumeration e = rendererVector.elements();
                while(e.hasMoreElements())
                {
                    ManagedRenderer mr = (ManagedRenderer)e.nextElement();
                    if(mr.getContentType().equals(contentType))
                    {
                        return mr;
                    }
                }
            }
        }
        return null;
    }


    private String getTagNameForMode(int renderMode) throws PortletException
    {
        String modeName = null;
        try
        {
            modeName = PortletRendererUtil.getModeName(renderMode);
        }
        catch (IllegalArgumentException iae)
        {
            throw new PortletException(iae);
        }
        StringTokenizer tkn = new StringTokenizer(modeName.toLowerCase(), "_");
        SimpleStringBuffer tagName = new SimpleStringBuffer();
        if(tkn.hasMoreTokens())
        {
            tagName.append(tkn.nextToken());
        }
        while(tkn.hasMoreTokens())
        {
            String token = tkn.nextToken();
            tagName.append(Character.toUpperCase(token.charAt(0)));
            tagName.append(token.substring(1));
        }
        tagName.append("Page");
        return tagName.toString();
    }
    
    /**
     * Attempts to retrieve the appropriate ManagedRenderer for the specified
     * mode.
     */
    private ManagedRenderer getManagedRenderer(int renderMode, String[] t)
        throws PortletException
    {
        ManagedRenderer mr = null;

        for (int i = 0; i < t.length && mr == null; i++)
        {
            mr = getEntry(renderMode, t[i]);

            // If we still don't have a page, we may fall back to the page for a
            // different mode, depending on the mode
            if(mr == null)
            {
                switch (renderMode)
                {
                case PortletRenderer.MODE_SHOW_DETAILS:
                    mr = getEntry(PortletRenderer.MODE_SHOW, t[i]);
                    break;
                case PortletRenderer.MODE_EDIT:
                    mr = getEntry(PortletRenderer.MODE_EDIT_DEFAULTS, t[i]);
                    break;
                case PortletRenderer.MODE_EDIT_DEFAULTS:
                    mr = getEntry(PortletRenderer.MODE_EDIT, t[i]);
                    break;
                }
            }
        }

        // If all else fails, fall back to the 'default' renderer
        if(mr == null)
        {
            for (int i = 0; i < t.length && mr == null; i++)
            {
                mr = getEntry(DEFAULT_PAGE_INDEX, t[i]);
            }
        }

        // On error, try to work out the name of the defining tag from the mode
        // name
        if (mr == null)
        {
            String tagName = getTagNameForMode(renderMode);
            throw new PortletException("Renderer improperly configured. " +
                                       "It's missing a " + tagName +
                                       "for type " + t);
        }
        return mr;
    }

}
