
/* Copyright (c) 2001, Oracle Corporation. All rights reserved. */

package oracle.portal.provider.v2.url;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletResponse;

import oracle.portal.log.LogManager; 
import oracle.portal.provider.v2.PortletException;
import oracle.portal.provider.v2.render.PortletRenderer;
import oracle.portal.provider.v2.render.PortletRendererUtil;
import oracle.portal.provider.v2.render.PortletRenderRequest;
import oracle.portal.provider.v2.render.RenderContext;
import oracle.portal.provider.v2.http.HttpCommonConstants; 
import oracle.portal.utils.NameValue;
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.webdb.utils.HTTPUtils;

/**
 * Contains utility methods and constants for urls.
 */
public class UrlUtils
{

    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 int EVENT_LINK = 5;

    /**
     * Generic utility to build a HTML link.
     *
     * If UrlRewriting is being used it will generate 
     * <code>&lt;A onclick=javascriptMethod(params, replaceParams) 
     * otherHtmlAttributes&gt;displayText&lt;/A&gt;</code> 
     * otherwise 
     * <code>&lt;A href=link+params otherHtmlAttributes&gt;displayText
     * &lt;/A&gt;</code>
     * either way the generated HTML links behave in the same way.
     *
     * @param pr render request
     * @param linkType the link to use.  Constants representing these types are
     *                     defined in UrlUtils.
     * @param displayText text displayed for this link
     * @param otherHtmlAttributes other attributes for the HTML link. No need 
     *                     for pre or end spaces in the string.
     * @param params array of NameValue 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 A HTML link eg <code>&lt;A ...&gt;displayText&lt;/A&gt;</code>
     */
    public static String constructHTMLLink(PortletRenderRequest pr,
                                        int linkType,
                                        String displayText,
                                        String otherHtmlAttributes,
                                        NameValue[] params,
                                        boolean encodeParams,
                                        boolean replaceParams)
        throws IllegalArgumentException, IOException
    {
        RenderContext renderContext = pr.getRenderContext();
        SimpleStringBuffer newHTMLLink = new SimpleStringBuffer(200);
        newHTMLLink.append("<A ");
        if (renderContext.isRewriteUrls() &&
            (linkType == PAGE_LINK ||
             linkType == EVENT_LINK ||
             linkType == DESIGN_LINK ||
             linkType == BACK_LINK  ) )
        {
            newHTMLLink.append("href=\"#\" onClick=\"")
                            .append(constructJavascriptLink(pr, linkType,
                                     params, encodeParams, replaceParams))
                            .append("return false;")
                            .append("\"");
        }
        else
        {
            String baseUrl;
            switch (linkType) {
            case PAGE_LINK:
                baseUrl = pr.getRenderContext().getPageURL();
                break;
            case EVENT_LINK:
                baseUrl = pr.getRenderContext().getEventURL();
                break;
            case DESIGN_LINK:
                baseUrl = pr.getRenderContext().getDesignURL();
                break;
            case REQUEST_LINK:
                 baseUrl = pr.getRequestURL();
                break;
            case LOGIN_LINK:
                baseUrl = pr.getRenderContext().getLoginServerURL();
                break;
            case BACK_LINK:
                baseUrl = pr.getRenderContext().getBackURL();
                break;
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
            newHTMLLink.append("href=")
                            .append(constructLink(pr, baseUrl, params, 
                               encodeParams, replaceParams));
        }
        newHTMLLink.append(" ").append(otherHtmlAttributes).append(" >")
                    .append(displayText).append("</A>");

        return newHTMLLink.toString();
    }


    /**
     * Generic utility to generate a javascript call that append parameters
     * to a link.
     *
     * @param pr render request
     * @param linkType the link to use.  Constants representing these types
     *                     are defined in UrlUtils.
     * @param params array of NameValue 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 call to a javascript function that will append the parameters
     *                     to a link and call that link
     */
    public static String constructJavascriptLink(PortletRenderRequest pr,
                                                  int linkType,
                                                  NameValue[] params,
                                                  boolean encodeParams,
                                                  boolean replaceParams)
       throws IllegalArgumentException, IOException
    {
        SimpleStringBuffer newLink = new SimpleStringBuffer(200);   
        RenderContext renderContext = pr.getRenderContext();

        if ( renderContext.isRewriteUrls() && 
             (linkType == PAGE_LINK ||  linkType == EVENT_LINK || 
              linkType == DESIGN_LINK || linkType == BACK_LINK))
        {
            String portletRef = getPortletPageReference(pr);

            newLink.append("p").append(portletRef);

            switch (linkType) {
            case PAGE_LINK:
                newLink.append(".pageUrl");
                break;
            case EVENT_LINK:
                newLink.append(".eventUrl");
                break;
            case DESIGN_LINK:
                newLink.append(".designUrl");
                break;
            case BACK_LINK:
                newLink.append(".backUrl");
                break;
            }
            if ( linkType == BACK_LINK)
            {
              newLink.append("();");
            }
            else
            {        
              newLink.append("('") 
                     .append(constructParams(pr, params, encodeParams))
                     .append("','")
                     .append(replaceParams?"1":"0")
                     .append("');");
            }
        }
        else
        {
            String baseUrl;
            switch (linkType) {
            case PAGE_LINK:
                baseUrl = pr.getRenderContext().getPageURL();
                break;
            case EVENT_LINK:
                baseUrl = pr.getRenderContext().getEventURL();
                break;
            case DESIGN_LINK:
                baseUrl = pr.getRenderContext().getDesignURL();
                break;
            case REQUEST_LINK:
                 baseUrl = pr.getRequestURL();
                break;
            case LOGIN_LINK:
                baseUrl = pr.getRenderContext().getLoginServerURL();
                break;
            case BACK_LINK:
                baseUrl = pr.getRenderContext().getBackURL();
                break;
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
            newLink.append("location.href='")
                   .append(constructLink(pr, baseUrl, params, 
                                         encodeParams, replaceParams))
                   .append("'");
        }

        return newLink.toString();
    }

    /**
     * Generic utility to construct a unique name for a form on a portlet page
     *
     * The form name is  "f" + portletRef + formWithinPortletName
     * eg f73_35680_73_35673_35673
     *
     * @param pr render request
     * @param formWithinPortletName There may be more than one form per portlet
     *                     this name is used to destinguish between different 
     *                     forms within a portlet. This may be null or empty
     *                     if there is only one form in a portlet.
     * @return A form name that is unique on a portal page
     */
    public static String htmlFormName(PortletRenderRequest pr,
                                      String formWithinPortletName)
    {
        SimpleStringBuffer newName = new SimpleStringBuffer(40);   
        newName.append("f")
               .append(getPortletPageReference(pr));

        if (   formWithinPortletName != null 
                && formWithinPortletName.length() > 0 )
        {
            newName.append(formWithinPortletName);
        }
        return newName.toString();
    }


    private static String emitJavascriptHiddenFields(PortletRenderRequest pr,
                                                 int linkType,
                                                 String formName)
       throws IllegalArgumentException
    {
        SimpleStringBuffer newJavascript = new SimpleStringBuffer(100);   
        String portletRef = getPortletPageReference(pr);
        
        newJavascript.append("<SCRIPT>p");
        newJavascript.append(portletRef);
        newJavascript.append(".setFormAction(");
        
        switch (linkType) {
        case PAGE_LINK:
            newJavascript.append("'p'");
            break;
        case EVENT_LINK:
            newJavascript.append("'e'");
            break;
        case DESIGN_LINK:
            newJavascript.append("'d'");
            break;
        case BACK_LINK:
            newJavascript.append("'b'");
            break;
        default:
            throw new IllegalArgumentException("Invalid linkType: " + linkType);
        }

        newJavascript.append(", '");
        newJavascript.append(formName);
        newJavascript.append("');</SCRIPT>");

        return newJavascript.toString();
    }

    /**
     * Generic utility to append parameters to a link.
     *
     * @param pr render request
     * @param linkType the link to use.  Constants representing these types are
     *                     defined in UrlUtils.
     * @param mode the portlet mode to be associated with this link
     * @param params array of NameValue 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 or a javascript call
     *         to the original link with additional parameters. 
     */
    public static String constructLink(PortletRenderRequest pr,
                                        int linkType,
                                        NameValue[] params,
                                        boolean encodeParams,
                                        boolean replaceParams)
        throws IllegalArgumentException, IOException
    {
        RenderContext renderContext = pr.getRenderContext();
        if (renderContext.isRewriteUrls() &&
            (linkType == PAGE_LINK ||
             linkType == EVENT_LINK ||
             linkType == DESIGN_LINK ||
             linkType == BACK_LINK  ) )
        {
            SimpleStringBuffer link = new SimpleStringBuffer(200);
            link.append("javascript:")
                .append(constructJavascriptLink(pr, linkType,
                                     params, encodeParams, replaceParams));
            return link.toString();
        }
        else
        {
            switch (linkType) {
            case PAGE_LINK:
                return constructLink(pr, pr.getRenderContext().getPageURL(),
                                     params, encodeParams, replaceParams);
            case EVENT_LINK:
                return constructLink(pr, pr.getRenderContext().getEventURL(),
                                     params, encodeParams, replaceParams);
            case DESIGN_LINK:
                return constructLink(pr, pr.getRenderContext().getDesignURL(),
                                     params, encodeParams, replaceParams);
            case REQUEST_LINK:
                 return constructLink(pr, pr.getRequestURL(),
                                      params, encodeParams, replaceParams);
            case LOGIN_LINK:
                return constructLink(pr, 
                                     pr.getRenderContext().getLoginServerURL(),
                                     params, encodeParams, replaceParams);
            case BACK_LINK:
                return constructLink(pr, pr.getRenderContext().getBackURL(),
                                     params, encodeParams, replaceParams);
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
        }
    }
    /**
     * Generic utility to append parameters to a link.
     *
     * @param pr render request
     * @param link the link to be parameterized
     * @param params array of NameValue 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,
                                        NameValue[] 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);
                }
            }
            else
            {
                // simply append query
                newLink.append(link);
            }
        }
        
        if(newLink.charAt(newLink.length()-1) != '?')
        {
            newLink.append("&");
        }

        // append all the new parameters from the array
        newLink.append( constructParams(pr, params, encodeParams) );
       
        return newLink.toString();
    }

    /**
     *  Enables the parameters in the query string to be POSTED when the form 
     *  is submited. This code generates hidden parameters when rewriteUrls
     *  is false or by setting the action value of the form to be the whole 
     *  url when rewriteUrls is true.
     *
     *  This function can only be used if there are only one form on a portlet.
     *
     *  This utility method either generates the hidden field tags from this 
     *  that will alter the forms action to be the URL and returns the 
     *  javascript as a String.
     *
     *  @param pr the request
     *  @param linkType the link to use.  Constants representing these 
     *                            types are defined in UrlUtils.
     *  @param formName unique name of a form within a page. Use htmlFormName()
     *                            to generate this name.
     */
    public static String htmlFormHiddenFields(PortletRenderRequest pr, 
                                              int linkType) 
      throws IllegalArgumentException 
    {
       String formName = htmlFormName(pr, null);
      return htmlFormHiddenFields(pr, linkType, formName); 
    }

    /**
     *  Enables the parameters in the query string to be POSTED when the form 
     *  is submited. This code generates hidden parameters when rewriteUrls is
     *  false or by setting the action value of the form to be the whole url
     *  when rewriteUrls is true.
     *
     *  This utility method either generates the hidden field tags from this URL 
     *  and returns them as a String. Or generates a javascript fragment that will 
     *  alter the forms action to be the URL and returns the javascript as a String.
     *
     *  @param pr the request
     *  @param linkType the link to use.  Constants representing these types 
     *                                  are defined in PortletRendererUtil.
     *  @param formName unique name of a form within a page. Use htmlFormName()
                                        to generate this name.
     */
    public static String htmlFormHiddenFields(PortletRenderRequest pr, 
                                             int linkType, String formName) 
      throws IllegalArgumentException 
    {
        LogManager.debug("Enter htmlFormHiddenFields");

        RenderContext renderContext = pr.getRenderContext();
        if ( renderContext.isRewriteUrls() )
        {
            switch (linkType) {
            case REQUEST_LINK:
                 return htmlFormHiddenFields(pr.getRequestURL());
            case LOGIN_LINK:
                return htmlFormHiddenFields(
                               pr.getRenderContext().getLoginServerURL());
            case PAGE_LINK:
            case EVENT_LINK:
            case DESIGN_LINK:
            case BACK_LINK:
                return emitJavascriptHiddenFields(pr, linkType, formName);
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
        }
        else
        {
            switch (linkType) {
            case PAGE_LINK:
                return htmlFormHiddenFields(pr.getRenderContext().getPageURL());
            case EVENT_LINK:
                return htmlFormHiddenFields(pr.getRenderContext().getEventURL());
            case DESIGN_LINK:
                return htmlFormHiddenFields(pr.getRenderContext().getDesignURL());
            case REQUEST_LINK:
                 return htmlFormHiddenFields(pr.getRequestURL());
            case LOGIN_LINK:
                return htmlFormHiddenFields(
                                  pr.getRenderContext().getLoginServerURL());
            case BACK_LINK:
                return htmlFormHiddenFields(
                                  pr.getRenderContext().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;
    }

    private static String emitHiddenFields(String queryString) {
        String hidden = "";
        StringTokenizer qsST = new StringTokenizer(queryString, "&");

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

        return hidden;
    }

    /**
     * Utility to emit a single HTML hidden field with a specified name and
     * value.
     *
     * @param name the encoded name of the hidden field
     * @param value the encoded 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(HTTPUtils.decodeUTF(name));
        buffer.append("\" value=\"");
// code used to call private decode method - changed to use MultiByteDecoder
        buffer.append(HTTPUtils.decodeUTF(value));
        buffer.append("\" />");
        buffer.append((char) Character.LINE_SEPARATOR);
        return  buffer.toString();
    }

    /**
     * Generic utility to build a parameter string.
     *
     * @param pr render request
     * @param params array of NameValue objects representing the parameters
     * @param encodeParams flag indicating whether parameter names and values
     *                     should be encoded
     *
     * @return parameters in a string that can be embeded in a link, the string
     *                    does not start with or end with an "&"
     */
   private static String constructParams( PortletRenderRequest pr,
                                          NameValue[] params,
                                        boolean encodeParams)
        throws IllegalArgumentException, IOException
    {
        StringBuffer newLink = new StringBuffer(100);

        // 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");
            }

            // the calling code 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();
    }

    /**
    * 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 IOException
    {
        return HTTPUtils.encode(param, pr.getURLCharSet());
    }

    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,
                                                  NameValue[] 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)
        {
            outStr = "";
        }
        return outStr;
    }

    /**
     *  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 UrlUtils
     *  @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 
    {
        LogManager.debug("Enter parameterizeLink");

        RenderContext renderContext = pr.getRenderContext();
        if ( renderContext.isRewriteUrls() )
        {
            switch (linkType) { 
            case REQUEST_LINK:
                 return parameterizeLink(pr.getRequestURL(), queryString);
            case LOGIN_LINK:
                 return parameterizeLink(
                            pr.getRenderContext().getLoginServerURL(), 
                            queryString);
            case PAGE_LINK:
            case EVENT_LINK:
            case DESIGN_LINK:
            case BACK_LINK:
                return "";
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
        }
        else
        { 
            switch (linkType) {
            case PAGE_LINK:
                return parameterizeLink(
                           pr.getRenderContext().getPageURL(), 
                           queryString);
            case EVENT_LINK:
                return parameterizeLink(
                           pr.getRenderContext().getEventURL(), 
                           queryString);
            case DESIGN_LINK:
                return parameterizeLink(
                           pr.getRenderContext().getDesignURL(), 
                           queryString);
            case REQUEST_LINK:
                return parameterizeLink(
                           pr.getRequestURL(), queryString);
            case LOGIN_LINK:
                return parameterizeLink(
                           pr.getRenderContext().getLoginServerURL(), 
                           queryString);
            case BACK_LINK:
                return parameterizeLink(
                           pr.getRenderContext().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 
    {
        LogManager.debug("Enter htmlFormActionLink");

        RenderContext renderContext = pr.getRenderContext();
        if ( renderContext.isRewriteUrls() )
        {
            switch (linkType) {
            case PAGE_LINK:
            case EVENT_LINK:
            case DESIGN_LINK:
            case BACK_LINK:
                return "";
            case REQUEST_LINK:
                return htmlFormActionLink(pr.getRequestURL());
            case LOGIN_LINK:
                return htmlFormActionLink(
                           pr.getRenderContext().getLoginServerURL());
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
        }
        else
        {
            switch (linkType) {
            case PAGE_LINK:
                return htmlFormActionLink(
                           pr.getRenderContext().getPageURL());
            case EVENT_LINK:
                return htmlFormActionLink(
                           pr.getRenderContext().getEventURL());
            case DESIGN_LINK:
                return htmlFormActionLink(
                           pr.getRenderContext().getDesignURL());
            case REQUEST_LINK:
                return htmlFormActionLink(
                           pr.getRequestURL());
            case LOGIN_LINK:
                return htmlFormActionLink(
                           pr.getRenderContext().getLoginServerURL());
            case BACK_LINK:
                return htmlFormActionLink(
                           pr.getRenderContext().getBackURL());
            default:
                throw new IllegalArgumentException("Invalid linkType: " 
                                                   + linkType);
            }
        }
    }
    
    
    /**
     *  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 NameValue objects representing the
     *                parameter names and associated values. Names cannot be NULL
     *  @param displayText text displayed for this link
     *  @param otherHtmlAttributes other attributes for the HTML link. No need 
     *                     for pre or end spaces in the string.
     *  @param encodeParams flag indicating whether the parameter names and values
     *                      should be URL encoded using the multibyte URL encoder.
     * @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
     */
    public static String constructDetailHTMLLink(PortletRenderRequest pr,
                                             NameValue[] params,
                                             String displayText,
                                             String otherHtmlAttributes,
                                             boolean encodeParams,
                                             boolean replaceParams)
        throws PortletException, IOException
    {
        try
        {
            // add parameter for SHOW mode
            NameValue[] linkParams = new NameValue[params.length + 2];
            int i;
            for (i=0; i < params.length; i++)
            {
                linkParams[i] = params[i];
            }
            linkParams[i++] = new NameValue("_mode",
                                          Integer.toString(PortletRenderer.MODE_SHOW_DETAILS));
            linkParams[i] = new NameValue("_title", PortletRendererUtil.getTitle(pr));
            return constructHTMLLink(pr,
                                 DESIGN_LINK,
                                 displayText,
                                 otherHtmlAttributes,
                                 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 NameValue objects representing the
     *                parameter names and associated values. Names cannot be NULL
     *  @param displayText text displayed for this link
     *  @param otherHtmlAttributes other attributes for the HTML link. No need 
     *                     for pre or end spaces in the string.
     *  @param encodeParams flag indicating whether the parameter names and values
     *                      should be URL encoded using the multibyte URL encoder.
     *  @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
     */
    public static String constructHelpHTMLLink(PortletRenderRequest pr,
                                           NameValue[] params,
                                           String displayText,
                                           String otherHtmlAttributes,
                                           boolean encodeParams,
                                           boolean replaceParams)
        throws PortletException, IOException
    {
        try
        {
            // add parameter for SHOW mode
            NameValue[] linkParams = new NameValue[params.length + 2];
            int i;
            for (i=0; i < params.length; i++)
            {
                linkParams[i] = params[i];
            }
            linkParams[i++] = new NameValue("_mode",
                                          Integer.toString(PortletRenderer.MODE_HELP));
            linkParams[i] = new NameValue("_title", PortletRendererUtil.getTitle(pr));

            return constructHTMLLink(pr,
                                 DESIGN_LINK,
                                 displayText,
                                 otherHtmlAttributes,
                                 linkParams,
                                 encodeParams,
                                 replaceParams);
        }
        catch (Exception e)
        {
            throw new PortletException(e);
        }
    }

    /**
     * Get the portlet Page Reference.
     *
     * The portlet page reference is sent by the PPE to the provider for all
     * display modes other than REVIEW mode. This function will return the 
     * portlet page reference or an empty string if in the REVIEW mode.
     * The underlying function call raises an exception if the portlet page
     * is not set in the RenderComtext.
     *
     * @param pr render request
     * @return the portlet page reference or the empty string in PREVIEW mode
     */
    private static String getPortletPageReference(PortletRenderRequest pr)
    {
        String pageRef = new String("");   
        RenderContext renderContext = pr.getRenderContext();
        if (renderContext.isRewriteUrls() &&
            renderContext.getMode() != PortletRenderer.MODE_PREVIEW)
        {
            // In PREVIEW MODE the PPE does not send a PageReference. 
            // it is not yet on a page. 
            pageRef = renderContext.getPortletPageReference();
        }
        else if(renderContext.getMode() != PortletRenderer.MODE_PREVIEW)
        {
            // the request may come from a 9.0.2 portal or a pre 9.0.2 portal
            // if it is from a pre 9.0.2 portal then the call to get
            // the PortletPageReference will raise an exception
            try
            {
                pageRef = renderContext.getPortletPageReference();
            }
            catch (java.lang.IllegalStateException e)
            {
                pageRef = pr.getPortletInstance().getInstanceName();
            }
        }
        else
        {
            pageRef = pr.getPortletInstance().getInstanceName();
        }

        return pageRef;
    }
}
