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

import java.util.*;

import javax.servlet.http.*;

import oracle.portal.provider.v2.*;
import oracle.portal.provider.v2.render.*;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.portal.provider.v2.http.HttpCommonConstants;
import oracle.portal.utils.v2.NameValuePair;
import oracle.portal.utils.xml.v2.*;
import oracle.webdb.provider.v2.utils.http.ProviderRequestWrapper;
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.log.LogManager;

/**
 * BaseManagedRenderer is the JPDK's base implementation of the {@link
 * oracle.portal.provider.v2.render.ManagedRenderer}
 * interface. BaseManagedRenderer maintains information about the page's
 * content type (MIME type) and cache expiry time. These may be set via calls
 * to the appropriate methods or declared at page level in
 * <code>provider.xml</code> using the <code>&ltcontentType&gt</code> and
 * <code>&ltpageExpires&gt</code> tags.<p>
 *
 * BaseManagedRenderer also maintains a reference to its parent {@link
 * oracle.portal.provider.v2.render.RenderManager} object so that page level
 * attributes can be defaulted at {@link
 * oracle.portal.provider.v2.render.RenderManager} level.
 *
 * Implementations needing to set HTTP headers other than content type and page
 * expiry should subclass BaseManagedRenderer and override the {@link
 * #prepareResponse(PortletRenderRequest)} method, taking care to call
 * <code>super.prepareResponse(pr)</code> as a first step.
 */
