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

import java.io.*;
import javax.servlet.http.*;
import oracle.portal.provider.v2.*;
import oracle.portal.provider.v2.security.AccessControlException;
import oracle.portal.provider.v2.http.HttpCommonConstants;
import oracle.portal.provider.v2.render.*;
import oracle.webdb.utils.SimpleStringBuffer;

import oracle.portal.log.LogManager;
import oracle.portal.provider.v2.monitor.PerfMonitor;

/**
 * The HttpPortletRendererUtil class provides many useful utility methods designed
 * to simplify coding your renderer. HttpPortletRendererUtil extends PortletRendererUtil.
 * It implements those utilities that have a dependency on the (Http) servlet
 * interfaces.  These are grouped into three categories:  wrappers for
 * PortletRendererUtil utilities, utilities to managing caching, and covers for
 * servlet function.  The wrappers for PortletRendererUtil utilities are provided
 * as a convenience for renderers implemented as JSPs or servlets.  They give
 * you the convenience of passing servlet objects thus avoiding extracting the
 * PortletRenderRequest that is required in the PortletRendererUtil utilities. The
 * caching utilities simplify enabling either the validation or expiry based
 * caching provided by the Portal. As for servlet covers, there is currently
 * only one, <code>sendRedirect</code>, that allows you send a redirect response
 * without directly accessing the servlet response object.
 */
public class HttpPortletRendererUtil extends PortletRendererUtil {

/* ***************************************************************************
 *
 *                PortletRendererUtil wrappers
 *
 * ***************************************************************************/

    private static final String[] WC_HEADERS = {
        HttpCommonConstants.ORACLE_CACHE_DEVICE_HEADER,
        HttpCommonConstants.ORACLE_CACHE_USER_HEADER,
        HttpCommonConstants.ORACLE_CACHE_LANG_HEADER,
        HttpCommonConstants.ORACLE_CACHE_SUBID_HEADER,
        HttpCommonConstants.ORACLE_CACHE_ENCRYPT_HEADER,
        HttpCommonConstants.HTTP_CONTENT_TYPE};

