// Copyright (c) 1999 Your Company

package oracle.portal.provider.v1;

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.Method;
import oracle.portal.provider.v1.http.*;
import oracle.portal.provider.v1.resource.*;
import oracle.portal.utils.v1.MultiByteEncoder;
import oracle.portal.utils.v1.MultiByteDecoder;
import oracle.portal.utils.v1.NameValuePair;
import oracle.portal.utils.v1.SimpleParameterMap;
import oracle.webdb.utils.SimpleStringBuffer;

/**
 * Contains utilities useful for rendering.
 */
public class PortletRendererUtil extends Object {

    public static final int PAGE_LINK = 0;
    public static final int DESIGN_LINK = 1;
    public static final int REQUEST_LINK = 2;
    public static final int LOGIN_LINK = 3;
    public static final int BACK_LINK = 4;

    public static final String CUSTOMIZE_FORM_NAME = "custform";


    /**
     * Container renderer to be used by portlet header/footer rendering methods
     * This is thread safe because this class does not maintain any state
     */
    private static DefaultContainerRenderer sContainerRenderer = new DefaultContainerRenderer();

    /**
     *  Utility method for rendering the Customize form.  This method should be
     *  used in conjunction with renderCustomizeFormFooter() to work correctly.
     *  This routine emits the proper html FORM tag, appropriate hidden fields,
     *  the standard look/feel for the Form container, and the customize form
     *  'APPLY', 'OK', and 'Cancel' button.  A submitted form (via 'OK' or 'APPLY')
     *  will recall the same portlet renderer with the same mode (EDIT or EDIT_DEFAULTS)
     *  passing the action in the hidden field named via <i>actionParam</i>.  Clients
     *  check the value of <i>actionParam</i> to determine if an 'OK' or 'APPLY' was
     *  clicked. A cancelled form isn't resubmitted to the renderer.  Rather the "Cancel" button
     *  links directly back to the page from whence the customize was invoked.
     *
     *  @param request the portlet render request.
     *  @param writer the response writer used to output the customize form header.
     *  @param title the to display in the header (titlebar)
     *  @param actionParam the name of the hidden field that will hold the value
     *  that indicates which button was clicked.  The value is 'OK' if the OK button
     *  was clicked.  The value is 'APPLY' if the APPLY button is clicked
     *  @param additionalHidden a String containing the HTML for additional hidden fields
     *  needed by the client.
     *  @param helpURL  a URL to this customize form's help screen.  If null the help link
     *  is not displayed.
     *
     * @deprecated use {@link PortletContainerRenderer#renderPortletHeader()}

     */
    public static void renderCustomizeFormHeader(PortletRenderRequest request,
                                                 Writer writer,
                                                 String title,
                                                 String actionParam,
                                                 String additionalHidden,
                                                 String helpURL)
        throws IOException
    {
        // NOTE: Original implementation did not do anything with the
        //       additionalHidden parameter. Consequently, this value is
        //       not passed into the container renderer

        PrintWriter out = getPrintWriter(request, writer);
        SimpleParameterMap params = new SimpleParameterMap();
        params.putParameterValue(PortletContainerRenderer.RENDER_CUSTOMIZE,
                                 new Boolean(true));

        if (title != null)
        {
            params.putParameterValue(PortletContainerRenderer.PORTLET_TITLE, title);
        }

        if (actionParam != null)
        {
            params.putParameterValue(PortletContainerRenderer.ACTION_PARAM_NAME,
                                     actionParam);
        }

        if (helpURL != null)
        {
            params.putParameterValue(PortletContainerRenderer.HELP_URL, helpURL);
        }

        try
        {
            getContainerRenderer(request).renderEditHeader(request, out, params);
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  Utility method for completing the rendering of the Customize form.
     *  This method should be
     *  used in conjunction with renderCustomizeFormHeader() to work correctly.
     *  This routine emits the proper html tags to close the FORM including
     *  the standard look/feel for the Form container.
     *
     *  @param request the portlet render request.
     *  @param writer the response writer used to output the customize form header.
     *
     * @deprecated use {@link PortletContainerRenderer#renderPortletFooter()}

     */
    public static void renderCustomizeFormFooter(PortletRenderRequest request,
                                                 Writer writer)
        throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);

        SimpleParameterMap params = new SimpleParameterMap();
        params.putParameterValue(PortletContainerRenderer.RENDER_CUSTOMIZE,
                                 new Boolean(true));

        try
        {
            getContainerRenderer(request).renderEditFooter(request, out, params);
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  Utility method for rendering the About/Help form.  This method should be
     *  used in conjunction with renderInformationFormFooter() to work correctly.
     *  This routine emits the proper html FORM tag, appropriate hidden fields,
     *  the standard look/feel for the Form container, and the customize form
     *  'Close' button.
     *
     * @deprecated use {@link DefaultContainerRenderer#renderInformationFormHeader()}
     */
    public static void renderInformationFormHeader(PortletRenderRequest request,
                                                   Writer writer,
                                                   String formName,
                                                   String title,
                                                   String actionParam,
                                                   String helpURL)
        throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);
        SimpleParameterMap params = new SimpleParameterMap();

        if (title != null)
        {
            params.putParameterValue(PortletContainerRenderer.PORTLET_TITLE, title);
        }

        if (formName != null)
        {
            params.putParameterValue(PortletContainerRenderer.EDIT_FORM_NAME,
                                     formName);
        }

        if (actionParam != null)
        {
            params.putParameterValue(PortletContainerRenderer.ACTION_PARAM_NAME,
                                     actionParam);
        }

        if (helpURL != null)
        {
            params.putParameterValue(PortletContainerRenderer.HELP_URL, helpURL);
        }

        try
        {
            PortletContainerRenderer pcr = getContainerRenderer(request);

            // renderInformationFormFooter() is a protected method of
            // DefaultContainerRenderer and it's subclasses. If the container
            // specified by the portlet is not a subclass of DefaultContainerRenderer
            // then substitute an instance of DefaultContainerRenderer
            if (pcr instanceof DefaultContainerRenderer)
            {
                // renderer is, or is derived from, DefaultContainerRenderer.
                // cast to DefaultContainerRenderer so we can all the appropriate
                // method
                DefaultContainerRenderer defaultPcr = (DefaultContainerRenderer)pcr;
                defaultPcr.renderInformationFormHeader(request, out, params);
            }
            else
            {
                // portlet's container renderer is not derived from
                // DefaultContainerRenderer so substitute use an instance of
                // DefaultContainerRenderer
                sContainerRenderer.renderInformationFormHeader(request,
                                                               out,
                                                               params);
            }
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  Utility method for completing the rendering of the Help/About information form.
     *  This method should be
     *  used in conjunction with renderInformationFormHeader() to work correctly.
     *  This routine emits the proper html tags to close the FORM including
     *  the standard look/feel for the Form container.
     *
     *  @param request the portlet render request.
     *  @param writer the response writer used to output the customize form header.
     *
     * @deprecated use {@link DefaultContainerRenderer#renderInformationFormFooter()}
     */
    public static void renderInformationFormFooter(PortletRenderRequest request,
                                                   Writer writer)
        throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);
        SimpleParameterMap params = new SimpleParameterMap();

        try
        {
            PortletContainerRenderer pcr = getContainerRenderer(request);

            // renderInformationFormFooter() is a protected method of
            // DefaultContainerRenderer and it's subclasses. If the container
            // specified by the portlet is not a subclass of DefaultContainerRenderer
            // then substitute an instance of DefaultContainerRenderer
            if (pcr instanceof DefaultContainerRenderer)
            {
                // renderer is, or is derived from, DefaultContainerRenderer.
                // cast to DefaultContainerRenderer so we can all the appropriate
                // method
                DefaultContainerRenderer defaultPcr = (DefaultContainerRenderer)pcr;
                defaultPcr.renderInformationFormFooter(request, out, params);
            }
            else
            {
                // portlet's container renderer is not derived from
                // DefaultContainerRenderer so substitute use an instance of
                // DefaultContainerRenderer
                sContainerRenderer.renderInformationFormFooter(request,
                                                               out,
                                                               params);
            }
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  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).
     *
     *  @param request the portlet render request
     *  @param title the titlebar label.  If <b>null</b> the portlet's static title is used.
     *
     * @deprecated use {@link PortletContainerRenderer#renderPortletHeader()}
     */
    public static void renderPortletHeader(PortletRenderRequest request,
                                           Writer writer,
                                           String title)
        throws IOException
    {
        renderPortletHeader(request, writer, title, PortletRenderer.MODE_SHOW);
    }

    /**
     *  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).
     *
     *  @param request the portlet render request
     *  @param title the titlebar label.  If <b>null</b> the portlet's static title is used.
     *  @param renderMode the mode currently being rendered. Used to control
     *                    rendering of the title bar so it does not include
     *                    active links in preview mode
     *
     * @deprecated use {@link PortletContainerRenderer#renderPortletHeader()}
     */
    public static void renderPortletHeader(PortletRenderRequest request,
                                           Writer writer,
                                           String title,
                                           int renderMode) throws IOException
    {
        SimpleParameterMap params = new SimpleParameterMap();
        if (writer != null)
        {
            PrintWriter printWriter = getPrintWriter(writer);
            params.putParameterValue(PortletContainerRenderer.PRINT_WRITER, 
                                     printWriter);
        }
        
        if (title != null)
        {
            params.putParameterValue(PortletContainerRenderer.PORTLET_TITLE, 
                                     title);
        }

        params.putParameterValue(PortletContainerRenderer.RENDER_MODE, 
                                 new Integer(renderMode));
                                 
        try
        {
            PortletContainerRenderer pcr = getContainerRenderer(request);
            pcr.renderPortletHeader(request, params);
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  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).
     *
     *  @param request the portlet render request
     *
     * @deprecated use {@link DefaultContainerRenderer#renderPortletFooter()}
     */
    public static void renderPortletFooter(PortletRenderRequest request,
                                           Writer writer)
        throws IOException
    {
        SimpleParameterMap params = new SimpleParameterMap();
        if (writer != null)
        {
            PrintWriter printWriter = getPrintWriter(writer);
            params.putParameterValue(PortletContainerRenderer.PRINT_WRITER, 
                                     printWriter);
        }
        
        try
        {
            PortletContainerRenderer pcr = getContainerRenderer(request);
            pcr.renderPortletFooter(request, params);
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  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
     *  wil rely on renderPortletBegin() to render the entire container including the
     *  titlebar.  If however, renderPortletBegin is not used, this method is
     *  provided for convenience.
     *
     *  @param request the portlet render request
     *  @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(PortletRenderRequest request,
                                             Writer writer,
                                             String title) throws IOException
    {
        renderPortletTitlebar(request, writer, title, PortletRenderer.MODE_SHOW);
    }

    /**
     *  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
     *  wil rely on renderPortletBegin() to render the entire container including the
     *  titlebar.  If however, renderPortletBegin is not used, this method is
     *  provided for convenience.
     *
     *  @param request the portlet render request
     *  @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(PortletRenderRequest request,
                                             Writer writer,
                                             String title,
                                             int renderMode) throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);
        SimpleParameterMap params = new SimpleParameterMap();

        try
        {
            if (title != null)
            {
                params.putParameterValue(PortletContainerRenderer.PORTLET_TITLE, title);
            }


            PortletContainerRenderer pcr = getContainerRenderer(request);

            // renderPortletTitlebar() is a protected method of
            // DefaultContainerRenderer and it's subclasses. If the container
            // specified by the portlet is not a subclass of DefaultContainerRenderer
            // then substitute an instance of DefaultContainerRenderer
            if (pcr instanceof DefaultContainerRenderer)
            {
                // renderer is, or is derived from, DefaultContainerRenderer.
                // cast to DefaultContainerRenderer so we can all the appropriate
                // method
                DefaultContainerRenderer defaultPcr = (DefaultContainerRenderer)pcr;
                defaultPcr.renderPortletTitlebar(request, out, params);
            }
            else
            {
                // portlet's container renderer is not derived from
                // DefaultContainerRenderer so substitute use an instance of
                // DefaultContainerRenderer
                sContainerRenderer.renderPortletTitlebar(request,
                                                         out,
                                                         params);
            }
        }
        catch (ContainerException e)
        {
            // can't throw PortletException or AccessControlException (both
            // derived from ContainerException) w/o breaking backwards
            // compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     *  Create a parameterized link for a portlet detail page
     *
     *  By default, a portlet can have a single "detail" page that allows you
     *  to view portlet information in a full-screen mode. This methods allows
     *  you to create many "detail" links with different sets of parameters.
     *  You can use these links anywhere in the body of your portlet to create
     *  a portlet that has more than one detail page.
     *
     *  When a user clicks on a detail link within your portlet, your portlet
     *  will be called and the renderer associated with "detail" mode will be
     *  invoked. You can use the parameters you add to the detail link to
     *  determine which "detail page" the renderer should emit.
     *
     *  @param pr the render request.
     *  @param params an array of NameValuePair objects representing the
     *                parameter names and associated values. Names cannot be NULL
     *  @param encodeParams flag indicating whether the parameter names and values
     *                      should be URL encoded using the multibyte URL encoder.
     */
    public static String constructDetailLink(PortletRenderRequest pr,
                                             NameValuePair[] params,
                                             boolean encodeParams,
                                             boolean replaceParams)
        throws PortletException
    {
        try
        {
            // add parameter for SHOW mode
            NameValuePair[] linkParams = new NameValuePair[params.length + 2];
            int i;
            for (i=0; i < params.length; i++)
            {
                linkParams[i] = params[i];
            }
            linkParams[i++] = new NameValuePair("_mode",
                                          Integer.toString(PortletRenderer.MODE_SHOW_DETAILS));
            linkParams[i] = new NameValuePair("_title", getTitle(pr));
            return constructLink(pr,
                                 pr.getDesignURL(),
                                 linkParams,
                                 encodeParams,
                                 replaceParams);
        }
        catch (Exception e)
        {
            throw new PortletException(e);
        }
    }

    /**
     *  Create a parameterized link for a portlet help page
     *
     *  By default, a portlet can have a single "help" page that allows you
     *  to display help information for your portlet in a full-screen mode.
     *  This methods allows you to create many "help" links with different
     *  sets of parameters. You can use these links anywhere in the body of
     *  your portlet to create context sensitive help pages.
     *
     *  When a user clicks on a help link within your portlet, your portlet
     *  will be called and the renderer associated with "help" mode will be
     *  invoked. You can use the parameters you add to the detail link to
     *  determine which "help page" the renderer should emit.
     *
     *  @param pr the render request.
     *  @param params an array of NameValuePair objects representing the
     *                parameter names and associated values. Names cannot be NULL
     *  @param encodeParams flag indicating whether the parameter names and values
     *                      should be URL encoded using the multibyte URL encoder.
     */
    public static String constructHelpLink(PortletRenderRequest pr,
                                           NameValuePair[] params,
                                           boolean encodeParams,
                                           boolean replaceParams)
        throws PortletException
    {
        try
        {
            // add parameter for SHOW mode
            NameValuePair[] linkParams = new NameValuePair[params.length + 2];
            int i;
            for (i=0; i < params.length; i++)
            {
                linkParams[i] = params[i];
            }
            linkParams[i++] = new NameValuePair("_mode",
                                          Integer.toString(PortletRenderer.MODE_HELP));
            linkParams[i] = new NameValuePair("_title", getTitle(pr));

            return constructLink(pr,
                                 pr.getDesignURL(),
                                 linkParams,
                                 encodeParams,
                                 replaceParams);
        }
        catch (Exception e)
        {
            throw new PortletException(e);
        }
    }

    /**
     * Generic utility to append parameters to a link.
     *
     * This method DOES NOT overwrite any existing parameters. If parameters
     * are duplicated, both values will be available via getParameterValues()
     *
     * If you need parameters to be substituted rather than simply added, you
     * should use one of the <code>parameterizeLink</code> methods
     *
     * @param pr render request
     * @param link the link to be parameterized
     * @param mode the portlet mode to be associated with this link
     * @param params array of NameValuePair objects representing the parameters
     * @param encodeParams flag indicating whether parameter names and values
     *                     should be encoded
     * @param replaceParams flag indicating whether parameters contained in the
     *                      params array should replace existing parameters of
     *                      the same name. true = replace or overwrite existing
     *                      parameters. false = simply add parameters
     *
     * @return original link with additional parameters.
     */
    public static String constructLink(PortletRenderRequest pr,
                                       String link,
                                       NameValuePair[] params,
                                       boolean encodeParams,
                                       boolean replaceParams)
        throws IllegalArgumentException, IOException
    {
        SimpleStringBuffer newLink = new SimpleStringBuffer(200);
        int qsStart = link.indexOf('?');

        if (qsStart == -1)
        {
            // base link has no parameters
            newLink.append(link);
            newLink.append("?");
        }
        else
        {
            // base link has one or more parameters
            if (replaceParams)
            {
                // separate the original link into base URL + query string
                newLink.append(link.substring(0, qsStart + 1));
                String qString = link.substring(qsStart + 1);

                // remove duplicate parameters from the base link 
                String temp = removeQueryStringParams(qString, params);
                if (temp != null && !temp.equals(""))
                {
                    // add remaining unique parameters
                    newLink.append(temp);
                    newLink.append("&");
                }
            }
            else
            {
                // simply append query
                newLink.append(link);
                newLink.append("&");
            }
        }

        // append all the new parameters from the array
        String paramName;
        for(int i=0; i< params.length; i++)
        {
            paramName = params[i].getName();
            if (paramName == null)
            {
                throw new IllegalArgumentException("Parameter name cannot be NULL");
            }

            // code above will have appended either ? or & based as necessary
            // so first time through, do no add a separator
            if (i > 0)
            {
                newLink.append("&");
            }

            if (encodeParams)
            {
                newLink.append(encodeParameter(paramName, pr));
                newLink.append("=");
                newLink.append(encodeParameter(params[i].getValue(), pr));
            }
            else
            {
                newLink.append(paramName);
                newLink.append("=");
                newLink.append(params[i].getValue());
            }
        }
        return newLink.toString();
    }


    /**
     *  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 is they exist.  I.e. existing (queryString) parameters
     *  will be removed if it duplicates (by name) a new parameter.
     *
     *  @param pr the 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(PortletRenderRequest pr, int linkType, String queryString) throws IllegalArgumentException {
        switch (linkType) {
        case PAGE_LINK:
            return parameterizeLink(pr.getPageURL(), queryString);
        case DESIGN_LINK:
            return parameterizeLink(pr.getDesignURL(), queryString);
        case REQUEST_LINK:
            return parameterizeLink(pr.getRequestURL(), queryString);
        case LOGIN_LINK:
            return parameterizeLink(pr.getLoginServerURL(), queryString);
        case BACK_LINK:
            return parameterizeLink(pr.getBackURL(), queryString);
        default:
            throw new IllegalArgumentException("Invalid linkType: " + linkType);
        }
    }

    /**
     *  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 is they exist.  I.e. existing (queryString) parameters
     *  will be removed if it duplicates (by name) a new parameter.
     *
     *  @param link the link to add parameters to.
     *  @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(String link, String queryString) 
    {
        SimpleStringBuffer newLink = new SimpleStringBuffer(200);
        int qsStart = link.indexOf('?');

        if (qsStart == -1)
        {
            // base link has no parameters
            newLink.append(link);
            newLink.append("?");
        }
        else
        {
            // separate the original link into base URL + query string
            newLink.append(link.substring(0, qsStart + 1));
            String qString = link.substring(qsStart + 1);

            // remove duplicate parameters from the base link 
            String temp = removeQueryStringParams(qString, queryString);
            if (temp != null && !temp.equals(""))
            {
                // add remaining unique parameters
                newLink.append(temp);
                newLink.append("&");
            }
       }
       newLink.append(queryString);
       return newLink.toString();
    }

    /**
     *  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 allows you to properly
     *  encode a relative path.  Note: this is equivalent to portletLink().  I.e
     *  the method only differs when the renderer is reached via a "forward" as
     *  is the case with a servlet or JSP.
     *
     *  @param pr the PortletRenderRequest
     *  @param relativePath the path we want to make absolute
     */
    public static String absoluteLink(PortletRenderRequest pr, String relativePath) {
        String requestPath = pr.getRequestURL();

        int last = requestPath.indexOf('?');
        last = requestPath.lastIndexOf('/', last);

        if (relativePath.charAt(0) == '/') {
            return requestPath.substring(0, last) + relativePath;
        } else {
            return requestPath.substring(0, last + 1) + relativePath;
        }
    }

    /**
     *  Transforms a URI into the appropriate form to be used in a FORM action tag.
     *
     *  @param ref the URI to be transformed
     */
    public static String htmlFormActionLink(String ref) {
        if (ref == null) return null;

        int last = ref.indexOf('?');

        if (last != -1) {
            return ref.substring(0, last);
        } else {
            return ref;
        }
    }

    /**
     *  Transforms one of the various URIs known to the request into the appropriate
     *  form to be used in a FORM action tag.
     *
     *  @param pr the PortletRenderRequest
     *  @param linkType  the link in this request that should be used.
     */
    public static String htmlFormActionLink(PortletRenderRequest pr, int linkType) throws IllegalArgumentException {
        switch (linkType) {
        case PAGE_LINK:
            return htmlFormActionLink(pr.getPageURL());
        case DESIGN_LINK:
            return htmlFormActionLink(pr.getDesignURL());
        case REQUEST_LINK:
            return htmlFormActionLink(pr.getRequestURL());
        case LOGIN_LINK:
            return htmlFormActionLink(pr.getLoginServerURL());
        case BACK_LINK:
            return htmlFormActionLink(pr.getBackURL());
        default:
            throw new IllegalArgumentException("Invalid linkType: " + linkType);
        }
    }

    /**
     *  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.
     *
     *  @param pr the request
     *  @param linkType the link to use.  Constants representing these types are defined in PortletRendererUtil.
     */
    public static String htmlFormHiddenFields(PortletRenderRequest pr, int linkType) throws IllegalArgumentException {
        switch (linkType) {
        case PAGE_LINK:
            return htmlFormHiddenFields(pr.getPageURL());
        case DESIGN_LINK:
            return htmlFormHiddenFields(pr.getDesignURL());
        case REQUEST_LINK:
            return htmlFormHiddenFields(pr.getRequestURL());
        case LOGIN_LINK:
            return htmlFormHiddenFields(pr.getLoginServerURL());
        case BACK_LINK:
            return htmlFormHiddenFields(pr.getBackURL());
        default:
            throw new IllegalArgumentException("Invalid linkType: " + linkType);
        }
    }

    /**
     *  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.
     *
     *  @param ref the URI
     */
    public static String htmlFormHiddenFields(String ref) {
        String hidden = "";

        if (ref != null) {

            int last = ref.indexOf('?');

            if (last != -1) {
                hidden = emitHiddenFields(ref.substring(last + 1));
            }
        }

        return hidden;
    }



    /**
     *  Returns the name of the render mode given its value.
     *
     *  @param mode render mode value
     *  @return render mode name
     */
    public static String getModeName(int mode) throws IllegalArgumentException {
        switch (mode) {
        case PortletRenderer.MODE_SHOW:
            return PortletRenderer.MODE_NAME_SHOW;
        case PortletRenderer.MODE_ABOUT:
            return PortletRenderer.MODE_NAME_ABOUT;
        case PortletRenderer.MODE_EDIT:
            return PortletRenderer.MODE_NAME_EDIT;
        case PortletRenderer.MODE_HELP:
            return PortletRenderer.MODE_NAME_HELP;
        case PortletRenderer.MODE_EDIT_DEFAULTS:
            return PortletRenderer.MODE_NAME_EDIT_DEFAULTS;
        case PortletRenderer.MODE_SHOW_DETAILS:
            return PortletRenderer.MODE_NAME_SHOW_DETAILS;
        case PortletRenderer.MODE_PREVIEW:
            return PortletRenderer.MODE_NAME_PREVIEW;
        case PortletRenderer.MODE_LINK:
            return PortletRenderer.MODE_NAME_LINK;
        default:
            throw new IllegalArgumentException("Invalid render mode parameter");
        }
    }

    /**
     *  Returns the render mode for the given render mode name.
     *
     *  @param name render mode name
     *  @return the render mode
     */
    public static int getModeValue(String name) throws IllegalArgumentException {
        if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_SHOW)) {
            return PortletRenderer.MODE_SHOW;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_ABOUT)) {
            return PortletRenderer.MODE_ABOUT;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_EDIT)) {
            return PortletRenderer.MODE_EDIT;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_HELP)) {
            return PortletRenderer.MODE_HELP;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_EDIT_DEFAULTS)) {
            return PortletRenderer.MODE_EDIT_DEFAULTS;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_SHOW_DETAILS)) {
            return PortletRenderer.MODE_SHOW_DETAILS;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_PREVIEW)) {
            return PortletRenderer.MODE_PREVIEW;
        } else if (name.equalsIgnoreCase(PortletRenderer.MODE_NAME_LINK)) {
            return PortletRenderer.MODE_LINK;
        } else {
            throw new IllegalArgumentException(name + " isn't a valid render mode name.");
        }
    }

    /**
     *  Utility for getting the customization data for a particular edit request.
     *  Hides the details of when to use the system/default vs. user customization.
     *
     *  @param pr the request.
     */

    public static Object getEditData(PortletRenderRequest pr) 
        throws IOException, AccessControlException 
    {
        // get ProviderLog and debug enabled flag
        Provider p = pr.getProvider();
        ProviderLog logger = null;
        boolean debug = false;
        if (p instanceof ProviderXtra2)
        {
            ProviderXtra2 px = (ProviderXtra2)p;
            logger = px.getProviderLog();
            debug = (px.isDebugEnabled() && (logger != null));
        }

        // get the Edit Data
        PortletPersonalizationManager pMgr = pr.getPersonalizationManager();
        if (pMgr == null) return null;

        ProviderUser user = pr.getUser();
        PortletReference ref = pr.getPortletReference();

        Object obj = null;
        if (!ref.isDefault()) 
        {
            obj = getUserEditData(pMgr, ref, user, logger, debug);
        } 
        else if (ref.isLanguageDefault()) 
        {
            obj = getLanguageDefaultEditData(pMgr, ref, user, logger, debug);
        } 
        else 
        {
            obj = getDefaultEditData(pMgr, ref, user, logger, debug);
        }

        // dump out whatever we got back
        if (debug)
        {
            if (obj == null)
            {
                logger.log("PortletRendererUtil.getEditData() - " +
                           "no edit data returned");
            }
            else
            {
                // NameValuePersonalizationObject implements toString to dump
                // all elements.  Other objects may/may not produce such detailed
                // output
                logger.log("PortletRendererUtil.getEditData() - " + 
                           "Edit data = " + obj.toString());
            }
        }

        return obj;
    }

    /**
    * Convenience method to display a single error message using the 
    * standard Portal style sheet
    */
    public static void renderError(PortletRenderRequest request,
                                   Writer writer, 
                                   Locale locale, 
                                   String errorText) throws IOException 
    {
        renderErrorHeader(request, writer);
        renderErrorLine(request, writer, locale, errorText);
        renderErrorFooter(request, writer);
    }

    /**
    * Utility to display a standard header before any error text.
    * Use in conjunction with <code>renderErrorHeader</code> and <code>renderErrorLine</code>
    */
    public static void renderErrorHeader(PortletRenderRequest request, Writer writer) throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);
        out.println("<BR>");
        out.println("<TABLE BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"0\" WIDTH=\"60%\" ALIGN=\"CENTER\">");
    }

    /**
    * Utility to display a single error message using the standard Portal style sheet.
    * Use in conjunction with <code>renderErrorHeader()</code> and
    * <code>renderErrorFooter()</code> when you need to display multiple error messages.
    */
    public static void renderErrorLine(PortletRenderRequest request,
                                       Writer writer,
                                       Locale locale,
                                       String errorText) throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);

        // FIXME - change to use styles from CSS when they are available.
        // CSS used by portal pages does not include OraErrorHeader or
        // OraErrorText which are used in other Portal pages when an error
        // is displayed

        out.println("<TR>");
        out.println("<TD ALIGN=\"RIGHT\">");
        out.println("<FONT color=\"#FF0000\" face=\"arial,helvetica\">");
        out.println("<B>");

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

        out.println("</B>");
        out.println("&nbsp");
        out.println("</FONT>");
        out.println("</TD>");
        
        out.println("<TD ALIGN=\"LEFT\">");
        out.println("<FONT face=\"arial,helvetica\">");
        out.println(errorText);
        out.println("</FONT>");
        out.println("</TD>");
        out.println("</TR>");
    }


    /**
    * Utility to display a standard footer for error messages.  Use in conjunction with
    * <code>renderErrorHeader</code> and <code>renderErrorLine</code>
    */
    public static void renderErrorFooter(PortletRenderRequest request, Writer writer) throws IOException
    {
        PrintWriter out = getPrintWriter(request, writer);
        out.println("</TABLE>");
        out.println("<BR>");
    }


    /**
     * gets the default title for a portlet based on the mode being rendered
     */
    public static String getModeTitle(PortletRenderRequest request) {
        ResourceBundle res = request.getProviderResource();
        switch (request.getMode()) {
            case PortletRenderer.MODE_HELP:
                return res.getString(ProviderBundle.HELP_LABEL);
            case PortletRenderer.MODE_ABOUT:
                return res.getString(ProviderBundle.ABOUT_LABEL);
            case PortletRenderer.MODE_EDIT:
            case PortletRenderer.MODE_EDIT_DEFAULTS:
                return res.getString(ProviderBundle.CUSTOMIZE_LABEL);
            default:
                return res.getString(ProviderBundle.GENERAL_LABEL);
        }
    }

    /**
     *  Utility for submitting the customization data for a particular edit request.
     *  Takes care of creating the customization if necessary.
     *
     *  @param pr the request.
     *  @param o object holding the customization data.
     */

    public static void submitEditData(PortletRenderRequest pr, Object o) throws IOException, AccessControlException {
        PortletPersonalizationManager pMgr = pr.getPersonalizationManager();
        if (pMgr == null) return;

        int renderMode = pr.getMode();
        ProviderUser user = pr.getUser();
        PortletReference ref = pr.getPortletReference();

        try {
            pMgr.write(ref, o, user);
        } catch (PortletNotFoundException pnfe) {
            // create the customization
            try {
                pMgr.create(ref, o, user);
            } catch (PortletAlreadyExistsException paee) {
                // did we have a race condition
                try {
                    pMgr.write(ref, o, user);
                } catch (PortletNotFoundException apnfe) {
                    // should never get here
                }
            }
        }
    }

    /**
     *  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.
     *
     *  @param request  the current request.
     *  @param param the name of the parameter to be qualified.
     */

     public static String portletParameter(PortletRenderRequest request, String param) {
        return portletParameter(request.getPortletReference(), param);
    }

    /**
     *  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.
     *
     *  @param ref  the current portlet reference used to qualify the parameter name.
     *  @param param the name of the parameter to be qualified.
     */
    public static String portletParameter(PortletReference ref, String param) {
        return ref.getProviderId() + "." + ref.getPortletId() + "." + ref.getName() + "." + param;
    }

    /**
     * Retrieves the {@link RenderManager} defaulted/set customize form action
     * parameter name. If this Portlet does not use {@link RenderManager} as
     * its {@link PortletRenderer}, null is returned.
     *
     * @param pr a {@link PortletRenderRequest}
     * @return edit form parameter name.
     */
    public static String getEditFormParameter(PortletRenderRequest pr)
    {
        PortletRenderer r = pr.getPortlet().getRenderer();
        if (!(r instanceof RenderManager))
        {
            return null;
        }
        else
        {
            return ((RenderManager)r).getEditFormParameter();
        }
    }

    /**
     * Renders the appropriate header based on the render mode contained within
     * the {@link PortletRenderRequest}. Setting <code>renderCustomize</code>
     * to <code>false</code> will give the developer control over rendering the
     * header for the <i>edit</i> and <i>edit defaults</i> modes. If the
     * developer chooses to let this method render the customize headers, a
     * String must be supplied for the action parameter.
     *
     * @param pr a {@link PortletRenderRequest}.
     * @param renderCustomize boolean flag indicating whether or not to render
     * the header for the customize modes.
     * @param param the name of the action parameter for the customize forms.
     *
     * @deprecated use {@link PortletContainerRenderer#renderPortletHeader(
     *                        PortletRenderRequest, ParameterMap)}
     */
    public static void renderPortletHeader(PortletRenderRequest request,
                                           boolean renderCustomize,
                                           String param)
        throws AccessControlException, IOException
    {
        SimpleParameterMap params = new SimpleParameterMap();

        params.putParameterValue(PortletContainerRenderer.RENDER_CUSTOMIZE,
                                 new Boolean(renderCustomize));

        if (param != null)
        {
            params.putParameterValue(PortletContainerRenderer.ACTION_PARAM_NAME,
                                     param);
        }

        try
        {
            PortletContainerRenderer pcr = getContainerRenderer(request);
            pcr.renderPortletHeader(request, params);
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Renders the appropriate footer based on the render mode contained within
     * the {@link PortletRenderRequest}. Setting <code>renderCustomize</code>
     * to <code>false</code> will give the developer control over rendering the
     * footer for the <i>edit</i> and <i>edit defaults</i> modes.
     *
     * @param request a {@link PortletRenderRequest}.
     * @param renderCustomize boolean flag indicating whether or not to render
     * the footer for the customize modes.
     *
     * @deprecated use {@link PortletContainerRenderer#renderPortletFooter()}
     */
    public static void renderPortletFooter(PortletRenderRequest request,
                                           boolean renderCustomize)
        throws AccessControlException, IOException
    {
        SimpleParameterMap params = new SimpleParameterMap();
        params.putParameterValue(PortletContainerRenderer.RENDER_CUSTOMIZE,
                                 new Boolean(renderCustomize));

        try
        {
            PortletContainerRenderer pcr = getContainerRenderer(request);
            pcr.renderPortletFooter(request, params);
        }
        catch (PortletException e)
        {
            // can't throw PortletException w/o breaking backwards compatibility.
            // get as much info from exception as we can & pass it on.
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Retrieves the title of this portlet. If a customized title exists for
     * the current user, this is returned. If not, the default title for the
     * current locale is returned.
     *
     * @param pr a {@link PortletRenderRequest}.
     * @return the portlet's title.
     */
    public static String getTitle(PortletRenderRequest pr)
        throws AccessControlException
    {
        Locale l = pr.getLocale();
        try
        {
            Object o = getEditData(pr);
            if (o == null)
            {
                return pr.getPortlet().getTitle(l);
            }
            //Support both retrieving the title from the 'old' and 'new' data
            //object classes
            if (o instanceof NameValuePersonalizationObject)
            {
                return ((NameValuePersonalizationObject) o).getPortletTitle();
            }
            else if (o instanceof BaseCustomization)
            {
                return ((BaseCustomization) o).getTitle();
            }
        }
        catch (IOException e)
        {
            //use the declared title so just fall through
        }
        return pr.getPortlet().getTitle(l);
    }

    /**
     * Retrieves the short title of this portlet. If a customized short title
     * exists for the current user, this is returned. If not, the default title
     * for the current locale is returned.
     *
     * @param pr a {@link PortletRenderRequest}.
     * @return the portlet's title.  If there is no short title then <code>null
     * </code> is returned.
     */
    public static String getShortTitle(PortletRenderRequest pr)
        throws AccessControlException
    {
        Locale l = pr.getLocale();
        Portlet p = pr.getPortlet();
        try
        {
            Object o = getEditData(pr);
            if (o == null)
            {
                if (p instanceof PortletXtra2)
                    return ((PortletXtra2) p).getShortTitle(l);
                else
                    return null;
            }
            //Support both retrieving the title from the 'old' and 'new' data
            //object classes
            if (o instanceof NameValuePersonalizationObject)
            {
                return ((NameValuePersonalizationObject) o).getPortletShortTitle();
            }
            // no support for ShortTitle with BaseCustomization object as that
            // was depracted by the time short title was added.
        }
        catch (IOException e)
        {
            //use the declared title so just fall through
        }
        if (p instanceof PortletXtra2)
            return ((PortletXtra2) p).getShortTitle(l);
        else
            return null;
    }


    /**
    * Encodes a parameter for inclusion in a URL.  Parameter is encoded
    * using the same character set that was used when the portal encoded
    * the request parameters.  The character sets must be the same so that
    * parameters that may be intended for another portlet implemented in
    * pl/sql can decode them correctly.
    *
    * @param param the parameter to be encoded
    * @param pr PortletRenderRequest object for this request
    * @return encoded parameter, suitable for inclusion in a URL
    */
    public static String encodeParameter(String param,
                                         PortletRenderRequest pr)
        throws java.io.IOException
    {
        return MultiByteEncoder.encode(param, pr.getURLCharSet());
    }

    /**
     * Utility to strip the query parameters from a URL
     *
     * @param url the URL string to be stripped
     */
    public static String getURLPrefix(String url)
        throws java.io.IOException
    {
        String newRef;

        int i = url.indexOf('?');
        if (i > 0)
        {
            // strip off the parameters - they will be added as hidden fields
            newRef = url.substring(0, i);
        }
        else
        {
            newRef = url;
        }
        return newRef;
    }

    /**
     * Utility to emit a set of HTML hidden fields based on a query string
     *
     * @param queryString the list of parameters to be emitted in the form of
     *                    a URL query string
     * @return a string containing the HTML markup for the hidden fields
     */
    public static String emitHiddenFields(String queryString) {
        SimpleStringBuffer hidden = new SimpleStringBuffer(256);
        StringTokenizer qsST = new StringTokenizer(queryString, "&");

        while (qsST.hasMoreTokens()) {
            String param = qsST.nextToken();
            int i = param.indexOf('=');
            hidden.append(emitHiddenField(param.substring(0, i),
                                          param.substring(i+1)));
        }

        return hidden.toString();
    }

    /**
     * Utility to emit a single HTML hidden field with a specified name and
     * value.
     *
     * Note: because the inputs come from routines that split
     * up a query string the name/value are encoded and must
     * be decoded for the html because that is what the browser
     * expects and it will encode them when the form is submitted.
     *
     * @param name the name of the hidden field
     * @param value the value of the hidden field
     * @return a string containing the HTML hidden field markup
     */
    public static String emitHiddenField(String name, String value) {
        if (name == null || value == null) return "";
        SimpleStringBuffer buffer = new SimpleStringBuffer(100);
        buffer.append("<INPUT TYPE=\"hidden\" name=\"");
        buffer.append(MultiByteDecoder.decodeUTF(name));
        buffer.append("\" value=\"");
// code used to call private decode method - changed to use MultiByteDecoder
        buffer.append(MultiByteDecoder.decodeUTF(value));
        buffer.append("\" />");
        buffer.append((char) Character.LINE_SEPARATOR);
        return  buffer.toString();
    }


    private static String removeQueryStringParams(String currentQS,
                                                  String removeQS)
    {
        String outQS = null;
        StringTokenizer rST = new StringTokenizer(removeQS, "&");
        String[] params = new String[rST.countTokens()];

        int j = 0;
        while (rST.hasMoreTokens())
        {
            String param = rST.nextToken();
            int i = param.indexOf('=');
            params[j++] = param.substring(0, i);
        }
        return removeQueryStringParams(currentQS, params);
    }

    private static String removeQueryStringParams(String currentQS,
                                                  NameValuePair[] params)
    {
        String[] paramNames = new String[params.length];
        for (int i=0; i<params.length; i++)
        {
            paramNames[i] = params[i].getName();
        }
        return removeQueryStringParams(currentQS, paramNames);
    }

    private static String removeQueryStringParams(String currentQS,
                                                  String[] removeParams)
    {
        StringTokenizer cST = new StringTokenizer(currentQS, "&");
        SimpleStringBuffer outQS = new SimpleStringBuffer(200);
        boolean firstParam = true;
        boolean remove = false;

        String param = null;
        String paramName = null;
        while (cST.hasMoreTokens())
        {
            param = cST.nextToken();
            int i = param.indexOf('=');
            paramName = param.substring(0, i);

            remove = false;
            for (int j = 0; j < removeParams.length; j++)
            {
                if (removeParams[j].equals(paramName))
                {
                    remove = true;
                    break;
                }
            }

            if (!remove)
            {
                if (firstParam)
                {
                    outQS.append(param);
                    firstParam = false;
                }
                else
                {
                    outQS.append("&");
                    outQS.append(param);
                }
            }
        }

        String outStr = outQS.toString();
        if (outQS == null)
            return "";
        return outStr;
    }

    private static PrintWriter getPrintWriter(PortletRenderRequest request, 
                                              Writer writer) throws IOException 
    {
        if (writer == null) 
        {
            // getWriter returns a PrintWriter
            return request.getWriter();
        }

        return getPrintWriter(writer);
    }

    private static PrintWriter getPrintWriter(Writer writer)
    {
        if (writer instanceof PrintWriter)
        {
            return (PrintWriter)writer;
        }
        else
        {
            return new PrintWriter(writer);
        }
    }



    private static Object getDefaultEditData(PortletPersonalizationManager pMgr, 
                                             PortletReference ref, 
                                             ProviderUser user,
                                             ProviderLog logger,
                                             boolean debug) 
        throws IOException, AccessControlException 
    {
        if (!pMgr.exists(ref, user)) 
        {            
            if (debug)
            {
                logger.log("PortletRendererUtil.getDefaultEditData() - " +
                           "Default level edit data does not exist for: " +
                           ref.toString() +
                           ": Creating new default data set");
            }
            // There are situations that occur where the initial default ref doesn't exist
            // If we detect on read -- create.
            try 
            {
                pMgr.create(ref, user);
            } 
            catch (PortletAlreadyExistsException paee) 
            {
                // ignore -- we already exist -- happens because we are multithreaded
            }
        }

        try 
        {
            if (debug)
            {
                logger.log("PortletRendererUtil.getDefaultEditData() - " +
                           "Reading Default level edit data for: " +
                           ref.toString());
            }
            return pMgr.read(ref, user);
        } 
        catch (PortletNotFoundException pnfe) 
        {
            // should never get here
            pnfe.printStackTrace();
            return null;
        }
    }

    private static Object getLanguageDefaultEditData(PortletPersonalizationManager pMgr, 
                                                     PortletReference ref, 
                                                     ProviderUser user,
                                                     ProviderLog logger,
                                                     boolean debug) 
        throws IOException, AccessControlException 
    {
        if (!pMgr.exists(ref, user)) 
        {
            if (debug)
            {
                logger.log("PortletRendererUtil.getLanguageDefaultEditData() - " +
                           "Language level edit data does not exist for: " +
                           ref.toString() +
                           ": Trying default");
            }
            // then just use the DefaultEditData.
            ref = new PortletReference(ref.getProvider(), 
                                       ref.getPortlet(), 
                                       ref.getName(), 
                                       null, 
                                       ref.getDefaultLocale(), 
                                       ref.getDefaultLocale(), 
                                       false);
            return getDefaultEditData(pMgr, ref, user, logger, debug);
        }

        try 
        {
            if (debug)
            {
                logger.log("PortletRendererUtil.getLanguageDefaultEditData() - " +
                           "Reading Language level edit data for: " +
                           ref.toString());
            }
            return pMgr.read(ref, user);
        }
        catch (PortletNotFoundException pnfe) 
        {
            // should never get here
            pnfe.printStackTrace();
            return null;
        }
    }

    private static Object getUserEditData(PortletPersonalizationManager pMgr, 
                                          PortletReference ref, 
                                          ProviderUser user,
                                          ProviderLog logger,
                                          boolean debug)
        throws IOException, AccessControlException 
    {
        if (!pMgr.exists(ref, user)) 
        {
            if (debug)
            {
                logger.log("PortletRendererUtil.getUserEditData() - " +
                           "User level edit data does not exist for: " +
                           ref.toString() +
                           ": Trying Language level default");
            }
            // then just try the LanguageDefaultEditData.
            ref = new PortletReference(ref.getProvider(), 
                                       ref.getPortlet(), 
                                       ref.getName(), 
                                       null, 
                                       ref.getLocale(), 
                                       ref.getDefaultLocale(), 
                                       false);
            return getLanguageDefaultEditData(pMgr, ref, user, logger, debug);
        }

        try 
        {
            if (debug)
            {
                logger.log("PortletRendererUtil.getUserEditData() - " +
                           "Reading user level edit data for: " +
                           ref.toString());
            }
            return pMgr.read(ref, user);
        } 
        catch (PortletNotFoundException pnfe) 
        {
            // should never get here
            pnfe.printStackTrace();
            return null;
        }
    }

    private static PortletContainerRenderer getContainerRenderer(PortletRenderRequest pr)
    {
        PortletContainerRenderer pcr = null;
        Portlet p = pr.getPortlet();

        if (p instanceof PortletXtra3)
        {
            // Portlet implements the necessary interface so get the container
            // renderer from the portlet. This allows us to continue to support
            // 3.0.6 portlet containers
            PortletXtra3 p3 = (PortletXtra3)p;
            return p3.getContainerRenderer();
        }

        // return the static container renderer so we don't have to keep
        // instantiating new objects each time we need to render a header
        // or footer
        return sContainerRenderer;
    }
}