public abstract class BaseManagedRenderer extends ManagedRenderer
    implements InitializableXMLObject, Validateable
{
    protected RenderManager mParent;
    private String mContentType;
    private int mPageExpires = -1;
    private boolean mUseInvalidationCaching = false;
    private boolean mGeneratesESI = false;
    private List mParameters = new ArrayList();

    /**
     * Performs the steps required to initialize a BaseManagedRenderer object
     * 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
    {
        // Keep a pointer to the RenderManager that should be this object's parent
        if(o instanceof RenderManager)
        {
            mParent = (RenderManager)o;

        }
        else
        {
            throw new NodeHandlerException("Parent of a ManagedRenderer must be a RenderManager");
        }
    }

    /**
     * Performs the extra steps required to initialize and validate a
     * BaseManagedRenderer object 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
    {
    }

    /**
     * Adds entries from a {@link oracle.portal.utils.v2.NameValuePair} object
     * to a parameter list stored by this <code>BaseManagedRenderer</code>.
     *
     * @param o Object representing the name-value pair (a {@link
     * NameValuePair}).
     * @exception PortletException if the given Object is not a valid {@link
     * NameValuePair}
     */
    public void addParameter(Object o) throws PortletException
    {
        if (!(o instanceof NameValuePair))
        {
            throw new PortletException("Parameters must be NameValuePairs");
        }
        mParameters.add(o);
    }

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

    /**
     *  Gets the default MIME type to be used as the content type for this
     *  renderer's pages. If a content type hasn't been set explicitly on this
     *  <code>BaseManagedRenderer</code>, then the value returned by calling
     *  the {@link RenderManager#getContentType()} method on the parent
     *  <code>RenderManager</code> is returned.
     *
     *  @return MIME type to be used as the content type for this renderer's
     *  pages.
     */
    public String getContentType()
    {
        if(mContentType == null)
        {
            return mParent.getContentType();
        }
        return mContentType;
    }

    /**
     * Sets the expiration time (in minutes) of the page cache.
     *
     * @param expires the number of minutes a page should be cached by
     * the Portal.
     */
    public void setPageExpires(int expires)
    {
        mPageExpires = expires;
    }

    /**
     * Sets the expiration time (in minutes) of the page cache.
     *
     * @param expires String representation of the integer number of minutes a
     * page should be cached by the Portal.
     */
    public void setPageExpires(String expires)
    {
        setPageExpires(Integer.parseInt(expires));
    }

    /**
     * Gets the page expriation (in minutes). If a page expiration hasn't been
     * set explicitly on this <code>BaseManagedRenderer</code>, then the value
     * returned by calling the {@link RenderManager#getPageExpires()} method on
     * the parent <code>RenderManager</code> is returned.
     *
     * @return page expiration in minutes.  If non-zero it indicates the
     * number of minutes the page should be cached by the Portal.
     */
    protected int getPageExpires()
    {
        if(mPageExpires == -1)
        {
            return mParent.getPageExpires();
        }
        return mPageExpires;
    }

    /**
     * Sets a flag to indicate whether or not to use invalidation caching
     *
     * @param useInvalidation if true use invalidation caching, otherwise don't
     */
    public void setUseInvalidationCaching(boolean useInvalidation)
    {
        mUseInvalidationCaching = useInvalidation;
    }

    /**
     * Sets a flag to indicate whether or not to use invalidation caching
     *
     * @param useInvalidation if true use invalidation caching, otherwise don't
     */
    public void setUseInvalidationCaching(String useInvalidation)
    {
        setUseInvalidationCaching(XMLUtil.getBooleanValue(useInvalidation));
    }

    /**
     * Returns a flag indicating whether or not ESI processing should be
     * performed on the rendered output.
     *
     * @return boolean - true if ESI processing should be performed 
     *         on the rendered output. Otherwise false.
     */
    public boolean getGeneratesESI()
    {
        return mGeneratesESI;
    }

    /**
     * Sets a flag to indicate whether or not ESI processing should be
     * performed on the rendered output.
     *
     * @param generatesESI true if ESI processing should be performed 
     *        on the rendered output. Otherwise false.
     */
    public void setGeneratesESI(boolean generatesESI)
    {
        mGeneratesESI = generatesESI;
    }

    /**
     * Sets a flag to indicate whether or not ESI processing should be
     * performed on the rendered output.
     *
     * @param generatesESI "true" if ESI processing should be performed 
     *        on the rendered output. Otherwise "false".
     */
    public void setGeneratesESI(String generatesESI)
    {
        setGeneratesESI(XMLUtil.getBooleanValue(generatesESI));
    }


    /**
     * Sets default HTTP headers for the response. This consists of
     * <code>content type</code> and page expiry time, both of which may be
     * set programatically via calls to {@link #setContentType(String)} and
     * {@link #setPageExpires(String)} or declaritivley in
     * <code>provider.xml</code> with the <code>&ltcontentType&gt</code> and
     * <code>&ltpageExpires&gt</code> tags.
     *
     * @param pr a {@link PortletRenderRequest}
     * @return boolean flag indicating whether or not to render the portlet's
     * contents (e.g. is the cached version still valid)
     */
    public boolean prepareResponse(PortletRenderRequest pr)
        throws PortletException, PortletNotFoundException
    {
        pr.setContentType(getContentType());

        // Add the local parameters to the request
        addLocalParameters(pr);

        // Check page expiry and invalidation if mode is not an edit mode
        int mode = pr.getMode();
        if (mode != PortletRenderer.MODE_EDIT &&
            mode != PortletRenderer.MODE_EDIT_DEFAULTS)
        {
            int pageExpires = getPageExpires();
            if(mUseInvalidationCaching)
            {
                if(pr.getProviderInstance().getProviderContext().
                                               isInvalidationCachingEnabled())
                {
                    // If invalidation caching is enabled, set the timeout to 
                    // the value defined in the expires tag, if present, or 
                    // use the default. It can still be overridden by the max 
                    // cache time in the render context of the request.
                    if(pageExpires > 0)
                    {
                        // Max caching time is specified in provider definition
                        LogManager.info("Using invalidation caching max " 
                                 + "expiry time from provider definition."
                                 + " Set to " + pageExpires + " minutes."); 
                    HttpPortletRendererUtil.useInvalidationCaching(pr, pageExpires);
                    }
                    else
                    {
                        // No max caching time set in the provider definition 
                        // - use the default.
                        HttpPortletRendererUtil.useInvalidationCaching
                          (pr, HttpCommonConstants.DEFAULT_WC_MAX_EXPIRY_TIME);
                        LogManager.info("Using default invalidation caching max expiry time.");
                    }
                }
            }
            else
            {
                if(pageExpires > 0)
                {
                    // Expires based caching
                    HttpPortletRendererUtil.setCacheExpires(pr,
                                                HttpCommonConstants.USER_LEVEL,
                                                pageExpires);
                }
            }
        }

        // If specified then add a header requesting processing of ESI tags
        if (getGeneratesESI())
        {
            HttpPortletRendererUtil.addGeneratesESIHeader(pr);
        }

        return true;
    }

    /**
     * Validates the internal state of this <code>BaseManagedRenderer</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, "BaseManagedRenderer",
                                                null );
                                                                            
        // Check the validity of the member variables                       
        if (mParent == null)
        {                                                                   
            errors.addError(ctx, "Parent must not be null", null);    
        }
        else if (!(mParent instanceof RenderManager))
        {
            errors.addError(ctx, "Parent must be of type RenderManager", null);
        }
    }

    protected void addLocalParameters(PortletRenderRequest pr)
    {
        ProviderRequestWrapper prw =
            (ProviderRequestWrapper)pr.getAttribute(HttpCommonConstants.SERVLET_REQUEST);
        Iterator it = mParameters.iterator();
        while(it.hasNext())
        {
            NameValuePair nvp = (NameValuePair)it.next();
            prw.addLocalParameter(nvp.getName(), nvp.getValue());
        }
    }
}