    private static final String CACHING_ERROR_MSG = 
        "Can not set header, ";
    private static final String WC_EXPIRES_HEADER_SET_ERROR_MSG =
        ". Invalidation caching or ESI tags already set for this portlet response.";
    private static final String EXPIRES_HEADER_SET_ERROR_MSG =
        ". Expiry based caching already set for this portlet response.";
    private static final String CACHE_VERSION_HEADER_SET_ERROR_MSG =
        ". Cache version header already set for this portlet response.";
    private static final String CACHE_USE_HEADER_SET_ERROR_MSG =
        ". Cache use header already set for this portlet response.";
    /**
     *  Opens rendering of the portlet's container.
     *  Portlets are responsible for rendering their container.  This method used
     *  in conjunction with renderPortletFooter renders the standard container including
     *  borders and titlebar.  Use the methods as bookends.  renderPortletHeader should be
     *  called before the portlet renders (its body) and renderPortletFooter should be called
     *  after the portlet renders (its body).<p>
     *  This is a convenience routine for use by renderers written as servlets or JSPs.  I.e.
     *  those whose code objects are the servlet objects not the WebProvider
     *  objects.
     *
     *  @param request the servlet request.
     *  @param writer the response writer.
     *  @param title the titlebar label.  If <b>null</b> the portlet's static title is used.
     */
    public static void renderPortletHeader(HttpServletRequest request, Writer writer, String title) throws IOException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            renderPortletHeader(pRequest, writer, title);
        }
    }

    /**
     *  Closes rendering of the portlet container.
     *  Portlets are responsible for rendering their container.  This method used
     *  in conjunction with renderPortletHeader renders the standard container including
     *  borders and titlebar.  Use the methods as bookends.  renderPortletHeader should be
     *  called before the portlet renders (its body) and renderPortletFooter should be called
     *  after the portlet renders (its body).<p>
     *  This is a convenience routine for use by renderers written as servlets or JSPs.  I.e.
     *  those whose code objects are the servlet objects not the WebProvider
     *  objects.
     *
     *  @param request the servlet request.
     *  @param writer the response writer.
     */
    public static void renderPortletFooter(HttpServletRequest request, Writer writer) throws IOException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            renderPortletFooter(pRequest, writer);
        }
    }

    /**
     *  Renders a portlet's titlebar.
     *  Portlets are responsible for rendering their container.  This method renders
     *  the standard portlet titlebar using information from the request and
     *  Portlet meta-data. Typically, clients will not use this method.  Rather they
     *  will rely on renderPortletHeader() to render the entire container including the
     *  titlebar.  If however, renderPortletHeader is not used, this method is
     *  provided for convenience.<p>
     *  This is a convenience routine for use by renderers written as servlets or JSPs.  I.e.
     *  those whose code objects are the servlet objects not the WebProvider
     *  objects.
     *
     *  @param request the servlet request.
     *  @param writer the response writer.
     *  @param title the label that should be displayed as the title.  if <b>null</b> then
     *  the method will render the static title retrieved via Portlet.getTitle();
     */
    public static void renderPortletTitlebar(HttpServletRequest request, Writer writer, String title) throws IOException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            renderPortletTitlebar(pRequest, writer, title);
        }
    }


    /**
     *  Adds new parameters to an existing link.  There are various links available
     *  to the portlet for use while rendering its response. This utility method
     *  conveniently adds parameters to one of these links.  The method overrides
     *  existing parameters if they exist.  I.e. existing (queryString) parameters
     *  will be removed if it duplicates (by name) a new parameter.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     *
     *  @param request the servlet request.
     *  @param linkType the id for the link we want to parameterize.  ID constants are defined
     *  in PortletRendererUtil.
     *  @param queryString  the parameters to be added in properly encodes queryString form.
     *  The string should begin with the first parameter name not a "?".
     */
    public static String parameterizeLink(HttpServletRequest request, int linkType, String queryString) throws IllegalArgumentException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            return parameterizeLink(pRequest, linkType, queryString);
        } else {
            return null;
        }
    }

    /**
     *  Returns a fully-qualified URI for a path relative to the current request.
     *  As Web (Http) Portlets are called indirectly via the portal the client
     *  browser's current path is to the portal not to the portlet.  Because of
     *  this all HREFs must be expressed using fully qualified paths vs. the
     *  traditional relative paths.  This method differs from
     *  PortletRendererUtil.absoluteLink() in that it uses the current renderer's
     *  servlet request path to generate the absolute link.  When the renderer
     *  is a JSP or servlet this is the "forwarded" path vs. the originating
     *  portlet request path. I.e. a path relative to the JSP or servlet path
     *  rather then then original request path.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     *
     *  @param request the servlet request
     *  @param relativePath the path you want to make absolute
     */
    public static String absoluteLink(HttpServletRequest request, String relativePath) {

        String path = request.getScheme() + "://" + request.getServerName() + 
            ":" + request.getServerPort() + request.getContextPath() + request.getServletPath();

        // strip the target
        path = path.substring(0, path.lastIndexOf("/"));


        return concatPaths(path, relativePath, "/");
    }

    /**
     *  Transforms one of the various URIs known to the request into the appropriate
     *  form to be used in a FORM action tag.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     *
     *  @param request the serlvet request object
     *  @param linkType  the link in this request that should be transformed.
     *  Constants representing these types are defined in PortletRendererUtil.
     */
    public static String htmlFormActionLink(HttpServletRequest request, int linkType) throws IllegalArgumentException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            return htmlFormActionLink(pRequest, linkType);
        }

        return null;
    }

    /**
     *  Encodes the parameters in the query string of the passed URL as hidden form fields.
     *  When "linking" back to the portlet via a form,
     *  the URI parameters must be added as hidden fields.  These parameters
     *  are found in the querystring of your URI.  This utility method generates the hidden
     *  field tags from this URL and returns them as a String.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     *
     *  @param request the servlet request.
     *  @param linkType the link to use.  Constants representing these types are defined in PortletRendererUtil.
     */
    public static String htmlFormHiddenFields(HttpServletRequest request, int linkType) throws IllegalArgumentException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            return htmlFormHiddenFields(pRequest, linkType);
        }

        return null;
    }

    /**
     *  Utility that turns a query string parameter name into fully qualified
     *  (unique) name.  Portlets on the same page share request parameters.  To ensure
     *  each Portlet can locate its specific parameters, query string parameter
     *  names must be fully qualified.  A fully qualified parameter name is one
     *  that contains the portlet reference in the parameter name.  For example if
     *  a portlet used the query string parameter "symbol", the fully qualified
     *  form of this parameter name would be "providerID.portletID.portletInstanceName.symbol".
     *  This utility constructs such references from the PortletReference and
     *  starting parameter name.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     *
     *  @param request  the current servlet request.
     *  @param param the name of the parameter to be qualified.
     */

     public static String portletParameter(HttpServletRequest request, String param) {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            return portletParameter(pRequest, param);
        }

        return param;
    }


    /**
     *  Retrieves this requests customization data object. By convention, the
     *  Portal expects portlets to implement a tiered search order for a
     *  given customization.  This utility implements this convention.  This
     *  allows developers to retrieve a requests customization object in a
     *  single call.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     */

    public static Object getEditData(HttpServletRequest request)
        throws PortletNotFoundException, IOException, AccessControlException
    {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            return getEditData(pRequest);
        } else {
            return null;
        }
    }

    /** Updates this requests customization data object creating the customization
     *  instance if necessary.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs in that it merely extracts the PortletRenderRequest from the servlet
     *  request and then calls the (real) utility method.
     */
    public static void submitEditData(HttpServletRequest request, Object o) throws IOException, AccessControlException {
        PortletRenderRequest pRequest = (PortletRenderRequest) request.getAttribute(HttpCommonConstants.PORTLET_RENDER_REQUEST);

        if (pRequest != null) {
            submitEditData(pRequest, o);
        }
    }

    /**
     * Utility for generating redirects after processing edit/edit default forms
     *
     * @param pr the request
     * @param action the action submitted with the edit form (OK/APPLY)
     */
    public static void handleEditAction(PortletRenderRequest pr, String action)
        throws IOException
    {
        if (action.equalsIgnoreCase("OK"))
        {
            HttpPortletRendererUtil.sendRedirect(pr, pr.getRenderContext().getBackURL());
        }
        else if (action.equalsIgnoreCase("APPLY"))
        {
            HttpPortletRendererUtil.sendRedirect(pr, pr.getRenderContext().getPageURL());
        }
    }


 /* ***************************************************************************
 *
 *                      Caching utilities
 *
 * ***************************************************************************/


    /**
     *  Sets the expiration duration of the current response.
     *  Oracle Portal supports expires-based caching.  I.e. a portlet can add a header
     *  field to its response that informs the Portal to reuse this response for
     *  a specified duration (in minutes).  Once the time expires, the Portal will
     *  recontact the portlet to generate a new response.  This method writes the
     *  set of header values that the Portal uses to identify and implement expiration
     *  based caching.<p>
     *  Note: this is a convenience routine for renderers implemented as servlets
     *  or JSPs.
     *
     *  @param response the servlet response.
     *  @param level identifies whether this is a user specific response or a system (default)
     *  response. Constants representing these levels are available in HttpCommonConstants.
     *  @param minutes  the number of minutes this response can be reused before it expires.
     *  The minimum is 0 and the maximum is 525600 (1 year).
     */
    public static void setCacheExpires(HttpServletResponse response,
                                        int level,
                                        int minutes)
    throws IllegalArgumentException
    {
        if (minutes < 0 || minutes > 525600)
        {
            throw new IllegalArgumentException("setCacheExpires failed:"
                          + " The numbers of minutes must be positive and "
                          + "less then 1 year (525600)");
        }
        setCacheTypeHeader(response,
                           HttpCommonConstants.EXPIRES_HEADER,
                           Integer.toString(minutes));

        if (level == HttpCommonConstants.USER_LEVEL)
        {
            response.setHeader(HttpCommonConstants.CACHE_LEVEL_HEADER,
                                          HttpCommonConstants.CACHE_LEVEL_USER);
        }
        else if (level == HttpCommonConstants.SYSTEM_LEVEL)
        {
            response.setHeader(HttpCommonConstants.CACHE_LEVEL_HEADER,
                                        HttpCommonConstants.CACHE_LEVEL_SYSTEM);
        }
        else
        {
            throw new IllegalArgumentException("setCacheExpires failed: unrecognized level.");
        }
    }

    /**
     *  Sets the expiration duration of the current response.
     *  Oracle Portal supports expires-based caching.  I.e. a portlet can add a header
     *  field to its response that informs the Portal to reuse this response for
     *  a specified duration (in minutes).  Once the time expires, the Portal will
     *  recontact the portlet to generate a new response.  This method writes the
     *  set of header values that the Portal uses to identify and implement expiration
     *  based caching. <p>
     *
     *  @param request the current portlet request
     *  @param level identifies whether this is a user specific response or a system (default)
     *  response. Constants representing these levels are available in HttpCommonConstants.
     *  @param minutes  the number of minutes this response can be reused before it expires.
     *  The minimum is 0 and the maximum is 525600 (1 year).
     */
    public static void setCacheExpires(PortletRenderRequest request, int level, int minutes) throws IllegalArgumentException {
        HttpServletResponse response = (HttpServletResponse) request.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);

        if (response != null)
        {
            setCacheExpires(response, level, minutes);

            // Log the relevant details
            PerfMonitor monitor = 
                             request.getRenderContext().getPerfMonitor();
            if (level == HttpCommonConstants.USER_LEVEL)
            {
                monitor.setProperty(PerfMonitor.CACHE_LEVEL, 
                                    HttpCommonConstants.CACHE_LEVEL_USER);
            }
            else
            {
                monitor.setProperty(PerfMonitor.CACHE_LEVEL,
                                    HttpCommonConstants.CACHE_LEVEL_SYSTEM);
            }
            monitor.setProperty(PerfMonitor.CACHE_TYPE, PerfMonitor.EXPIRES_MISS);
            monitor.setProperty(PerfMonitor.EXPIRY_TIME, minutes);         
        }
    }

    /**
     * Set the response header to cache content in Web Cache. This uses 
     * the max expiry time specified in the request's render context for this 
     * portlet.<p> 
     *
     * @param request the current portlet request
     */
    public static void useInvalidationCaching(PortletRenderRequest request)
    {
        int WCMaxExpiryTime = (request.getRenderContext()).getWCMaxExpiryTime();        
        useInvalidationCaching(request, WCMaxExpiryTime);
    }

    /**
     * Set the response header to cache the content for a specified duration in Web Cache.
     * This uses the max. expiry time in the render context which is set when the portal
     * request is first processed in <code>ProviderAdapter</code>. The max expiry time is
     * set to the minimum of the render context max expiry time and the expiry time 
     * passed to this funtion.
     * @param request the current portlet request
     * @param minutes The time for which the content should be cached in Web Cache before it expires.
     */
    public static void useInvalidationCaching(PortletRenderRequest request, 
                                              int minutes)
    {
        HttpServletResponse response = (HttpServletResponse)request.
                          getAttribute(HttpCommonConstants.SERVLET_RESPONSE);
        
        int WCMaxExpiryTime = (request.getRenderContext()).getWCMaxExpiryTime();

        String surrogateControlValue;

        if(response != null)
        {
            if(isCachableRequest(request))
            {
                // Set cache headers if this is a cacheable request, i.e,
                // all the invalidation cache headers were in the original request.
                int maxCacheTime = Math.min(WCMaxExpiryTime, minutes);
                
                // Convert to seconds
                maxCacheTime *= 60;
           
                // Set the header part of the vary part of the max age directive
                // ... vary=headers(<cache header names>)
                SimpleStringBuffer maxAgeDirective = new SimpleStringBuffer(100);
                maxAgeDirective.append("max-age=");
                maxAgeDirective.append(maxCacheTime);
                maxAgeDirective.append(HttpCommonConstants.WC_VARY_HEADERS);
                
                // Set the HMAC cookie part of the max age directive if it is an 
                // HMAC request.
                // cookie=(<cookie header names>)
                if(isHmacRequest(request))
                {
                    maxAgeDirective.append(HttpCommonConstants.WC_VARY_COOKIES);
                }               
                
                surrogateControlValue = maxAgeDirective.toString();
            }
            else
            {
                surrogateControlValue = HttpCommonConstants.WC_NO_STORE;
            }

            // Set the surrogate control header to be 'max-age...' or 
            // 'no-store'.
            setCacheTypeHeader(response,
                               HttpCommonConstants.WC_EXPIRES_HEADER,
                               surrogateControlValue);
        }
    }

    private static boolean isCachableRequest(PortletRenderRequest req)
    {
        // All of the headers which will be used as cache keys must be present
        // in the original request.
        for(int i = 0;i < WC_HEADERS.length;i++)
        {
            if(req.getOriginalHeader(WC_HEADERS[i]) == null)
            {
                return false;
            }
        }
        return true;        
    }

    private static boolean isHmacRequest(PortletRenderRequest req)
    {
        // Is there an HMAC cookie in the request.
        Cookie[] cookies = (Cookie[])req.getCookies();
        if(cookies != null)
        {            
            for(int i = 0;i < cookies.length;i++)
            {
                if(cookies[i].getName().equals(HttpCommonConstants.HMAC_CSUM_NAME))
                {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Adds response header that enables processing of ESI tags by Web Cache.<p>
     *
     * Adds the header: <code>Surrogate-Control: content="ORAESI/9.0.2"</code>
     * 
     * @param request the current portlet request
     */
    public static void addGeneratesESIHeader(PortletRenderRequest request)
    {
        HttpServletResponse response = (HttpServletResponse)request.
                          getAttribute(HttpCommonConstants.SERVLET_RESPONSE);

        if(response != null)
        {
            // Add the surrogate control header that requests
            // processing of ESI tags by Web Cache.
            setCacheTypeHeader(response,
                               HttpCommonConstants.WC_EXPIRES_HEADER,
                               HttpCommonConstants.WC_GENERATES_ESI);
        }
    }

    /**
     *  Sets the response (cached) version Id and level.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method registers (by setting response header fields) the portlet's cached
     *  version ID and user level for the response it has generated.
     *
     *  @param response the serlvet response object
     *  @param version identifies the response version.  Portlets should be able to use this
     *  in subsequent requests to determine if a new response should be generated.
     *  @param level  whether this is a user or system level response.  Constants for
     *  these levels are defined in HttpCommonConstants.
     */
    public static void setCachedVersion(HttpServletResponse response, String version, int level) throws IllegalArgumentException {
        setCacheTypeHeader(response,
                           HttpCommonConstants.CACHE_VERSION_HEADER,
                           version);

        if (level == HttpCommonConstants.USER_LEVEL) {
            response.setHeader(HttpCommonConstants.CACHE_LEVEL_HEADER, HttpCommonConstants.CACHE_LEVEL_USER);
        } else if (level == HttpCommonConstants.SYSTEM_LEVEL) {
            response.setHeader(HttpCommonConstants.CACHE_LEVEL_HEADER, HttpCommonConstants.CACHE_LEVEL_SYSTEM);
        } else {
            throw new IllegalArgumentException("setCacheExpires failed: unrecognized level.");
        }
        response.setHeader(HttpCommonConstants.CACHE_USE_HEADER, HttpCommonConstants.CACHE_UPDATE_HEADER_VALUE);
    }

    /**
     *  Sets the response (cached) version Id and level.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method registers (by setting response header fields) the portlet's cached
     *  version ID and user level for the response it has generated.
     *
     *  @param request the portlet render response object
     *  @param version identifies the response version.  Portlets should be able to use this
     *  in subsequent requests to determine if a new response should be generated.
     *  @param level  whether this is a user or system level response.  Constants for
     *  these levels are defined in HttpCommonConstants.
     */
    public static void setCachedVersion(PortletRenderRequest request, String version, int level) throws IllegalArgumentException {
        HttpServletResponse response = (HttpServletResponse) request.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);

        if (response != null)
        {
            setCachedVersion(response, version, level);
            
            // Log the relevant caching details for a cache miss.
            PerfMonitor monitor = 
                             request.getRenderContext().getPerfMonitor();
            if (level == HttpCommonConstants.USER_LEVEL)
            {
                monitor.setProperty(PerfMonitor.CACHE_LEVEL,
                                    HttpCommonConstants.CACHE_LEVEL_USER);
            }
            else
            {
                monitor.setProperty(PerfMonitor.CACHE_LEVEL,
                                    HttpCommonConstants.CACHE_LEVEL_SYSTEM);
            }
            String requestKey = getCachedVersion(request);
            monitor.setProperty(PerfMonitor.CACHE_KEY_REQUEST, requestKey);
            monitor.setProperty(PerfMonitor.CACHE_KEY_RESPONSE, version);
            String status = PerfMonitor.VALIDATION_STALE;
            if (requestKey == null)
            {
                status = PerfMonitor.VALIDATION_NEW;
            }
            monitor.setProperty(PerfMonitor.CACHE_TYPE, status);
            
        }
    }

    /**
     *  Indicates to the portal that the cached version should be used to respond to the client.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method is used when a portlet determines that the cached version is still valid and
     *  should be used.  The method writes the appropriate response header indicating
     *  the cached version is still valid.
     *
     *  @param response the servlet response object.
     */
    public static void useCachedVersion(HttpServletResponse response) {
        setCacheTypeHeader(response,
                           HttpCommonConstants.CACHE_USE_HEADER,
                           HttpCommonConstants.CACHE_USE_HEADER_VALUE);
    }

    /**
     *  Indicates to the portal that the cached version should be used to respond to the client.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method is used when a portlet determines that the cached version is still valid and
     *  should be used.  The method writes the appropriate response header indicating
     *  the cached version is still valid.
     *
     *  @param request the portlet render request object.
     */
    public static void useCachedVersion(PortletRenderRequest request) {
        HttpServletResponse response = (HttpServletResponse) request.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);

        if (response != null)
        {
            useCachedVersion(response);

            // Log cache details
            PerfMonitor monitor = request.getRenderContext().getPerfMonitor();

            int level = getCachedLevel(request);

            if (level == HttpCommonConstants.USER_LEVEL)
            {
                monitor.setProperty(PerfMonitor.CACHE_LEVEL,
                                    HttpCommonConstants.CACHE_LEVEL_USER);
            }
            else
            {
                monitor.setProperty(PerfMonitor.CACHE_LEVEL,
                                    HttpCommonConstants.CACHE_LEVEL_SYSTEM);
            }
            String requestKey = getCachedVersion(request);
            monitor.setProperty(PerfMonitor.CACHE_KEY_REQUEST, requestKey);
            monitor.setProperty(PerfMonitor.CACHE_KEY_RESPONSE, requestKey);
            monitor.setProperty(PerfMonitor.CACHE_TYPE, PerfMonitor.VALIDATION_HIT);             
        }
    }

    /**
     *  Gets the (cached) version Id passed by the portal for the current request.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method is used when a portlet (using validation based caching) receives a request.
     *  It returns the version ID for the current cached version held by the portal.  Portlets
     *  use this value in conjunction with getCachedLevel() to determine whether to
     *  generate a new response or indicate that the portal can reuse its cached version.
     *
     *  @param request the servlet request object.
     */
    public static String getCachedVersion(HttpServletRequest request) {
        return request.getHeader(HttpCommonConstants.CACHE_VERSION_HEADER);
    }

    /**
     *  Gets the (cached) version Id passed by the portal for the current request.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method is used when a portlet (using validation based caching) receives a request.
     *  It returns the version ID for the current cached version held by the portal.  Portlets
     *  use this value in conjunction with getCachedLevel() to determine whether to
     *  generate a new response or indicate that the portal can reuse its cached version.
     *
     *  @param request the portlet render request object.
     */
    public static String getCachedVersion(PortletRenderRequest request) {
        HttpServletRequest sRequest = (HttpServletRequest) request.getAttribute(HttpCommonConstants.SERVLET_REQUEST);

        if (sRequest != null) {
            return getCachedVersion(sRequest);
        } else {
            return null;
        }
    }

    /**
     *  Gets the (cached) level passed by the portal for the current request.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method is used when a portlet (using validation based caching) receives a request.
     *  It returns the cache level for the current cached version held by the portal.  Portlets
     *  use this value in conjunction with getCachedVersion() to determine whether to
     *  generate a new response or indicate that the portal can reuse its cached version.
     *
     *  @param request the servlet request object.
     */
    public static int getCachedLevel(HttpServletRequest request) throws IllegalStateException {
        String l = request.getHeader(HttpCommonConstants.CACHE_LEVEL_HEADER);

        if (l == null) {
            throw new IllegalStateException("getCacheLevel failed: cache level doesn't exist");
        }

        if (l.equalsIgnoreCase(HttpCommonConstants.CACHE_LEVEL_USER)) {
            return HttpCommonConstants.USER_LEVEL;
        } else if (l.equalsIgnoreCase(HttpCommonConstants.CACHE_LEVEL_SYSTEM)) {
            return HttpCommonConstants.SYSTEM_LEVEL;
        } else {
            throw new IllegalStateException("getCacheLevel failed: invalid cache level value.");
        }
    }

    /**
     *  Gets the (cached) level passed by the portal for the current request.
     *  Oracle Portal supports validation-based caching.  I.e. a portlet can
     *  validate on a per request basis if the current cached version is still
     *  valid (and can be used instead of regenerating the response).  The mechanism
     *  works by having the portlet set special header fields that indicate to the
     *  portal (cache) both a version ID and a level (whether this response is the
     *  public, default or a paticular user response).  When the portlet is rerendered
     *  the portal calls the portlets render method.  However the version id and
     *  user level are passed in the request (in header fields).  The portlet is
     *  expected to check these values and determine whether the specified version
     *  is still valid.  If it is then the portlet doesn't generate a new reponse.  Rather
     *  it replies to the portal (via a header) that the portal should use its cached
     *  version.  If a new version needs to be generated, the portlet does so, and adds
     *  the new version id to the response header.<p>
     *  This method is used when a portlet (using validation based caching) receives a request.
     *  It returns the cache level for the current cached version held by the portal.  Portlets
     *  use this value in conjunction with getCachedVersion() to determine whether to
     *  generate a new response or indicate that the portal can reuse its cached version.
     *
     *  @param request the portlet render request object.
     */
    public static int getCachedLevel(PortletRenderRequest request) throws IllegalStateException {
        HttpServletRequest sRequest = (HttpServletRequest) request.getAttribute(HttpCommonConstants.SERVLET_REQUEST);

        if (sRequest != null) {
            return getCachedLevel(sRequest);
        } else {
            throw new IllegalStateException("getCacheLevel failed: can't access servlet request.");
        }

    }

    /**
     * Utility method to set caching headers. If another type of caching header has 
     * already been set an exception is thrown.
     */
    private static void setCacheTypeHeader(HttpServletResponse response,
                                   String headerName,
                                   String headerValue)
    {
        String exMsg = null;

        // The invalid header combinations are indicated by an 'x' in the following
        // table:
        //                               SC XOE XOC:R XOC:W E  
        // Surrogate-Control(SC)            x   x     x     x
        // X-ORACLE-EXPIRES(XOE)         x      x     x     x
        // X-ORACLE-CACHE:READ(XOC:R)    x  x                  
        // X-ORACLE-CACHE:WRITE(XOC:W)   x  x                   
        // Etag(E)                       x  x               
        // Note: Strictly speaking some of the 'valid' combinations should not
        // be allowed, e.g. X-ORACLE-CACHE:READ should not be set if the Etag is
        // already set but checking for this would involve getting the header 
        // values from the response object which is not straightfroward.

        // A cache header of the same type may be set more than once.
        if (!response.containsHeader(headerName))
        {
            if (response.containsHeader(HttpCommonConstants.WC_EXPIRES_HEADER))
            {
                // No other cache type headers may be used with 
                // 'Surrogate-Control' header.
                exMsg = new SimpleStringBuffer(100)
                               .append(CACHING_ERROR_MSG)
                               .append(headerName)
                               .append(":")
                               .append(headerValue)
                               .append(WC_EXPIRES_HEADER_SET_ERROR_MSG)
                               .toString();
            }
            else if (response.containsHeader(HttpCommonConstants.EXPIRES_HEADER))
            {
                // No other cache type headers may be used with expires based
                // caching.
                exMsg = new SimpleStringBuffer(100)
                               .append(CACHING_ERROR_MSG)
                               .append(headerName)
                               .append(":")
                               .append(headerValue)
                               .append(EXPIRES_HEADER_SET_ERROR_MSG)
                               .toString();
            }
            else if (response.containsHeader(HttpCommonConstants.CACHE_VERSION_HEADER))
            {
                // The only header that may be set with the cache version 
                // header is X-ORACLE-CACHE.
                if (!headerName.equals(HttpCommonConstants.CACHE_USE_HEADER))
                {
                    exMsg = new SimpleStringBuffer(100)
                        .append(CACHING_ERROR_MSG)
                        .append(headerName)
                        .append(":")
                        .append(headerValue)
                        .append(CACHE_VERSION_HEADER_SET_ERROR_MSG)
                        .toString();
                }
            }
            else if (response.containsHeader(HttpCommonConstants.CACHE_USE_HEADER))
            {
                // The only valid header here is the cache version header. 
                if (!headerName.equals(HttpCommonConstants.CACHE_VERSION_HEADER))
                {
                    exMsg = new SimpleStringBuffer(100)
                        .append(CACHING_ERROR_MSG)
                        .append(headerName)
                        .append(":")
                        .append(headerValue)
                        .append(CACHE_USE_HEADER_SET_ERROR_MSG)
                        .toString();
                }
            }
        }

        // If another invalid cache header has already been set, throw 
        // an exception.
        if (exMsg != null)
        {
            IllegalStateException ise = new IllegalStateException(exMsg);
            LogManager.throwing(exMsg, ise);
            throw ise;
        }

        LogManager.debug(new SimpleStringBuffer(100)
                             .append("Set cache header, ")
                             .append(headerName)
                             .append(":")
                             .append(headerValue)
                             .toString());

        // Set the header
        response.addHeader(headerName, headerValue);
    }

/* ***************************************************************************
 *
 *                Servlet covers
 *
 * ***************************************************************************/

    public static void sendRedirect(PortletRenderRequest pr, String url) throws IllegalStateException, IOException {
        HttpServletResponse response = (HttpServletResponse) pr.getAttribute(HttpCommonConstants.SERVLET_RESPONSE);

        if (response != null) {
            response.sendRedirect(url);
        } else {
            throw new IllegalStateException("HttpServletResponse not accessible");
        }
    }


    protected static String concatPaths(String root, String relative, String pathSep) {
        int sLen = root.length();
        boolean pathSuffixed = root.charAt(sLen - 1) == pathSep.charAt(0);
        boolean pathPrefixed = relative.charAt(0) == pathSep.charAt(0);

        if (sLen == 1 && pathSuffixed && pathPrefixed) {
            return relative;
        } else if ((pathSuffixed && !pathPrefixed) || (!pathSuffixed && pathPrefixed)) {
            return root + relative;
        } else if (!pathSuffixed && !pathPrefixed) {
            return root + pathSep + relative;
        } else {
            // root path is suffixed and the relative path is prefixed
            return root.substring(0, sLen - 1) + relative;
        }
    }
}

